Fixes from code review of canvas-on-do migration (commit c3f7c02):
- worker.js /api/ws: rewrite request URL to '/ws' so the DO pathname
switch dispatches correctly. The original c.req.raw kept '/api/ws'
which the DO never matched → 404 on every WS upgrade.
- migrate-from-upstash.js pickSampleOffsets: use TOTAL_PIXELS - 1 for the
last byte instead of CANVAS_WIDTH * CANVAS_WIDTH (only correct when
the canvas is square; constants explicitly invite non-square).
- chunk-storage.js writePixels: clarify atomicity comment — the loop is
atomic *because it has no awaits*, not because of any implicit DO
transaction. Added guidance for future maintainers.
- cooldown-store.js tryAcquire: GC sweep wrapped in try/catch so a
transient failure can't drop the user's allowed: true response.
Docs:
- README.md: drop Upstash from tech stack, redraw architecture,
document new project layout (durable-objects/lib, admin/), add
CHUNK_BYTES to configuration table.
- docs/system-architecture.md: full rewrite for DO-storage data flow,
document SQLite schema, race-safe rate-limit pattern, free-tier table.
- docs/deployment-guide.md: drop Upstash setup, add optional one-shot
migration runbook, update free-tier table to actual May 2026 limits.
Tests: 112 pass, 6 skipped (pending Phase 4 rewrite via
@cloudflare/vitest-pool-workers). Bundle dry-run clean.
Local wrangler dev smoke test was attempted but the sandboxed env
hangs HTTP requests at the workerd layer (TCP connects, no response).
Routing fix verified by code inspection; user must verify in their
own dev or production.
3.9 KiB
Deployment Guide
Prerequisites
- Node.js 18+
- Cloudflare account (Free plan is sufficient)
wranglerCLI (installed as dev dependency)
No external storage to provision. Canvas pixels and rate-limit cooldowns
live inside the CanvasRoom Durable Object's SQLite-backed storage.
Step 1: Deploy
npm install
npm run deploy
This runs vite build (compiles Svelte → dist/) then wrangler deploy
(uploads the Worker, static assets, and Durable Object class).
The first deploy applies the wrangler.json migration that registers
CanvasRoom as a SQLite-backed DO class.
Step 2: Verify
- Visit your Worker URL (e.g.,
https://rplace.your-subdomain.workers.dev). - Canvas should load (empty / black on first visit — palette index 0).
- Select a color, click to place a pixel.
- Open a second browser tab — pixel should appear via WebSocket.
curl -I https://your-url/api/canvasshould reportcf-cache-status: HITafter a couple of warm requests (10 s edge cache).
(Optional) One-Shot Migration from Upstash
Only if you have an existing Upstash-backed deployment to import.
# 1. Set credentials for the legacy Upstash instance
npx wrangler secret put UPSTASH_REDIS_REST_URL
npx wrangler secret put UPSTASH_REDIS_REST_TOKEN
# 2. Generate and set a migration token
npx wrangler secret put MIGRATION_TOKEN
# Paste a random 32-byte hex value
# 3. Deploy
npm run deploy
# 4. Run the import once
curl -X POST -H "Authorization: Bearer $TOKEN" \
https://your-worker.workers.dev/admin/migrate-from-upstash
# Expect: {"ok":true,"bytes_imported":16777216,"samples_checked":N,"mismatches":[]}
# 5. Verify in browser; wait 7 days as rollback safety
# 6. Run Phase 4 cleanup (see plans/260509-2309-canvas-on-do-storage)
After Phase 4 cleanup deletes the migration code, also delete the secrets:
npx wrangler secret delete UPSTASH_REDIS_REST_URL
npx wrangler secret delete UPSTASH_REDIS_REST_TOKEN
npx wrangler secret delete MIGRATION_TOKEN
Custom Domain
# Add a custom domain via Cloudflare dashboard or:
npx wrangler domains add rplace.yourdomain.com
Monitoring
- Cloudflare dashboard:
- Workers analytics — requests/day, errors, CPU time
- Durable Object metrics — storage size, request rate
- Cache analytics —
cf-cache-statusHIT ratio on/api/canvas
Free-tier Footprint
| Resource | Free Cap (May 2026) | rplace at hobby scale |
|---|---|---|
| Workers requests | 100,000 / day | ~100 / day @ 50 users |
| DO storage / object | 10 GB | 16 MB canvas |
| DO storage / account | 5 GB | 16 MB total |
| BLOB row size | 2 MB | 64 KB chunks (32× under) |
| Per-DO request rate | 1,000 / s soft | ~1 / s |
Bandwidth is unlimited on Workers. With the 10 s edge cache on
/api/canvas, the dominant request driver is /api/place (1 per
placement). At 1-req-per-second-per-user rate-limit, 50 concurrent
users × 24h × 3600s = 4.3 M theoretical max — but realistic hobby
sessions stay well under 100K/day.
Troubleshooting
- Canvas loads empty: expected on first deploy — DO
canvas_chunkstable is empty until pixels are placed (or migration runs). - WebSocket not connecting: verify the wrangler migration applied
via
wrangler tail— should see no errors on DO instantiation. cf-cache-statusshows MISS: edge caching may need an extracaches.default.putwrap ifCache-Controlheaders aren't honored through the worker → DO → response chain. Verify with two consecutivecurl -Irequests; second should HIT.- Migration import fails with
size_mismatch: the legacy Upstash data isn't 16 MB. Resize CHUNK constants or delete the partial Upstash data and start fresh. already_populatedfrom migration endpoint: pass?force=1to overwrite. Use only when you're certain.- Storage billing meter ticking up: per-account 5 GB free cap. A 16 MB canvas is harmless; the worry only appears if you stand up many rooms or hit a runaway insert.