Lifecycle Reducers
Special reducers handle system events during the database lifecycle.
Init Reducer
Runs once when the module is first published or when the database is cleared.
- Rust
- C#
- TypeScript
#[reducer(init)]
pub fn init(ctx: &ReducerContext) -> Result<(), String> {
log::info!("Database initializing...");
// Set up default data
if ctx.db.settings().count() == 0 {
ctx.db.settings().insert(Settings {
key: "welcome_message".to_string(),
value: "Hello, SpacetimeDB!".to_string(),
})?;
}
Ok(())
}[SpacetimeDB.Reducer(ReducerKind.Init)]
public static void Init(ReducerContext ctx)
{
Log.Info("Database initializing...");
// Set up default data
if (ctx.Db.settings.Count == 0)
{
ctx.Db.settings.Insert(new Settings
{
Key = "welcome_message",
Value = "Hello, SpacetimeDB!"
});
}
}spacetimedb.init((ctx) => {
console.log('Database initializing...');
// Set up default data
if (ctx.db.settings.count === 0) {
ctx.db.settings.insert({
key: 'welcome_message',
value: 'Hello, SpacetimeDB!'
});
}
});The init reducer:
- Cannot take arguments beyond
ReducerContext - Runs when publishing with
spacetime publish - Runs when clearing with
spacetime publish -c - Failure prevents publishing or clearing
Client Connected
Runs when a client establishes a connection.
- Rust
- C#
- TypeScript
#[reducer(client_connected)]
pub fn on_connect(ctx: &ReducerContext) -> Result<(), String> {
log::info!("Client connected: {}", ctx.sender);
// ctx.connection_id is guaranteed to be Some(...)
let conn_id = ctx.connection_id.unwrap();
// Initialize client session
ctx.db.sessions().insert(Session {
connection_id: conn_id,
identity: ctx.sender,
connected_at: ctx.timestamp,
})?;
Ok(())
}[SpacetimeDB.Reducer(ReducerKind.ClientConnected)]
public static void OnConnect(ReducerContext ctx)
{
Log.Info($"Client connected: {ctx.Sender}");
// ctx.ConnectionId is guaranteed to be non-null
var connId = ctx.ConnectionId!.Value;
// Initialize client session
ctx.Db.sessions.Insert(new Session
{
ConnectionId = connId,
Identity = ctx.Sender,
ConnectedAt = ctx.Timestamp
});
}spacetimedb.clientConnected((ctx) => {
console.log(`Client connected: ${ctx.sender}`);
// ctx.connectionId is guaranteed to be defined
const connId = ctx.connectionId!;
// Initialize client session
ctx.db.sessions.insert({
connection_id: connId,
identity: ctx.sender,
connected_at: ctx.timestamp
});
});The client_connected reducer:
- Cannot take arguments beyond
ReducerContext ctx.connection_idis guaranteed to be present- Failure disconnects the client
- Runs for each distinct connection (WebSocket, HTTP call)
Client Disconnected
Runs when a client connection terminates.
- Rust
- C#
- TypeScript
#[reducer(client_disconnected)]
pub fn on_disconnect(ctx: &ReducerContext) -> Result<(), String> {
log::info!("Client disconnected: {}", ctx.sender);
// ctx.connection_id is guaranteed to be Some(...)
let conn_id = ctx.connection_id.unwrap();
// Clean up client session
ctx.db.sessions().connection_id().delete(&conn_id);
Ok(())
}[SpacetimeDB.Reducer(ReducerKind.ClientDisconnected)]
public static void OnDisconnect(ReducerContext ctx)
{
Log.Info($"Client disconnected: {ctx.Sender}");
// ctx.ConnectionId is guaranteed to be non-null
var connId = ctx.ConnectionId!.Value;
// Clean up client session
ctx.Db.sessions.ConnectionId.Delete(connId);
}spacetimedb.clientDisconnected((ctx) => {
console.log(`Client disconnected: ${ctx.sender}`);
// ctx.connectionId is guaranteed to be defined
const connId = ctx.connectionId!;
// Clean up client session
ctx.db.sessions.connection_id.delete(connId);
});The client_disconnected reducer:
- Cannot take arguments beyond
ReducerContext ctx.connection_idis guaranteed to be present- Failure is logged but doesn't prevent disconnection
- Runs when connection ends (close, timeout, error)
Scheduled Reducers
Reducers can be triggered at specific times using scheduled tables. See Scheduled Tables for details on:
- Defining scheduled tables
- Triggering reducers at specific timestamps
- Running reducers periodically
- Canceling scheduled executions
Scheduled Reducer Context
Scheduled reducer calls originate from SpacetimeDB itself, not from a client. Therefore:
ctx.senderwill be the module's own identityctx.connection_idwill beNone/null/undefined