Skip to main content

Full API Reference

This page is the precise lookup reference for Rivet's public runtime. If you are learning the framework, start with Rivet Overview, Installation, and the core guides first. Use this page when you need exact method names, accepted shapes, behavior, and error expectations.

Public API scope

The public API is the Rivet module, Rivet.Clean, Rivet.Codec, Rivet.Debug, Unit fields, surface declarations, plugin hooks, and exported types under Rivet.Types. Internal modules such as Loader, Graph, Registry, Runtime, and Network are implementation details even though they exist in the repository.

Package

Wally dependency:

[dependencies]
Rivet = "sqrcy/rivet@1.0.1"

Typical require path in a consuming project:

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Rivet = require(ReplicatedStorage.Packages.Rivet)

Repository examples may use:

local Rivet = require(ReplicatedStorage.Rivet)

Rivet

The Rivet module is the entrypoint.

local Rivet = {
Clean = Clean,
Codec = Codec,
Debug = Debug,
Use = function(plugin) end,
Start = function(config) end,
Get = function(selfOrId, id) end,
Destroy = function() end,
}

Rivet.Start(config)

Starts a Rivet runtime.

Rivet.Start({
Roots = {
ReplicatedStorage.Units,
},
Debug = {
Network = true,
},
})

Signature:

Rivet.Start(config: StartConfig): Rivet

Config:

type StartConfig = {
Roots: { Instance },
Debug: {
Network: boolean?,
}?,
}

Behavior:

  • validates config
  • locks plugin registration
  • loads all Unit ModuleScripts under config.Roots
  • normalizes Unit ids, dependencies, surfaces, and lifecycle methods
  • rejects duplicate ids
  • prepares runtime helpers
  • dependency-sorts Units
  • creates server remotes or client proxies for Client surfaces
  • validates custom codec names used by surfaces
  • runs plugin Init
  • runs every Unit Init in dependency order
  • runs every Unit Start in dependency order
  • runs plugin Start
  • stores the active runtime
  • returns Rivet

Errors:

  • called while a runtime is active
  • config is not a table
  • Roots missing or not an array of Instances
  • invalid Unit module shape
  • duplicate Unit id
  • missing or circular dependencies
  • invalid surfaces
  • missing codec for a custom contract
  • lifecycle or plugin hook failure
One active runtime

Rivet.Start errors if Rivet is already running. Call Rivet.Destroy() before starting again in tests, tools, or benchmarks.

Rivet.Get(id) and Rivet:Get(id)

Returns a started Unit or client proxy by id.

local Inventory = Rivet.Get("Inventory")
local SameInventory = Rivet:Get("Inventory")

Signature:

Rivet.Get(selfOrId: Rivet | string, id: string?): UnitContext

Both function-call and method-call forms are supported. The id must be a non-empty string.

Behavior:

  • errors if Rivet has not started
  • resolves server Unit records first
  • resolves client proxies when available
  • errors if no Unit or proxy exists for the id

Common errors:

Rivet has not started; call Rivet.Start(config) before Rivet:Get(id)
Rivet.Get expects a non-empty unit id
Unknown Rivet unit "Inventory"

Rivet.Destroy()

Destroys the active runtime if one exists.

Rivet.Destroy()

Signature:

Rivet.Destroy(): ()

Behavior:

  • returns without error when no runtime is active
  • clears active runtime state before destroy
  • destroys Units in reverse dependency order
  • calls each Unit's Destroy method if present
  • calls each Unit's Clean:Cleanup()
  • fires plugin OnDestroy
  • unlocks plugin registration
  • reports destroy or cleanup failures

Rivet.Use(plugin)

Registers a plugin before startup.

Rivet.Use(LogPlugin)

Signature:

Rivet.Use(plugin: Plugin): Rivet

Behavior:

  • validates the plugin table
  • validates non-empty unique plugin.Id
  • validates hook fields are functions when present
  • appends the plugin to registration order
  • returns Rivet

Errors:

Rivet.Use expects a plugin table
Rivet plugin Id must be a non-empty string
Rivet duplicate plugin id "LogPlugin"
Rivet plugins must be registered before Rivet.Start()

Rivet.Clean

Rivet.Clean is the cleanup helper constructor.

Rivet.Clean.new()

Creates a standalone cleaner.

local clean = Rivet.Clean.new()

Signature:

Rivet.Clean.new(): Clean

Every Unit also receives self.Clean automatically.

Clean:Add(object, methodName?)

Adds a cleanup task and returns the same object.

local connection = self.Clean:Add(signal:Connect(callback))
self.Clean:Add(folder)
self.Clean:Add(function()
print("cleaned")
end)
self.Clean:Add(resource, "Close")

Signature:

Clean:Add<T>(object: T, methodName: string?): T

Supported task shapes:

ShapeCleanup call
functionobject()
Instanceobject:Destroy()
RBXScriptConnectionobject:Disconnect()
table with Destroyobject:Destroy()
table with Disconnectobject:Disconnect()
table with Cleanupobject:Cleanup()
explicit methodNameobject:methodName()

Errors:

  • object is nil
  • explicit method name is empty
  • cleanup runs and the object cannot be cleaned
  • cleanup runs and the explicit method does not exist

Clean:Remove(object)

Removes the first matching pending cleanup task without running it.

local task = clean:Add(function()
print("will not run")
end)

local removed = clean:Remove(task)

Signature:

Clean:Remove(object: unknown): boolean

Returns true when a task was removed, false otherwise.

Clean:Cleanup()

Runs all pending cleanup tasks in reverse insertion order.

clean:Cleanup()

Signature:

Clean:Cleanup(): ()

If tasks fail, Clean attempts the remaining tasks and reports the first failure.

Clean:Destroy()

Alias for Clean:Cleanup().

clean:Destroy()

Rivet.Codec

Rivet.Codec registers custom network value codecs and applies them at contract boundaries.

Rivet.Codec:Register(id, codec)

Registers an encoder/decoder pair.

Rivet.Codec:Register("Item", {
Encode = function(value)
return {
Id = value.Id,
Count = value.Count,
}
end,
Decode = function(data)
return Item.new(data.Id, data.Count)
end,
})

Signature:

Rivet.Codec:Register(id: string, codec: CodecDefinition): ()

type CodecDefinition = {
Encode: (value: unknown) -> unknown,
Decode: (data: unknown) -> unknown,
}

Rules:

  • id must be a non-empty string
  • ids must be unique for the runtime
  • codec must be a table
  • Encode must be a function
  • Decode must be a function
  • encoded output must contain only remote-safe values
  • cyclic encoded tables are rejected

Use the codec id as a contract name:

GetItem = {
Kind = "Query",
Returns = "Item",
}

Rivet.Debug

Rivet.Debug:GetNetworkStats()

Returns a snapshot of network debug counters.

local stats = Rivet.Debug:GetNetworkStats()

Signature:

Rivet.Debug:GetNetworkStats(): NetworkStats

Stats are populated only when startup enables network debug:

Rivet.Start({
Roots = {
ReplicatedStorage.Units,
},
Debug = {
Network = true,
},
})

Return shape:

type NetworkSurfaceStats = {
Kind: "Query" | "Action" | "Signal",
Calls: number,
Failures: number,
}

type NetworkStats = {
[string]: {
[string]: NetworkSurfaceStats,
},
}

Example:

{
Inventory = {
GetItems = {
Kind = "Query",
Calls = 12,
Failures = 0,
},
EquipItem = {
Kind = "Action",
Calls = 4,
Failures = 1,
},
},
}

When debug is disabled, the method returns an empty table.

Unit API

A Unit module returns a table.

local Unit = {}

Unit.Id = "Inventory"
Unit.Dependencies = { "Data" }
Unit.Surfaces = {}

function Unit:Init()
end

function Unit:Start()
end

function Unit:Destroy()
end

return Unit

Unit.Id

Optional public id.

Unit.Id = "Inventory"

Type:

Id: string?

If omitted, Rivet uses the ModuleScript name. If provided, it must be a non-empty string. Duplicate ids across loaded roots are rejected.

Unit.Dependencies

Optional array of Unit ids that must boot first.

Unit.Dependencies = { "Data", "Economy" }

Type:

Dependencies: { string }?

Rules:

  • must be an array
  • values must be non-empty strings
  • no holes
  • each dependency id must resolve to a loaded Unit
  • cycles are rejected

Unit.Surfaces

Optional surface declaration table.

Unit.Surfaces = {
Client = {
GetItems = "Query",
EquipItem = {
Kind = "Action",
Args = { "string" },
},
ItemAdded = {
Kind = "Signal",
Payload = { "string" },
},
},
Shared = {
"CanStack",
},
}

Supported scopes:

  • Client
  • Shared

Unsupported scopes are rejected.

Unit:Init()

Optional lifecycle method. Runs in dependency order after prepare and before any Unit starts.

function Unit:Init()
self.Data = self:Get("Data")
self.State = {}
end

Use for dependency references and local state.

Unit:Start()

Optional lifecycle method. Runs in dependency order after every Unit has initialized.

function Unit:Start()
self.Clean:Add(Players.PlayerRemoving:Connect(function(player)
self.State[player] = nil
end))
end

Use for active runtime work.

Unit:Destroy()

Optional lifecycle method. Runs during Rivet.Destroy() before self.Clean:Cleanup().

function Unit:Destroy()
table.clear(self.State)
end

Destroy order is reverse dependency order.

Runtime Unit Fields

Rivet attaches these fields before lifecycle methods run.

self.Clean

Unit cleaner.

self.Clean:Add(connection)

Type:

Clean

self:Get(id)

Runtime Unit lookup from inside a Unit.

function Inventory:Init()
self.Data = self:Get("Data")
end

Signature:

self:Get(id: string): UnitContext

self.Client

Server-side Signal helper table for Client Signal surfaces.

self.Client.ItemAdded:Fire(player, itemId)
self.Client.ItemAdded:FireAll(itemId)
self.Client.ItemAdded:FireExcept(player, itemId)

Created when the Unit declares Client Signal surfaces.

Surface API

Client Query

Shorthand:

GetItems = "Query"

Expanded:

GetItems = {
Kind = "Query",
Args = { "string" },
Returns = "table",
}

Server method:

function Unit:GetItems(player: Player, category: string)
return {}
end

Client call:

local result = UnitProxy:GetItems("Weapons")

Client Action

Shorthand:

EquipItem = "Action"

Expanded:

EquipItem = {
Kind = "Action",
Args = { "string" },
}

Server method:

function Unit:EquipItem(player: Player, itemId: string)
end

Client call:

UnitProxy:EquipItem("Sword")

Client Signal

Shorthand:

ItemAdded = "Signal"

Expanded:

ItemAdded = {
Kind = "Signal",
Payload = { "string" },
}

Server fire:

self.Client.ItemAdded:Fire(player, "Sword")
self.Client.ItemAdded:FireAll("Sword")
self.Client.ItemAdded:FireExcept(player, "Sword")

Client listen:

UnitProxy.ItemAdded:Connect(function(itemId)
print(itemId)
end)

Shared Surface

Shared surfaces are metadata only and do not create remotes.

Unit.Surfaces = {
Shared = {
"CanStack",
},
}

function Unit:CanStack(itemId: string): boolean
return true
end

Contract API

Contract tuple fields:

Args: { string }?
Payload: { string }?

Single return field:

Returns: string?

Built-in names:

  • nil
  • boolean
  • number
  • string
  • table
  • Instance
  • Player
  • Vector3
  • CFrame
  • Color3
  • EnumItem
  • buffer
  • any

Custom names require codecs.

Validation behavior:

  • tuple count must match contract count
  • each tuple value must match its expected contract
  • Query return must match Returns
  • any accepts every value
  • Player requires an Instance that IsA("Player")
  • unsupported custom names are allowed only when a codec is registered

Plugin API

Type:

type Plugin = {
Id: string,

Init: ((self: Plugin, rivet: Rivet) -> ())?,
Start: ((self: Plugin, rivet: Rivet) -> ())?,
OnUnitLoaded: ((self: Plugin, unit: UnitContext) -> ())?,
OnUnitPrepared: ((self: Plugin, unit: UnitContext) -> ())?,
OnUnitInit: ((self: Plugin, unit: UnitContext) -> ())?,
OnUnitStart: ((self: Plugin, unit: UnitContext) -> ())?,
OnSurfaceRegistered: ((self: Plugin, unit: UnitContext, surface: SurfaceRecord) -> ())?,
OnNetworkCall: ((self: Plugin, context: unknown) -> ())?,
OnNetworkError: ((self: Plugin, context: unknown) -> ())?,
OnDestroy: ((self: Plugin) -> ())?,

[string]: unknown,
}

Example:

local Plugin = {
Id = "LogPlugin",
}

function Plugin:OnUnitStart(unit)
print("Started", unit.Id)
end

Rivet.Use(Plugin)

Hook order is registration order.

Exported Types

Import:

local Types = require(ReplicatedStorage.Packages.Rivet.Types)

Types.Clean

export type Clean = {
Add: <T>(self: Clean, object: T, methodName: string?) -> T,
Remove: (self: Clean, object: unknown) -> boolean,
Cleanup: (self: Clean) -> (),
Destroy: (self: Clean) -> (),
}

Types.CodecDefinition

export type CodecDefinition = {
Encode: (value: unknown) -> unknown,
Decode: (data: unknown) -> unknown,
}

Types.SurfaceKind

export type SurfaceKind = "Query" | "Action" | "Signal"

Types.SurfaceScope

export type SurfaceScope = "Client" | "Shared"

Types.SurfaceDefinition

export type SurfaceDefinition = {
Kind: SurfaceKind,
Args: { string }?,
Returns: string?,
Payload: { string }?,
}

Types.SurfaceRecord

export type SurfaceRecord = {
Name: string,
Kind: SurfaceKind,
Scope: SurfaceScope,
Args: { string }?,
Returns: string?,
Payload: { string }?,
}

Types.NormalizedSurfaces

export type NormalizedSurfaces = {
Client: { [string]: SurfaceRecord },
Shared: { [string]: SurfaceRecord },
}

Types.UnitContext

export type UnitContext = {
Id: string,
Dependencies: { string },
Surfaces: NormalizedSurfaces,
Clean: Clean,
Client: { [string]: unknown }?,
Get: (self: UnitContext, id: string) -> UnitContext,

Init: ((self: UnitContext) -> ())?,
Start: ((self: UnitContext) -> ())?,
Destroy: ((self: UnitContext) -> ())?,

[string]: unknown,
}

Types.StartConfig

export type StartConfig = {
Roots: { Instance },
Debug: {
Network: boolean?,
}?,
}

Types.NetworkStats

export type NetworkSurfaceStats = {
Kind: SurfaceKind,
Calls: number,
Failures: number,
}

export type NetworkStats = {
[string]: {
[string]: NetworkSurfaceStats,
},
}

Types.Rivet

export type Rivet = {
Clean: CleanConstructor,
Codec: CodecRegistry,
Debug: Debug,
Use: (plugin: Plugin) -> Rivet,
Start: (config: StartConfig) -> Rivet,
Get: (selfOrId: Rivet | string, id: string?) -> UnitContext,
Destroy: () -> (),
}

Generated Remote Metadata

Server remotes are created under:

ReplicatedStorage.RivetRemotes

For each Client surface, Rivet creates:

RivetRemotes
UnitId
SurfaceName
__RivetKind
__RivetContracts
Args
1
2
Returns
Payload
1

This metadata is primarily for client proxy creation and debugging. User code normally interacts through Rivet:Get("UnitId"), not by touching remotes directly.

Quick Recipes

Start Server Runtime

Rivet.Start({
Roots = {
ReplicatedStorage.Units,
},
})

Start Client Proxy Runtime

ReplicatedStorage:WaitForChild("RivetRemotes")

Rivet.Start({
Roots = {},
})

Add A Query

Unit.Surfaces = {
Client = {
GetState = {
Kind = "Query",
Returns = "table",
},
},
}

function Unit:GetState(player: Player)
return {}
end

Add An Action

Unit.Surfaces = {
Client = {
SetReady = {
Kind = "Action",
Args = { "boolean" },
},
},
}

function Unit:SetReady(player: Player, ready: boolean)
end

Add A Signal

Unit.Surfaces = {
Client = {
ReadyChanged = {
Kind = "Signal",
Payload = { "Player", "boolean" },
},
},
}

self.Client.ReadyChanged:FireAll(player, true)

Register A Plugin

Rivet.Use({
Id = "LifecycleLogger",
OnUnitStart = function(_self, unit)
print("Started", unit.Id)
end,
})

Register A Codec

Rivet.Codec:Register("Item", {
Encode = function(item)
return {
Id = item.Id,
Count = item.Count,
}
end,
Decode = function(data)
return Item.new(data.Id, data.Count)
end,
})

Error Index

AreaExample message
Start stateRivet has already started; call Rivet.Destroy() before starting again
Not startedRivet has not started; call Rivet.Start(config) before Rivet:Get(id)
ConfigRivet.Start requires config.Roots to be an array of Instances
Unit returnRivet unit "..." must return a table
Duplicate idDuplicate Rivet unit id "Inventory"
Missing dependencyUnit "Inventory" depends on missing unit "Data"
Circular dependencyCircular Rivet dependency detected: A -> B -> C -> A
Invalid surfaceClient.GetItems ... method "GetItems" does not exist
Contract failureInventory.EquipItem arg #1 expected string, got number
Missing codecregister a codec with Rivet.Codec:Register("Item", codec)
Codec encodeRivet codec encode failed at ...
Codec decodeRivet codec decode failed at ...
CleanRivet Clean:Add cannot add nil
PluginRivet plugin "LogPlugin" OnUnitStart failed: ...

For debugging guidance, see Debugging And Errors.