Skip to content

feat: add Cloudflare Workers deployment with Durable Objects#24

Open
galligan wants to merge 6 commits intoEveryInc:mainfrom
galligan:galligan/cloudflare-deployment
Open

feat: add Cloudflare Workers deployment with Durable Objects#24
galligan wants to merge 6 commits intoEveryInc:mainfrom
galligan:galligan/cloudflare-deployment

Conversation

@galligan
Copy link

@galligan galligan commented Mar 14, 2026

Summary

Adds an alternative deployment target for the Proof editor using Cloudflare Workers + Durable Objects at apps/proof-cloudflare/. Each document is managed by its own Durable Object instance, providing:

  • Real-time collaborative editing via Hocuspocus-compatible Yjs WebSocket sync
  • Full agent bridge — 27 HTTP endpoints matching the Express server's contract
  • SPA hosting at /d/:slug with static asset serving via Cloudflare Assets
  • Document creation at root / and POST /documents with D1 catalog

Also includes:

  • docs/DEPLOYMENT.md — unified deployment guide covering Express and Workers
  • docs/adr/2026-03-cloudflare-workers-deployment.md — decision record
  • .github/workflows/deploy-cloudflare.yml — manual-trigger CI/CD workflow
  • Deployment-agnostic callouts in AGENT_CONTRACT.md, agent-docs.md, proof.SKILL.md
  • Module-level JSDoc in CF worker source matching upstream conventions

Why Cloudflare Workers

Proof's workload is document-scoped. Each document has its own collab session, marks, connected viewers. Durable Objects map 1:1 to this model — each document gets an isolated runtime with its own SQLite and WebSocket handler.

Dimension Express Workers + DO
Scaling Vertical (single process) Per-document isolation
State One SQLite file, all docs DO SQLite per document + D1 catalog
Failure blast radius Process crash = all docs DO crash = one document
Cold start None ~25-50ms
Horizontal scaling Architecture rewrite Built-in

The Express server remains the reference deployment. The Worker is an alternative, not a replacement.

Architecture

Worker (index.ts)
├── Static assets, SPA serving, doc creation, discovery
├── /api/agent/:slug/* → Durable Object
├── /documents/:slug/* → Durable Object
└── /ws/:slug → Durable Object

DocumentSession (Durable Object)
├── Yjs Y.Doc with SQLite persistence + compaction
├── Hocuspocus WebSocket protocol
├── Agent bridge HTTP routes (state, edit, marks, events, presence)
└── DODocumentStorage (events, idempotency, access control)

Route Parity

Full agent bridge parity (21 routes) plus document lifecycle routes (15 routes). See apps/proof-cloudflare/README.md for the complete parity table.

Express-only routes not implemented: /api/capabilities, /d/:slug/bridge/* mount, legacy /api/documents create. These are either Express middleware concerns or legacy paths superseded by canonical routes.

Verification

  • npm test — 74/74 pass, 0 failures (no changes to shared code)
  • TypeScript — CF worker compiles clean; pre-existing errors in src/editor/schema/proof-marks.ts are unrelated
  • Manual testing against deployed worker

Test plan

  • Visit root URL → creates doc, redirects to /d/:slug
  • Editor loads, WebSocket connects, real-time collab between tabs
  • GET /api/agent/:slug/state returns markdown matching browser content
  • POST /api/agent/:slug/edit modifies document visible in browser
  • POST /api/agent/:slug/marks/comment adds comment visible in state
  • Idempotency: same edit with same key returns cached response
  • POST /share/markdown creates document from raw markdown
  • GET /.well-known/agent.json returns discovery metadata
  • GET /health returns 200
  • wrangler dev local development works

🤘🏻 In-collaboration-with: Claude Code

@galligan galligan force-pushed the galligan/cloudflare-deployment branch from 8243860 to c8f4967 Compare March 14, 2026 02:09
@galligan galligan force-pushed the galligan/cloudflare-deployment branch from c8f4967 to fbba72f Compare March 14, 2026 12:11
…roof-cloudflare

- Add docs/DEPLOYMENT.md covering Express and Cloudflare Workers deployment
- Add docs/adr/2026-03-cloudflare-workers-deployment.md
- Rename apps/proof-sdk-cloudflare → apps/proof-cloudflare to match naming convention (proof-example)
- Add deployment-agnostic notes to AGENT_CONTRACT.md, agent-docs.md, proof.SKILL.md
- Add module-level JSDoc to CF worker source files matching upstream conventions
- Update README.md workspace layout and docs section

🤘🏻 In-collaboration-with: [Claude Code](https://claude.com/claude-code)
@galligan
Copy link
Author

Follow-up commit adds documentation and convention alignment for the CF Worker deployment.

What changed:

  • docs/DEPLOYMENT.md — Unified deployment guide covering both Express and Cloudflare Workers. Environment variables, setup steps, architecture comparison, verification checklist. Matches the style of docs/agent-docs.md.

  • docs/adr/2026-03-cloudflare-workers-deployment.md — ADR documenting the decision to add a Workers target, how Proof SDK concepts map to Cloudflare primitives (D1 for catalog, DO for per-document state, Workers Assets for static files), and consequences for the project.

  • Rename apps/proof-sdk-cloudflareapps/proof-cloudflare — The existing app is proof-example, not proof-sdk-example. Dropped the redundant -sdk to match the convention.

  • Deployment-agnostic doc updatesAGENT_CONTRACT.md, docs/agent-docs.md, and docs/proof.SKILL.md all had hardcoded localhost:4000 examples without noting they apply to any deployment target. Added brief callouts and linked the new deployment guide. Also cleaned up stale absolute paths in proof.SKILL.md's References section.

  • JSDoc alignment — Added module-level comments and one-line JSDoc on exported functions in the CF worker source files (index.ts, document-ops.ts, agent-edit-ops.ts) to match the upstream convention: terse purpose statements, no @param/@returns tags, internal helpers left undocumented. Files like document-engine.ts and storage-interface.ts already followed this pattern.

workflow_dispatch-only — no auto-deploy on push. Builds the frontend
bundle and runs wrangler deploy. Add CLOUDFLARE_API_TOKEN secret to
enable. One-line upgrade to auto-deploy documented in DEPLOYMENT.md.

🤘🏻 In-collaboration-with: [Claude Code](https://claude.com/claude-code)
@galligan
Copy link
Author

Added a GitHub Actions workflow for Cloudflare Workers deployment (.github/workflows/deploy-cloudflare.yml).

Why manual-only: This is an SDK repo. Most contributors and forks won't have a Cloudflare account or care about deploying the Worker — auto-deploying on every merge would be noisy at best and broken at worst. workflow_dispatch keeps it opt-in: if you're running your own deployment, add the CLOUDFLARE_API_TOKEN secret and trigger when you're ready.

What it does: npm cinpm run build (Vite frontend bundle) → wrangler deploy from apps/proof-cloudflare/. Wrangler handles D1 migrations and DO class migrations automatically, so there's no separate migration step.

Upgrading to auto-deploy: For anyone running a fork who wants continuous deployment, the docs (docs/DEPLOYMENT.md, CI/CD section) walk through adding a push trigger with a paths filter so doc-only changes don't trigger deploys. It's a ~10 line addition to the workflow file.

Matches proof-example convention. Documents architecture, quick start,
all agent bridge and document routes with Express parity status, and
file inventory.

🤘🏻 In-collaboration-with: [Claude Code](https://claude.com/claude-code)
@galligan
Copy link
Author

One thing worth calling out explicitly: the GitHub Actions workflow (.github/workflows/deploy-cloudflare.yml) is completely opt-in. It's workflow_dispatch only — it will never run on push, merge, PR, or any other event. It sits inert unless someone:

  1. Adds a CLOUDFLARE_API_TOKEN secret to the repo
  2. Manually triggers it from the Actions tab or CLI

Without both of those steps, it does nothing. No CI minutes consumed, no failed checks on PRs, no noise in the Actions tab. It's just a file.

The value is that anyone who does want to deploy the CF Worker gets a working workflow out of the box instead of writing their own. And upgrading to auto-deploy-on-merge is a one-line change — add push: branches: [main] to the trigger. The docs/DEPLOYMENT.md CI/CD section walks through both paths.

@galligan galligan marked this pull request as ready for review March 16, 2026 16:43
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 146ced2dfb

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@galligan
Copy link
Author

@dshipper Finally marked this PR as ready. Tried to cover all of the bases that would mean adding Cloudflare is seamless, respects all of the current conventions, and stays out of the way if you're not deploying to Cloudflare. It's a monster of an update, which was pretty necessary given the scope of what needed to come in for Workers compatibility and be as light of a touch on your existing code.

TBH a fast-follow of this work, where your existing server patterns are extracted and set up a new shared package e.g. packages/doc-engine could mean that any deployment alternative could be a lighter-weight addition. I've spec'ed that out in this gist. It would be a worthwhile refactor to consider if additional deployment options are considered.

Otherwise, we're all ready for review! I've got all this working on my local machine, as well as in https://proof-sdk-cloudflare.galligan.workers.dev and it's running smoothly. Would love to help out further!

- POST /documents now reads request body (markdown, title) matching
  the contract instead of creating empty docs
- handleAgentOps forwards parsed payload via new Request so downstream
  handlers can read the body (fixes double request.json() consumption)
- handleLifecycle persists share_state to DO SQLite so revoke/delete
  survives reconnects
- CI workflow uses npm install (no lockfile in repo) instead of npm ci

🤘🏻 In-collaboration-with: [Claude Code](https://claude.com/claude-code)
@galligan
Copy link
Author

Addressed all four Codex review items in 339845d:

1. POST /documents ignoring request body — Now reads markdown and title from the JSON body (or raw text via content-type), writes markdown to the DO via rewrite, and extracts title from the first heading as a fallback. Matches the /share/markdown contract.

2. handleAgentOps double body consumption — The ops dispatcher now constructs a new Request with the extracted payload before forwarding to handleMarksRoute/handleAgentRewrite, so downstream handlers can call request.json() without hitting an already-consumed stream.

3. handleLifecycle not persisting state — Revoke/delete/pause now writes share_state to a document_meta table in DO SQLite, so the lifecycle state survives WebSocket disconnects and DO hibernation.

4. npm ci without lockfile — Changed to npm install since the repo doesn't commit package-lock.json. Also updated the DEPLOYMENT.md CI/CD section to match.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant