chief/dependencies
The Dependencies extension for Chief: topological dependency injection.
chief/dependencies
The Dependencies extension for Chief: topological dependency injection.
Modules declare dependencies with Dependencies.use(moduleScript) markers assigned to fields on their module table. During boot the extension:
- Transitively loads any dependency that wasn't explicitly added to Chief (during
PostLoad). - Topologically sorts the dependency graph (Kahn's algorithm) at
PreInit, so every module'sInitruns after theInitof everything it depends on.Prioritybreaks ties between modules with no ordering constraint between them. - Errors with the full cycle path if a dependency cycle exists, instead of silently producing a broken order.
- Injects the real, required module into every marker field — by the time
Initruns,self.Datais the actual module, not a proxy.
Installation
pesde add chief/dependencies
Usage
Register the extension:
--!strict
local Chief = require('path/to/core')
local Dependencies = require('path/to/dependencies')
Chief.new()
:AddModules(script.Modules:GetChildren())
:AddExtension(Dependencies.new())
:Start()
Declare dependencies as fields on the module table. Cast with typeof(require(...)) for full types — Luau evaluates that at type level only, so it does not cause a runtime circular require:
--!strict
local Dependencies = require('path/to/dependencies')
local ShopModule = {}
ShopModule.Data = Dependencies.use(script.Parent.DataModule) :: typeof(require(script.Parent.DataModule))
function ShopModule.Init (self: Self)
-- self.Data is the real DataModule, and its Init has already run.
self.Data:Get('coins')
end
type Self = typeof(ShopModule)
return ShopModule
API
Dependencies.use(moduleScript: ModuleScript): any
Declares a dependency on another module. Returns a marker table; assign it to a field on your module table and the extension replaces the field with the real module during boot, before any Init runs.
Dependencies.new(): Extension
Creates the Dependencies extension. Add it to Chief with AddExtension. Each instance tracks the state of a single boot — create one per Chief instance rather than sharing an instance between them.
Registration order relative to other module-loading extensions does not matter: ordering and injection run at PreInit, after every extension's PostLoad loading has finished.
Rules
- Markers must be assigned to fields on the module table, never to locals. The extension injects by replacing fields; a local holding a marker can't be reached.
- Circular requires can't happen —
usetakes the ModuleScript, not a require, so module bodies never require each other. Circular dependencies are still impossible to order and are reported as an error (CyclicDependency) with the offending path, e.g.Shop -> Data -> Shop. - Do not
table.freezea module that declares dependencies — injection writes to its fields (FrozenModuleerror otherwise).
Errors
BadArgument:—usereceived something other than a ModuleScript.MissingDependency:— a module declares a dependency that was never loaded (possible when a later-registered extension loads modules after this extension'sPostLoadwalk). Add the dependency to Chief directly.CyclicDependency:— the dependency graph contains a cycle; the message includes the cycle path. Break it by moving the cross-calls out ofInit, or invert one of the dependencies.FrozenModule:— injection target module table is frozen.
License
MIT