CloudflareCluster
Runs Effect Cluster entities on Cloudflare Durable Objects.
On this path every entity instance is one Durable Object: the Worker encodes
an (entityType, entityId) address into a Durable Object name, resolves the
object stub with getByName, and the object's SQLite storage is the system
of record. There is no shard routing, no runner fleet, and no external
message storage; layer provides the cluster Sharding service on top of
the Durable Object namespace bindings instead of Sharding.layer.
Decoding
Decodes a Durable Object name produced by encodeName back into its entity address parts.
Details
Returns undefined for names that were not produced by encodeName,
including non-canonical length prefixes. A Durable Object uses this to
recover its own address from ctx.id.name.
Signature
declare const decodeName: (name: string) => ClusterName | undefinedEncoding
Returns the singleton name of the seed singleton that ClusterCron.make
registers for the cron job with the given name.
When to use
Use to wake a ClusterCron from a Worker Cron Trigger, by passing the
result to cluster.wake with Alchemy or by prefixing it with Singleton/
for getByName on the singleton namespace with Wrangler.
Details
The result is "ClusterCron/<name>", the singleton name without the
Singleton/ Durable Object name prefix.
Gotchas
A singleton runs only when its Durable Object is woken, so a ClusterCron
never schedules its first run until a Cron Trigger wakes this singleton.
Each later run is scheduled through a destination alarm.
Signature
declare function clusterCronSingletonName(name: string): stringEncodes an entity address into the Durable Object name used with
getByName.
Details
The name is the entity type length-prefixed as `${type.length}:${type}${id}`,
which keeps (type, id) pairs collision-free without restricting the
characters an entity id may contain. Workflow, queue, and singleton names use
the same scheme on their own namespaces.
Signature
declare const encodeName: (type: string, id: string) => stringLayers
Builds the cluster on Cloudflare Durable Objects.
Details
Provides the cluster Sharding service on top of the four same-Worker
Durable Object namespace bindings, plus the WorkflowEngine backed by the
workflow class and the PersistedQueueFactory backed by the queue class, so
the DurableQueue user API works out of the box. Entity.client resolves
an entity to its Durable Object by
encoding (type, id) with encodeName and calling getByName; an
unknown entity type or a bad encode fails at the Worker before any Durable
Object is contacted. Entity handlers registered with Entity.toLayer are
recorded per EntityType at Worker init and built once per Durable Object
wake; workflow handlers registered with Workflow.toLayer follow the same
pattern on the workflow class.
Signature
declare function layer(options: LayerOptions): Layer<Sharding | WorkflowEngine | PersistedQueueFactory>LayerOptions
interfaceThe Durable Object namespace bindings and entity definitions the cluster layer is built from.
Details
entities is the complete set of entity definitions the Worker serves.
Handlers are attached per entity type with Entity.toLayer; a client or
handler registration for an entity type outside this set fails at the
Worker, before any Durable Object is contacted.
keepAliveHeartbeat (default 30 seconds) is the interval of the alarm an
entity Durable Object keeps armed while it holds Entity.keepAlive or runs
a persisted request. Cloudflare never re-creates an evicted object on its
own, so this alarm is what rebuilds a kept-alive entity and replays an
interrupted request after a restart or deploy. It bounds recovery latency;
it is not a liveness check.
Signature
interface LayerOptions { readonly entities: readonly Array<Entity<any, any>>; readonly entityNamespace: DurableObjectNamespace; readonly keepAliveHeartbeat?: Input; readonly queueNamespace: DurableObjectNamespace; readonly singletonNamespace: DurableObjectNamespace; readonly workflowNamespace: DurableObjectNamespace;}Models
ClusterName
interfaceA Durable Object name decoded back into its entity address parts.
Signature
interface ClusterName { readonly id: string; readonly type: string;}Services
An SqlClient on the current entity Durable Object's own SQLite storage.
When to use
Use to keep rows next to the entity's mailbox from an entity handler build
or handler, such as state that must survive eviction and commit atomically
with each step. Register the handlers with Entity.toLayer and read the
client with DurableObjectSqlClient.orDie in the build.
Details
Each entity Durable Object provides this service, an @effect/sql-sqlite-do
client, to its handler build and handlers, including handlers registered
with Entity.toLayerQueue. Yielding the tag directly leaves it as a layer
requirement; orDie has none. It is a separate tag, so any
SqlClient from the Worker layer (a D1 database, say) stays reachable inside
the handlers. To run code written against SqlClient.SqlClient, provide this
client as that service locally.
withTransaction uses the object's storage transaction, so several rows
commit or roll back together. Rows live and die with the entity's Durable
Object and cannot be queried from outside it.
Gotchas
- Outside an entity Durable Object,
orDiedies on the first handler build. In tests, provide this service from any SQL layer. - Table names starting with
cluster_are reserved for the entity mailbox. - A row, like an encoded message, cannot exceed 2 MB.
- A storage transaction closes the Durable Object's input gate until it
finishes, so timers and I/O awaited inside
withTransactionnever resume. Keep transactions to SQL work. - Mailbox writes wait for an open transaction to finish, so a rollback never takes another handler's stored reply with it.
Signature
declare class DurableObjectSqlClient extends Shape<"@effect/platform-cloudflare/CloudflareCluster/DurableObjectSqlClient", SqlClient, this> { constructor(_: never); static readonly orDie: Effect<SqlClient>;}Example Storing rows from entity handlers
import { CloudflareCluster } from "@effect/platform-cloudflare"import { Effect } from "effect"
const JournalLayer = Journal.toLayer(Effect.gen(function*() { const sql = yield* CloudflareCluster.DurableObjectSqlClient.orDie yield* sql`CREATE TABLE IF NOT EXISTS journal (entry TEXT NOT NULL)`.pipe(Effect.orDie) return Journal.of({ Append: ({ payload }) => sql`INSERT INTO journal (entry) VALUES (${payload.entry})`.pipe(Effect.orDie) })}))