offline_cache_sync library
Offline-first data engine for Dart and Flutter.
Reads return the server-confirmed base with pending local writes folded
on top (fold(base, pendingOps)); writes go through a durable outbox with
per-entity ordering, idempotency keys, retries and crash recovery;
refreshes never clobber pending edits; eviction never drops them.
Classes
- Accepted
- 2xx. If the server returned the entity, pass it as serverValue (JSON-compatible); it becomes the new base.
- AssumeOnline
- Default when the app supplies no provider: always try the network.
- CacheHit
- CacheMiss
- Cancelable
- Something that can be cancelled.
- Clock
-
Source of time. Replace with
FakeClockin tests. -
Codec<
T> - Converts between your model and a JSON-compatible value.
-
Conflict<
T> - Everything a resolver needs. Conflicts are detected by the SERVER (409 / 412); the client never guesses.
- Conflicted
- 409 (domain conflict) or 412 (If-Match failed). Include the current server value when you have it; otherwise the engine refetches.
-
ConflictResolver<
T> - ConflictStrategies
- Built-in strategies.
- ConnectivityProvider
-
A HINT about the network, used only for scheduling. The engine never
treats
onlineas proof that requests will succeed. -
Delete<
T> - Built-in: delete the entity. Senders receive it with kind kindName.
- ErrorInfo
- A classified error, safe to persist (messages must already be redacted).
- EventSink
- Receives every event. Forward to your logger / APM here.
- Evicted
- ExponentialBackoff
-
Exponential backoff with "full jitter": delay = random(0, min(cap, base * 2^n)).
Honours a server
Retry-Afteras a lower bound. - FetchContext
- Passed to RemoteSource.fetch.
- Fetched
- 200 with a body. value must be JSON-compatible (it is stored as-is and decoded with the resource codec).
- FetchFailed
- FetchResult
- FreshnessWindows
-
The four freshness windows of section 6.1, measured from
fetchedAt(the last 200 or 304). - GiveUp
- Gone
- 404 / 410: the entity does not exist on the server.
- IdempotencyProvider
- Mints the idempotency key ONCE, inside the enqueue transaction.
- InMemoryLocalStore
- A LocalStore kept in memory.
-
JsonCodecOf<
T> - A Codec from two functions.
- LeaseChanged
- LocalStore
- The storage port. Records, the op log and engine metadata live behind ONE transaction boundary, so "write locally + enqueue" is atomic.
-
MergeWith<
T> - Replace the op by a full-entity write of value against the server version. The replacement gets a NEW idempotency key (different payload).
- MetaTable
- Small key/value table for engine bookkeeping (sequence, lease, epochs).
-
Mutation<
T> - A typed, durable intent to change one entity.
- MutationSender
- Sends one operation. Returns a classified SendResult. A thrown exception is treated as an ambiguous, retryable failure (the request may or may not have reached the server).
- NoIdempotency
-
No keys: use when the backend ignores them and you rely on
client-generated ids or
Delivery.atMostOnce. - NotModified
- 304: the cached body is still current. Renews freshness.
- OfflineEngine
- The offline-first engine: one per account/store.
- OpCancelled
- OpConflicted
- OpDeadLettered
- OpEnqueued
- Operation
- One durable entry in the outbox. Immutable; transitions produce copies.
- OperationDraft
- Draft of an operation, given to IdempotencyProvider.mint.
- OpEvent
- OpFailed
- OpHandle
- Handle to an enqueued operation.
- OpLog
- OpRecovered
-
An op found
inFlightat startup (the previous process died mid-send). - OpRetryScheduled
- OpSent
- OpSucceeded
-
Park<
T> -
Keep the op in
conflictedand let the app/user decide. - PolicyWarning
- Something unusual but non-fatal (e.g. pins exceed the retention budget).
- ReachabilityChanged
- RecordTable
- Rejected
- Permanent rejection (4xx validation, 403...). Not retried.
-
RemoteSource<
K> - Reads one entity from the server. Knows your URLs; the engine does not.
-
Replace<
T> - Built-in: replace the whole entity. Used by conflict merges and handy for "save form" style writes. Senders receive it with kind kindName.
-
Repository<
K, T> - Typed facade over one Resource.
-
ResendLocal<
T> - Rebase the op on the server version and send it again ("client wins").
-
Resolution<
T> -
Resource<
K, T> - Definition of one kind of entity: key, codec, remote access, mutations and policies. Create one per entity type and register it with the engine.
- RetentionPolicy
- Budgets and ordering for eviction. Freshness decides whether a value may be SERVED; retention decides whether the row should still EXIST. They are separate on purpose: an expired record is exactly what stale-if-offline needs.
- Retryable
- Temporary failure the server reported explicitly (429, 503, or 409 "an operation with this Idempotency-Key is still processing").
- RetryAt
- RetryDecision
- RetryPolicy
- Decides what happens after a retryable failure.
- Revalidated
- Scheduler
-
Schedules callbacks. Replace with
FakeClock(which also implements this) in tests to make retries and backoff deterministic. - SendContext
- Passed to MutationSender.send.
- SendResult
- ServedStale
- SetFields
-
Built-in for
Map<String, Object?>entities: shallow-merge fields (a null value removes the field). Senders receive it with kind kindName and can map it to a PATCH. -
Snapshot<
T> - The result of a read: the folded value (server base plus pending local writes) and everything the UI needs to present it honestly.
- StaleResponseDiscarded
- A fetched value was discarded because a newer write was acknowledged after the fetch started, or the response carried an older version.
- StoredRecord
- A server-confirmed base value plus the metadata the engine needs for freshness and retention. Immutable.
- StoreStats
- StoreTxn
- Tables available inside one transaction.
- SyncEvent
- Typed engine events. They carry keys, ids, kinds, timings and error codes, never payloads or headers.
- SyncMetrics
- Running counters folded from events. Queue gauges come from the store.
- SystemClock
-
TakeServer<
T> - Drop the op; the server value becomes the base.
- TimerScheduler
- UuidIdempotency
- Random UUIDv4 keys (recommended by the IETF Idempotency-Key draft).
- UuidV4
- Generates RFC 4122 version-4 UUIDs without any package dependency.
- Validators
- HTTP validators used for conditional requests (304 Not Modified).
- Version
- Opaque server version of an entity. Prefer revision or a strong etag; updatedAt is the weakest signal (clock skew, millisecond collisions).
Enums
- DataSource
- Where the returned base came from.
- Delivery
- What to do when a send's outcome is ambiguous (timeout after the request was written, or a crash while the op was in flight) and the op carries NO idempotency key. With a key, the engine always resends: the server deduplicates.
- Freshness
- How fresh the server-confirmed base is. Pending local writes are layered on top regardless of freshness.
- HttpDirectiveMode
-
How to treat HTTP
Cache-Controlhints stored on a record. - LeaseMode
- Who drains the outbox when several engines share one store (e.g. the UI isolate and a background isolate started by workmanager).
- OpOutcome
- How an operation ended, as seen by this engine instance.
- OpStatus
- Durable lifecycle of an Operation.
- Reachability
- StaleReason
Extension Types
- IdempotencyKey
-
Key sent as the
Idempotency-Keyheader. Minted once per operation. - OpId
- Local identity of one queued operation.
- RecordKey
-
Identifies one entity:
<type>:<id>, e.g.user:123.
Functions
-
jsonEquals(
Object? a, Object? b) → bool - Deep equality for JSON-compatible values.
Typedefs
-
MutationDecoder<
T> = Mutation< T> Function(Object? json) - Rebuilds a mutation from its frozen JSON.
Exceptions / Errors
- OfflineMiss
- Thrown into Snapshot.error when an entity is needed, the device is offline and nothing usable is cached.