Skip to content

v0.12.2

Choose a tag to compare

@pathosDev pathosDev released this 04 Aug 09:56
· 1464 commits to main since this release
811d7f1

The payload fidelity release. Every journal, snapshot store and durable-state store wrote payloads with bare JSON.stringify — so a persisted Set or Map recovered as {}, a Date came back a string, a Uint8Array an index-keyed object, and a bigint threw outright. Because the write path folds the original object into state, none of it surfaced until the next recovery. Payloads now go through the tagged JSON tree format JsonSerializer already used, on every backend, and that format grew to cover essentially everything JavaScript can hold.

Numbered a patch, but read the breaking change below before upgrading: this window carries one.

🚀 New features

  • Full type fidelity for stored payloads and JsonSerializer (#889) — NaN / Infinity / -Infinity / -0, undefined in value positions (array slots, Set members, Map entries — object properties still drop, matching JSON.stringify), RegExp (source + flags), URL, Error (name + message + cause, including subclass constructors and AggregateError.errors) and every typed array / DataView / ArrayBuffer now round-trip through every store and the JSON serializer. Error stacks are deliberately not stored — they would leak filesystem paths into long-lived rows. Number/String/Boolean wrapper objects unwrap like JSON.stringify does; Promise, WeakMap and WeakSet throw a SerializationError at persist time instead of being silently stored as {}.
  • Per-store serializer option (#888, the persistence half of #450) — every journal / snapshot store / durable-state store options builder, and every Register<X>Plugins bundle, takes withSerializer(serializer) to route a custom Serializer into stored rows through a self-describing __serialized__ framing. Default-format rows and framed rows coexist in one stream, so you can switch a running system's serializer without a migration; reading a framed row without — or with a mismatching — serializer fails with an actionable SerializationError rather than garbage. Registry auto-binding and the cluster wire remain tracked in #450.

⚠️ Breaking changes (pre-1.0)

  • Persistence stores no longer silently corrupt rich payload types (#888) — the fix changes what gets written. Payloads are stored as the tagged JSON tree (__date__, __bytes__, __map__, __set__, __bigint__, plus a new __literal__ escape so user data shaped like a tag round-trips as data) on every backend.

    Migration: none for readers. Rows written by older versions decode unchanged, so an existing journal keeps replaying. What changes is the write side: rows written from this version on carry tag objects wherever plain JSON would have corrupted the value, so older framework versions — and non-actor-ts consumers reading your tables as plain JSON — see the tag shape instead of a bare value. Plan a rolling downgrade accordingly, and check any external reader (a BI job, a dashboard query) that parses payload columns directly.

    JsonSerializer also now honours toJSON(), reports circular references as a SerializationError naming the key path instead of overflowing the stack, and only interprets a tag when it is an object's sole own key.

🔧 Changed

  • The in-memory journal, snapshot store and durable-state store round-trip payloads through the same codec as the real backends (#888) — dev/prod parity. An event that cannot be stored now fails in your test suite instead of on the first production recovery, and mutating an object after persist no longer aliases into the store. Like the real stores, they still return and publish the caller's original objects, so nothing observable changes for payloads that were always storable.

📚 Documentation

  • "What events and state may contain" answers the payload question with a table instead of a caveat — a new section on the Persistent actor page lists every payload category against its actual round-trip behavior: stored as plain JSON, round-tripped as a real instance via a tag, dropped like JSON.stringify, or thrown at persist time. It holds for every store on every backend, including the in-memory ones, and it states the reader guarantee — rows written by earlier versions keep decoding unchanged, tags appear only in newly written rows where plain JSON would have corrupted the value. The JSON serializer page gained the matching fidelity list (the tag set, toJSON(), the __literal__ escape, circular references reported by key path), and the envelope-format migration page now describes _e as rich-type-capable rather than JSON-safe-only.
  • Per-store serializers are documented end to end — a new "Using a custom serializer for persistence" section covers the __serialized__ framing, why old and framed rows coexist in one stream, the SerializationError you get when the serializer's id no longer matches, how Register<X>Plugins fans one serializer out to a backend's stores (a leaf's own wins), and the deliberate exception: the in-memory stores ignore the option and always use the stricter default codec, so a test cannot pass on something production would reject.
  • Both, as always, EN + DE 1:1 — 8 pages per language.