···910# `<aspect>.<class>` transposition for Dendritic Nix
110000012<table>
13<tr>
14<td>
···1617```nix
18{
19- foo = {
20 nixos = ...;
00021 };
22- bar = {
23 nixos = ...;
24 darwin = ...;
25 };
26- baz = {
27 darwin = ...;
028 };
29}
30```
···39```nix
40{
41 nixos = {
42- foo = ...;
43- bar = ...;
44 };
45 darwin = {
46- bar = ...;
47- baz = ...;
000000000048 };
49}
50```
···53</tr>
54</table>
5556-## Motivation
5758-On [Dendritic](https://github.com/mightyiam/dendritic) setups it is common to expose modules using `flake.modules.<class>.<aspect>` - see [aspect-oriented nix configurations](https://vic.github.io/dendrix/Dendritic.html).
5960-However, for humans, it might be more intuitive to use a transposed attrset `<aspect>.<class>`. Because it feels more natural to nest classes on aspects than the other way around.
61-62-## Usage
63-64-As a deps-free library from `./default.nix`:
6566```nix
67let transpose = import ./default.nix { lib = pkgs.lib; }; in
68transpose { a.b.c = 1; } # => { b.a.c = 1; }
69```
7071-As a *Dendritic* flake-parts module that provides the `flake.aspects` option:
0007273> `flake.aspects` transposes into `flake.modules`.
7475```nix
076{ inputs, ... }: {
77 imports = [ inputs.flake-aspects.flakeModule ];
78- flake.aspects.sliding-desktop = {
79- nixos = { ... }; # configure Niri
80- darwin = { ... }; # configure Paneru
000000000000000000000081 };
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000082}
83```
84
···910# `<aspect>.<class>` transposition for Dendritic Nix
1112+On [aspect oriented](https://vic.github.io/dendrix/Dendritic.html) [Dendritic](https://github.com/mightyiam/dendritic) setups, it is common to expose modules using `flake.modules.<class>.<aspect>`.
13+However, for humans, it might be more intuitive to use a transposed attrset `<aspect>.<class>`. Because it feels more natural to nest classes inside aspects than the other way around.
14+15+This project provides a [`transpose`](tree/main/default.nix) primitive, small and powerful enough to implement [cross-aspect dependencies](tree/main/aspects.nix) for *any* nix configuration class, and a [flake-parts module](./tree/main/flakeModule.nix) for turning `flake.aspects` into `flake.modules`.
16+17<table>
18<tr>
19<td>
···2122```nix
23{
24+ vim-btw = {
25 nixos = ...;
26+ darwin = ...;
27+ homeManager = ...;
28+ nixvim = ...;
29 };
30+ tiling-desktop = {
31 nixos = ...;
32 darwin = ...;
33 };
34+ macos-develop = {
35 darwin = ...;
36+ hjem = ...;
37 };
38}
39```
···48```nix
49{
50 nixos = {
51+ vim-btw = ...;
52+ tiling-desktop = ...;
53 };
54 darwin = {
55+ vim-btw = ...;
56+ tiling-desktop = ...;
57+ macos-develop = ...;
58+ };
59+ homeManager = {
60+ vim-btw = ...;
61+ };
62+ hjem = {
63+ macos-develop = ...;
64+ };
65+ nixvim = {
66+ vim-btw = ...;
67 };
68}
69```
···72</tr>
73</table>
7475+## Usage
7677+### As a deps-free library from `./default.nix`:
7879+Our [`transpose`](tree/main/default.nix) library takes an optional `emit` function that
80+can be used to ignore some items, modify them or produce many other items on its place.
0008182```nix
83let transpose = import ./default.nix { lib = pkgs.lib; }; in
84transpose { a.b.c = 1; } # => { b.a.c = 1; }
85```
8687+This `emit` function is used by our [`aspects`](tree/main/aspects.nix) library
88+(both libs are flakes-independent) to provide cross-aspects same-class module dependencies.
89+90+### As a *Dendritic* flake-parts module that provides the `flake.aspects` option:
9192> `flake.aspects` transposes into `flake.modules`.
9394```nix
95+# code in this example can (and should) be split into different dendritic modules.
96{ inputs, ... }: {
97 imports = [ inputs.flake-aspects.flakeModule ];
98+ flake.aspects = {
99+100+ sliding-desktop = {
101+ description = "nextgen tiling windowing";
102+ nixos = { }; # configure Niri on Linux
103+ darwin = { }; # configure Paneru on MacOS
104+ };
105+106+107+ awesome-cli = {
108+ description = "enhances environment with best of cli an tui";
109+ nixos = { }; # os services
110+ darwin = { }; # apps like ghostty, iterm2
111+ homeManager = { }; # fish aliases, tuis, etc.
112+ nixvim = { }; # plugins
113+ };
114+115+ work-network = {
116+ description = "work vpn and ssh access.";
117+ nixos = {}; # enable openssh
118+ darwin = {}; # enable MacOS ssh server
119+ terranix = {}; # provision vpn
120+ hjem = {}; # home link .ssh keys and configs.
121+ }
122+123 };
124+}
125+```
126+127+#### Declaring cross-aspect dependencies
128+129+`flake.aspects` also allow to dependencies between aspects.
130+131+Of course each module can have its own `imports`, however aspect requirements
132+are aspect-level instead of module-level. Dependencies will ultimately resolve to
133+modules and get imported only when they exist.
134+135+In the following example, our `development-server` aspect can be applied into
136+linux and macos hosts.
137+Note that `alice` prefers to use `nixos`+`homeManager`, while `bob` likes `darwin`+`hjem`.
138+139+The `development-server` is a "usability concern", that configures the exact same
140+development tools on two different OS.
141+When it is applied to a NixOS machine, the `alice.nixos` module will likely
142+configure the alice user, but there is no nixos user for `bob`.
143+144+```nix
145+{
146+ flake.aspects = {config, ...}: {
147+ development-server = {
148+ requires = with config; [ alice bob ];
149+150+ # without flake-aspects, you'd normally do:
151+ # nixos.imports = [ inputs.self.modules.nixos.alice ];
152+ # darwin.imports = [ inputs.self.modules.darwin.bob ];
153+ };
154+155+ alice = {
156+ nixos = {};
157+ };
158+159+ bob = {
160+ darwin = {};
161+ };
162+ };
163+}
164+```
165+166+It is out of scope for this library to create OS configurations.
167+As you might have guessed, exposing configurations would look like this:
168+169+```nix
170+{ inputs, ... }:
171+{
172+ flake.nixosConfigurations.fooHost = inputs.nixpkgs.lib.nixosSystem {
173+ system = "x86_64-linux";
174+ modules = [ inputs.self.modules.nixos.development-server ];
175+ };
176+177+ flake.darwinConfigurations.fooHost = inputs.darwin.lib.darwinSystem {
178+ system = "aarm64-darwin";
179+ modules = [ inputs.self.modules.darwin.development-server ];
180+ };
181+}
182+```
183+184+#### Advanced aspect dependencies.
185+186+You have already seen that an `aspect` can have a `requires` list:
187+188+```nix
189+# A foo aspect that depends on aspects bar and baz.
190+flake.aspects = { config, ... }: {
191+ foo.requires = [ config.bar config.baz ];
192+}
193+```
194+195+cross-aspect requirements work like this:
196+197+When a module `flake.modules.nixos.foo` is requested (eg, included in a nixosConfiguration),
198+a corresponding module will be computed from `flake.aspects.foo.nixos`.
199+200+`flake.aspects.foo.requires` is a list of functions (named **providers**)
201+that will be called with `{name = "foo"; class = "nixos"}` to obtain another aspect
202+providing a module having the same `class` (`nixos` in our example).
203+204+_providers_ are a way of asking: if I have a (`foo`, `nixos`) module what other
205+aspects can you provide that have `nixos` modules to be imported in `foo`.
206+207+> This way, it is aspects *being included* who decide what configuration must
208+> be used by its caller aspect.
209+210+by default, all aspects have a `<aspect>.provides.itself` function that ignores its argument
211+and always returns the `<aspect>` itself.
212+This is why you can use the `with config; [ bar baz ]` syntax.
213+They are actually `[ config.bar.provides.itself config.baz.provides.itself ]`.
214+215+but you can also define custom providers that can inspect the argument's `name` and `class`
216+and return some another aspect accordingly.
217+218+```nix
219+flake.aspects.alice.provides.os-user = { name, class, ... }: {
220+ # perhaps regexp matching on name or class. eg, match all "hosts" aspects.
221+ nixos = { };
222+}
223+```
224+225+the `os-user` provider can be now included in a `requires` list:
226+227+```nix
228+flake.aspects = {config, ...}: {
229+ home-server.requires = [ config.alice.provides.os-user ];
230}
231```
232