By the end of Part 5 you have a Worker
deploying to multiple stages from CI. From here, the Cloudflare
track adds one stateful primitive at a time. First up: a Counter
Durable Object that keeps a per-key count in transactional storage,
exposes RPC methods, and streams a sequence of numbers back to the
caller.
A Durable Object is a globally-unique stateful instance addressed
by name (or by id). Like Workers, it has two Effect.gen blocks:
the outer one runs at init time, and the inner one runs every time
a new instance starts up and returns the public API.
Create src/counter.ts with the smallest possible DO — empty
public API, no state yet:
Provides a way to write effectful code using generator functions, simplifying
control flow and error handling.
When to Use
gen allows you to write code that looks and behaves like synchronous
code, but it can handle asynchronous tasks, errors, and complex control flow
(like loops and conditions). It helps make asynchronous code more readable
and easier to manage.
The generator functions work similarly to async/await but with more
explicit control over the execution of effects. You can yield* values from
effects and return the final result at the end.
Provides a way to write effectful code using generator functions, simplifying
control flow and error handling.
When to Use
gen allows you to write code that looks and behaves like synchronous
code, but it can handle asynchronous tasks, errors, and complex control flow
(like loops and conditions). It helps make asynchronous code more readable
and easier to manage.
The generator functions work similarly to async/await but with more
explicit control over the execution of effects. You can yield* values from
effects and return the final result at the end.
Each DO instance has its own key/value storage, backed by SQLite.
Pull the current count out of storage in the inner init so it
survives restarts and hibernation:
returnEffect.gen(function* () {
conststate=yield*Cloudflare.DurableObjectState;
let count = (yield*state.storage.get<number>("count")) ??0;
return {};
});
Cloudflare.DurableObjectState is the same per-instance handle
Cloudflare exposes for storage, setAlarm, acceptWebSocket,
and friends. We’ll use it more in the next part for WebSockets.
Any function you return from the inner Effect that produces an
Effect becomes a typed RPC method. Add one to mutate the count
and one to read it:
returnEffect.gen(function* () {
conststate=yield*Cloudflare.DurableObjectState;
let count = (yield*state.storage.get<number>("count")) ??0;
return {};
return {
increment: () =>
Effect.gen(function* () {
count +=1;
yield*state.storage.put("count", count);
return count;
}),
get: () =>Effect.succeed(count),
};
});
Workers will see counter.increment() returning
Effect<number> and counter.get() returning Effect<number>,
fully type-checked through the Cloudflare RPC machinery.
returnHttpServerResponse.text("Hello from my Worker!");
}),
};
}),
);
counters.getByName(name) returns a typed stub: the Worker sees
increment(): Effect<number> and get(): Effect<number> exactly as
you defined them. Calling .increment() round-trips through
Cloudflare’s RPC machinery to the durable instance.
RPC isn’t limited to single values — any Effect (or Stream) you
return becomes a typed RPC method. Let’s add a tick(n) method that
emits a sequence of numbers 100ms apart, then expose an HTTP route
that streams them back to the client.
The DO produces values lazily, the runtime ferries each chunk back
to the Worker, and the Worker pipes them straight onto the HTTP
response — all type-checked end-to-end.
Next you’ll teach a Durable Object to
accept WebSocket connections —
and learn how Cloudflare hibernates idle DOs while keeping
connections alive.