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-06-10T18:10:59Z", "stargazersCount": 13, "topics": [], "updatedAt": "2026-06-10T18:11:18Z", "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”Run unit tests for a single scenario:
cd tests/scenarios/nix-unitnix run .#testRun all E2E test scenarios:
nix run .#test-e2eTest 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 |
| E2E scenarios | tests/scenarios/*/ | End-to-end integration per backend |
All tests are merged into flake.tests and run together via nix-unit --flake .#tests. E2E scenarios are run via nix run .#test-e2e.
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.
Documentation Generator
Section titled “Documentation Generator”nix-lib includes a built-in documentation generator that produces a markdown API reference from your lib metadata. It renders types, function arguments, descriptions, implementation bodies, and test cases.
Generating Docs
Section titled “Generating Docs”nix build .#nix-lib-docscat result/docs.mdConfiguration
Section titled “Configuration”Configure via nix-lib.docs.* in your perSystem:
| Option | Type | Default | Description |
|---|---|---|---|
src | path | null | null | Source root for fn body extraction (set to self to enable) |
showIndex | bool | true | Include a function index at the top |
showTitle | bool | true | Include the title and lib count header |
enableOutput | bool | true | Export docs as packages.nix-lib-docs |
Example
Section titled “Example”perSystem = { config, ... }: { nix-lib.docs = { src = self; # Enable tree-sitter fn body extraction showIndex = true; showTitle = true; };};What Gets Rendered
Section titled “What Gets Rendered”For each lib function, the generated docs.md includes:
- Function arguments — set-pattern (
{ a, b, c ? default }) or curried (x → y → z), rendered in fenced code blocks - Type — human-readable type signature
- Description — from the lib definition
- Source file — link to the source
- Implementation body — collapsible section with the function body (requires
srcto be set, uses tree-sitter-nix) - Test cases — collapsible table with input/expected columns
- Namespace headings — libs are grouped hierarchically by source (see below)
Doc Namespacing
Section titled “Doc Namespacing”Libs in the generated docs are automatically namespaced by where they are defined. The doc generator prefixes each lib name with its source module system, then organizes them into a hierarchical heading structure.
| Defined in | Doc namespace prefix | Example in docs |
|---|---|---|
Flake-level nix-lib.lib.* | flake. | flake.math.double |
NixOS nix-lib.lib.* | nixos. | nixos.mkService |
home-manager nix-lib.lib.* | home. | home.mkShell |
nix-darwin nix-lib.lib.* | darwin. | darwin.mkApp |
nixvim nix-lib.lib.* | vim. | vim.mkPlugin |
system-manager nix-lib.lib.* | system. | system.mkUnit |
nix-wrapper-modules nix-lib.lib.* | wrappers. | wrappers.mkFlags |
perSystem nix-lib.lib.* | (none) | myHelper |
Nested namespaces create hierarchical headings in the output. For example, libs defined as nix-lib.lib.math.double and nix-lib.lib.math.add at the flake level produce:
### flake#### math##### `double`##### `add`Body Extraction
Section titled “Body Extraction”When nix-lib.docs.src is set, the generator uses tree-sitter-nix to parse your source files and extract function implementation bodies. Without it, docs are generated in pure Nix (faster, but no implementation bodies).
E2E Test Runner
Section titled “E2E Test Runner”nix-lib provides an E2E test runner that executes all test scenarios:
nix run .#test-e2eThis runs each scenario in tests/scenarios/ and reports pass/fail:
| Scenario | Description |
|---|---|
nix-unit | Tests using nix-unit backend |
nix-tests | Tests using nix-tests backend with devour-flake |
standalone | Standalone test setup |
mkFlake-flake-parts | mkFlake with flake-parts integration |
mkFlake-standalone | mkFlake without flake-parts |
linter-fail | Verifies linter correctly rejects invalid code |
See Also
Section titled “See Also”examples/- Working examples for each module systemexamples/docs.nix- Documentation generator configuration and optionsexamples/e2e-tests.nix- E2E test runner usage and scenario structuretests/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