Reference
Streams, presence, and artifacts
Live data is a first-class lane, not an application hack on top of commits. Chunks are ephemeral in transit and authoritative at completion. Presence never completes. Artifacts never become truth.
Stream state
type StreamState = {
epoch: number
offset: number
prefix: Json
metadata: Json
}
A stream is an append-only live prefix on an extension-backed slice. The extension adapter defines units (code points, points, …), append, length, and how completion folds into durable state. Core owns epochs, accepted offsets, overlap, gap recovery, catch-up, and retirement.
- Only one epoch is live on a slice.
- Chunks never enter the commit log.
stream_endis the durable terminal operation.- A new completion requires the matching live server epoch. The server substitutes its accepted prefix and metadata before commit.
- Durable replay uses the logged prefix. It does not need live heap.
- Complete, abort, and supersede retire the epoch through snapshots and reload.
- Compact snapshots discard unfinished live prefixes.
- Core does not interpret text, bytes, points, time, or size. Adapters define units; applications define admission limits.
Opening and chunking
session.openStream(slice_id, epoch, metadata)
session.chunkStream(slice_id, epoch, offset, chunk)
session.wantStream(slice_id, epoch, offset)
session.mutate({ type: "stream_end", slice_id, epoch, status, prefix, metadata })
The playground document button sends canned word chunks, then a complete end. Canvas strokes add_child a stroke slice, open epoch 1, append points, then complete into the point extension. If a client sees an offset gap it emits stream_want; the server re-sends from that offset or stream_reject.
A stale completion for an old epoch must not retire a newer live stream. A completion cannot substitute a client prefix after the server has lost live state — that is why the logged prefix is canonical.
Text and points
The text battery counts Unicode code points and folds a completed stream into one generated CRDT run anchored at the metadata's anchor. visibleText already shows the live prefix before completion.
The demo point adapter counts points and appends a completed prefix to durable point state. Admission limits (4,096 points, 256 KiB, finite coordinates, positive finite widths) live in that adapter and in canvas field checks — not in core.
Presence
{ type: "presence", client_id, slice_id?, payload: Json }
{ type: "presence_gone", client_id }
Last-write-wins, keyed by client id. Core fans it out, includes current entries during catch-up, and removes an entry when the peer is forgotten. It does not persist presence, interpret user ids, or apply a TTL. Hibernation can drop the heap peer map; connected clients may then need a re-announce. The demo validates cursor payloads in application code and binds user_id at the host.
Do not mount presence. It is document-scoped and worthless after a short time. A separate domain would wake another isolate for data that should die with the tab.
Paging holes
keep_last: N keeps the newest N children inline and elides the rest to { hash, size } plus any elide_keep fields. That hole is the paging system. Chat uses it with no artifact. Canvas uses the same hole and may cover it with a raster.
Clients ask for bodies with want. The server answers have without moving head. Hydration restores fields and extension state and checks the combined hash.
keep_last is not a continuously bounded working set and not a full partial-sync protocol. A later set_field against an elided body currently fails client replay. The intended contract is a commit-stamped replacement handle for unmaterialized subscribers, with hydration that names the body version.
Artifacts
type Artifact = {
id: string
version: number
through: number
hash: string
payload: Json
}
An artifact is optional covering of the hole — used when the hole still paints under the working set, as overlapping strokes do. It is derived, replaceable, and never a snapshot. Compact folds the commit log in SQLite. An artifact does not.
Yttrium owns covered-prefix identity, invalidation, invocation, delivery, and catch-up. The application supplies artifact.build(state). The demo builder rasters polylines to a PNG. Presence, live chunks, wants, catch-up, nacks, and duplicate mutations do not invoke it. A null or thrown build cannot fail the content commit. If the cover is stale, the client omits it and can want pages.
The generic name is intentional. Search indexes, thumbnails, and summaries can use the same slot. Async or remote builders are future work.
Playground mapping
| UI | Lane |
|---|---|
| Document stream button | Text stream on the document root, then stream_end. |
| Canvas pointer down/move/up | add_child stroke, point stream, complete into points. |
| Caret and pointer ghosts | presence JSON. |
| Old chat messages | keep_last: 12, no artifact. |
| Old ink under new strokes | keep_last: 12 plus raster artifact. |