Skip to content
AbloAblo
Esc
navigateopen⌘Jpreview
On this page

v0.28.0

Minor Changes

  • Commits are now crash-durable — a sealed envelope replays the exact request after a restart, on both transports. Before a batch reaches the wire, the WebSocket client seals it into one atomic outbox record: the stable idempotency key, the exact JSON-normalized ordered operations, and the source mutations it supersedes. Recovery replays the sealed request as it was sent — never re-projected from model state in the new process — and a multi-operation commit is only ever replayed whole, so an atomic A+B can’t come back later as A and B separately.

  • The stateless HTTP client replays its outbox on startup. It persists the exact request (method, path, body, idempotency key) and re-sends unacknowledged envelopes before its first new request. A definitive rejection (a 4xx other than 429) settles the envelope instead of retrying it.

  • New commitOutbox client option + CommitOutboxStore interface. Browsers default to strict IndexedDB storage when persistence is on; Node agents and workers can inject workflow-, SQLite-, or filesystem-backed storage so commits survive process restarts. The client’s transaction journal now writes with strict durability, so an acknowledged outbox record means disk-backed.

  • Replay is fenced and fails closed. Reusing an idempotency key with a different request is rejected on both transports; replay is scoped to the actor and server that sealed the envelope and bounded to the server’s idempotency retention, so a stale envelope is held for review instead of re-sent; a write queued behind an ambiguous predecessor waits for it to settle rather than overtaking it; and a persistence adapter that cannot guarantee durability reports failure instead of phantom success. Generated create ids are sealed with the request, so a crash mid-create recovers the same row.

  • @abloatai/ablo/commit publishes the transport-independent v2 commit contract. Branded identifiers, ordered operations, explicit write preconditions (unchanged_since, version_matches, claim_fence), and a discriminated CommitReceiptcommitted, conflicted (carrying the smallest current state needed to reconcile), or rejected (a typed error plus the capability a retry would need). This release publishes the contract so tooling and tests can validate against it today; the engine’s live commit path still speaks the current protocol, and the cutover to v2 ships server-side.

  • New wire exports. @abloatai/ablo/wire gains the matching CommitRequestMessage / CommitResultMessage frames, re-exports the current transport’s operation schemas under legacyCommit* names, and exports BootstrapReason — the machine-readable reasons a live delta stream must resume via catch-up (too_far_behind, too_many_deltas, missing_entities, stream_gap).

  • Reactive reads are typed as what they actually return: snapshot rows, not model instances. useAblo selectors now receive AbloReads<Schema> — model reads typed as the row’s data fields plus schema computeds, without relation accessors or model methods. Reading row.layers inside a selector is a compile error instead of a silent undefined at runtime; compose relations through a dedicated hook or selector instead. The new Row<'slides'> / InferRow types are the snapshot-row companion to Model<'slides'>, mirroring what toReactiveSnapshot() produces. This is a type-level change only: selectors that compiled against relation accessors now fail to compile, and each of those reads was already returning undefined — the new errors point at real bugs. Runtime behavior is unchanged.

Patch Changes

  • The resume cursor advances only through the contiguous durable prefix of a delta frame. A frame spanning several local stores commits per store, and those transactions can fail independently — taking the maximum id from any successful store could permanently skip an earlier failed delta on every later catch-up.

  • Conflicting undo-scope options warn instead of being silently ignored. A scope keeps the options it was created with; the silent version of that rule could leave a surface believing it enabled stream recording when it hadn’t.

  • Docs and error copy name the real HTTP mount. REST paths are documented as /api/v1/... — the OpenAPI server URLs carry the /api mount — and the jwt_issuer_untrusted message no longer points at an endpoint that doesn’t exist.

  • The resume cursor advances only through the contiguous durable prefix of a delta frame. A frame spanning several local stores commits per store, and those transactions can fail independently — taking the maximum id from any successful store could permanently skip an earlier failed delta on every later catch-up.

  • Conflicting undo-scope options warn instead of being silently ignored. A scope keeps the options it was created with; the silent version of that rule could leave a surface believing it enabled stream recording when it hadn’t.

  • Docs and error copy name the real HTTP mount. REST paths are documented as /api/v1/... — the OpenAPI server URLs carry the /api mount — and the jwt_issuer_untrusted message no longer points at an endpoint that doesn’t exist.

Was this page helpful?