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 FakeClock in 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 online as 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-After as 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 inFlight at startup (the previous process died mid-send).
OpRetryScheduled
OpSent
OpSucceeded
Park<T>
Keep the op in conflicted and 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-Control hints 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-Key header. 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.