···991010# `<aspect>.<class>` transposition for Dendritic Nix
11111212+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>`.
1313+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.
1414+1515+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`.
1616+1217<table>
1318<tr>
1419<td>
···16211722```nix
1823{
1919- foo = {
2424+ vim-btw = {
2025 nixos = ...;
2626+ darwin = ...;
2727+ homeManager = ...;
2828+ nixvim = ...;
2129 };
2222- bar = {
3030+ tiling-desktop = {
2331 nixos = ...;
2432 darwin = ...;
2533 };
2626- baz = {
3434+ macos-develop = {
2735 darwin = ...;
3636+ hjem = ...;
2837 };
2938}
3039```
···3948```nix
4049{
4150 nixos = {
4242- foo = ...;
4343- bar = ...;
5151+ vim-btw = ...;
5252+ tiling-desktop = ...;
4453 };
4554 darwin = {
4646- bar = ...;
4747- baz = ...;
5555+ vim-btw = ...;
5656+ tiling-desktop = ...;
5757+ macos-develop = ...;
5858+ };
5959+ homeManager = {
6060+ vim-btw = ...;
6161+ };
6262+ hjem = {
6363+ macos-develop = ...;
6464+ };
6565+ nixvim = {
6666+ vim-btw = ...;
4867 };
4968}
5069```
···5372</tr>
5473</table>
55745656-## Motivation
7575+## Usage
57765858-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).
7777+### As a deps-free library from `./default.nix`:
59786060-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.
6161-6262-## Usage
6363-6464-As a deps-free library from `./default.nix`:
7979+Our [`transpose`](tree/main/default.nix) library takes an optional `emit` function that
8080+can be used to ignore some items, modify them or produce many other items on its place.
65816682```nix
6783let transpose = import ./default.nix { lib = pkgs.lib; }; in
6884transpose { a.b.c = 1; } # => { b.a.c = 1; }
6985```
70867171-As a *Dendritic* flake-parts module that provides the `flake.aspects` option:
8787+This `emit` function is used by our [`aspects`](tree/main/aspects.nix) library
8888+(both libs are flakes-independent) to provide cross-aspects same-class module dependencies.
8989+9090+### As a *Dendritic* flake-parts module that provides the `flake.aspects` option:
72917392> `flake.aspects` transposes into `flake.modules`.
74937594```nix
9595+# code in this example can (and should) be split into different dendritic modules.
7696{ inputs, ... }: {
7797 imports = [ inputs.flake-aspects.flakeModule ];
7878- flake.aspects.sliding-desktop = {
7979- nixos = { ... }; # configure Niri
8080- darwin = { ... }; # configure Paneru
9898+ flake.aspects = {
9999+100100+ sliding-desktop = {
101101+ description = "nextgen tiling windowing";
102102+ nixos = { }; # configure Niri on Linux
103103+ darwin = { }; # configure Paneru on MacOS
104104+ };
105105+106106+107107+ awesome-cli = {
108108+ description = "enhances environment with best of cli an tui";
109109+ nixos = { }; # os services
110110+ darwin = { }; # apps like ghostty, iterm2
111111+ homeManager = { }; # fish aliases, tuis, etc.
112112+ nixvim = { }; # plugins
113113+ };
114114+115115+ work-network = {
116116+ description = "work vpn and ssh access.";
117117+ nixos = {}; # enable openssh
118118+ darwin = {}; # enable MacOS ssh server
119119+ terranix = {}; # provision vpn
120120+ hjem = {}; # home link .ssh keys and configs.
121121+ }
122122+81123 };
124124+}
125125+```
126126+127127+#### Declaring cross-aspect dependencies
128128+129129+`flake.aspects` also allow to dependencies between aspects.
130130+131131+Of course each module can have its own `imports`, however aspect requirements
132132+are aspect-level instead of module-level. Dependencies will ultimately resolve to
133133+modules and get imported only when they exist.
134134+135135+In the following example, our `development-server` aspect can be applied into
136136+linux and macos hosts.
137137+Note that `alice` prefers to use `nixos`+`homeManager`, while `bob` likes `darwin`+`hjem`.
138138+139139+The `development-server` is a "usability concern", that configures the exact same
140140+development tools on two different OS.
141141+When it is applied to a NixOS machine, the `alice.nixos` module will likely
142142+configure the alice user, but there is no nixos user for `bob`.
143143+144144+```nix
145145+{
146146+ flake.aspects = {config, ...}: {
147147+ development-server = {
148148+ requires = with config; [ alice bob ];
149149+150150+ # without flake-aspects, you'd normally do:
151151+ # nixos.imports = [ inputs.self.modules.nixos.alice ];
152152+ # darwin.imports = [ inputs.self.modules.darwin.bob ];
153153+ };
154154+155155+ alice = {
156156+ nixos = {};
157157+ };
158158+159159+ bob = {
160160+ darwin = {};
161161+ };
162162+ };
163163+}
164164+```
165165+166166+It is out of scope for this library to create OS configurations.
167167+As you might have guessed, exposing configurations would look like this:
168168+169169+```nix
170170+{ inputs, ... }:
171171+{
172172+ flake.nixosConfigurations.fooHost = inputs.nixpkgs.lib.nixosSystem {
173173+ system = "x86_64-linux";
174174+ modules = [ inputs.self.modules.nixos.development-server ];
175175+ };
176176+177177+ flake.darwinConfigurations.fooHost = inputs.darwin.lib.darwinSystem {
178178+ system = "aarm64-darwin";
179179+ modules = [ inputs.self.modules.darwin.development-server ];
180180+ };
181181+}
182182+```
183183+184184+#### Advanced aspect dependencies.
185185+186186+You have already seen that an `aspect` can have a `requires` list:
187187+188188+```nix
189189+# A foo aspect that depends on aspects bar and baz.
190190+flake.aspects = { config, ... }: {
191191+ foo.requires = [ config.bar config.baz ];
192192+}
193193+```
194194+195195+cross-aspect requirements work like this:
196196+197197+When a module `flake.modules.nixos.foo` is requested (eg, included in a nixosConfiguration),
198198+a corresponding module will be computed from `flake.aspects.foo.nixos`.
199199+200200+`flake.aspects.foo.requires` is a list of functions (named **providers**)
201201+that will be called with `{name = "foo"; class = "nixos"}` to obtain another aspect
202202+providing a module having the same `class` (`nixos` in our example).
203203+204204+_providers_ are a way of asking: if I have a (`foo`, `nixos`) module what other
205205+aspects can you provide that have `nixos` modules to be imported in `foo`.
206206+207207+> This way, it is aspects *being included* who decide what configuration must
208208+> be used by its caller aspect.
209209+210210+by default, all aspects have a `<aspect>.provides.itself` function that ignores its argument
211211+and always returns the `<aspect>` itself.
212212+This is why you can use the `with config; [ bar baz ]` syntax.
213213+They are actually `[ config.bar.provides.itself config.baz.provides.itself ]`.
214214+215215+but you can also define custom providers that can inspect the argument's `name` and `class`
216216+and return some another aspect accordingly.
217217+218218+```nix
219219+flake.aspects.alice.provides.os-user = { name, class, ... }: {
220220+ # perhaps regexp matching on name or class. eg, match all "hosts" aspects.
221221+ nixos = { };
222222+}
223223+```
224224+225225+the `os-user` provider can be now included in a `requires` list:
226226+227227+```nix
228228+flake.aspects = {config, ...}: {
229229+ home-server.requires = [ config.alice.provides.os-user ];
82230}
83231```
84232