Schema 3.4.0

Schema and wire-format change

ClientSample.scoreReasons changes shape, so producers and consumers should move to the 3.4.0 generated schemas together. (3.6.0 changed it once more, to a map of contributions.)

Changed — scoreReasons becomes a list

ClientSample.scoreReasons changed from an optional string to an optional array of strings, so several independent reasons can be attached to a calculated score instead of encoding all the detail into one string value.

// before
scoreReasons?: string | null;

// 3.4.0
scoreReasons?: string[] | null;

Added — @observertc/samples-protobuf-codec

One package that both encodes and decodes, replacing the separate @observertc/samples-encoder and @observertc/samples-decoder. Same wire idea as before: each message carries only what changed since the previous sample, over the all-optional ClientSample proto, where explicit presence is what “changed” means.

  • Dual ESM/CommonJS build with an exports map and full type declarations; no Buffer or other Node built-ins, so it runs in the browser that produces the samples.
  • ProtobufCodecError with a code (MALFORMED_INPUT, STREAM_DESYNC, INVALID_VALUE, INVALID_OPTION) and a context.path pointing at the offending value, plus a non-throwing decoder.tryDecode().
  • identifiers: { callId | clientId | peerConnectionId | trackId: 'utf8' | 'uuid' } replaces the *IsUuid booleans. A value declared uuid that is not a UUID is now an error rather than a silent fallback.
  • createClientSampleCodec() builds both halves from one options object, so they cannot disagree about identifier packing.
  • reset() on either half is a keyframe: the next message is a full snapshot.
  • Silent by default — pass a logger to hear anything.
  • Regression suite with byte-exact golden fixtures for two recorded streams, plus randomised streams checked against an independent forward-fill model.

The generator emits only the sample and protobuf bindings into the package: the codec reads the field list, scalar types and nesting off the protobuf descriptor at runtime, so a new schema field reaches the published codec without any new per-field code.

Full documentation →

Added — @observertc/samples-json-codec

The same delta codec over plain JSON, with zero runtime dependencies and a ~2.1 KB gzipped bundle (the protobuf codec is ~29.8 KB, since it carries the protobuf runtime and descriptor).

  • A delta is a ClientSample with the unchanged parts left out — nothing renamed, tagged or wrapped — so a message is readable in a log without a decoder, and a golden-fixture diff shows exactly which field started or stopped being sent.
  • Identical semantics to the protobuf codec: forward-fill, keyed collections, reset() as a keyframe, matching error codes. The two are interchangeable.
  • On the recorded test stream, JSON is 2.73× the protobuf payload raw and 1.41× gzipped — so with permessage-deflate or any compressing transport the real cost is about 40%.
  • NaN and Infinity are rejected rather than silently serialised to null.
  • The decoder returns a sample it does not retain, so a pipeline that annotates samples in place cannot corrupt the stream.
  • The whole codec is two pure functions — a diff and a merge that are exact inverses — in under 300 lines. The only configuration is which arrays are keyed collections; a new schema field needs no change to it.

Unlike the protobuf artifact it does not require proto — JSON is self-describing, so the package only needs the sample types.

Full documentation →

Fixed — what the old encoder / decoder pair got wrong

Corrected in the new package; the old packages are unchanged.

BugConsequence
false could never be transmittedThe boolean encoder skipped any falsy value, so active, nominated and powerEfficientDecoder could go true but never come back
An empty string could never be transmittedSame cause
Two entries of the same type in one sample’s clientEvents lost the second one’s typeAll events shared one stateful field encoder
ClientMetaData.timestamp was dropped entirely on encode
ClientMetaData.peerConnectionId and .trackId ignored the uuid settingsAlways written as utf8
codecId, mid and transportId used a one-time-pass encoderA value that legitimately changed mid-call was never re-sent. They are ordinary delta fields now
attachments was compared by referenceAn object rebuilt with identical content was re-sent on every sample. It is compared by value now
A decoder that joined a stream late returned a half-built sampleIt now raises STREAM_DESYNC

Removed — the encoder and decoder packages

samples-encoder and samples-decoder are deprecated

They stopped at 3.3.0 and will not be regenerated, versioned or published again; their sources were removed from the repository along with the encoder and decoder generator artifacts.

3.3.0 stays installable from npm — removing the sources is not an unpublish — and the wire format is unchanged, so either package still interoperates with @observertc/samples-protobuf-codec and the two ends of a stream can migrate independently.

See the protobuf codec’s migration table for the API mapping.

← Back to version history · 3.3.0 →