Dauliac/nix-lib
{ "createdAt": "2026-01-29T02:25:23Z", "defaultBranch": "main", "description": null, "fullName": "Dauliac/nix-lib", "homepage": null, "language": "Nix", "name": "nix-lib", "pushedAt": "2026-03-06T09:55:46Z", "stargazersCount": 14, "topics": [], "updatedAt": "2026-03-06T09:55:50Z", "url": "https://github.com/Dauliac/nix-lib"}nix-lib
Section titled “nix-lib”A Nix library framework implementing the Lib Modules Pattern - where library functions are defined as module options with built-in types, tests, and documentation.
The Problem
Section titled “The Problem”Writing Nix libraries typically means:
- Functions scattered across files with no consistent structure
- Tests living separately (or not existing at all)
- Types and documentation as afterthoughts
- No standard way to compose libraries
The Solution: Lib Modules Pattern
Section titled “The Solution: Lib Modules Pattern”Define functions as config values that bundle everything together:
nix-lib.lib.double = { type = lib.types.functionTo lib.types.int; fn = x: x * 2; description = "Double a number"; tests."doubles 5" = { args.x = 5; expected = 10; };};This gives you:
- Type safety - explicit Nix types for your functions
- Built-in testing - tests live with the code (nix-unit integration)
- Documentation - descriptions in one place
- Composition - use the NixOS module system to combine libraries
- Nested propagation - libs from nested modules (home-manager in NixOS) are accessible in parent scope
Quick Start
Section titled “Quick Start”Using mkFlake (Recommended)
Section titled “Using mkFlake (Recommended)”nlib.mkFlake is the main entry point. It evaluates lib modules and optionally integrates with flake-parts:
{ inputs = { nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable"; flake-parts.url = "github:hercules-ci/flake-parts"; nlib.url = "github:Dauliac/nlib"; };
outputs = inputs: inputs.nlib.mkFlake { inherit inputs; modules = [ ./libs/math.nix ]; flake-parts = inputs.flake-parts; # Optional: enables flake-parts integration } { systems = [ "x86_64-linux" "aarch64-linux" ];
perSystem = { lib, pkgs, ... }: { # lib.math.* available in OPTIONS phase! packages.default = pkgs.writeText "result" "double 5 = ${toString (lib.math.double 5)}"; }; };}Lib Module Format
Section titled “Lib Module Format”{ lib, config, ... }: { lib.math.double = { fn = x: x * 2; description = "Double a number"; tests."doubles 5" = { args.x = 5; expected = 10; }; };
# Self-referencing via config lib.math.quadruple = { fn = x: config.lib.math.double.fn (config.lib.math.double.fn x); description = "Quadruple using double"; };}Standalone Mode (no flake-parts)
Section titled “Standalone Mode (no flake-parts)”{ outputs = inputs: inputs.nlib.mkFlake { inherit inputs; modules = [ ./libs/math.nix ]; } { packages.x86_64-linux.default = ...; };}Importing External Libs
Section titled “Importing External Libs”inputs.nlib.mkFlake { inherit inputs; modules = [ ./libs/math.nix # Your lib modules { inherit soonix; } # External: soonix.lib -> lib.soonix.* { custom = otherLib; } # Renamed: otherLib.lib -> lib.custom.* ]; flake-parts = inputs.flake-parts;} { ... }Using flake-parts Module (Alternative)
Section titled “Using flake-parts Module (Alternative)”{ inputs.nlib.url = "github:Dauliac/nlib";
outputs = { nlib, ... }: nlib.inputs.flake-parts.lib.mkFlake { inherit inputs; } { imports = [ nlib.flakeModules.default ];
# Define a pure flake-level lib nix-lib.lib.double = { type = lib.types.functionTo lib.types.int; fn = x: x * 2; description = "Double a number"; tests."doubles 5" = { args.x = 5; expected = 10; }; }; };}See examples/ and tests/scenarios/ for complete working examples.
Lib Modules Architecture
Section titled “Lib Modules Architecture”┌──────────────────────────────────────────────────────────┐│ nlib.mkFlake ││ ┌────────────────────────────────────────────────────┐ ││ │ 1. Evaluate lib modules (BEFORE flake-parts) │ ││ │ → produces lib.* │ ││ └──────────────────────┬─────────────────────────────┘ ││ │ inject into lib ││ ▼ ││ ┌────────────────────────────────────────────────────┐ ││ │ 2. flake-parts.lib.mkFlake (if provided) │ ││ │ specialArgs.lib = nixpkgs.lib // evaluatedLibs │ ││ │ → lib.* available in OPTIONS phase! │ ││ └──────────────────────┬─────────────────────────────┘ ││ │ ││ ▼ ││ ┌────────────────────────────────────────────────────┐ ││ │ 3. NixOS/home-manager adapters │ ││ │ → config.lib.* │ ││ └────────────────────────────────────────────────────┘ │└──────────────────────────────────────────────────────────┘mkFlake Options
Section titled “mkFlake Options”| Option | Type | Description |
|---|---|---|
inputs | attrset | Flake inputs (required) |
modules | list | Lib modules to evaluate |
flake-parts | input | Optional: flake-parts input for integration |
Lib Module Format
Section titled “Lib Module Format”Lib modules are NixOS-style modules that define lib.*:
{ lib, config, ... }: { lib.<namespace>.<name> = { fn = ...; # Required: the function description = "..."; # Optional: documentation tests = { ... }; # Optional: test cases type = ...; # Optional: type signature visible = true; # Optional: public/private };}Importing External Libs (soonix-style)
Section titled “Importing External Libs (soonix-style)”For libs that follow the soonix pattern (input.lib = { pkgs }: { ... }), use nix-lib.imports in perSystem:
perSystem = { pkgs, config, ... }: { nix-lib.imports = [ { inherit soonix; } # soonix.lib { inherit pkgs; } -> config.lib.soonix.* { inherit anotherLib; } # -> config.lib.anotherLib.* { custom = someLib; } # -> config.lib.custom.* ];
# Now available: devShells.default = pkgs.mkShell { shellHook = config.lib.soonix.mkShellHook { ... }; };};For pure libs (no pkgs needed), use nix-lib.imports at flake level:
nix-lib.imports = [ { inherit pureLib; } # pureLib.lib.* -> flake.lib.pureLib.*];API Reference
Section titled “API Reference”Defining Libraries
Section titled “Defining Libraries”Define libs at nix-lib.lib.<name> (supports nested namespaces like nix-lib.lib.utils.helper):
nix-lib.lib.myFunc = { type = lib.types.functionTo lib.types.int; # Required: function signature fn = x: x * 2; # Required: implementation description = "What it does"; # Required: documentation tests."test name" = { # Optional: test cases args.x = 5; expected = 10; }; visible = true; # Optional: public (true) or private (false)};Lib Flow
Section titled “Lib Flow”flowchart TB subgraph Input["Define (nix-lib.lib.*)"] D1["nix-lib.lib.myFunc = {<br/>type, fn, description, tests}"] end
subgraph Process["nix-lib Processing"] P1["Extract fn → config.lib.*"] P2["Extract tests → flake.tests"] P3["Store metadata → nix-lib._libsMeta"] end
subgraph Output["Outputs"] O1["config.lib.myFunc<br/>(use in module)"] O2["flake.lib.namespace.myFunc<br/>(flake export)"] O3["flake.tests.test_myFunc_*<br/>(nix-unit tests)"] end
D1 --> P1 D1 --> P2 D1 --> P3 P1 --> O1 P1 --> O2 P2 --> O3flowchart TB subgraph Define["Define (nix-lib.lib.*)"] D1[type + fn + description + tests] end
subgraph Use["Use (config.lib.*)"] U1[NixOS config.lib.foo] U2[home-manager config.lib.bar] U3[nixvim config.lib.baz] end
subgraph Propagate["Nested Propagation"] P1[NixOS config.lib.home.*] P2[NixOS config.lib.home.vim.*] end
subgraph Export["Flake Export (flake.lib.*)"] E1[flake.lib.nixos.*] E2[flake.lib.home.*] E3[flake.lib.vim.*] end
D1 --> U1 D1 --> U2 D1 --> U3 U2 --> P1 U3 --> P2 U1 --> E1 U2 --> E2 U3 --> E3Lib Output Layers
Section titled “Lib Output Layers”Libs defined in different module systems are available at different paths:
Flake-Level Libs (pure, no pkgs)
Section titled “Flake-Level Libs (pure, no pkgs)”| Defined in | Module to import | Access within module | Flake output |
|---|---|---|---|
flake-parts nix-lib.lib.* | flakeModules.default | config.lib.flake.<name> | flake.lib.flake.<name> |
perSystem nix-lib.lib.* | flakeModules.default | config.lib.<name> | legacyPackages.<system>.nix-lib.<name> |
System Configuration Libs
Section titled “System Configuration Libs”| Defined in | Module to import | Access within module | Flake output |
|---|---|---|---|
NixOS nix-lib.lib.* | nixosModules.default | config.lib.<name> | flake.lib.nixos.<name> |
home-manager nix-lib.lib.* | homeModules.default | config.lib.<name> | flake.lib.home.<name> |
nix-darwin nix-lib.lib.* | darwinModules.default | config.lib.<name> | flake.lib.darwin.<name> |
nixvim nix-lib.lib.* | nixvimModules.default | config.lib.<name> | flake.lib.vim.<name> |
system-manager nix-lib.lib.* | systemManagerModules.default | config.lib.<name> | flake.lib.system.<name> |
Nested Module Propagation
Section titled “Nested Module Propagation”When a parent module imports a nested module system, the nested libs are automatically accessible in the parent scope under a namespace prefix.
flowchart LR subgraph NixOS N[config.lib.*] end
subgraph home-manager H[nix-lib.lib.*] end
subgraph nixvim V[nix-lib.lib.*] end
H -->|home.*| N V -->|vim.*| H V -->|home.vim.*| NNested Libs Access Table
Section titled “Nested Libs Access Table”| Parent module | Nested module | Libs defined in nested | Access in parent |
|---|---|---|---|
| NixOS | home-manager | nix-lib.lib.foo | config.lib.home.foo |
| NixOS | home-manager → nixvim | nix-lib.lib.bar | config.lib.home.vim.bar |
| nix-darwin | home-manager | nix-lib.lib.foo | config.lib.home.foo |
| nix-darwin | home-manager → nixvim | nix-lib.lib.bar | config.lib.home.vim.bar |
| home-manager | nixvim | nix-lib.lib.bar | config.lib.vim.bar |
Namespace Prefixes
Section titled “Namespace Prefixes”| Module system | Namespace prefix |
|---|---|
| home-manager | home |
| nixvim | vim |
| nix-darwin | darwin |
| system-manager | system |
Flake Outputs Summary
Section titled “Flake Outputs Summary”All libs are collected and exported at the flake level under flake.lib.<namespace>:
| Namespace | Source | Description |
|---|---|---|
flake.lib.flake.* | nix-lib.lib.* in flake-parts | Pure flake-level libs |
flake.lib.nix-lib.* | nix-lib internals | mkAdapter, backends utilities |
flake.lib.nixos.* | nixosConfigurations.*.config.lib.* | NixOS configuration libs |
flake.lib.home.* | homeConfigurations.*.config.lib.* | Standalone home-manager libs |
flake.lib.darwin.* | darwinConfigurations.*.config.lib.* | nix-darwin libs |
flake.lib.vim.* | nixvimConfigurations.*.config.lib.* | Standalone nixvim libs |
flake.lib.system.* | systemConfigs.*.config.lib.* | system-manager libs |
flake.lib.wrappers.* | wrapperConfigurations.*.config.lib.* | nix-wrapper-modules libs |
Per-system libs are available at legacyPackages.<system>.lib.<namespace>.*.
Available Modules
Section titled “Available Modules”Import the adapter for your module system. Libs are automatically available at config.lib.*:
| Module | Import path |
|---|---|
flakeModules.default | inputs.nix-lib.flakeModules.default |
nixosModules.default | nix-lib.nixosModules.default |
homeModules.default | nix-lib.homeModules.default |
darwinModules.default | nix-lib.darwinModules.default |
nixvimModules.default | nix-lib.nixvimModules.default |
systemManagerModules.default | nix-lib.systemManagerModules.default |
wrapperModules.default | nix-lib.wrapperModules.default |
Test Formats
Section titled “Test Formats”Simple expected value
Section titled “Simple expected value”tests."test name" = { args.x = 5; # Argument passed to fn expected = 10; # Expected return value};Multiple arguments
Section titled “Multiple arguments”tests."test name" = { args.x = { a = 2; b = 3; }; # For fn = { a, b }: a + b expected = 5;};Multiple assertions
Section titled “Multiple assertions”tests."test name" = { args.x = 5; assertions = [ { name = "is positive"; check = result: result > 0; } { name = "is even"; check = result: lib.mod result 2 == 0; } { name = "equals 10"; expected = 10; } ];};Wrapper Module Systems
Section titled “Wrapper Module Systems”nix-lib supports wrapper-based module systems that create wrapped executables:
- nix-wrapper-modules - Module system for wrapped packages with DAG-based flag ordering
- Lassulus/wrappers - Library for creating wrapped executables via module evaluation
Both use lib.evalModules internally, making them compatible with nix-lib’s adapter system.
Basic Usage
Section titled “Basic Usage”{ inputs = { nix-lib.url = "github:Dauliac/nix-lib"; nix-wrapper-modules.url = "github:BirdeeHub/nix-wrapper-modules"; # Or: wrappers.url = "github:Lassulus/wrappers"; };
outputs = { nixpkgs, nix-lib, nix-wrapper-modules, ... }: nix-lib.inputs.flake-parts.lib.mkFlake { inherit inputs; } { imports = [ nix-lib.flakeModules.default ];
# Define wrapper configurations flake.wrapperConfigurations.myApp = nixpkgs.lib.evalModules { modules = [ # nix-lib adapter for wrappers nix-lib.wrapperModules.default
# Your wrapper libs { nix-lib.enable = true; nix-lib.lib.mkFlags = { type = lib.types.functionTo lib.types.attrs; fn = name: flags: { drv.flags.${name} = flags; }; description = "Generate wrapper flags"; }; } ]; }; };}With nix-wrapper-modules
Section titled “With nix-wrapper-modules”# Use BirdeeHub's wrapper definitionsflake.wrapperConfigurations.alacritty = inputs.nix-wrapper-modules.wrappers.alacritty.wrap { inherit pkgs; modules = [ nix-lib.wrapperModules.default { nix-lib.enable = true; nix-lib.lib.terminalHelper = { type = lib.types.functionTo lib.types.attrs; fn = shell: { settings.terminal.shell.program = shell; }; description = "Set terminal shell"; }; } ]; # Use the helper settings = config.lib.terminalHelper "${pkgs.zsh}/bin/zsh"; };With Lassulus/wrappers
Section titled “With Lassulus/wrappers”# Use Lassulus's wrapper modulesflake.wrapperConfigurations.mpv = inputs.wrappers.wrapperModules.mpv.apply { inherit pkgs; modules = [ nix-lib.wrapperModules.default { nix-lib.enable = true; nix-lib.lib.addScript = { type = lib.types.functionTo lib.types.attrs; fn = script: { scripts = [ script ]; }; description = "Add mpv script"; }; } ]; };Accessing Wrapper Libs
Section titled “Accessing Wrapper Libs”Libs defined in wrapper configurations are collected at:
| Location | Path |
|---|---|
| Within wrapper module | config.lib.<name> |
| Flake output | flake.lib.wrappers.<name> |
Custom Module Systems
Section titled “Custom Module Systems”mkAdapter is generic and works with any NixOS-style module system:
# Create adapter for your custom module systemflake.myModules.default = inputs.nix-lib.outputs.lib.nix-lib.mkAdapter { name = "my-module-system"; namespace = "my";};
# Use in your module system{ lib, config, ... }: { imports = [ myModules.default ];
nix-lib.enable = true; nix-lib.lib.myHelper = { type = lib.types.functionTo lib.types.attrs; fn = x: { result = x; }; description = "Custom helper"; };
# Available at: config.lib.myHelper}Requirements
Section titled “Requirements”- Module system must support NixOS-style modules (
config,lib,optionsargs) - No domain-specific options required - mkAdapter only sets
nix-lib.*andlib.*
Custom Collectors
Section titled “Custom Collectors”Collectors aggregate libs from flake outputs into flake.lib.<namespace>. Define custom collectors via nix-lib.collectorDefs:
# In your flake-parts modulenix-lib.collectorDefs.wrappers = { pathType = "flat"; # "flat" or "perSystem" configPath = [ "wrapperConfigurations" ]; # Path in flake outputs namespace = "wrappers"; # Output at flake.lib.wrappers.* description = "nix-wrapper-modules libs";};Path Types
Section titled “Path Types”| Type | Description | Collection Path |
|---|---|---|
flat | Direct configuration set | flake.<configPath>.<name>.config.nix-lib._fns |
perSystem | Per-system in legacyPackages | flake.legacyPackages.<system>.<configPath> |
Disabling Built-in Collectors
Section titled “Disabling Built-in Collectors”nix-lib.collectorDefs.nixos.enable = false; # Disable NixOS collectionOverriding Namespaces
Section titled “Overriding Namespaces”nix-lib.collectorDefs.nixos.namespace = "os"; # flake.lib.os.* instead of flake.lib.nixos.*Testing
Section titled “Testing”nix-lib supports multiple testing frameworks through a pluggable backend system. Tests defined in nix-lib.lib.*.tests are automatically converted to the selected backend format.
Supported Testing Frameworks
Section titled “Supported Testing Frameworks”| Backend | Framework | Description |
|---|---|---|
nix-unit | nix-unit | Default. Catches eval errors, uses Nix C++ API, in nixpkgs |
nixtest | nixtest | Pure Nix, no nixpkgs dependency, lightweight |
nix-tests | nix-tests | Rust CLI, parallel execution, helpers API |
runTests | lib.debug.runTests | Built-in nixpkgs testing function |
nixt | nixt | TypeScript-based, describe/it blocks |
namaka | namaka | Snapshot testing with review workflow |
Configuring the Backend
Section titled “Configuring the Backend”nix-lib.testing = { backend = "nix-unit"; # or "nixtest", "nix-tests", "runTests", "nixt", "namaka" reporter = "junit"; outputPath = "test-results.xml";};Running Tests
Section titled “Running Tests”cd testsnix run .#testOutput:
=== Running nix-unit tests ===🎉 97/97 successful=== All tests passed! ===Test Architecture
Section titled “Test Architecture”flowchart TB subgraph Define["Define Libraries"] L1["nix-lib.lib.double = {<br/>fn, type, tests...}"] L2["nix-lib.lib.add = {<br/>fn, type, tests...}"] end
subgraph BDD["BDD Tests (tests/bdd/)"] B1["collectors.nix"] B2["adapters.nix"] B3["libDef.nix"] end
subgraph PerSystem["perSystem.nix-unit.tests"] PS["System-specific tests"] end
subgraph Generate["Auto-Generated"] G1["test_double_doubles_5"] G2["test_add_adds_positives"] end
subgraph Merge["flake.tests"] M["All tests merged"] end
subgraph Run["nix run .#test"] R["nix-unit --flake .#tests<br/>🎉 97/97 successful"] end
L1 --> G1 L2 --> G2 G1 --> M G2 --> M B1 --> M B2 --> M B3 --> M PS --> M M --> RTests are organized in three layers:
| Layer | Location | Purpose |
|---|---|---|
| Unit tests | nix-lib.lib.*.tests | Function behavior (defined with libs) |
| BDD tests | tests/bdd/*.nix | Structure validation (namespaces, adapters) |
| perSystem tests | perSystem.nix-unit.tests | System-specific lib checks |
All tests are merged into flake.tests and run together via nix-unit --flake .#tests.
Writing Tests
Section titled “Writing Tests”Tests are defined alongside lib definitions:
nix-lib.lib.add = { type = lib.types.functionTo lib.types.int; fn = { a, b }: a + b; description = "Add two numbers"; tests = { "adds positives" = { args.x = { a = 2; b = 3; }; expected = 5; }; "adds negatives" = { args.x = { a = -1; b = -2; }; expected = -3; }; };};For BDD-style structure tests, create modules in tests/bdd/:
{ lib, config, ... }:{ # System-agnostic tests flake.tests = { "test_myFeature_works" = { expr = lib.hasAttr "myAttr" config.flake.lib; expected = true; }; };
# System-specific tests perSystem = { config, ... }: { nix-unit.tests = { "test_perSystem_lib_exists" = { expr = config.legacyPackages.lib != { }; expected = true; }; }; };}Note: nix-unit requires test names to start with test.
See Also
Section titled “See Also”examples/- Working examples for each module systemtests/scenarios/mkFlake-standalone/- mkFlake standalone exampletests/scenarios/mkFlake-flake-parts/- mkFlake with flake-parts exampletests/bdd/- BDD tests for structure validationCONTRIBUTING.md- Development and testing guide