Skip to content

SyncedStoreClient API Reference

Full reference for the store client returned by Poe.setupStore(...). For the typed wrapper, see InferSyncedStoreClient<Schema> in api-patterns.md.

Typed Client

typescript
import type { InferSyncedStoreClient } from "poe-apps-sdk/v1/client.js";
import type { TodoSchema } from "./schema";

export type TodoStoreClient = InferSyncedStoreClient<TodoSchema>;
// Gives you typed: store.mutate.setTodo, store.subscribe, store.action.generateWithAI, etc.

Reading Data

query(fn) — One-time read

typescript
const todo = await store.query((ctx) => ctx.table("todos").get("todo-1"));

subscribe(queryFn, callback) — Reactive updates

Callback fires immediately, then on every change. Returns an unsubscribe function.

typescript
const unsub = store.subscribe(
  (ctx) => ctx.table("todos").entries().toArray(),
  (todos) => renderTodoList(todos),
);

Writing Data

mutate — Optimistic mutations

typescript
const { id, confirmed } = await store.mutate.setTodo({ id: "abc", text: "Buy milk" });
await confirmed; // optionally wait for server

action — Server-side actions

Waits for pending mutations to flush first, then calls the server.

typescript
const result = await store.action.generateWithAI({ id: "abc", prompt: "dinner ideas" });

Connection & State

Property / MethodReturns
state'initializing' | 'ended'
connectionStatus'connecting' | 'connected' | 'disconnected'
isOnlineboolean
isBootstrappedboolean (authoritative data ready to render)
hasServerDataboolean (first server pull response arrived this session)
isEnded()boolean
endReason'kicked' | 'auth_failed' | 'application' | null

Waiting for Data

None of these are required — queries and mutations work immediately.

MethodResolves when
waitForLocalData()Device storage loaded
waitForServerData()First server pull completes
waitForBootstrap()Authoritative data is ready to render (either source)

IDs

typescript
const ordinal = await store.getClientOrdinal(); // Sequential: 0, 1, 2...
const id = await store.makeUniqueId();          // Sortable: "5-61"

Pending Mutations

typescript
const pending = await store.getPendingMutations();
const count = store.getPendingCount();
await store.waitForSync(); // Wait for all pending to confirm

store.onPendingMutationsChanged((mutations) => {
  showSavingIndicator(mutations.length > 0);
});

Subscriptions

typescript
store.subscribeToConnectionStatus((status) => { /* ... */ });
store.onOnlineChanged((isOnline) => { /* ... */ });
store.subscribeToTable("todos", (entries, changes, ctx) => {
  // ctx.userId — current user ID
  // First callback: all entries appear in changes.added (compared against empty)
  // Subsequent: changes.added, changes.modified, changes.removed are deltas
});
store.subscribeToScanEntries("user:", (entries, changes) => { /* ... */ });

All subscription methods return an unsubscribe function.

Error & Lifecycle Events

typescript
store.onFailedMutation((info) => {
  console.error(`${info.mutation.name} failed:`, info.error.message);
});
store.onKicked((reason) => { /* duplicate clientId or admin action */ });
store.onAuthFailed((reason) => { /* expired token */ });
store.onDisposed(() => { /* cleanup */ });

Cleanup

typescript
store.dispose(); // Closes WebSocket, clears timers. Not reversible.