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: