fake.modules transposition for aspect-oriented Dendritic Nix. with cross-aspect dependencies. Discussions: https://oeiuwq.zulipchat.com/join/nqp26cd4kngon6mo3ncgnuap/
dendrix.oeiuwq.com/Dendritic.html
dendritic
nix
aspect
oriented
1<!-- Badges -->
2
3<p align="right">
4 <a href="https://github.com/sponsors/vic"><img src="https://img.shields.io/badge/sponsor-vic-white?logo=githubsponsors&logoColor=white&labelColor=%23FF0000" alt="Sponsor Vic"/>
5 </a>
6 <a href="https://vic.github.io/dendrix/Dendritic-Ecosystem.html#vics-dendritic-libraries"> <img src="https://img.shields.io/badge/Dendritic-Nix-informational?logo=nixos&logoColor=white" alt="Dendritic Nix"/> </a>
7 <a href="https://github.com/vic/flake-aspects/actions">
8 <img src="https://github.com/vic/flake-aspects/actions/workflows/test.yml/badge.svg" alt="CI Status"/> </a>
9 <a href="LICENSE"> <img src="https://img.shields.io/github/license/vic/flake-aspects" alt="License"/> </a>
10</p>
11
12# `<aspect>.<class>` Transposition for Dendritic Nix
13
14> `flake-aspects` and [vic](https://bsky.app/profile/oeiuwq.bsky.social)'s [dendritic libs](https://vic.github.io/dendrix/Dendritic-Ecosystem.html#vics-dendritic-libraries) made for you with Love++ and AI--. If you like my work, consider [sponsoring](https://github.com/sponsors/vic)
15
16In [aspect-oriented](https://vic.github.io/dendrix/Dendritic.html) [Dendritic](https://github.com/mightyiam/dendritic) setups, it is common to expose modules using the structure `flake.modules.<class>.<aspect>`.
17
18However, for many users, a transposed attribute set, `<aspect>.<class>`, can be more intuitive. It often feels more natural to nest classes within aspects rather than the other way around.
19
20This project provides a small, dependency-free [`transpose`](nix/default.nix) primitive that is powerful enough to implement [cross-aspect dependencies](nix/aspects.nix) for any Nix configuration class. It also includes a [flake-parts module](nix/flakeModule.nix) that transforms `flake.aspects` into `flake.modules`.
21
22<table>
23<tr>
24<td>
25<b><code>flake.aspects</code></b>
26
27```nix
28{
29 vim-btw = {
30 nixos = ...;
31 darwin = ...;
32 homeManager = ...;
33 nixvim = ...;
34 };
35 tiling-desktop = {
36 nixos = ...;
37 darwin = ...;
38 };
39 macos-develop = {
40 darwin = ...;
41 hjem = ...;
42 };
43}
44```
45
46</td>
47<td>
48<img width="400" height="400" alt="image" src="https://github.com/user-attachments/assets/dd28ce8d-f727-4e31-a192-d3002ee8984e" />
49</td>
50<td>
51<code>flake.modules</code>
52
53```nix
54{
55 nixos = {
56 vim-btw = ...;
57 tiling-desktop = ...;
58 };
59 darwin = {
60 vim-btw = ...;
61 tiling-desktop = ...;
62 macos-develop = ...;
63 };
64 homeManager = {
65 vim-btw = ...;
66 };
67 hjem = {
68 macos-develop = ...;
69 };
70 nixvim = {
71 vim-btw = ...;
72 };
73}
74```
75
76</td>
77</tr>
78</table>
79
80Unlike `flake.modules.<class>.<aspect>` which is _flat_, aspects can be nested forming a _tree_ by using the `provides` (short alias: `_`) attribute. Each aspect can also specify a list of `includes` of other aspects, forming a _graph_ of dependencies.
81
82```nix
83{
84 flake.aspects = {
85 gaming = {
86 nixos = {};
87 darwin = {};
88
89 _.emulation = { aspect, ... }: {
90 nixos = {};
91
92 _.nes.nixos = {};
93 _.gba.nixos = {};
94
95 includes = with aspect._; [ nes gba ];
96 };
97 };
98 };
99}
100```
101
102## Usage
103
104The library can be used in two ways: as a flakes-independent dependency-free utility or as a `flake-parts` module.
105
106### As a Dependency-Free Library (`./nix/default.nix`)
107
108The core of this project is the [`transpose`](nix/default.nix) function, which is powerful enough to implement cross-aspect dependencies for any Nix configuration class. It accepts an optional `emit` function that can be used to ignore items, modify them, or generate multiple items from a single input.
109
110```nix
111let transpose = import ./nix/default.nix { lib = pkgs.lib; }; in
112transpose { a.b.c = 1; } # => { b.a.c = 1; }
113```
114
115This `emit` function is utilized by the [`aspects`](nix/aspects.nix) library to manage module dependencies between different aspects of the same class. Both `transpose` and `aspects` are independent of flakes.
116
117#### Use aspects without flakes.
118
119It is possible to use the aspects system as a library, [without flakes](https://github.com/vic/flake-aspects/blob/b94d806/checkmate.nix#L76). This can be used, for example, to avoid poluting flake-parts' `flake.modules` or by libraries that want to create own isolated aspects scope. For examples of this, see our own [flake-parts integration](nix/flakeModule.nix), and how [`den`](https://github.com/vic/den) creates its own [`den.aspects` scope](https://github.com/vic/den/blob/main/nix/scope.nix) independent of `flakes.aspects`/`flake.modules`.
120
121### As a Dendritic Flake-Parts Module (`flake.aspects` option)
122
123When used as a `flake-parts` module, the `flake.aspects` option is automatically transposed into `flake.modules`, making the modules available to consumers of your flake.
124
125```nix
126# The code in this example can (and should) be split into different Dendritic modules.
127{ inputs, ... }: {
128 imports = [ inputs.flake-aspects.flakeModule ];
129 flake.aspects = {
130
131 sliding-desktop = {
132 description = "Next-generation tiling windowing";
133 nixos = { }; # Configure Niri on Linux
134 darwin = { }; # Configure Paneru on macOS
135 };
136
137 awesome-cli = {
138 description = "Enhances the environment with the best of CLI and TUI";
139 nixos = { }; # OS services
140 darwin = { }; # Apps like ghostty, iTerm2
141 homeManager = { }; # Fish aliases, TUIs, etc.
142 nixvim = { }; # Plugins
143 };
144
145 work-network = {
146 description = "Work VPN and SSH access";
147 nixos = {}; # Enable OpenSSH
148 darwin = {}; # Enable macOS SSH server
149 terranix = {}; # Provision VPN
150 hjem = {}; # Home: link .ssh keys and configs
151 };
152
153 };
154}
155```
156
157#### Declaring Cross-Aspect Dependencies
158
159Aspects can declare dependencies on other aspects using the `includes` attribute. This allows you to compose configurations in a modular way.
160
161Dependencies are defined at the aspect level, not within individual modules. When a module from an aspect is evaluated (e.g., `flake.modules.nixos.development-server`), the library resolves all dependencies for the `nixos` class and imports the corresponding modules if they exist.
162
163In the example below, the `development-server` aspect includes the `alice` and `bob` aspects. This demonstrates how to create a consistent development environment across different operating systems and user configurations.
164
165```nix
166{
167 flake.aspects = { aspects, ... }: {
168 development-server = {
169 # This aspect now includes modules from 'alice' and 'bob'.
170 includes = with aspects; [ alice bob ];
171
172 # Without flake-aspects, you would have to do this manually for each class.
173 # nixos.imports = [ inputs.self.modules.nixos.alice ];
174 # darwin.imports = [ inputs.self.modules.darwin.bob ];
175 };
176
177 alice = {
178 nixos = {};
179 homeManager = {};
180 };
181
182 bob = {
183 darwin = {};
184 hjem = {};
185 };
186 };
187}
188```
189
190Creating the final OS configurations is outside the scope of this library—for that, see [`vic/den`](https://github.com/vic/den). However, exposing them would look like this:
191
192```nix
193{ inputs, ... }:
194{
195 flake.nixosConfigurations.fooHost = inputs.nixpkgs.lib.nixosSystem {
196 system = "x86_64-linux";
197 modules = [ inputs.self.modules.nixos.development-server ];
198 };
199
200 flake.darwinConfigurations.fooHost = inputs.darwin.lib.darwinSystem {
201 system = "aarch64-darwin";
202 modules = [ inputs.self.modules.darwin.development-server ];
203 };
204}
205```
206
207### Advanced Aspect Dependencies: Providers
208
209Dependencies are managed through a powerful abstraction called **providers**. A provider is a value that returns an aspect object, which can then supply modules to the aspect that includes it.
210
211A provider can be either a static aspect object or a function that dynamically returns one. This mechanism enables sophisticated dependency chains, conditional logic, and parameterization.
212
213#### Default Provider (`__functor`)
214
215Each aspect is itself a provider via its hidden option `__functor` (see `nix/types.nix`). You can include aspects directly.
216
217```nix
218# A 'foo' aspect that depends on 'bar' and 'baz' aspects.
219flake.aspects = { aspects, ... }: {
220 foo.includes = with aspects; [ bar baz ];
221}
222```
223
224#### Custom Providers
225
226You can define custom providers to implement more complex logic. A provider function receives the current `class` (e.g., `"nixos"`) and the `aspect-chain` (the list of aspects that led to the call). This allows a provider to act as a conditional proxy or router for dependencies.
227
228In this example, the `kde-desktop` aspect defines a custom `karousel` provider that only returns a module if certain conditions are met:
229
230```nix
231flake.aspects.kde-desktop._.karousel = { aspect-chain, class }:
232 if someCondition aspect-chain && class == "nixos" then { nixos = { ... }; } else { };
233```
234
235The `karousel` provider can then be included in another aspect:
236
237```nix
238flake.aspects = { aspects, ... }: {
239 home-server.includes = [ aspects.kde-desktop._.karousel ];
240}
241```
242
243This pattern allows an included aspect to determine which configuration its caller should use, enabling a tree of dependencies where each node can be either static or parametric.
244
245#### Parameterized Providers
246
247Providers can be implemented as curried functions, allowing you to create parameterized modules. This is useful for creating reusable configurations that can be customized at the inclusion site.
248
249For real-world examples, see how `vic/den` defines [auto-imports](https://github.com/vic/den/blob/main/modules/aspects/batteries/import-tree.nix) and [home-managed](https://github.com/vic/den/blob/main/modules/aspects/batteries/home-managed.nix) parametric aspects.
250
251```nix
252flake.aspects = { aspects, ... }: {
253 system = {
254 nixos.system.stateVersion = "25.11";
255 _.user = userName: {
256 darwin.system.primaryUser = userName;
257 nixos.users.${userName}.isNormalUser = true;
258 };
259 };
260
261 home-server.includes = [
262 aspects.system
263 (aspects.system._.user "bob")
264 ];
265}
266```
267
268See the `aspects."test provides"` and `aspects."test provides using fixpoints"` sections in the [checkmate tests](checkmate.nix) for more examples of chained providers.
269
270#### The `_` Alias for `provides`
271
272For convenience, `_` is an alias for `provides`. This allows for more concise chaining of providers. For example, `foo.provides.bar.provides.baz` can be written as `foo._.bar._.baz`.
273
274## Testing
275
276```shell
277nix run github:vic/checkmate#fmt --override-input target .
278nix flake check github:vic/checkmate --override-input target . -L
279```