Dependencies
Dependencies make Unit startup order explicit. A Unit declares the ids of other Units that must be available before it initializes or starts.
local Inventory = {}
Inventory.Id = "Inventory"
Inventory.Dependencies = { "Data" }
function Inventory:Init()
self.Data = self:Get("Data")
end
return Inventory
That single line says a lot: Inventory is not a standalone system. It needs the Data Unit. Rivet should boot Data first, and maintainers should read Data before they try to understand Inventory startup.
Dependency Shape
Dependencies is optional. When present, it must be an array of non-empty strings.
Unit.Dependencies = { "Data", "Economy", "Notifications" }
These are invalid:
Unit.Dependencies = "Data" -- not an array
Unit.Dependencies = { "" } -- empty id
Unit.Dependencies = { [2] = "Data" } -- array hole
Unit.Dependencies = { Data = true } -- dictionary, not array
Rivet normalizes missing dependencies to {}. That means a Unit without Dependencies is simply independent.
Boot Order
Rivet uses dependency sorting. Dependencies appear before dependents.
Given this graph:
Data
↑
Inventory
↑
Shop
And this code:
Data.Dependencies = {}
Inventory.Dependencies = { "Data" }
Shop.Dependencies = { "Inventory" }
Rivet runs lifecycle methods in this shape:
Data:Init()
Inventory:Init()
Shop:Init()
Data:Start()
Inventory:Start()
Shop:Start()
On destroy, the order reverses:
Shop:Destroy()
Shop.Clean:Cleanup()
Inventory:Destroy()
Inventory.Clean:Cleanup()
Data:Destroy()
Data.Clean:Cleanup()
If Inventory depends on Data, Inventory shuts down before Data. That gives Inventory:Destroy() a chance to use Data while cleaning up.
Init And Start With Dependencies
Init is the right place to cache dependency references.
function Inventory:Init()
self.Data = self:Get("Data")
end
Start is the right place to begin work that expects every Unit to be initialized.
function Inventory:Start()
for _, player in ipairs(Players:GetPlayers()) do
self:LoadPlayer(player)
end
end
The difference matters. If Inventory:Start() calls into Round, and Round does not depend on Inventory, Round has still completed Init because Rivet runs all Init methods before any Start.
Lookup Rules
Use self:Get(id) inside Units:
function Inventory:Init()
self.Data = self:Get("Data")
end
Use Rivet:Get(id) outside Units after startup:
local Inventory = Rivet:Get("Inventory")
Lookup returns the actual server Unit table on the server. On the client, lookup may return a client proxy for declared server surfaces.
If Inventory:Start() calls self:Get("Data"), lookup may succeed as long as Data exists. But without Inventory.Dependencies = { "Data" }, startup order no longer documents the relationship. Declare the dependency so the graph is visible and destroy order is correct.
Missing Dependencies
If a Unit declares a dependency that was not loaded, Rivet fails startup:
Rivet missing dependency. Unit "Inventory" depends on missing unit "Data". Add a Unit with Id "Data" under config.Roots or remove it from Dependencies.
Check these things:
- Does a Unit with
Id = "Data"exist? - Is the Unit under one of the roots passed to
Rivet.Start? - Did the Unit omit
Id, meaning its ModuleScript name must be"Data"? - Did you typo the dependency string?
- Did startup fail earlier while requiring the dependency Unit?
Circular Dependencies
Rivet rejects cycles.
A.Dependencies = { "B" }
B.Dependencies = { "C" }
C.Dependencies = { "A" }
Startup error:
Rivet circular dependency. Circular Rivet dependency detected: A -> B -> C -> A
Cycles usually mean responsibilities are tangled. Do not solve them by hiding a lookup in Start or moving work to top-level requires. Instead, split one system, introduce a mediator Unit, or invert the relationship through a Signal or callback.
Dependency Design Patterns
Direct Ownership
Use direct dependencies when one Unit clearly owns data another Unit needs.
Inventory.Dependencies = { "Data" }
Shop.Dependencies = { "Data", "Inventory" }
Data owns profiles. Inventory reads items. Shop charges currency and adds items. The graph tells the story.
Event Fanout
If many Units need to react to one event, a direct dependency graph can get noisy. A Notifications or Events Unit can coordinate shared event streams, or the owner Unit can expose Signals to clients while server-side Units call methods directly.
Round.Dependencies = { "Notifications" }
Quests.Dependencies = { "Notifications" }
Read-Only Helpers
Do not turn static helpers into Units just so they can be dependencies. If something is a pure function or static table, require it normally.
local ItemDefinitions = require(ReplicatedStorage.Shared.ItemDefinitions)
Dependencies are for managed Units, not every module import.
Checklist
- Add
Dependencieswhen one Unit requires another Unit's initialized state. - Store dependency references in
Init. - Keep expensive work in
Start. - Avoid circular dependencies by splitting responsibilities.
- Do not use dependency arrays for helper modules.
- Use dependency ids, not file paths.
Next: Lifecycle And Cleanup.