Rivet.DurableObject reference
DurableObject
Section titled “DurableObject”Source:
packages/rivet/src/DurableObject.ts
A Durable Object on Rivet: a single-threaded, stateful instance per name, with its own SQLite database, key-value storage and alarm on the Rivet control plane. Each instance runs as a Rivet actor.
The implementation has the same two phases as Cloudflare.DurableObject:
the outer Effect runs once per node and resolves bindings, and the inner
Effect runs once per instance and returns its RPC methods, fetch and
alarm.
Where it runs is a pool Layer provided to the outer Effect —
RivetDocker.SwarmPool("Default"), RivetAWS.EcsPool("Default") — and the
pool is given the control plane. Every Durable Object given the same pool
runs on the same nodes.
yield* Counter from any runtime — a Cloudflare Worker, a Lambda, a
container, another Durable Object — returns its namespace. Calls go
through the control plane’s gateway over plain fetch, which creates the
instance in its pool on first use, so callers need no RivetKit and no
route to the nodes.
Given only main, it is external: a RivetKit program, async or
@rivetkit/effect, that its pool bundles and runs as it is. It needs a
pool of its own, and has no bindings or yield* calls.
An instance’s calls run in parallel, storage included: unlike Cloudflare,
there is no input gate. Make a read-then-write atomic with
state.storage.transaction(...), or provide Workers.InputGate (from
alchemy/Workers) to run code written for Cloudflare’s gate unchanged.
DurableObject: Declaring a Durable Object
Section titled “DurableObject: Declaring a Durable Object”export const Cluster = RivetDocker.SwarmControlPlane("Rivet");export const Default = RivetDocker.SwarmPool("Default").pipe(Layer.provide(Cluster));
export default class Counter extends Rivet.DurableObject<Counter>()( "Counter", { main: import.meta.url }, Effect.gen(function* () { return Effect.gen(function* () { const state = yield* Rivet.DurableObjectState; return { // Calls run in parallel: the transaction keeps the read and // the write together. increment: () => state.storage.transaction( Effect.gen(function* () { const count = ((yield* state.storage.get<number>("count")) ?? 0) + 1; yield* state.storage.put("count", count); return count; }), ), }; }); }).pipe(Effect.provide(Default)),) {}DurableObject: Calling a Durable Object
Section titled “DurableObject: Calling a Durable Object”export default class Api extends Cloudflare.Worker<Api>()( "Api", { main: import.meta.url }, Effect.gen(function* () { const counters = yield* Counter; return { fetch: Effect.gen(function* () { const count = yield* counters.getByName("visits").increment(); return HttpServerResponse.text(String(count)); }), }; }),) {}DurableObject: Declaring and implementing separately
Section titled “DurableObject: Declaring and implementing separately”export class Counter extends Rivet.DurableObject<Counter, CounterShape>()("Counter") {}
export const CounterLive = Counter.make( { main: import.meta.url, handler: "CounterLive" }, Effect.gen(function* () { return Effect.gen(function* () { const state = yield* Rivet.DurableObjectState; return { get: () => state.storage.get<number>("count").pipe(Effect.map((n) => n ?? 0)), }; }); }).pipe(Effect.provide(Default)),);DurableObject: Deploying RivetKit Actors
Section titled “DurableObject: Deploying RivetKit Actors”export const Counters = RivetDocker.SwarmPool("Counters").pipe(Layer.provide(Cluster));
export const Counter = Rivet.DurableObject("Counter", { main: "./src/actors.ts" }).pipe( Effect.provide(Counters),);