クイックスタート
plasma では次のように動きます。
- ブラウザは mutator を IndexedDB に対して optimistic に実行します
- Worker は同じ mutator を D1 に対して canonical に実行します
- sync、push、pull、rebase、live query、retry は plasma が担います
書くのは 1 つ の schema と 1 セット の mutation 関数だけです。両方の runtime がそれらを使います。
-
schema と mutator を一度だけ定義する
Section titled “schema と mutator を一度だけ定義する”ブラウザと Worker の両方が import する共有ファイルを作成します。
src/schema.ts import {defineMutators,defineSchema,eq,id,int,table,text,} from "@sh1n4ps/plasma-core"export const todos = table("todos", {id: id(),title: text(),done: int().default(0),updatedAt: int(),})export const schema = defineSchema({ todos })export const SCHEMA_VERSION = "todos-v1"interface Ctx { userId: string }export const mutators = defineMutators<typeof schema, Ctx>()({createTodo: async ({ db, args }) => {await db.insert(todos).values({id: args.id,title: args.title,updatedAt: args.updatedAt,})},markDone: async ({ db, args }) => {await db.update(todos).set({ done: 1, updatedAt: args.updatedAt }).where(eq(todos.id, args.id))},})defineMutators<typeof schema, Ctx>()という形は、schema(db.insert(todos)が型チェックされるように)と ctx 型(各 mutator の中でctx.userIdが使えるように)の両方を捕捉します。 -
Worker に sync エンドポイントをマウントする
Section titled “Worker に sync エンドポイントをマウントする”worker.ts import { createSyncHandler, ensureSchema, fromD1 } from "@sh1n4ps/plasma-server"import { schema, mutators, SCHEMA_VERSION } from "./schema"interface Env {DB: D1Database}export default {async fetch(req: Request, env: Env): Promise<Response> {const executor = fromD1(env.DB)await ensureSchema({ schema, executor })const handler = createSyncHandler({schema,mutators,executor,schemaVersion: SCHEMA_VERSION,auth: async (req) => ({ok: true,clientGroupID: req.headers.get("x-group") ?? "demo",clientID: req.headers.get("x-client") ?? "anonymous",ctx: { userId: req.headers.get("x-user") ?? "demo-user" },}),})return handler(req)},}ensureSchemaは追加のみで安全な DDL を実行します。テーブル、change-log トリガー一式、plasma の内部管理テーブルを作成します。authはすべてのリクエストで呼ばれます。拒否するには{ ok: false, reason }を返します。 -
ブラウザを同じ schema に向ける
Section titled “ブラウザを同じ schema に向ける”src/main.tsx import { createPlasmaClient } from "@sh1n4ps/plasma-client"import { PlasmaProvider } from "@sh1n4ps/plasma-react"import { createRoot } from "react-dom/client"import { schema, mutators, SCHEMA_VERSION } from "./schema"import { TodoList } from "./TodoList"export const plasma = createPlasmaClient({schema,mutators,dbName: "todos",endpoint: "/sync",clientGroupID: "demo",schemaVersion: SCHEMA_VERSION,getContext: async () => ({ userId: "demo-user" }),})plasma.start()createRoot(document.getElementById("root")!).render(<PlasmaProvider client={plasma}><TodoList /></PlasmaProvider>,)plasma.start()は pull ポーリングループを開始します。WebSocket 経由のリアルタイム poke はオプトインです。準備ができたらcreatePlasmaClientにsubscribe: createWebSocketSubscription({...})を渡してください。endpointの値はブラウザではlocation.originを基準に解決されます。Node / Workers では絶対 URL を渡す必要があります。ここで
plasmaをexportしているのは、後述のTodoList.tsxが import できるようにするためです。 -
useLiveQuery で書き込みに反応する
Section titled “useLiveQuery で書き込みに反応する”src/TodoList.tsx import { useLiveQuery, useMutation } from "@sh1n4ps/plasma-react"import { asc, eq } from "@sh1n4ps/plasma-core"import { plasma } from "./main"import { mutators, todos } from "./schema"export function TodoList() {const rows = useLiveQuery(() => plasma.db.select().from(todos).where(eq(todos.done, 0)).orderBy(asc(todos.updatedAt)),[],)const create = useMutation<typeof mutators, "createTodo">("createTodo")const markDone = useMutation<typeof mutators, "markDone">("markDone")return (<div><buttononClick={() => create.mutate({id: plasma.newId(),title: "Try plasma",updatedAt: Date.now(),})}>Add todo</button><ul>{rows.map((row) => (<li key={row.id}>{row.title}<button onClick={() => markDone.mutate({id: row.id,updatedAt: Date.now(),})}>done</button></li>))}</ul></div>)}useLiveQueryの結果は、次のいずれかが起きた瞬間に更新されます。- ローカルの mutation が発火したとき(optimistic に)、または
- pull ループがサーバーからの変更を届けたとき(canonical に)。
明示的に subscribe することはありません。plasma の IVM(Incremental View Maintenance)が、query のウィンドウにどの行が含まれるかを追跡し、diff をコンポーネントに push します。
いま何が起きたか
Section titled “いま何が起きたか”あなたは schema.ts を 一度だけ 書きました。
- ブラウザはそれを型付きのローカル query と optimistic な書き込みに使いました。
- Worker はそれを D1 への canonical な書き込みに使いました。
- 同じ
createTodomutator が両方の場所で実行されました。 - live query はローカルの mutation が反映された瞬間に更新され、その後 sync ループがバックグラウンドでサーバーと整合を取りました。
- sync、retry、rebase、change log は plasma が処理しました。
次のステップ
Section titled “次のステップ”- Next Steps — todo アプリが動いたら次に読むもの
- メンタルモデル — 1 つの schema が 2 つの runtime に対応するための 3 つのレイヤー
- ガイド / デプロイ — Worker + D1 構成を Cloudflare の本番環境へデプロイする