Transactions & Atomicity
Reducers run inside the host reducer transaction and may yield Db directly.
Procedures and HTTP handlers open explicit transactions with Tx or HttpTx.
import * as Effect from "effect/Effect"import * as Stdb from "effect-spacetimedb"
const counter = Stdb.table("counter", { columns: { id: Stdb.string().primaryKey(), value: Stdb.u32().default(0), },})
const Counters = Stdb.StdbGroup.make("Counters") .add(Stdb.StdbFn.reducer("counter_inc", { params: Stdb.struct({ id: Stdb.string() }) })) .add(Stdb.StdbFn.procedure("counter_read", { params: Stdb.struct({ id: Stdb.string() }), returns: Stdb.option(counter.row), }))
const Module = Stdb.StdbModule.make("app").addTables(counter).add(Counters)const { Db, Tx } = Module
const CountersLive = Stdb.StdbBuilder.group(Module, "Counters", { counter_inc: Effect.fn(function* ({ id }) { const db = yield* Db const current = yield* db.counter.id.find(id) yield* db.counter.id.replace({ id, value: (current?.value ?? 0) + 1 }) }), counter_read: Effect.fn(function* ({ id }) { const tx = yield* Tx return yield* tx.run( Effect.gen(function* () { const db = yield* Db return yield* db.counter.id.find(id) }), ) }),})Effect capability scopes make transaction boundaries visible in
the type system. Keep Tx.run(...) and HttpTx.run(...) bodies idempotent and
database-only; put external side effects outside the transaction body.
SpaceTimeDB owns transaction commit, abort, rollback, and optimistic-commit
retry behavior. effect-spacetimedb delegates to the native host withTx call; it
does not add retry logic of its own. An Effect failure inside the body aborts the
transaction. Native host abort outcomes surface as typed errors such as
StdbUniqueAlreadyExistsError, StdbNoSuchRowError, and
StdbAutoIncOverflowError.
HTTP handlers use HttpTx for the same explicit transaction boundary:
import * as Effect from "effect/Effect"import * as Schema from "effect/Schema"import * as Stdb from "effect-spacetimedb"
const counter = Stdb.table("counter", { columns: { id: Stdb.string().primaryKey(), value: Stdb.u32().default(0), },})
const Routes = Stdb.StdbHttpGroup.make("Routes").add( Stdb.StdbHttp.post("counter_inc", "/counter/inc", { request: Schema.Struct({ id: Schema.String }), response: Schema.Void, }),)
const Module = Stdb.StdbModule.make("app").addTables(counter).add(Routes)const { Db, HttpTx } = Module
const RoutesLive = Stdb.StdbBuilder.group(Module, "Routes", { counter_inc: Effect.fn(function* ({ id }) { const tx = yield* HttpTx yield* tx.run( Effect.gen(function* () { const db = yield* Db const current = yield* db.counter.id.find(id) yield* db.counter.id.replace({ id, value: (current?.value ?? 0) + 1, }) }), ) }),})See Runtime Model for the server Effect constraints.
Not affiliated with SpacetimeDB (Clockwork Laboratories) or Effect (Effectful Technologies).