Skip to content

Sinks and reduce

A sink on a kind collects the elements that aspects give, and reduces them to one value. of checks each element.

ReduceValueError
list (default)the elements in scope ordernone
setthe elements without duplicatesnone
onethe single element0 or 2+ elements
mergea deep attrsettwo different leaves at one path
entries[ { from; value; } ]: each value with the scope that gave itnone
a function[ { from; value; } ] -> { value; errors; }the errors you give
modules/sinks.nix
# A sink collects elements and reduces them. `of` checks each element.
{ yen, lib, ... }:
let
inherit (yen.lib.reduce)
list
set
one
merge
entries
;
sorted = es: {
value = lib.sort (a: b: a < b) (map (e: e.value) es);
errors = [ ];
}; # your own reduce
in
{
yen.kinds.host.sinks = {
motd = {
reduce = list;
of = lib.types.str;
};
ports = {
reduce = set;
of = lib.types.port;
};
admin.reduce = one;
nginx.reduce = merge;
owners.reduce = entries;
groups.reduce = sorted;
};
# every sink of a host, read as args, to one flake output
yen.aspects.report = {
at = yen.kinds.host;
out =
{
host,
motd,
ports,
admin,
nginx,
owners,
groups,
...
}:
{
${host.name} = {
inherit
motd
ports
admin
nginx
groups
;
owners = map (e: e.from.kind) owners;
};
};
};
}

Two aspects give elements:

modules/aspects.nix
{ yen, ... }:
{
yen.aspects.base = {
at = yen.kinds.host;
motd = "welcome";
ports = 22;
admin = "alice";
nginx.http.gzip = true;
owners = "ops";
groups = "wheel";
};
yen.aspects.web = {
at = yen.host.web;
motd = "this host serves web";
ports = 22; # a duplicate: `set` keeps one
nginx.http.servers.www.port = 80; # `merge` joins the attrsets
groups = "audio";
};
}

And each sink reduces them:

result
{
admin = "alice";
groups = [
"audio"
"wheel"
];
motd = [
"welcome"
"this host serves web"
];
nginx = {
http = {
gzip = true;
servers = {
www = {
port = 80;
};
};
};
};
owners = [
"host"
];
ports = [
22
];
}

A failed reduce is an error record. The other sinks still work:

variants/conflicts.nix
# Each line breaks one sink rule.
{ yen, ... }:
{
yen.aspects.mistakes = {
at = yen.host.web;
admin = "bob"; # `one`: two admins
ports = 70000; # `of`: not a port
nginx.http.gzip = false; # `merge`: two different leaves at one path
};
}
result
[
{
class = "fixable";
code = "one";
error = "one: got 2 elements";
from = "fleet:main/host:web";
got = 2;
path = [ ];
sink = "admin";
want = 1;
}
{
class = "fixable";
code = "merge";
error = "merge conflict at http.gzip";
from = "fleet:main/host:web";
path = [
"http"
"gzip"
];
sink = "nginx";
}
{
class = "fixable";
code = "error";
error = "sink ports: element of aspect mistakes from fleet:main/host:web fails `of`";
from = "fleet:main/host:web";
path = [ ];
}
]

A reduce is any function from [ { from; value; } ] to { value; errors; }. Write one when the merge order is the point: ranked layers, per-key strategies with provenance, or an export store where a scope can only write its own entries. The engine sets from. An element cannot say where it comes from, so a scope cannot write for another.

Contribute Community Sponsor