···75757676## Usage
77777878+The library can be used in two ways: as a dependency-free utility or as a `flake-parts` module.
7979+7880### As a Dependency-Free Library (`./nix/default.nix`)
79818080-The [`transpose`](nix/default.nix) library accepts an optional `emit` function that can be used to ignore items, modify them, or generate multiple items from a single input.
8282+The 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.
81838284```nix
8385let transpose = import ./nix/default.nix { lib = pkgs.lib; }; in
8486transpose { a.b.c = 1; } # => { b.a.c = 1; }
8587```
86888787-This `emit` function is utilized by the [`aspects`](nix/aspects.nix) library (both libraries are independent of flakes) to manage cross-aspect, same-class module dependencies.
8989+This `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.
88908991### As a Dendritic Flake-Parts Module (`flake.aspects` option)
90929191-The `flake.aspects` option is transposed into `flake.modules`.
9393+When used as a `flake-parts` module, the `flake.aspects` option is automatically transposed into `flake.modules`, making the modules available to other parts of your flake.
92949395```nix
9496# The code in this example can (and should) be split into different Dendritic modules.
···124126125127#### Declaring Cross-Aspect Dependencies
126128127127-`flake.aspects` also allows aspects to declare dependencies among themselves.
129129+Aspects can declare dependencies on other aspects using the `includes` attribute. This allows you to compose configurations in a modular way.
128130129129-Each module can have its own `imports`, but aspect dependencies are defined at the aspect level, not the module level. Dependencies are eventually resolved to modules and are imported only if they exist.
131131+Dependencies 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.
130132131131-In the example below, the `development-server` aspect can be applied to both Linux and macOS hosts. Note that `alice` uses `nixos` + `homeManager`, while `bob` uses `darwin` + `hjem`.
132132-133133-The `development-server` aspect addresses a usability concern by configuring the same development environment on different operating systems. When applied to a NixOS machine, the `alice.nixos` module will likely configure the `alice` user; there is no corresponding NixOS user for `bob`.
133133+In 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.
134134135135```nix
136136{
137137 flake.aspects = { aspects, ... }: {
138138 development-server = {
139139+ # This aspect now includes modules from 'alice' and 'bob'.
139140 includes = with aspects; [ alice bob ];
140141141141- # Without flake-aspects, you would normally do:
142142+ # Without flake-aspects, you would have to do this manually for each class.
142143 # nixos.imports = [ inputs.self.modules.nixos.alice ];
143144 # darwin.imports = [ inputs.self.modules.darwin.bob ];
144145 };
145146146147 alice = {
147148 nixos = {};
149149+ homeManager = {};
148150 };
149151150152 bob = {
151153 darwin = {};
154154+ hjem = {};
152155 };
153156 };
154157}
155158```
156159157157-Creating OS configurations is outside the scope of this library - for that, see [`vic/den`](https://github.com/vic/den) -. Exposing os configurations might look like this:
160160+Creating 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:
158161159162```nix
160163{ inputs, ... }:
···171174}
172175```
173176174174-#### Advanced Aspect Dependencies
177177+### Advanced Aspect Dependencies: Providers
175178176176-An aspect can declare a `includes` list:
179179+Dependencies 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.
180180+181181+A 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.
182182+183183+#### The Default Provider: `provides.itself`
184184+185185+Every aspect automatically has a default provider called `itself`, located at `<aspect>.provides.itself`. This provider simply returns the aspect itself.
186186+187187+The `with aspects; [ bar baz ]` syntax is a convenient shorthand that relies on this default:
177188178189```nix
179190# A 'foo' aspect that depends on 'bar' and 'baz' aspects.
180191flake.aspects = { aspects, ... }: {
181181- foo.includes = [ aspects.bar aspects.baz ];
192192+ foo.includes = with aspects; [ bar baz ];
193193+ # This is equivalent to:
194194+ # foo.includes = [ aspects.bar.provides.itself aspects.baz.provides.itself ];
182195}
183196```
184197185185-Cross-aspect dependencies work as follows:
186186-187187-When a module like `flake.modules.nixos.foo` is requested (for example, included in a `nixosConfiguration`), a corresponding module is computed from `flake.aspects.foo.nixos`.
188188-189189-`flake.aspects.foo.includes` is a list of functions (providers). A **provider** is either a constant `<aspect-object>` or a function `{ class, aspect-chain } => <aspect-object>`. They are called with `{ aspect-chain = [ aspects.foo ]; class = "nixos" }`, functions can inspect the chain of aspects that have led to the call. These providers return an aspect object that contains a module of the same `class` (in this case, `nixos`). Providers allow us to have a tree of aspects where each provided aspect can be either static or parametric.
190190-191191-Providers answer the question: given we have `nixos` modules from `[foo]` aspects, what other aspects can provide `nixos` modules that need to be imported?
198198+#### Custom Providers
192199193193-This means that the included aspect determines which configuration its caller should use.
200200+You 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.
194201195195-By default, all aspects have an `<aspect>.provides.itself` provider function that always returns the `<aspect>` itself. This is why `with aspects; [ bar baz ]` works: it is shorthand for `[ aspects.bar.provides.itself aspects.baz.provides.itself ]`. It is possible to override the default provider, by setting `__functor`, see how [test](https://github.com/vic/flake-aspects/blob/4f88b4ecefbe46ccfa5d9cfa11451a88be169a70/checkmate.nix#L270) or [vic/den](https://github.com/vic/den/blob/def1c396e7ce884578d6589391cca8a4c6a650d3/nix/aspects-config.nix#L55) do it.
196196-197197-##### Dynamic modules using provider function's `{ aspect-chain, class }` argument.
198198-199199-You can also define custom providers that inspect the `aspect-chain` and `class` values and return a set of modules accordingly. This allows providers to act as conditional proxies or routers for dependencies.
202202+In this example, the `kde-desktop` aspect defines a custom `karousel` provider that only returns a module if certain conditions are met:
200203201204```nix
202205flake.aspects.kde-desktop.provides.karousel = { aspect-chain, class }:
···211214}
212215```
213216214214-##### Parametrized modules from providers.
217217+This 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.
218218+219219+#### Parameterized Providers
215220216216-Providers can be curried (but *must* use explicit argument names). This lets you have
217217-modules parametrized by some values, outside the aspect scope. For a real-world
218218-usage of this feature, see how `vic/den` [defines](https://github.com/vic/den/blob/def1c396e7ce884578d6589391cca8a4c6a650d3/nix/aspects-config.nix#L40) `flake.aspects.default.host` and their [use](https://github.com/vic/den/blob/def1c396e7ce884578d6589391cca8a4c6a650d3/templates/default/modules/_example/aspects.nix#L32).
221221+Providers can be implemented as curried functions, allowing you to create parameterized modules. All arguments must be explicitly named. This is useful for creating reusable configurations that can be customized at the inclusion site.
222222+223223+For a real-world example, see how `vic/den` [defines](https://github.com/vic/den/blob/def1c396e7ce884578d6589391cca8a4c6a650d3/nix/aspects-config.nix#L40) `flake.aspects.default.host` and its [usage](https://github.com/vic/den/blob/def1c396e7ce884578d6589391cca8a4c6a650d3/templates/default/modules/_example/aspects.nix#L32).
219224220225```nix
221226flake.aspects = { aspects, ... }: {
···224229 provides.user = { userName }: { aspect-chain, class }: {
225230 darwin.system.primaryUser = userName;
226231 nixos.users.${userName}.isNormalUser = true;
227227- }
232232+ };
228233 };
229234230235 home-server.includes = [
···234239}
235240```
236241237237-See `aspects."test provides"` [checkmate tests](checkmate.nix) for more examples on chained providers.
242242+See the `aspects."test provides"` and `aspects."test provides using fixpoints"` sections in the [checkmate tests](checkmate.nix) for more examples of chained providers.
243243+244244+#### The `_` Alias for `provides`
245245+246246+For 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`.
238247239248## Testing
240249
+9-1
checkmate.nix
···163163 {
164164 name = "aspectTwo.foo";
165165 description = "aspectTwo foo provided";
166166- includes = [ aspects.aspectThree.provides.moo ];
166166+ includes = [
167167+ aspects.aspectThree.provides.moo
168168+ aspects.aspectTwo.provides.baz
169169+ ];
167170 classOne.bar = [ "two:${class}:${lib.concatStringsSep "/" (lib.map (x: x.name) aspect-chain)}" ];
168171 classTwo.bar = [ "foo class two not included" ];
169172 };
···171174 provides.bar = {
172175 # classOne is missing on bar
173176 classTwo.bar = [ "bar class two not included" ];
177177+ };
178178+ # _ is an shortcut alias of provides.
179179+ _.baz = {
180180+ # classOne is missing on bar
181181+ classTwo.bar = [ "baz" ];
174182 };
175183 };
176184 aspectThree.provides.moo =