Skip to content
// EFFECT DAYS 2026

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.

7 exports Since v4.1.3 Source

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 | undefined

Encoding

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): string

Encodes 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) => string

Layers

Since v4.1.3

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

interface
Since v4.1.3

The 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

interface
Since v4.1.3

A 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, orDie dies 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 withTransaction never 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)
})
}))