Skip to content

Schema and bindings ​

Use named row and input schemas so table wrappers stay shallow and input/output types remain concrete. Adapt this syntax example to the existing scaffold's names and declarations; it supplies no application rules or authorization.

synced-store/schema.ts:

ts
import { z } from "zod";
import { defineSchema, table } from "poe-tiles-sdk/v1/backend.js";
import { TILE_SCHEMA_VERSION } from "./tile-schema-version";

const recordRow = z.object({ text: z.string().max(500) });
const updateRecordInput = z.object({
  id: z.string().min(1).max(100),
  text: z.string().max(500),
});

export const tileSchema = defineSchema({
  schemaVersion: TILE_SCHEMA_VERSION,
  tables: { records: { schema: table(recordRow) } },
  mutators: { updateRecord: { input: updateRecordInput } },
});
export type TileSchema = typeof tileSchema;

A table entry wraps table(rowSchema) in { schema: ... }; mutator input takes the Zod schema directly. Keep the scaffold's version constant in its client-safe file. Browser code imports TileSchema as a type, never the backend schema as a runtime value. Preserve existing declarations and migrations. For changes to persisted shapes, follow schema migrations.

Type handlers with InferMutatorHandlers<TileSchema> and readers with InferReadContext<TileSchema> from poe-tiles-sdk/v1/client.js. Read the authenticated actor from ctx.userId, not store.userId. Table calls:

ts
const current = await ctx.table("records").get(input.id);
await ctx.table("records").set({ itemKey: input.id, value: { text: input.text } });
await ctx.table("records").delete(input.id);
const records = await ctx.table("records").scan().values().toArray();

These are separate operation examples, not one handler. Validate actor, membership, action and expected round/revision before committing game changes. itemKey cannot contain :. Schema defaults do not make fields optional in typed output rows: update affected seed data, fixtures and UI props together. Decide public/private/server-only visibility and whether each table is bounded; use sort keys for accumulating ordered history.

Keep the same browser-safe mutators and hooks in both configurations:

ts
// synced-store/client-config.ts
import { defineClientConfig } from "poe-tiles-sdk/v1/client.js";
import type { TileSchema } from "./schema";
import { TILE_SCHEMA_VERSION } from "./tile-schema-version";
import { tileMutators } from "./mutators";
import { tileHooks } from "./hooks";

export const tileClientConfig = defineClientConfig<TileSchema>({
  schemaVersion: TILE_SCHEMA_VERSION,
  mutators: tileMutators,
  hooks: tileHooks,
});
ts
// synced-store/backend-config.ts
import { defineBackendConfig } from "poe-tiles-sdk/v1/backend.js";
import { tileSchema } from "./schema";
import { tileMutators } from "./mutators";
import { tileHooks } from "./hooks";

export const tileBackendConfig = defineBackendConfig({
  schema: tileSchema,
  mutators: tileMutators,
  hooks: tileHooks,
});

Merge the scaffold's other configuration fields, actions and hooks; these examples show the schema/mutator/hook connection only. Do not import backend configuration into the UI.