Three ways to build Actors
An Actor is Rivet’s unit of state: one named program, with its own data, that Rivet runs on demand. There are three ways to write one, and Alchemy deploys all three to the same control planes and pools.
Durable Objects: Alchemy’s design
Section titled “Durable Objects: Alchemy’s design”A Rivet.DurableObject is an Actor written as an Effect, with the same shape as a Cloudflare Durable Object:
export default class Room extends Rivet.DurableObject<Room>()( "Room", { main: import.meta.url }, Effect.gen(function* () { return Effect.gen(function* () { const state = yield* Rivet.DurableObjectState; return { join: () => Effect.gen(function* () { const visitors = ((yield* state.storage.get<number>("visitors")) ?? 0) + 1; yield* state.storage.put("visitors", visitors); return visitors; }), }; }); }).pipe(Effect.provide(Default)),) {}It’s the default, because it’s part of your Stack the way every other resource is. It has bindings: in its Construction phase a room can yield* another Durable Object, a bucket or a database, and Alchemy wires the access up when it deploys. And any runtime calls it by yield* Room, with typed methods, without knowing where it runs.
The tutorial and the Durable Object, Storage and Calling a Durable Object chapters all use this form.
RivetKit Actors: Rivet’s own API
Section titled “RivetKit Actors: Rivet’s own API”RivetKit is Rivet’s own library. Its Actors are plain TypeScript, with state, actions, and features of their own like realtime connections and broadcast events:
import { actor, setup } from "rivetkit";
export const counter = actor({ state: { count: 0 }, actions: { increment: (c, x: number) => { c.state.count += x; c.broadcast("newCount", c.state.count); return c.state.count; }, },});
export const registry = setup({ use: { counter } });
registry.start();Alchemy deploys that file as it is. Point a Rivet.DurableObject at it, and give it a pool of its own:
import * as Rivet from "@alchemy.run/rivet";import * as RivetDocker from "@alchemy.run/rivet/Docker";import * as Effect from "effect/Effect";import * as Layer from "effect/Layer";import { Cluster } from "./cluster.ts";
export const Counters = RivetDocker.SwarmPool("Counters").pipe(Layer.provide(Cluster));
export const Counter = Rivet.DurableObject("Counter", { main: "./src/actors.ts" }).pipe( Effect.provide(Counters),);Use it when you already have RivetKit Actors, or need a RivetKit feature Durable Objects don’t offer. See RivetKit Actors.
RivetKit Effect Actors: Rivet’s Effect SDK
Section titled “RivetKit Effect Actors: Rivet’s Effect SDK”@rivetkit/effect is Rivet’s Effect SDK, in beta. Actors declare their actions as schemas and implement them as Layers:
export const Add = Action.make("Add", { payload: { amount: Schema.Number }, success: Schema.Number,});
export const Counter = Actor.make("Counter", { actions: [Add] });A program, src/main.ts, implements them and serves them with Registry.serve. Alchemy deploys that program as it is, the same way:
import * as Rivet from "@alchemy.run/rivet";import * as RivetDocker from "@alchemy.run/rivet/Docker";import * as Effect from "effect/Effect";import * as Layer from "effect/Layer";import { Cluster } from "./cluster.ts";
export const Counters = RivetDocker.SwarmPool("Counters").pipe(Layer.provide(Cluster));
export const Counter = Rivet.DurableObject("Counter", { main: "./src/main.ts" }).pipe( Effect.provide(Counters),);Use it when you’ve built on Rivet’s Effect SDK. See RivetKit Effect Actors.
What the other two give up
Section titled “What the other two give up”A RivetKit program is code Alchemy doesn’t look inside. It deploys the program, but it can’t wire up what the program uses, so there are no bindings: the program reaches other resources with its own configuration. And there’s no yield* from your other runtimes; callers use RivetKit’s own client, as Rivet’s docs describe.
A RivetKit program also runs its own process on every node of its pool, so it needs a pool to itself. A pool given a RivetKit program and anything else fails to deploy with Rivet.PoolProgramConflict.
The same control planes and pools
Section titled “The same control planes and pools”All three are a Rivet.DurableObject given a pool: a Durable Object passes its implementation, and a RivetKit program passes only its entry file. So all three run on any control plane and any pool, on any cloud, and move between them the same way.
Next, Durable Object: how Alchemy’s form is written.