I’ll try to use more concrete terms…
A cell
is like a module of code consisting of smaller sub-modules called “organelles”. An organelle is a well typed Nix value whose type is verified at eval time. There are many organelle types available, from “data” for things that would render to JSON or another data format, to “runnables” as they are called, basically something that can actually be executed. “functions” are reusable Nix functions and “devshells” are self explanatory.
Cells can reference other cells from within the same repo, or cells from external flake inputs which also implement a std
api. Cells cannot reference the flake self
indiscriminately, and this is by design to avoid crossing otherwise well defined boundaries between the various cells and their various organelles.
A cell is really just a directory containing organelles. An organelle can be either a “named.nix” file or a directory “named/default.nix”. Organelles are only exposed if they are listed in the API spec. This spec is where we define what types the organelles inside the cell should be. So in the linked example, if I make a directory cells/someCell
, according to the api spec I can them make an organelle cells/someCell/lib.nix
which should contain an attribute set containing only Nix functions.
Every organelle takes an attribute set as an argument with exactly two values { inputs, cell }
and returns another attribute set of values whose type much match what was specified in the API. inputs
is a reference to the flake inputs, excluding self. You can still access the sourceInfo
of self
from here, but that’s it, which is what I meant about enforcing code boundaries. One cannot just access self.packages.whatever
, instead one must access a given package through it’s proper std
ized API.
If the inputs implement std
as well, their cells can be accessed without having to explicitly specify which system to use like so: inputs.someFlake.someCell.someOrganelle
, whichever system is currently being evaluated will be assumed.
cell
is a reference to the current cell being defined in order to access other organelles from the same cell, and inputs.cells
is a reference to any other cells that exist in the same repository.
In this way, it is possible to define and maintain clear code boundaries of well typed values, and a clear separation of concerns. You can still write Nix values however you want inside the organelle directories so long as they conform to the type spec in the end.