Procedures
Procedures are declared in callable groups with params and a return value type.
import * as Effect from "effect/Effect"import * as Stdb from "effect-spacetimedb"
const user = Stdb.table("user", { public: true, columns: { id: Stdb.string().primaryKey(), name: Stdb.string(), },})
const Users = Stdb.StdbGroup.make("Users").add( Stdb.StdbFn.procedure("user_get", { params: Stdb.struct({ id: Stdb.string() }), returns: Stdb.option(user.row), }),)
const Module = Stdb.StdbModule.make("app").addTables(user).add(Users)const { Db, Tx } = Module
const UsersLive = Stdb.StdbBuilder.group(Module, "Users", { user_get: Effect.fn(function* ({ id }) { const tx = yield* Tx return yield* tx.run( Effect.gen(function* () { const db = yield* Db return yield* db.user.id.find(id) }), ) }),})Procedure handlers cannot yield Db directly. Use the module’s
Tx runner and keep transaction bodies pure database work because the native
runtime may retry them after optimistic commit conflicts.
Outbound HTTP
Section titled “Outbound HTTP”A procedure is the only place a module can make an outbound request. Yield the
module’s Http accessor outside any transaction — the host refuses a request
while a transaction is open, so fetch first, then open the transaction that
records the result.
const { Http, Tx, Db } = Module
const syncLive = Stdb.StdbBuilder.group(Module, "Sync", { sync_profile: Effect.fn(function* ({ userId }) { const http = yield* Http const response = yield* http.fetch(`https://example.test/users/${userId}`) const profile = yield* http.json<{ readonly name: string }>(response)
const tx = yield* Tx return yield* tx.run( Effect.gen(function* () { const db = yield* Db yield* db.user.userId.replace({ userId, name: profile.name }) }), ) }),})Failures arrive as StdbHostCallError in the error channel, tagged with the
operation (http.fetch, http.json, and so on).
Two host policies decide whether a request is allowed at all, and both are the operator’s, not the module’s:
- Egress filtering. Requests to loopback, link-local, and private or
otherwise special-purpose addresses are refused —
127.0.0.1,10/8,172.16/12,192.168/16, the IPv6 equivalents, and the rest of the special-purpose registry. Ordinary server builds enforce this; only a purpose-built test binary relaxes it for loopback. A module cannot reach a service on its own host or elsewhere inside a private network. Where a hostname resolves to both a blocked and a routable address, the routable one is used. - A global off switch. A standalone operator can disable module-originated
HTTP entirely with
[module-http] enabled = falsein the server’sconfig.toml. It defaults to enabled and takes a server restart to change. With it off, every request fails withmodule outbound HTTP requests are disabled.
Requests time out after 30 seconds unless you pass a shorter one, and the host clamps any requested timeout to 180 seconds.
Scheduling
Section titled “Scheduling”Scheduled reducers and procedures are declared with the callable group API and
backed by a scheduled table. The scheduled endpoint receives
{ data: table.row }; insert future work with the generated table
.schedule(row) helper from a reducer or transaction body.
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, allowExternalCallers: false, }),)
const Module = Stdb.StdbModule.make("app") .addTables(reminderSchedule) .add(Reminders)
const { Db } = Module
const RemindersLive = Stdb.StdbBuilder.group(Module, "Reminders", { reminderFire: Effect.fn(function* ({ data }) { yield* Effect.logInfo(`reminder: ${data.note}`) }),})
const enqueueReminder = Effect.gen(function* () { const db = yield* Db yield* db.reminderSchedule.schedule({ scheduledAt: Stdb.ScheduleAt.interval("5 minutes"), note: "wake up", })})Use Stdb.ScheduleAt.interval(...) for persistent re-fire rows,
Stdb.ScheduleAt.at(...) for one-shot absolute times,
Stdb.ScheduleAt.after(ctx.timestamp, duration) for one-shot relative work, and
Stdb.ScheduleAt.nextCron(cron, ctx.timestamp) for cron-derived one-shots.
See Scheduled Tables for the table
side of the contract, the host’s dispatch semantics, and why a scheduled
procedure that does external work between transactions has to claim its work
rows in the transaction that selects them.
Not affiliated with SpacetimeDB (Clockwork Laboratories) or Effect (Effectful Technologies).