juice_sync 0.2.0
juice_sync: ^0.2.0 copied to clipboard
Offline outbox / mutation queue with durable persistence, ordering, retries and dead-lettering, as a Juice bloc.
Changelog #
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
0.2.0 - 2026-09-15 #
Changed — behaviour, not cleanup (ISSUES #22) #
- Requires
juice: ^1.6.0. EveryUseCaseBuildernow declares anEventConcurrencymode (the package predated 1.5.0 and ran everythingconcurrent, which silently allowed same-type use cases to interleave across anawait):InitializeSyncEvent→droppable(a second init mid-load is ignored).EnqueueMutationEvent,RetryFailedEvent,DiscardMutationEvent,OnlineChangedEvent→sequential(same-type events run one at a time, in order; a read before anawaitcan no longer be written stale).FlushRequestedEvent→concurrent, explicitly, WITH its hand-rolled single-owner guard kept: a trigger arriving mid-flush sets a re-run flag so the pass repeats and catches mutations enqueued after its snapshot. Neitherdroppable(would drop the trigger) norsequential(one extra pass per trigger) reproduces that.
- Known, documented, deferred: modes are keyed by exact event type, so
RetryFailedEventandDiscardMutationEventare each serialized against themselves, not each other —retryFailed()thendiscard(id)on the same id inside one store write can resurrect it. A bloc-owned mutation FIFO (thejuice_storage/juice_i18npattern) closes it and is deferred until a consumer needs it.
Tests #
- Gated-store coverage: overlapping initializations coalesce to one
loadAll; overlapping retries serialize (the secondputwaits for the first); a mutation enqueued mid-flush is sent by the same flush (the re-run flag).
0.1.2 - 2026-06-16 #
Changed #
- Allow
juice_storage2.0.0 (Hive CE migration). Test now useshive_ce. No API change.
0.1.1 - 2026-06-16 #
Fixed #
StorageSyncStorenow opens its own Hive boxes (outbox + the private meta box) before first use. Previously the app was expected to pre-open them viaStorageConfig.hiveBoxesToOpen, but it can't — the meta box name is internal — soloadAllfailed withboxNotOpenat startup, puttingSyncBlocintoSyncStatus.error. Open is idempotent, so the store self-heals. Surfaced by the Glean dogfood.
Changed #
StorageSyncError.toString()now includes itscause, so a wrapped storage failure is visible instead of opaque.
0.1.0 - 2026-05-28 #
Added #
- Initial release.
SyncBloc— an offline outbox / mutation queue: durably persist writes, then flush them to a backend when online.- Durable persistence —
SyncStoreseam;StorageSyncStore(juice_storage- backed, FIFO via a persistedseqcounter) +InMemorySyncStore(tests). MutationExecutor— injected transport seam.PermanentSyncError⇒ dead-letter; any other throw ⇒ retryable.- Partitioned FIFO ordering — mutations sharing an
orderingKeyare strict in-order; independent partitions proceed past a blocked one. - Exponential-backoff retries with
maxAttempts⇒ dead-letter (a poison mutation can't wedge the queue). - Crash-safe at-least-once —
inFlightis persisted before send; a recoveredinFlightis re-sent (relies on server idempotency onmutation.id). - Auto-flush on a
false→trueedge ofonlineSignal; optionalperiodicRetry. - Fail-loud —
enqueuethrows if the write can't be persisted;loadAllfailure ⇒ error status (never a silently-empty queue); non-JSON payloads rejected atenqueue. - API —
enqueue,flush,retryFailed,discard. - Rebuild groups —
sync:status,sync:queue,sync:failed,sync:mutation:<id>.
Not yet included #
- Backoff jitter, lean-state for very large queues, and offline-read pausing
(
juice_network_connectivity) — planned post-0.1.