Store 5 component → Store 6 map
Store 5 documents a Store, Fetcher, SourceOfTruth, Converter, Validator, and, for writes,
MutableStore, Updater, and Bookkeeper. Store 6 keeps some names, absorbs some jobs into the engine,
and replaces the write assembly with one journalled path. The seven translation rows below cover
all eight Store 5 components because MutableStore and Updater move together.
How to read this map
A Store 6 core store requires exactly one configured input: a fetcher. Persistence and the expert extension points are optional seams, while freshness, single-flight fetching, bounded idle residency, and invalidation are engine behavior.
| Store 5 component | Store 6 replacement | Learn it here |
|---|---|---|
Store | Store<K, V> with stream(key, freshness), suspending get, namespace-aware invalidation and clear, and explicit close() | Read contract, invalidate or clear |
Fetcher | fetcher { }, fetcherOfResult { }, or the experimental seam Fetcher | Fetchers |
SourceOfTruth | The two-parameter persistence seam, or a Room/SQLDelight adapter | Persistence, Room, SQLDelight |
Converter | No direct analog. Mapping lives in fetcher and persistence callbacks. | Fetchers, Persistence |
Validator | Native per-call Freshness, durable invalidation, and the expert FreshnessValidator read-planning seam | Freshness, Important defaults |
MutableStore + Updater | MutableStore becomes mutationStore plus typed mutate. Updater has no direct analog: its transport job moves to an app-owned MutationServer invoked by foreground drain. | Mutations, MutationServer |
Failed-sync Bookkeeper | No direct analog. Durable mutation-journal records and inspection replace the job. Store 6 core has a different Bookkeeper for freshness. | Journal storage, Inspection |
Store to Store
Store 5 centers reads on stream(StoreReadRequest). Store 6 uses
stream(key, freshness) and adds suspending get(key, freshness). Both doors follow one failure
channel:
streamemitsStoreResult.Errorand does not throw retrieval failures. AFreshness.MustBeFreshinitial-cycle failure emits one error and completes the flow.getreturns a value or throwsStoreException; it never emits a result wrapper.
Store 5's clear(key) and clearAll() become two namespace-aware operation families.
invalidate* marks data stale and preserves it, while clear* destructively removes values and
their associated per-key freshness records. Namespace and global watermarks remain conservative
and are not reset by forget operations. Store 6 also makes lifecycle explicit through close().
The request and response translation tables are in Migrating from Store 5.
Fetcher to fetcher, fetcherOfResult, or the seam Fetcher
Store 6 has three fetcher installation points. The last registration wins across all three:
fetcher { key -> value }is success-or-throw sugar.fetcherOfResult { key -> FetcherResult }exposes the complete result vocabulary.fetcher(fetcher)installs the experimental regular-interface seam, whose suspend function receives the ETag selected for a conditional request.
Store 5's FetcherResult.Data and three error shapes become Store 6
FetcherResult.Success(value, etag) and FetcherResult.Error(cause). Store 6 adds
NotModified, which produces one StoreResult.Revalidated frame, and Deleted, which clears the
resident value without an automatic refetch.
There is no engine-level replacement for a Store 5 fallback fetcher chain. Store 6 performs zero retries and no fallback chain. Compose retry, backoff, or fallback endpoints inside your fetcher.
SourceOfTruth to the persistence seam
Store 5's SourceOfTruth<Key, Local, Output> separates the Local value written to persistence
from the Output value emitted by its reader. The fetcher's Network type is separate: a
Converter maps Network into Local when those representations differ. Store 6's seam is
SourceOfTruth<K, V>, with one value type and a nullable-row reader contract.
A Store 6 implementation must immediately first-emit the current row or null; keep the reader
live; publish changes made through that instance; provide read-your-writes on normal mutation
return; and make mutations exception-atomic, including cancellation. deleteNamespace is new.
Use the contract kit in store6-testing when implementing the seam. The Room and SQLDelight
adapters implement it over an existing application database.
Converter to no seam: mapping lives in callbacks
Store 5's Converter<Network, Local, Output> defines fromNetworkToLocal and
fromOutputToLocal. Store 6 has no converter seam and no direct replacement component. A Store 6
store is typed on one value type V.
Map the network payload to V inside the fetcher before returning it. Map V to and from database
rows inside the persistence adapter's callbacks. For example, SqlDelightSourceOfTruth takes
readQuery, writeRow, and delete callbacks over generated queries. The conversion still exists;
it is owned at the boundary where the representation changes instead of by a Store component.
Validator to native freshness
Store 5's optional Validator.isValid(item) asks one per-item question. Store 6 absorbs the common
job into per-call policies and durable state. A read is planned from resident availability,
freshness metadata, durable staleness, and one of five policies:
CachedOrFetchMaxAgeMustBeFreshStaleIfErrorLocalOnly
Every StoreResult.Data reports isStale, and invalidate, invalidateNamespace, and
invalidateAll record staleness directly.
The seam still contains a FreshnessValidator, but it is not a Store 5-style per-item validity
hook. Its pure plan(context) function returns one of the FetchPlan outcomes Skip, Fetch, or
Conditional for one coherent read snapshot. Most applications should use the native policies.
MutableStore and Updater to mutationStore, mutate, and drain
There is no standalone Store 6 Updater. Store 5's MutableStore.write(...) and
Updater.post(...) move into one journalled path:
- Register named, typed write shapes in a
MutatorRegistry. - Enqueue one with
mutate(key, ref, args). - Push a foreground pass with
drain(key)ordrain(). - Implement the app-owned
MutationServertransport contract. It has exactly two methods:push(request): MutationAckandretire(request): MutationRetirementAck.
The Store 5 per-key write queue becomes a durable FIFO ordered by client sequence. Its current
state is inspectable through pending(key), pendingWrites(), and deadLetters().
Conflict handling moves into an optional conflicts { precondition(...); merge(...) } block.
Without a registered merge, server-wins remains the non-removable terminal. Write shapes are
registered once with mutator, update, create, delete, or upsert; no call-site closure
becomes a durable intent. update declines when the confirmed base is absent, delete always
applies MutationPresence.Absent, and upsert cannot decline.
The remote endpoint must treat a repeated idempotency key as the same request. If remote acceptance
happens before the local acknowledgement-receipt transaction commits, the durable phase remains
INFLIGHT and a later drain may replay the same immutable generation. Once ACKED is durable,
recovery may repeat local adoption, effects, and retirement, but it never calls
MutationServer.push again for that generation.
Continue with Authoring mutators, Implementing a MutationServer, Conflict resolution, and Draining and restart.
Bookkeeper to the journal, with a name collision
Store 5's Bookkeeper records failed-sync timestamps per key so later reads can detect unsynced
local changes and resolve conflicts. There is no direct Store 6 component with that job.
The mutation journal is the replacement system. It records durable intents, attempt generations,
acknowledgement progress, normalized failures, and retirement. Its truthful inspection surfaces
are pending(key), pendingWrites(), and deadLetters(). The default journal is in-memory; use
the SQLDelight adapter or another conforming durable implementation when queued work must survive
process restart.
Not in the map
Store 5 memory-cache configuration does not become another component. Store 6 uses
maxIdleKeys, default 128, to bound quiescent per-key engine residency. Eviction discards derived
engine state, not durable rows, metadata, stale marks, or watermarks.
Store 5's builder scope(...) also has no counterpart. Store 6 stores own their work and release
it through close().
Read Memory and lifecycle for both boundaries.
Source recorded 2026-08-12 ·main@c67a94ed· pre-6.0.0-alpha01