Cloudflare backend
pulsify-cloud-server/cloudflare runs Pulsify Cloud on Cloudflare Workers. A stateless Worker authenticates every request; one Durable Object per root holds the root's documents, grants and sockets in its own SQLite database.
Why Durable Objects
- Automatic scaling. Each root is its own object, so capacity grows with the number of roots. There is no shared database to size.
- Global distribution. An object is created near the first request for its root, which is usually its owner.
- Pay as you go. Sockets use the hibernation API: an idle root is evicted from memory while its sockets stay open, and wakes up on the next frame. Heartbeat pings are answered by the runtime (
setWebSocketAutoResponse) without waking it. - Serialized writes. An object processes one event at a time, so appends, acknowledgements and broadcasts of a root are ordered without locks.
1. Install
bun add pulsify-core pulsify-cloud pulsify-cloud-server
bun add -d wrangler
2. Write the Worker
Two exports: the Durable Object class and the fetch handler.
// worker.ts
export { PulsifyCloudRoot, cloudWorker as default } from "pulsify-cloud-server/cloudflare";
Or mount the cloud routes next to your own:
// worker.ts, when the Worker also serves other routes
import { handleCloudRequest, type PulsifyCloudEnv } from "pulsify-cloud-server/cloudflare";
export { PulsifyCloudRoot } from "pulsify-cloud-server/cloudflare";
export default {
async fetch(request: Request, env: PulsifyCloudEnv) {
const { pathname } = new URL(request.url);
if (pathname.startsWith("/v1/")) return handleCloudRequest(request, env);
return new Response("Hello");
},
};
3. Configure Wrangler
The binding must be named PULSIFY_ROOTS and the class must use SQLite storage (new_sqlite_classes).
// wrangler.jsonc
{
"name": "pulsify-cloud",
"main": "worker.ts",
"compatibility_date": "2026-11-05",
"durable_objects": {
"bindings": [{ "name": "PULSIFY_ROOTS", "class_name": "PulsifyCloudRoot" }]
},
"migrations": [{ "tag": "v1", "new_sqlite_classes": ["PulsifyCloudRoot"] }]
// Optional data residency for every root:
// "vars": { "PULSIFY_JURISDICTION": "eu" }
}
4. Set the secrets and deploy
openssl rand -base64 48 | bunx wrangler secret put PULSIFY_TOKEN_SECRET
openssl rand -base64 48 | bunx wrangler secret put PULSIFY_API_KEY
bunx wrangler deploy
# https://pulsify-cloud.<account>.workers.dev → your PULSIFY_URL
Use the same PULSIFY_TOKEN_SECRET in the backend that signs tokens. Keep PULSIFY_API_KEY on your servers. Then follow the tutorial with the Worker URL.
Local development
# .dev.vars (never commit)
PULSIFY_TOKEN_SECRET="a local secret of at least thirty-two characters"
PULSIFY_API_KEY="a local api key of at least thirty-two characters"
bunx wrangler dev # http://localhost:8787, Durable Objects and SQLite included
curl http://localhost:8787/v1/health
# {"ok":true}
curl -X PUT http://localhost:8787/v1/roots/alice/grants \
-H "Authorization: Bearer $PULSIFY_API_KEY" \
-d '{"subject":"alice","prefix":"alice","role":"owner"}'
What runs where
| Piece | Role |
|---|---|
| Worker | Routes /v1/roots/{root}/…, verifies the token or API key, negotiates the envelope, strips the credential and forwards to the root's object. |
PulsifyCloudRoot | Wraps CloudRoot from pulsify-cloud-server over the object's SQLite: patch rows, the grant table and the subscription table that survives hibernation. |
| Socket attachment | Connection ID, subject and envelope, restored after wake-up. |
PULSIFY_JURISDICTION places every root in a jurisdiction such as eu. Set it before the first deployment: the jurisdiction is part of the object ID, so changing it later points every root to a new, empty object.
Limits and costs
- A root is one object: one thread and one SQLite database, with the per-object storage limit of your Cloudflare plan. Split very large or very busy namespaces into several roots.
- Cloudflare bills Durable Object requests, duration and storage. Every WebSocket message is a request; hibernated sockets add no duration.
- Frames are limited to 8 MiB by Pulsify; uploads are chunked well below that.
bun run test:cloud --backend cloudflare # this repository: workerd + conformance suite