Reference
API
The public surface is small. Most applications need createYttrium, Hub/Session, and the host factories. The reducer and codec are shared.
apply
apply(state, op, commit, shape_version, runtime?) → Applied | Rejected
Imported from src/engine. Applied is { ok: true, state, op }. Rejected is { ok: false, reason }. Reasons include non_monotonic_commit, shape_too_old, shape_unknown, missing targets, and extension failures. Client and host must pass the same runtime that was used to create the state.
Session
new Session(client_id, hooks?, runtime?, { cachePrivateViews?: string[] }?)
createSliceId() → SliceId
mutate(op) → MutateFrame
view() → DomainState
hello() / catchUp() / want(ids)
onFrame(frame) / emit(frame)
setPresence(payload, slice?)
openStream / chunkStream / wantStream
undo()
dump() / restore(dump)
snapshot() / restoreSnapshot(cached) // SnapshotCache entry: confirmed state + viewHints
pendingMutates()
beginConnection() / endConnection()
privateView(name, { allowCached?: boolean })
synchronized / privateViews / viewHints / presence / artifact / denied
view() is foldOverlay(auth, overlay, runtime). mutate always allocates a fresh UUID. undo replays a remembered inverse through mutate and currently skips generated stream inserts. Call beginConnection() before each new transport; it mints a subscription_id and clears private views, presence, and stream errors.
Hooks: onChange, onWire, onDenied, onStreamGap, onSynchronized. Use onStreamGap to send the returned stream_want.
dump() is authoritative state plus overlay and optional viewHints for names selected by cachePrivateViews. Fresh private frames, presence and the outbox are excluded. Restore starts unsynchronized. privateView(name, { allowCached: true }) can return status cached; default reads remain fresh-only.
Hub
new Hub(client_id, hooks?, runtime?, options?)
open(domain) → Session
positions() → { [domain]: head }
sessions
One session per domain, reused on later open. The playground uses one hub per pane.
DomainServer
new DomainServer({ shape?, runtime?, artifact?, validateSlice?, compactAfter?, ... })
handle(from, frame) → outs
effect(from, frame) → { outs, persist, compact? }
applyPersist(persist)
canDeliver(recipient, out)
wake() / forget(from)
dump() / load(...)
handle is effect plus persist plus optional compact. Hosts that need to persist before applying memory should call effect, write persist, then applyPersist. canDeliver is addressing only — all versus a directed to. Authorization is the host's job.
artifact.build(state) returns JSON or null. The engine hashes the covered prefix, caches that dependency, and skips the builder unless a commit changes it. Builder exceptions cannot fail the content commit. validateSlice runs at admission for new work; historical replay stays compatible.
Codec and ids
encodeFrame / decodeFrame encodeOp / decodeOp clientId / domainId / mutationId / sliceId / commit ROOT_ID / SYSTEM_ID
Client persistence
new SnapshotCache(kv?, { namespace?, limit? })
peek(scope) / load(scope) / save(scope, { state, viewHints? })
forget(scope) / flush()
new ClientStore(new DebouncedKv(kv))
// snapshot cache + per-mutation outbox records
new Outbox(store, scope, send, receive)
enqueue(client_id, frames) // persist, then return
drain() // send in predecessor order
idbKv() / memoryKv() / DebouncedKv
Scope the store by authenticated account, workspace, and domain. The playground key is live:{ns}:{user}:{domain}. Ack/nack retires only that record. Temporary auth or policy failures should not nack if the user may recover.
Application factory
const application = createYttrium({
authenticate: (request, env) => ...,
serviceSession: (source, policy, env) => ...,
access: (domain) => ({
principalKey,
policyDependencies,
authorize,
sessionValid?,
privateViews?,
onDecision?,
}),
serverOptions: (domain) => ({ shape, runtime, artifact, validateSlice }),
initialize: (domain) => ops, // trusted bootstrap, once
});
createDomainHost(application, env => env.DOMAIN, observe?)
createUpgradeHandler(application, env => env.DOMAIN, observe?)
Pass the same observability options to both host factories. Details are on Access and Host.
Runtime and extensions
defineRuntime({ extensions, schema })
emptyRuntime
type ExtensionAdapter = {
id, version,
initial(config?),
validateState(value),
apply(state, operation),
stream?: StreamAdapter,
}
Stream adapters define prefix init, metadata, append, length, slice, truncate, complete, and optional visible value. Core owns epochs, offsets, overlap, gap recovery, and terminal admission.
Text battery
Optional, installed by the example runtime:
textExtension textDocument(slice) / visibleText(slice) textInsertOp(slice_id, doc, actor, value, after?) textDeleteOp(slice_id, id) textStreamMetadata(anchor) appendAfter(slice)
Visible text includes a live stream prefix folded as a generated insert. Completion persists that prefix through stream_end. This battery is a stand-in until an established CRDT integration replaces it.
Example helpers
Not library API. Useful when reading the playground:
playgroundRuntime/demoShapeRegistry— text + points + demo shapes.pointsOf/visiblePoints— canvas extension.rasterArtifactPayload— PNG cover for elided strokes.playgroundApplication— demo auth, roles, private views, bootstrap catalog.