CBOR serializer
CborSerializer encodes values using CBOR (RFC 8949) — a
compact binary serialization format. More compact than JSON,
faster for binary-heavy data, similar API surface.
import { CborSerializer } from 'actor-ts';
const serializer = new CborSerializer();const bytes = serializer.toBinary({ kind: 'increment', n: 1 });// → Uint8Array (binary CBOR, smaller than JSON)
const decoded = serializer.fromBinary(bytes, '');// → { kind: 'increment', n: 1 }When CBOR makes sense
Section titled “When CBOR makes sense”| You should switch to CBOR if… |
|---|
| Messages carry binary data (images, encoded payloads). |
| Cluster bandwidth is measurable / metered (cross-region, cellular IoT). |
| Persistence storage costs scale with payload size. |
| Sustained millions of small messages where JSON parse cost shows in profiles. |
For most apps, JSON is fine — CBOR’s gains don’t justify the loss of human-debuggability.
Size comparison
Section titled “Size comparison”| Payload | JSON | CBOR | Savings |
|---|---|---|---|
{ kind: 'increment' } | 14 bytes | 9 bytes | 36 % |
| Order with 5 items | 250 bytes | 180 bytes | 28 % |
| Image bytes (10 KB) | 13.3 KB (base64) | 10 KB (native) | 25 % |
| Deeply nested object | varies | varies | typically 20-40 % |
Bigger savings on binary data (no base64 overhead) and repeated field names (CBOR can string-table at the protocol level).
What CBOR handles better than JSON
Section titled “What CBOR handles better than JSON”Both serializers round-trip the same rich types, so the win is mostly size — CBOR encodes them as compact binary instead of tagged JSON text:
| Type | JsonSerializer | CborSerializer |
|---|---|---|
Uint8Array | base64 string (+33 %) | native byte string |
| Numbers | decimal text | compact binary int / float |
Date | {"__date__":"…"} tag object | tag 1 + epoch (compact) |
bigint | {"__bigint__":"…"} tag object | bignum tag 2 / 3 (compact) |
Map | {"__map__":[…]} tag object | tag 259 + native CBOR map |
Set | {"__set__":[…]} tag object | tag 258 + array |
URL | {"__url__":"…"} tag object | tag 32 + text |
| Typed arrays | base64 string (+33 %) | native byte string |
“The same rich types” is meant literally: Map, Set,
BidirectionalMap,
BidirectionalMultiMap,
RegExp, URL, Error, every typed array, NaN / Infinity /
-0 and bigint all come back as real instances from either
serializer, and a shared test suite asserts the two agree on every
one of them.
This was not always true: CborSerializer used to flatten Map,
Set and the two bidirectional collections to an empty {} and lose
every entry without raising anything
(#1036). If you
worked around that by converting to arrays or plain objects before
encoding, you no longer need to.
Two differences remain, both deliberate:
undefined.CborSerializercarries it natively (CBOR simple value 23), so{ a: undefined }round-trips withapresent andundefined.JsonSerializerrejectsundefinedoutright — which makes CBOR the more permissive of the two here.- Non-
Uint8Arraybinary is little-endian. Typed arrays travel as their platform’s bytes. Every runtime actor-ts supports is little-endian, so this only matters if you hand the bytes to a big-endian peer yourself.
Making CBOR the extension default
Section titled “Making CBOR the extension default”import { SerializationExtensionId, CborSerializer } from 'actor-ts';
const ext = system.extension(SerializationExtensionId);ext.setDefault(new CborSerializer());This changes which serializer the SerializationExtension treats
as its default — the one ext.encode(value) uses for any value
without a specific class binding. It does not retrofit CBOR
onto the cluster wire or the persistence stores: cross-node
tells still go over the wire as JSON.stringify (see the
overview), and the journal /
durable-state / snapshot stores write their own JSON. To get CBOR
bytes for a specific payload, use CborSerializer directly (see
Per-call serialization below).
Per-class binding still works — register the serializer, then
bind the class to its numeric id:
const eventSerializer = new ProtobufSerializer<MyEvent>();ext.register(eventSerializer);ext.bind(MyEvent, eventSerializer.id);// MyEvent → Protobuf; other values → the extension defaultPer-call serialization
Section titled “Per-call serialization”import { CborSerializer } from 'actor-ts';
const ser = new CborSerializer();const bytes = ser.toBinary({ data: someUint8Array });
// Later:const back = ser.fromBinary(bytes, '') as { data: Uint8Array };Useful when you want CBOR for a specific use case (e.g., HTTP response bodies for binary data) without switching the cluster default.
Wire format
Section titled “Wire format”CBOR is a binary tagged format. Inspecting raw bytes requires a CBOR-aware tool:
$ cat events.cbor | cbor-diag# decoded to diagnostic notationFor debugging, you may want to briefly switch to JSON when hunting a bug, then switch back.
Library
Section titled “Library”The framework’s CBOR implementation is internal — pure JS, no extra dependencies. Compliant with RFC 8949.
For external interop (sending CBOR to non-actor-ts systems), the framework’s encoding follows the standard — any RFC-8949-compliant decoder reads it.
Where to next
Section titled “Where to next”- Serialization overview — the bigger picture.
- JSON serializer — the default.
- Custom serializers — for typed schemas.
- Object storage compression — the other size-reduction lever.
