Skip to content

Schema Migrations

Increment schemaVersion when you change your schema. Share the version between client and server via a plain constant file (avoids bundling Zod on the client):

app-schema-version.ts   ← export const MY_SCHEMA_VERSION = 2;
schema.ts               ← uses MY_SCHEMA_VERSION in defineSchema()
client-config.ts        ← uses MY_SCHEMA_VERSION in defineClientConfig()

When versions mismatch without a migration, the client clears local data, fires onSchemaVersionMismatch, and disposes.

Defining a Migration

Provide migrations for each version step:

typescript
import { defineMigration } from "poe-apps-sdk/v1/backend.js";

const migration1to2 = defineMigration(v1Mutators, v2Mutators, {
  migrateData: async (ctx) => {
    // Transform existing data
    const items = await ctx.table("todos").scan().entries().toArray();
    for (const [key, value] of items) {
      await ctx.table("todos").set({
        itemKey: key.itemKey,
        value: { ...value, priority: 0 },
      });
    }
  },
  migratePendingMutation: {
    // Transform in-flight mutations from old clients
    addTodo: (args, emit) => {
      emit("addTodo", { ...args, priority: 0 });
    },
  },
});

const schema = defineSchema({
  schemaVersion: 2,
  migrations: { "1to2": migration1to2 },
  // ...
});

Pending Mutation Handlers

Each mutation handler in migratePendingMutation can:

  • Transform args — call emit() with modified input
  • Rename — emit a different mutation name
  • Drop — don't call emit()
  • Expand — call emit() multiple times to produce several mutations from one

Testing Migrations

Write a test that seeds data at the old schema version, bumps to the new one, and asserts both the migrated data and pending-mutation replays land correctly. See packages/synced-store-backend/protocol/migrations/ for the enforcement code.