Expand description
The normalized protocol’s JSON wire rendering — byte-for-byte the shape the
@rindle/remote client validates (bare cells, camelCase keys, lowercase op tags,
hex fingerprints, f64-safe counters). Moved here from rindle-replica so every party
on the wire shares ONE codec: the napi addon and the Rust daemon’s ws front emit
with it (via rindle-replica’s re-export), and the room — the first Rust consumer
of these frames (RINDLE-REALTIME-DESIGN.md §3) — parses with it. The
..._from_json decoders are new with the room’s upstream leg; until they landed,
decode existed only in TS (@rindle/remote/src/normalized.ts).
Two conventions are deliberate and load-bearing:
- Counters are JS numbers (
epoch/seq/cv/comparatorVersionas f64 — the emitters writeas f64, so a Rust producer renders3as3.0while a JS producer writes3; the decoders accept both), and fingerprints are 16-hex-char strings (a u64 would lose precision as a JS number). - Cells are bare (
number | string | boolean | null):Intcollapses to a JS number andJsonto its string form on encode, so decode yieldsFloat/Str— the same inference the wasmDb’sjs_to_owned_valueapplies. The wire is value-faithful, not variant-faithful, by design (the engine’s single numeric model).
The serde derives on the protocol types are not this wire — they are the in-Rust round-trip form (oracles, journals). Sockets carry this rendering.
Shape-only by design: normalized_hello_from_json checks structure, not semantics —
PK bounds, duplicate tables, and fingerprint verification belong to
RoomStore::open (in rindle-room-core) / the
NormalizedSubscriber, which
return the protocol’s own errors.
Functions§
- fnv1a64
- FNV-1a-64 over raw bytes — the flush batch identity (
batch_hash, §5.3 step 4: computed once by the emitter over its canonical batch bytes; the authority stores and compares it, never recomputes). Render with{:016x}. - json_
to_ owned - A bare JS cell →
OwnedValue, inferring from the runtime JSON type (matches the wasmjs_to_owned_value: number→Float always, string→Str, bool→Bool, null→Null). A json column already crossed as its stringified form, so it lands here as aString. - nbatch_
frame - Assemble one full
nbatchframe forclient_qid, splicing the already-encodedops_fragmentinto the scalar envelope. Byte-identical to thejson!frame inRouterSink::batch:{"batch":{"cv":…,"epoch":…,"normalizedFp":"…","ops":…,"seq":…}, "queryId":…,"t":"nbatch"}(all keys alphabetical). - nbatch_
ops_ fragment - Encode
batch.opsas its[…]JSON array — the fragmentnbatch_framesplices into each subscriber’s envelope.RouterSink::batchencodes it ONCE per batch, outside the router lock, then assembles a frame per subscriber (only the envelope’squeryId/seq/epochdiffer across a fan-out). - normalized_
batch_ from_ json - Parse an
nbatchframe’sbatchobject into aNormalizedBatch. - normalized_
batch_ to_ json - A
NormalizedBatch→{ epoch, seq, cv, normalizedFp, ops }. The fingerprint is a hex string (a u64 would lose precision as a JS number);cvis the commit version the optimistic client buffers by (OPTIMISTIC-WRITES-DESIGN.md §8.6). - normalized_
hello_ from_ json - Parse an
nhelloframe’shelloobject into aNormalizedHello(shape only — see the module docs for where semantic validation lives). - normalized_
hello_ to_ json - A
NormalizedHello→{ epoch, comparatorVersion, tables: [{ name, columns, primaryKey }], normalizedFp }— the slim per-table-schema handshake (§3). - normalized_
op_ from_ json - Parse one
{ table, op, … }wire object into aNormalizedOp. Note the shape is the OUTBOUND batch wire —removecarriesrowandeditcarriesold/new— distinct from the change-source ingest shape (rindle-replica’smutation_from_json), whereremovecarriesoldandedit’s new row rides asrow. - normalized_
op_ to_ json - A
NormalizedOp→ its camelCase JS object:{ table, op, row | old/new }(bare cells), the path-free twin of a flat change (NORMALIZED-CHANGES-DESIGN.md §3). - owned_
to_ json OwnedValue→ a bare JS value.Int/Floatcollapse to a JSnumber(the engine’s single numeric model);Str/Jsonto a string (a json column is parsed at read time by the view’s typed schema, exactly as on the local path).- packed_
row_ to_ json - A packed engine row → its JSON array of bare cells (the
wire_row_to_jsontwin for [OwnedRow]). - row_
change_ to_ json - One row change in the change-source ingest shape — the
changes[]elementrindled’s/apply-row-change-txnconsumes (rindle-replica’smutation_from_jsonis the decode dual):add → {row},remove → {old},edit → {old, row}whererowis the NEW image. Deliberately distinct fromnormalized_op_to_json— that is the OUTBOUND batch wire, whereremovecarriesrowandeditcarriesnew. The room’s write-behind flush emits this shape (RINDLE-REALTIME-DESIGN.md§5.3), witholdcarrying the base image the CAS precondition asserts. NOTE: this emitter renders cells viaowned_to_json, which is FLOAT-FORM forInt— it is NOT the integer-exact ingest codec (rindle-replica::wire_json::mutation_to_json, design 226 Stage D). Room cells are Number-plane today so nothing is lost; unify onto the exact codec before any exact-int producer feeds this path. - wire_
row_ from_ json - One JSON array of bare cells → a positional wire row.
- wire_
row_ to_ json - One positional wire row → its JSON array of bare cells.