Modular, context-aware and aspect-oriented dendritic Nix configurations. Discussions: https://oeiuwq.zulipchat.com/join/nqp26cd4kngon6mo3ncgnuap/ den.oeiuwq.com
configurations den dendritic nix aspect oriented

feat: default.nix exposes clean den API without NixOS domain (#230)

Closes #224

authored by oeiuwq.com and committed by

GitHub 57aef304 dd995a7e

+210 -36
+1 -1
.github/workflows/test.yml
··· 74 74 } 75 75 EOF 76 76 git add templates/noflake/modules/ci-runtime.nix 77 - - run: cd templates/noflake && nix-shell . -A den.sh --run 'igloo build' 77 + - run: cd templates/noflake && nix-build -A flake.nixosConfigurations.igloo.config.system.build.toplevel 78 78 minimal: 79 79 needs: [approved] 80 80 name: minimal
+10 -1
default.nix
··· 1 1 { 2 - inherit (import ./nix) nixModule lib namespace; 2 + __functor = self: import ./nix/lib.nix; 3 + nixModule = 4 + inputs: 5 + { config, lib, ... }: 6 + let 7 + den-lib = import ./nix/lib.nix { inherit inputs config lib; }; 8 + in 9 + { 10 + imports = [ den-lib.nixModule ]; 11 + }; 3 12 }
+18 -18
docs/src/content/docs/explanation/library-vs-framework.mdx
··· 7 7 8 8 ## Den as a Library 9 9 10 - Den's core (`nix/lib.nix`) is domain-agnostic. It provides: 10 + Den's core (`/default.nix`) is domain-agnostic. It provides: 11 11 12 12 | Function | Purpose | 13 13 |---|---| ··· 28 28 29 29 ## Using Den for Non-OS Domains 30 30 31 + <Aside title="Source" icon="github"> 32 + [default.nix](https://github.com/vic/den/tree/main/default.nix) - [CI test: den-as-lib.nix](https://github.com/vic/den/tree/main/templates/ci/modules/features/den-as-lib.nix) 33 + </Aside> 34 + 31 35 Nothing about `den.lib` assumes NixOS, Darwin, or Home Manager. You can build 32 36 context pipelines for any Nix module system: 33 37 34 38 ```nix 35 - { my-aspects, den, ... }: { 36 - 37 - # create a private isolated aspect namespace independent of den.aspects 38 - imports = [ (den.namespace "my-aspects" false) ]; 39 + # see den-as-lib.nix CI test for working example 39 40 40 - # Define aspects for a custom domain 41 - my-aspects = { 42 - web-server = den.lib.parametric { 43 - terranix.resource.aws_instance.web = { ami = "..."; }; 44 - includes = [ 45 - # configures using the terranix Nix class 46 - ({ env, ... }: { terranix.resource.aws_instance.web.tags.Env = env; }) 47 - ]; 48 - }; 41 + # Define aspects for a custom domain 42 + den.aspects = { 43 + web-server = den.lib.parametric { 44 + terranix.resource.aws_instance.web = { ami = "..."; }; 45 + includes = [ 46 + # configures using the terranix Nix class 47 + ({ env, ... }: { terranix.resource.aws_instance.web.tags.Env = env; }) 48 + ]; 49 49 }; 50 + }; 50 51 51 - # Resolve for your custom class 52 - aspect = my-aspects.web-server { env = "production"; }; 53 - module = aspect.resolve { class = "terranix"; }; 54 - } 52 + # Resolve for your custom class 53 + aspect = den.aspects.web-server { env = "production"; }; 54 + module = aspect.resolve { class = "terranix"; }; 55 55 ``` 56 56 57 57 ## Den as a Framework
-6
modules/aspects.nix
··· 4 4 denfulType = lib.types.attrsOf aspectsType; 5 5 in 6 6 { 7 - config._module.args.den = config.den; 8 - options.den.aspects = lib.mkOption { 9 - description = "Den Aspects"; 10 - default = { }; # generated from den.{hosts, homes, users} 11 - type = aspectsType; 12 - }; 13 7 options.den.ful = lib.mkOption { 14 8 default = { }; # namespaces (local or merged from inputs) 15 9 type = denfulType;
+2 -1
modules/context/types.nix nix/nixModule/ctx.nix
··· 1 - { den, lib, ... }: 1 + { config, lib, ... }: 2 2 let 3 + inherit (config) den; 3 4 inherit (den.lib.aspects.types) aspectSubmodule; 4 5 inherit (den.lib) ctxApply; 5 6
+3 -6
modules/lib.nix
··· 5 5 ... 6 6 }: 7 7 { 8 - config.den.lib = inputs.den.lib { inherit inputs lib config; }; 9 - options.den.lib = lib.mkOption { 10 - internal = true; 11 - visible = false; 12 - type = lib.types.attrsOf lib.types.raw; 13 - }; 8 + imports = [ 9 + (inputs.den.lib { inherit inputs lib config; }).nixModule 10 + ]; 14 11 }
-3
nix/default.nix
··· 3 3 flakeModules.dendritic = ./dendritic.nix; 4 4 in 5 5 { 6 - # for non-flakes, our default modules needs no flakes 7 - nixModule = flakeModules.default; 8 - 9 6 # flake-parts conventions 10 7 flakeModule = flakeModules.default; 11 8 inherit flakeModules;
+10
nix/lib.nix
··· 117 117 118 118 home-env = import ./home-env.nix { inherit lib den inputs; }; 119 119 120 + nixModule = import ./nixModule { 121 + inherit 122 + lib 123 + inputs 124 + config 125 + den-lib 126 + ; 127 + }; 128 + 120 129 den-lib = { 121 130 inherit 122 131 parametric ··· 131 140 ctxApply 132 141 nh 133 142 home-env 143 + nixModule 134 144 ; 135 145 }; 136 146 in
+11
nix/nixModule/aspects.nix
··· 1 + { config, lib, ... }: 2 + let 3 + inherit (config.den.lib.aspects.types) aspectsType; 4 + in 5 + { 6 + options.den.aspects = lib.mkOption { 7 + description = "Den Aspects"; 8 + default = { }; 9 + type = aspectsType; 10 + }; 11 + }
+15
nix/nixModule/default.nix
··· 1 + { 2 + den-lib, 3 + config, 4 + lib, 5 + inputs, 6 + ... 7 + }@args: 8 + { 9 + _module.args.den = config.den; 10 + imports = map (f: import f args) [ 11 + ./lib.nix 12 + ./ctx.nix 13 + ./aspects.nix 14 + ]; 15 + }
+14
nix/nixModule/lib.nix
··· 1 + { 2 + lib, 3 + config, 4 + den-lib, 5 + ... 6 + }: 7 + { 8 + config.den.lib = den-lib; 9 + options.den.lib = lib.mkOption { 10 + internal = true; 11 + visible = false; 12 + type = lib.types.attrsOf lib.types.raw; 13 + }; 14 + }
+126
templates/ci/modules/features/den-as-lib.nix
··· 1 + { 2 + inputs, 3 + lib, 4 + config, 5 + ... 6 + }: 7 + let 8 + denPath = inputs.den.outPath; 9 + denModule = (import denPath).nixModule inputs; 10 + in 11 + { 12 + flake.tests.den-as-lib = { 13 + 14 + test-expose-lib-functions = 15 + let 16 + den-lib = import denPath { inherit lib config inputs; }; 17 + expr = den-lib.canTake.exactly { x = 1; } ({ x, y }: { }); 18 + expected = false; 19 + in 20 + { 21 + inherit expr expected; 22 + }; 23 + 24 + test-module-usable-in-any-module-system = 25 + let 26 + ev = lib.evalModules { modules = [ denModule ]; }; 27 + expr = ev.config.den ? lib.parametric; 28 + expected = true; 29 + in 30 + { 31 + inherit expr expected; 32 + }; 33 + 34 + test-module-has-empty-ctx = 35 + let 36 + ev = lib.evalModules { modules = [ denModule ]; }; 37 + expr = lib.attrNames ev.config.den.ctx; 38 + expected = [ ]; 39 + in 40 + { 41 + inherit expr expected; 42 + }; 43 + 44 + test-module-has-empty-aspects = 45 + let 46 + ev = lib.evalModules { modules = [ denModule ]; }; 47 + expr = lib.attrNames ev.config.den.aspects; 48 + expected = [ ]; 49 + in 50 + { 51 + inherit expr expected; 52 + }; 53 + 54 + test-module-has-no-nixos-domain = 55 + let 56 + names = [ 57 + "hosts" 58 + "homes" 59 + "base" 60 + "default" 61 + ]; 62 + ev = lib.evalModules { modules = [ denModule ]; }; 63 + expr = builtins.all (name: !ev.config.den ? ${name}) names; 64 + expected = true; 65 + in 66 + { 67 + inherit expr expected; 68 + }; 69 + 70 + test-module-can-resolve-custom-domain = 71 + let 72 + ev = lib.evalModules { 73 + modules = [ 74 + denModule 75 + module 76 + ]; 77 + }; 78 + 79 + module = 80 + { den, lib, ... }: 81 + { 82 + den.ctx.foo.provides.foo = 83 + { name }: 84 + { 85 + my.names = [ "foo ${name}" ]; 86 + }; 87 + den.ctx.foo.into.bar = { name }: lib.singleton { shout = lib.toUpper name; }; 88 + den.ctx.foo.provides.bar = 89 + { shout }: 90 + { 91 + my.names = [ "foo shouted ${shout}" ]; 92 + }; 93 + 94 + den.ctx.bar.provides.bar = 95 + { shout }: 96 + { 97 + my.names = [ "bar ${shout}" ]; 98 + }; 99 + 100 + den.aspects.foobar.includes = [ 101 + (den.ctx.foo { name = "good"; }) 102 + ]; 103 + }; 104 + 105 + myMod = ev.config.den.aspects.foobar.resolve { class = "my"; }; 106 + nameMod.options.names = lib.mkOption { type = lib.types.listOf lib.types.str; }; 107 + ev2 = lib.evalModules { 108 + modules = [ 109 + nameMod 110 + myMod 111 + ]; 112 + }; 113 + 114 + expr = ev2.config.names; 115 + expected = [ 116 + "foo shouted GOOD" 117 + "bar GOOD" 118 + "foo good" 119 + ]; 120 + in 121 + { 122 + inherit expr expected; 123 + }; 124 + 125 + }; 126 + }