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

Add documentation for types.nix (#28)

authored by oeiuwq.com and committed by

GitHub e0cf6b89 d0a226c8

+122 -9
+122 -9
nix/types.nix
··· 1 1 lib: 2 2 let 3 + # Import the resolve function which handles aspect resolution and dependency injection 3 4 resolve = import ./resolve.nix lib; 4 5 6 + # Top-level aspects container type 7 + # This is the entry point for defining all aspects in a flake 8 + # Structure: aspects.<aspectName> = { ... } 9 + # Makes the entire aspects config available as 'aspects' in module args 10 + # allowing cross-referencing between aspects 5 11 aspectsType = lib.types.submodule ( 6 12 { config, ... }: 7 13 { 8 - freeformType = lib.types.attrsOf (lib.types.either aspectSubmoduleAttrs providerType); 14 + # Allow arbitrary aspect definitions as attributes 15 + # Each aspect can be either: 16 + # - An aspect submodule (aspectSubmoduleAttrs) 17 + # - A provider function (providerType) 18 + freeformType = lib.types.lazyAttrsOf (lib.types.either aspectSubmoduleAttrs providerType); 19 + # Inject the aspects config into _module.args for cross-referencing 9 20 config._module.args.aspects = config; 10 21 } 11 22 ); 12 23 13 - # checks the argument names to be those of a provider function: 24 + # Type checker for provider functions with specific argument patterns 25 + # Valid provider function signatures: 26 + # 1. { class } => aspect-object 27 + # 2. { aspect-chain } => aspect-object 28 + # 3. { class, aspect-chain } => aspect-object 29 + # 4. { class, ... } => aspect-object (with other ignored args) 30 + # 5. { aspect-chain, ... } => aspect-object (with other ignored args) 14 31 # 15 - # { class, aspect-chain } => aspect-object 16 - # { class, ... } => aspect-object 17 - # { aspect-chain, ... } => aspect-object 32 + # This ensures that provider functions receive the proper context when invoked 18 33 functionToAspect = lib.types.addCheck (lib.types.functionTo aspectSubmodule) ( 19 34 f: 20 35 let ··· 29 44 classOnly || chainOnly || both 30 45 ); 31 46 47 + # Provider functions can be: 48 + # 1. A function taking { class, aspect-chain } and returning an aspect (functionToAspect) 49 + # 2. A function taking parameters and returning another provider (curried) 50 + # This allows for parametric aspects and lazy evaluation 32 51 functionProviderType = lib.types.either functionToAspect (lib.types.functionTo providerType); 52 + 53 + # Provider type allows three forms: 54 + # 1. A function provider (functionProviderType) 55 + # 2. An aspect configuration (aspectSubmodule) 56 + # This enables both immediate aspect definitions and deferred/parametric ones 33 57 providerType = lib.types.either functionProviderType aspectSubmodule; 34 58 59 + # Additional validation for aspect submodules to ensure they're not mistyped functions 60 + # An aspectSubmoduleAttrs is either: 61 + # - Not a function at all (plain attribute set) 62 + # - A function with submodule-style arguments (lib, config, options, aspect) 63 + # This prevents accidentally treating provider functions as aspect configs 35 64 aspectSubmoduleAttrs = lib.types.addCheck aspectSubmodule ( 36 65 m: (!builtins.isFunction m) || (isAspectSubmoduleFn m) 37 66 ); 67 + 68 + # Helper to identify if a function is a submodule-style function 69 + # Submodule functions take args like { lib, config, options, aspect, ... } 70 + # Returns true if the function accepts at least one of these special args 38 71 isAspectSubmoduleFn = 39 72 m: 40 73 lib.pipe m [ ··· 49 82 (x: lib.length x > 0) 50 83 ]; 51 84 85 + # Special type that accepts any value but always merges to null 86 + # Used for internal computed values that shouldn't be serialized 87 + # This prevents type errors when values don't have proper types 52 88 ignoredType = lib.types.mkOptionType { 53 89 name = "ignored type"; 54 90 description = "ignored values"; ··· 56 92 check = _: true; 57 93 }; 58 94 95 + # Core aspect definition type 96 + # Each aspect represents a reusable configuration module that can: 97 + # - Define configuration for multiple "classes" (e.g., nixos, home-manager, darwin) 98 + # - Include other aspects as dependencies 99 + # - Provide sub-aspects for selective composition 100 + # - Be parametrized via __functor 59 101 aspectSubmodule = lib.types.submodule ( 60 102 { 61 103 name, ··· 63 105 ... 64 106 }: 65 107 { 108 + # Allow arbitrary class configurations (e.g., nixos, home-manager, etc.) 109 + # Each class maps to a deferred module that will be resolved later 66 110 freeformType = lib.types.attrsOf lib.types.deferredModule; 111 + 112 + # Make the aspect config available as 'aspect' in module args 113 + # This allows modules within the aspect to reference their own aspect 67 114 config._module.args.aspect = config; 115 + 116 + # Create "_" as a shorthand alias for "provides" 117 + # Allows writing: aspect._.foo instead of aspect.provides.foo 118 + # This improves ergonomics for the common case of defining sub-aspects 68 119 imports = [ (lib.mkAliasOptionModule [ "_" ] [ "provides" ]) ]; 120 + 121 + # Human-readable aspect name, defaults to the attribute name 122 + # Used in aspect-chain tracking and for display purposes 69 123 options.name = lib.mkOption { 70 124 description = "Aspect name"; 71 125 default = name; 72 126 type = lib.types.str; 73 127 }; 128 + 129 + # Optional description for documentation purposes 130 + # Defaults to a generic description using the aspect name 74 131 options.description = lib.mkOption { 75 132 description = "Aspect description"; 76 133 default = "Aspect ${name}"; 77 134 type = lib.types.str; 78 135 }; 136 + 137 + # Dependencies: list of other providers this aspect includes 138 + # During resolution, included aspects are merged with this aspect 139 + # Includes can be: 140 + # - Direct aspect references: aspects.otherAspect 141 + # - Parametrized providers: aspects.other.provides.foo "param" 142 + # - Functorized aspects: aspects.otherAspect { param = value; } 143 + # The resolution order matters for module merging semantics 79 144 options.includes = lib.mkOption { 80 145 description = "Providers to ask aspects from"; 81 146 type = lib.types.listOf providerType; 82 147 default = [ ]; 83 148 }; 149 + 150 + # Sub-aspects that can be selectively included by other aspects 151 + # This allows aspects to expose multiple named variants or components 152 + # Creates a fixpoint where provides can reference the aspects in their scope 153 + # The provides scope gets its own 'aspects' arg for internal cross-referencing 84 154 options.provides = lib.mkOption { 85 155 description = "Providers of aspect for other aspects"; 86 156 default = { }; 87 157 type = lib.types.submodule ( 88 158 { config, ... }: 89 159 { 160 + # Allow arbitrary sub-aspect definitions 90 161 freeformType = lib.types.attrsOf providerType; 162 + # Make the provides scope available as 'aspects' for fixpoint references 163 + # This enables provides.foo to reference provides.bar via aspects.bar 91 164 config._module.args.aspects = config; 92 165 } 93 166 ); 94 167 }; 168 + 169 + # Functor enables aspects to be callable like functions 170 + # When defined, calling aspect { param = value; } invokes the functor 171 + # The functor receives: 172 + # 1. The aspect config itself 173 + # 2. The parameters passed by the caller (which must include class and aspect-chain) 174 + # This allows aspects to be parametrized and context-aware 175 + # 176 + # The default functor: 177 + # - Takes the aspect config 178 + # - Takes { class, aspect-chain } parameters 179 + # - Returns the aspect unchanged (identity function with parameter access) 180 + # - The weird `if true || (class aspect-chain) then` is to silence nixf-diagnose 181 + # about unused variables while ensuring they're in scope 95 182 options.__functor = lib.mkOption { 96 183 internal = true; 97 184 visible = false; ··· 100 187 default = 101 188 aspect: 102 189 { class, aspect-chain }: 103 - # silence nixf-diagnose :/ 190 + # silence nixf-diagnose about unused variables 104 191 if true || (class aspect-chain) then aspect else aspect; 105 192 }; 193 + 194 + # Convenience accessor: aspect.modules.<class> automatically resolves 195 + # This is equivalent to calling aspect.resolve { class = "<class>"; } 196 + # Returns a map of all classes with their resolved modules 197 + # 198 + # For example: aspect.modules.nixos == aspect.resolve { class = "nixos"; } 199 + # 200 + # This is computed lazily and uses ignoredType to avoid serialization issues 106 201 options.modules = lib.mkOption { 107 202 internal = true; 108 203 visible = false; 109 204 readOnly = true; 110 205 description = "resolved modules from this aspect"; 111 206 type = ignoredType; 207 + # For each class in the aspect, resolve it with empty aspect-chain 112 208 apply = _: lib.mapAttrs (class: _: config.resolve { inherit class; }) config; 113 209 }; 210 + 211 + # Main resolution function that converts an aspect into a nixpkgs module 212 + # Takes { class, aspect-chain } and returns a resolved module 213 + # - class: The target configuration class (e.g., "nixos", "home-manager") 214 + # - aspect-chain: List of aspects traversed so far (for tracking dependencies) 215 + # 216 + # The resolution process: 217 + # 1. Invokes the aspect config with class and aspect-chain parameters 218 + # This triggers the __functor if defined, allowing parametrization 219 + # 2. Calls resolve.nix to recursively resolve includes 220 + # 3. Returns a module with imports from the aspect and its dependencies 221 + # 222 + # The aspect-chain parameter allows aspects to introspect their dependency tree 223 + # This is useful for debugging and for aspects that need to know their context 114 224 options.resolve = lib.mkOption { 115 225 internal = true; 116 226 visible = false; ··· 123 233 class, 124 234 aspect-chain ? [ ], 125 235 }: 236 + # Invoke config (the aspect) with class and aspect-chain parameters 237 + # This works because config is wrapped with __functor via the submodule system 238 + # Then pass the result to resolve for dependency resolution 126 239 resolve class aspect-chain (config { 127 240 inherit class aspect-chain; 128 241 }); ··· 133 246 in 134 247 { 135 248 inherit 136 - aspectsType 137 - aspectSubmodule 138 - providerType 249 + aspectsType # Main entry point for flake.aspects 250 + aspectSubmodule # Individual aspect definition type 251 + providerType # Type for provider expressions 139 252 ; 140 253 }