This file defines the minimal JS API surface aligned with the current core protocol.
- factories/functions only,
- storage and transport are pluggable,
- wire semantics are normative in
docs/protocol/*.md.
For published package entry points and recommended imports, see
docs/reference/package-entrypoints.md. The runtime contracts below describe
the API shape after you choose an entry point.
/**
* @typedef {Object} SubmitItem
* @property {string} id
* @property {string} partition
* @property {string} projectId
* @property {string} [userId]
* @property {string} type
* @property {number} schemaVersion
* @property {object} payload
* @property {{ clientId?: string, clientTs?: number, [key: string]: any }} meta
* @property {number} createdAt
*/
/**
* @typedef {Object} CommittedEvent
* @property {number} committedId
* @property {string} id
* @property {string} partition
* @property {string} projectId
* @property {string} [userId]
* @property {string} type
* @property {number} schemaVersion
* @property {object} payload
* @property {{ clientId?: string, clientTs?: number, [key: string]: any }} meta
* @property {number} serverTs
*//**
* @param {{
* serverLastCommittedId?: number,
* maxBufferedSubmits?: number,
* onBufferedSubmit?: (entry: { id?: string, bufferedCount: number }) => void
* }} [options]
* @returns {{
* connect: () => Promise<void>,
* disconnect: () => Promise<void>,
* send: (message: object) => Promise<void>,
* onMessage: (handler: (message: object) => void) => () => void,
* setOnlineTransport: (transport: object) => Promise<void>,
* setOffline: () => Promise<void>,
* getState: () => {
* connected: boolean,
* online: boolean,
* waitingForOnlineConnected: boolean,
* bufferedSubmitCount: number
* }
* }}
*/
export function createOfflineTransport(options) {}/**
* @param {Object} deps
* @param {{
* send: (message: object) => Promise<void>,
* connect: () => Promise<void>,
* disconnect: () => Promise<void>,
* onMessage: (handler: (message: object) => void) => () => void
* }} deps.transport
* @param {{
* init: () => Promise<void>,
* loadCursor: () => Promise<number>,
* insertDraft: (item: SubmitItem) => Promise<void>,
* insertDrafts?: (items: SubmitItem[]) => Promise<void>,
* loadDraftsOrdered: () => Promise<SubmitItem[]>,
* applySubmitResult: (input: { result: object }) => Promise<void>,
* applyCommittedBatch: (input: { events: CommittedEvent[], nextCursor?: number }) => Promise<void>,
* loadMaterializedView?: (input: { viewName: string, partition: string }) => Promise<unknown>,
* evictMaterializedView?: (input: { viewName: string, partition: string }) => Promise<void>,
* invalidateMaterializedView?: (input: { viewName: string, partition: string }) => Promise<void>,
* flushMaterializedViews?: () => Promise<void>
* }} deps.store
* @param {string} deps.token
* @param {string} deps.clientId
* @param {string} deps.projectId
* @param {() => number} [deps.now]
* @param {() => string} [deps.uuid]
* @param {() => string} [deps.msgId]
* @param {(item: SubmitItem) => void} [deps.validateLocalEvent]
* @param {(input: { type: string, payload: any }) => void} [deps.onEvent]
* @param {(entry: object) => void} [deps.logger]
* @param {{
* enabled?: boolean,
* initialDelayMs?: number,
* maxDelayMs?: number,
* factor?: number,
* jitter?: number,
* maxAttempts?: number,
* handshakeTimeoutMs?: number
* }} [deps.reconnect]
* @param {{
* maxEvents?: number,
* maxBytes?: number
* }} [deps.submitBatch]
* @param {(ms: number) => Promise<void>} [deps.sleep]
* @returns {SyncClient}
*/
export function createSyncClient(deps) {}/**
* @typedef {Object} SyncClient
* @property {() => Promise<void>} start
* @property {() => Promise<void>} stop
* @property {(items: {
* id?: string,
* partition: string,
* projectId?: string,
* userId?: string,
* type: string,
* schemaVersion: number,
* payload: object,
* meta?: object,
* }[]) => Promise<string[]>} submitEvents
* @property {(item: {
* id?: string,
* partition: string,
* projectId?: string,
* userId?: string,
* type: string,
* schemaVersion: number,
* payload: object,
* meta?: object,
* }) => Promise<string>} submitEvent
* @property {(options?: { sinceCommittedId?: number }) => Promise<void>} syncNow
* @property {() => Promise<void>} flushDrafts
* @property {() => {
* started: boolean,
* stopped: boolean,
* connected: boolean,
* syncInFlight: boolean,
* reconnectInFlight: boolean,
* reconnectAttempts: number,
* connectedServerLastCommittedId: number | null,
* activeProjectId: string,
* lastError: null | { code?: string, message?: string, details?: object }
* }} getStatus
*/Client runtime events:
connectedsync_pagesyncedcommittedrejectednot_processedbroadcasterrorreconnect_scheduled
/**
* @param {Object} deps
* @param {{
* verifyToken: (token: string) => Promise<{ clientId: string, claims: object }>,
* validateSession?: (identity: { clientId: string, claims: object }) => Promise<boolean>
* }} deps.auth
* @param {{ authorizeProject: (identity: object, projectId: string) => Promise<boolean> }} deps.authz
* @param {{ validate: (item: SubmitItem, ctx: object) => Promise<void> }} deps.validation
* @param {{
* commitOrGetExisting: (input: {
* id: string,
* partition: string,
* projectId?: string,
* userId?: string,
* type: string,
* schemaVersion: number,
* payload: object,
* meta: object,
* now: number
* }) => Promise<{
* deduped: boolean,
* committedEvent: CommittedEvent
* }>,
* listCommittedSince: (input: {
* projectId: string,
* sinceCommittedId: number,
* limit: number,
* syncToCommittedId?: number
* }) => Promise<{ events: CommittedEvent[], hasMore: boolean, nextSinceCommittedId: number }>,
* getMaxCommittedIdForProject: (input: { projectId: string }) => Promise<number>,
* getMaxCommittedId: () => Promise<number>
* }} deps.store
* @param {{ now: () => number }} deps.clock
* @param {(entry: object) => void} [deps.logger]
* @param {{
* maxInboundMessagesPerWindow?: number,
* rateWindowMs?: number,
* maxEnvelopeBytes?: number,
* closeOnRateLimit?: boolean,
* closeOnOversize?: boolean
* }} [deps.limits]
* @returns {SyncServer}
*/
export function createSyncServer(deps) {}/**
* @typedef {Object} SyncServer
* @property {(transport: { connectionId: string, send: (message: object) => Promise<void>, close: (code?: number, reason?: string) => Promise<void> }) => ConnectionSession} attachConnection
* @property {() => Promise<void>} shutdown
*/
/**
* @typedef {Object} ConnectionSession
* @property {(message: object) => Promise<void>} receive
* @property {(reason?: string) => Promise<void>} close
*/- Client submit path may send one or more items in one
submit_eventsrequest. - Client runtime drains drafts in ordered batches and keeps one submit batch in flight at a time.
schemaVersionis a required top-level field on every submitted and committed event.submit_events_resultremains an outcome-only message; clients correlate results byidand do not expect echoed event fields.submitEvent()remains a thin wrapper oversubmitEvents().- Client store methods that mutate committed/draft/cursor state should use single DB transactions when available, or equivalent idempotent/monotonic semantics when transactional APIs are not available.
- A
createSyncClient(...)instance is project-scoped. Reuse one store per project unless your store implementation explicitly namespaces cursors and drafts. - All behavior must match
docs/protocol/*.md. - Client-generated
msgIdvalues should be stable per outbound message for traceability. metais open-ended JSON-safe metadata. The runtime reservesmeta.clientIdandmeta.clientTsand may overwrite them.
Runtime exports include these persistence families:
- In-memory:
createInMemoryClientStore(options?)frominsieme,insieme/client, orinsieme/browsercreateInMemorySyncStore(startCommittedId?)frominsieme/nodeorinsieme/server
- SQLite-style DB object (
exec,prepare, optionaltransaction):createSqliteClientStore(db, options?)frominsieme/nodeorinsieme/servercreateSqliteSyncStore(db, options?)frominsieme/nodeorinsieme/server
- LibSQL client:
createLibsqlClientStore(client, options?)frominsieme,insieme/client,insieme/node, orinsieme/servercreateLibsqlSyncStore(client, options?)frominsieme/nodeorinsieme/server
- IndexedDB:
createIndexedDbClientStore(options?)frominsieme,insieme/client, orinsieme/browser
Client store adapters may expose an optional materialized-view API:
- factory option:
materializedViews: [{ name, version?, initialState?, reduce, matchPartition?, checkpoint? }] - runtime method:
loadMaterializedView({ viewName, partition }) - runtime method:
evictMaterializedView({ viewName, partition }) - runtime method:
invalidateMaterializedView({ viewName, partition }) - runtime method:
flushMaterializedViews()
Checkpoint config:
checkpoint: {
mode: "immediate" | "manual" | "debounce" | "interval",
debounceMs?: number,
intervalMs?: number,
maxDirtyEvents?: number,
}Reducer contract:
reduce({
state, // previous state for this (viewName, loaded partition)
event, // committed event record
partition, // loaded partition being reduced
}) => nextState;Optional partition matcher:
matchPartition({
loadedPartition,
eventPartition,
event,
}) => boolean;The reducer runs only for newly inserted committed events that match the loaded partition.
reduce is required for every materialized view definition.
Read semantics:
loadMaterializedView(...)returns exact state for that partition at the local committed snapshot used by the read.evictMaterializedView(...)drops hot in-memory state only.invalidateMaterializedView(...)drops hot state and the persisted checkpoint for that partition.flushMaterializedViews()persists dirty checkpoints immediately.
createReducer({ schemaHandlers }) dispatches by committed-event type.
Handlers receive { state, event, payload, partition, type } and run in an
immer recipe context, so they may mutate state directly or return a
replacement object. Default reducer fallback throws for unknown event types
unless fallback is overridden.
Operational guidance:
- Keep materialized views to a small set (
1-3typical, usually up to~10lightweight views). - Reuse your app's event apply reducer as the single source of truth for both replay and materialized views.
- See
docs/client/materialized-views.mdfor usage patterns.