コンテンツにスキップ

Deployment

このガイドは pnpm dev から、デプロイされた Worker + D1 + R2 バックエンドまで導きます。Quick Start に従っており、それを出荷したいことを前提としています。

  • Workers が有効な Cloudflare アカウント
  • wrangler CLI 3+ がインストール済み(pnpm add -g wrangler
  • wrangler login 完了済み
  • ローカルに Node.js 20+
{
"$schema": "node_modules/wrangler/config-schema.json",
"name": "my-todo-app",
"main": "./worker.ts",
"compatibility_date": "2026-01-01",
"compatibility_flags": ["nodejs_compat"],
"d1_databases": [
{
"binding": "DB",
"database_name": "my-todo-app-prod",
"database_id": "<from wrangler d1 create output>"
}
],
"r2_buckets": [
{
"binding": "BUCKET",
"bucket_name": "my-todo-app-blobs"
}
],
"durable_objects": {
"bindings": [
{
"name": "COORDINATOR",
"class_name": "SyncCoordinator"
}
]
},
"migrations": [
{
"tag": "v1",
"new_sqlite_classes": ["SyncCoordinator"]
}
],
"observability": {
"enabled": true
}
}
  • d1_databases.DB — plasma が書き込む SQLite DB。
  • r2_buckets.BUCKETfile() column のバイトが置かれる場所。
  • durable_objects.COORDINATOR — リアルタイムの WebSocket poke ファンアウト用。ポーリングのみで満足なら任意。
Terminal window
wrangler d1 create my-todo-app-prod
# 出力された database_id を wrangler.jsonc に貼り付ける
wrangler r2 bucket create my-todo-app-blobs
worker.ts
import {
createSyncHandler,
ensureSchema,
fromD1,
gcOrphanedBlobs,
r2Storage,
runMigrations,
SyncCoordinator,
} from "@sh1n4ps/plasma-server"
import { schema, mutators, SCHEMA_VERSION } from "./shared/schema"
export { SyncCoordinator } // wrangler が DO クラスを見つけられるよう re-export
interface Env {
DB: D1Database
BUCKET: R2Bucket
COORDINATOR: DurableObjectNamespace
}
export default {
async fetch(req: Request, env: Env, ctx: ExecutionContext) {
const executor = fromD1(env.DB)
// 初回リクエストの DDL。CREATE TABLE IF NOT EXISTS は no-op なので、
// table が存在すれば安価。
await ensureSchema({ schema, executor })
const handler = createSyncHandler({
schema,
mutators,
executor,
schemaVersion: SCHEMA_VERSION,
auth: async (req) => {
const token = req.headers.get("authorization")?.replace("Bearer ", "")
if (!token) return { ok: false, reason: "no token" }
const user = await verifyJWT(token)
if (!user) return { ok: false, reason: "bad token" }
return {
ok: true,
clientGroupID: user.id,
clientID: req.headers.get("x-client") ?? crypto.randomUUID(),
ctx: { userId: user.id },
}
},
blobs: {
default: r2Storage({ bucket: env.BUCKET }),
},
})
return handler(req)
},
async scheduled(_event, env, ctx) {
// orphaned な blob を毎週 GC。cron トリガーはダッシュボードで設定。
const executor = fromD1(env.DB)
ctx.waitUntil(
gcOrphanedBlobs({
executor,
storage: r2Storage({ bucket: env.BUCKET }),
minOrphanAgeMs: 7 * 24 * 3600 * 1000,
limit: 500,
}),
)
},
}

migration は自動ではありません。2 つのパターンがあります:

パターン A — 1 回限りの管理ルート

Section titled “パターン A — 1 回限りの管理ルート”
// worker.ts の fetch ディスパッチに追加
if (url.pathname === "/admin/migrate") {
if (req.headers.get("x-admin-token") !== env.ADMIN_TOKEN) {
return new Response("forbidden", { status: 403 })
}
const result = await runMigrations({ schema, executor: fromD1(env.DB) })
return Response.json(result)
}

デプロイスクリプトからトリガーします:

Terminal window
wrangler deploy
curl -X POST -H "x-admin-token: $ADMIN_TOKEN" https://my-app.workers.dev/admin/migrate

パターン B — wrangler 経由のデプロイ前 migration

Section titled “パターン B — wrangler 経由のデプロイ前 migration”

生 SQL として表現できる schema 変更には wrangler d1 migrations を使えます — ただし schema.ts との同期を保つのは自己責任です。plasma の runMigrations が canonical なパスです。

Terminal window
wrangler deploy

Worker は my-todo-app.<your-subdomain>.workers.dev にデプロイされます。フロントエンドの endpoint をそこに向けます:

const plasma = createPlasmaClient({
...,
endpoint: "https://my-todo-app.<your-subdomain>.workers.dev/sync",
})

カスタムドメインには、Cloudflare ダッシュボードでルートを追加し、endpoint を更新してください。

wrangler.jsonc
{
"vars": {
"PUBLIC_KEY": "..."
}
}

シークレット(ADMIN_TOKEN、JWT 署名キーなど):

Terminal window
wrangler secret put ADMIN_TOKEN
wrangler secret put JWT_SIGNING_KEY

worker.ts でアクセス:

interface Env {
DB: D1Database
BUCKET: R2Bucket
COORDINATOR: DurableObjectNamespace
PUBLIC_KEY: string // vars から
ADMIN_TOKEN: string // secrets から
JWT_SIGNING_KEY: string // secrets から
}

wrangler.jsonc"observability": { "enabled": true } は Workers observability ダッシュボードを有効にします。plasma の sync ハンドラは Log Streams に現れる構造化エラーを発行します:

  • push-http / pull-http / network / schema-mismatch / rebase-replay / blob-upload-failed — クライアント側の onError ハンドラを console.error に接続していれば、そこから
  • blob-read-auth-cap-hit — blob 読み取り auth チェック中に readAuthMaxRefs に達したときに現れる

Workers が提供する以上のメトリクスには、Analytics Engine を使って毎分の push / pull 呼び出しをカウントし、ダッシュボードには Grafana Cloud を使ってください。

plasma はフロントエンドのホストを指定しません。Quick Start は Vite を使います; それを以下にデプロイできます:

  • Cloudflare Workers Static Assets(同一ドメインのデプロイに推奨 — /sync に CORS がない)
  • 別ドメインにフロントエンドを置き、Worker に CORS を有効にした Vercel または Netlify
  • フロントエンドを dist/ にビルドして wrangler をそこに向ける 同じ Worker の assets バインディング

wrangler dev は Miniflare でエミュレートされた D1 と R2 に対して Worker をローカルで実行します:

Terminal window
wrangler dev --local

開発中はフロントエンドの endpointhttp://localhost:8787/sync に向けられます。

プロダクションチェックリスト

Section titled “プロダクションチェックリスト”
  • wrangler.jsonc に D1、R2、DO バインディングが正しく命名されている
  • SCHEMA_VERSION がクライアントとサーバーのエクスポート間で一致している
  • auth() が単に {ok: true} を返すのではなく、実際のトークンを検証している
  • クライアントの endpoint が HTTPS で、デプロイされた Worker またはそのカスタムドメインを指している
  • runMigrations がデプロイ時に実行される
  • file() を使うなら gcOrphanedBlobsscheduled ハンドラがある
  • フロントエンドのビルドが同一ドメイン(/sync)にあるか、Worker に CORS が設定されている
  • wrangler.jsonc で observability が有効
  • Migrations — デプロイ時に実行される DDL-diff ランタイム
  • Files and blobs — R2 バケットのサイジングとライフサイクル
  • Auth and permissions — プロダクションの auth() ハンドラの書き方
  • Multi-region — 単一 D1 を超えたスケーリング