Skip to content

Scheduled Tables

SpacetimeDB: Scheduled Tables ↗

Scheduled tables are private by default and reserve scheduledId and scheduledAt. A scheduled endpoint receives { data: table.row }.

import * as Effect from "effect/Effect"
import * as Stdb from "effect-spacetimedb"
const reminderSchedule = Stdb.scheduledTable("reminderSchedule", {
columns: {
note: Stdb.string(),
},
})
const Reminders = Stdb.StdbGroup.make("Reminders").add(
Stdb.StdbFn.scheduledProcedure("reminderFire", {
table: reminderSchedule,
}),
)
const Module = Stdb.StdbModule.make("app")
.addTables(reminderSchedule)
.add(Reminders)
const RemindersLive = Stdb.StdbBuilder.group(Module, "Reminders", {
reminderFire: Effect.fn(function* ({ data }) {
yield* Effect.log(`reminder: ${data.note}`)
}),
})

To seed schedule rows inside a reducer or transaction body, use the generated .schedule(...) helper. It fills the auto-increment sentinel for you.

yield* db.reminderSchedule.schedule({
scheduledAt: Stdb.ScheduleAt.interval("5 minutes"),
note: "wake up",
})
Schedule formHost behavior
Stdb.ScheduleAt.at(...) or after(...)One-shot row. Reducers consume it after execution; procedures consume it before execution.
Stdb.ScheduleAt.interval(...)Row persists and re-fires until deleted.

For fixed wall-clock or tick alignment, prefer self-rescheduling one-shot rows: handle a Time row, insert the next Time row, then return. Use Interval when simple persistent re-fire spacing is enough.

Stdb.scheduledTable(...) creates the reserved scheduled_id/scheduled_at columns and StdbFn.scheduledReducer(...) or StdbFn.scheduledProcedure(...) binds exactly one typed handler to the table.