local-replica 0.0.0 → 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,871 @@
1
+ /**
2
+ * First-class pagination for replicas.
3
+ *
4
+ * Vocabulary (direction-agnostic):
5
+ * - HEAD page: the newest window, what a refresh re-fetches (`cursor` undefined).
6
+ * - NEXT pages: further pages in the paging direction (older history).
7
+ * - `forward`: the view is head-first, next pages append at the END
8
+ * (sidebar lists: newest topic first, "load more" below).
9
+ * - `backward`: the view is oldest-first, next pages PREPEND at the start
10
+ * (chat transcripts: newest message last, "load older" on scroll-up).
11
+ * - `offset` mode: the cursor is the page index (0 = head).
12
+ * `cursor` mode: the server hands back an opaque `nextCursor`.
13
+ *
14
+ * Every function is pure; the store binding owns staleness and scope.
15
+ */
16
+ type ReplicaPagingMode = 'cursor' | 'offset';
17
+ type ReplicaPagingDirection = 'backward' | 'forward';
18
+ interface ReplicaPagingConfig<TItem, TCursor = any> {
19
+ /**
20
+ * Cursor mode: cursor to the page past `item`, the last cursor-eligible row
21
+ * on the far side, for a server that pages by row but reports no
22
+ * `nextCursor` (a plain list). Only safe when the row carries a lossless
23
+ * position; omit it when the server owns the cursor, and paging then waits
24
+ * for one.
25
+ */
26
+ deriveCursor?: (item: TItem) => TCursor;
27
+ direction: ReplicaPagingDirection;
28
+ /**
29
+ * Per-entry direction, for a resource whose entries read differently (a
30
+ * transcript read from its first round). Defaults to `direction`. Persisting
31
+ * an entry always uses `direction`, so keep such entries in memory.
32
+ */
33
+ directionOf?: (key: string) => ReplicaPagingDirection;
34
+ getId: (item: TItem) => string;
35
+ /**
36
+ * Rows that may anchor a join or derive a cursor. Synthetic rows the query
37
+ * injects (group / summary nodes) have no position of their own on the
38
+ * server, so they are skipped. Defaults to every row.
39
+ */
40
+ isCursorable?: (item: TItem) => boolean;
41
+ mode: ReplicaPagingMode;
42
+ /** Which part survives a reload. Defaults to the head page only. */
43
+ persist?: {
44
+ /** Hard cap on persisted items (taken from the head side). */
45
+ maxItems?: number;
46
+ /** Number of pages from the head side. Default 1. */
47
+ pages?: number;
48
+ };
49
+ /**
50
+ * Optional total order re-applied after every merge (e.g. messages by
51
+ * createdAt). The sort is stable: rows the comparator ties keep the order
52
+ * the server sent them in, so compare positions only — an id tie-break
53
+ * would reorder rows created within the same tick.
54
+ */
55
+ sort?: (a: TItem, b: TItem) => number;
56
+ }
57
+ interface ReplicaPageResult<TItem, TCursor> {
58
+ items: TItem[];
59
+ /**
60
+ * Cursor mode: next page cursor; `null` = exhausted, `undefined` = unknown.
61
+ * Offset mode: the server's own verdict — `null` = exhausted, any other
62
+ * value = more pages (the engine still advances by page index); `undefined`
63
+ * falls back to `total` / a full page. A server that filters rows after
64
+ * paging returns short pages that are not the end, so it must report this.
65
+ */
66
+ nextCursor?: TCursor | null;
67
+ total?: number;
68
+ }
69
+ interface ReplicaPageInfo<TCursor> {
70
+ /** Items this page contributed after de-duplication. */
71
+ count: number;
72
+ /** Cursor of the page after this one; `null` = exhausted, `undefined` = unknown. */
73
+ next?: TCursor | null;
74
+ }
75
+ /**
76
+ * The paged view a resource keeps in the store. Domain views extend it, so
77
+ * selectors read `items` / `hasMore` / `isLoadingMore` as before.
78
+ */
79
+ interface ReplicaPagedData<TItem, TCursor = number> {
80
+ /**
81
+ * Boundary row of the head page once more pages are loaded (forward: its
82
+ * last row, backward: its first row). A refreshed head page that no longer
83
+ * contains it has slid past the loaded pages — merging would hide a gap.
84
+ */
85
+ anchorId?: string;
86
+ /** Index of the last merged page (0 = head only). */
87
+ currentPage: number;
88
+ hasMore: boolean;
89
+ /**
90
+ * Synthetic (non-cursorable) rows of the head page. A sort can place them
91
+ * among older pages, so a head refresh drops them by id rather than by
92
+ * position — the fresh head brings its own.
93
+ */
94
+ headSyntheticIds?: string[];
95
+ isLoadingMore?: boolean;
96
+ items: TItem[];
97
+ loadMoreError?: unknown;
98
+ /** Cursor for the next page (offset: page index). `null` = exhausted. */
99
+ nextCursor?: TCursor | null;
100
+ /** Per-page bookkeeping, head first. Missing on legacy data = one page. */
101
+ pages?: ReplicaPageInfo<TCursor>[];
102
+ pageSize: number;
103
+ total?: number;
104
+ }
105
+ interface ReplicaPagingContext<TItem> {
106
+ /**
107
+ * Client-only rows (optimistic inserts whose server row may not exist yet).
108
+ * They survive head refreshes and are never persisted.
109
+ */
110
+ isClientOnly?: (item: TItem) => boolean;
111
+ }
112
+ /** Concatenate in view order: forward = head first, backward = head last. */
113
+ declare const joinPages: <TItem>(head: TItem[], rest: TItem[], direction: ReplicaPagingDirection) => TItem[];
114
+ /** The paging config one entry reads with: `directionOf` resolved for `key`. */
115
+ declare const pagingFor: <TItem, TCursor>(config: ReplicaPagingConfig<TItem, TCursor>, key: string) => ReplicaPagingConfig<TItem, TCursor>;
116
+ /**
117
+ * Cursor to request the next page with; `null` → exhausted, `undefined` →
118
+ * cannot page yet. Offset views without bookkeeping (seeded by legacy code or
119
+ * an older persisted shape) derive it from `currentPage` / `hasMore`.
120
+ */
121
+ declare const getNextPageCursor: <TItem, TCursor>(data: ReplicaPagedData<TItem, TCursor> | undefined, config: Pick<ReplicaPagingConfig<TItem, TCursor>, 'deriveCursor' | 'direction' | 'isCursorable' | 'mode'>) => TCursor | null | undefined;
122
+ /**
123
+ * Fold a fresh HEAD page into the view.
124
+ *
125
+ * - First load, a query change (`reset`) or an unextended view → the head page.
126
+ * - Extended view, `offset` mode → keep the loaded depth: fresh head first,
127
+ * then previously loaded rows not in it (shifted offsets self-heal by id).
128
+ * - Extended view, `cursor` mode → keep older pages only when the fresh head
129
+ * still contains the join anchor and the next cursor is known; otherwise
130
+ * collapse to the head page (never show a hidden gap).
131
+ *
132
+ * Client-only rows still in the view are kept on the head side.
133
+ */
134
+ declare const applyHeadPage: <TItem, TCursor>(current: ReplicaPagedData<TItem, TCursor> | undefined, page: ReplicaPageResult<TItem, TCursor>, options: {
135
+ pageSize: number;
136
+ reset?: boolean;
137
+ }, config: ReplicaPagingConfig<TItem, TCursor>, ctx?: ReplicaPagingContext<TItem>) => ReplicaPagedData<TItem, TCursor>;
138
+ /** Merge the NEXT page (fetched with `getNextPageCursor`) into the view. */
139
+ declare const applyNextPage: <TItem, TCursor>(current: ReplicaPagedData<TItem, TCursor>, page: ReplicaPageResult<TItem, TCursor>, config: ReplicaPagingConfig<TItem, TCursor>, ctx?: ReplicaPagingContext<TItem>) => ReplicaPagedData<TItem, TCursor>;
140
+ /**
141
+ * Insert new rows on the HEAD side (a streamed message, a created topic).
142
+ * Cursors point at the far end, so they stay valid; offsets shift and are
143
+ * healed by id de-duplication on the next page merge.
144
+ */
145
+ declare const insertHeadItems: <TItem, TCursor>(current: ReplicaPagedData<TItem, TCursor>, items: TItem[], config: ReplicaPagingConfig<TItem, TCursor>) => ReplicaPagedData<TItem, TCursor>;
146
+ /**
147
+ * Patch (or remove, when `fn` returns `undefined`) one row by id. Returns the
148
+ * same reference when the row is absent or unchanged.
149
+ */
150
+ declare const mapPagedItem: <TItem, TCursor, TData extends ReplicaPagedData<TItem, TCursor>>(current: TData, id: string, fn: (item: TItem) => TItem | undefined, config: Pick<ReplicaPagingConfig<TItem>, 'getId'>) => TData;
151
+ /** Whether a paged view holds a row. */
152
+ declare const hasPagedItem: <TItem>(data: ReplicaPagedData<TItem, unknown> | undefined, id: string, config: Pick<ReplicaPagingConfig<TItem>, 'getId'>) => boolean;
153
+ /** Drop every loaded page but the head (e.g. after an edit inside older history). */
154
+ declare const collapseToHead: <TItem, TCursor>(current: ReplicaPagedData<TItem, TCursor>, config: ReplicaPagingConfig<TItem, TCursor>) => ReplicaPagedData<TItem, TCursor>;
155
+ /**
156
+ * What survives a reload: whole pages from the head side, bounded by
157
+ * `persist.pages` and `persist.maxItems`, client-only rows and transient
158
+ * flags stripped. Offset views are cut at page-size boundaries so the
159
+ * persisted `currentPage` stays a valid offset; a cut inside a cursor page
160
+ * forgets the cursor (pagination resumes after the next server head page).
161
+ */
162
+ declare const toPersistedPage: <TItem, TCursor, TData extends ReplicaPagedData<TItem, TCursor>>(data: TData, config: ReplicaPagingConfig<TItem, TCursor>, ctx?: ReplicaPagingContext<TItem>) => TData;
163
+ /** Address of one persisted row: the resource's row key inside an identity scope. */
164
+ interface ReplicaRowKey {
165
+ queryKey: string;
166
+ scope: string;
167
+ }
168
+ interface ReplicaRow<T> {
169
+ data: T;
170
+ updatedAt: number;
171
+ }
172
+ /**
173
+ * Where a replica keeps its rows between sessions (IndexedDB, localStorage …).
174
+ * The server stays the durable source of truth, so `get` is best-effort: it
175
+ * swallows its own I/O failures and reports a miss. Writes are not: `set` and
176
+ * `remove` must reject when they did not land (see each method), so the
177
+ * engine can retry them instead of treating a dropped row as persisted.
178
+ */
179
+ interface ReplicaStorage<T> {
180
+ get: (key: ReplicaRowKey) => Promise<ReplicaRow<T> | undefined>;
181
+ /**
182
+ * Strict read for destructive passes: like `get`, but **rejects** when the
183
+ * row could not be read (I/O error, unparsable value) instead of reporting a
184
+ * miss. The engine reads its per-scope index (and every row it patches)
185
+ * through it before `remove` / `clear` / an entity patch / an eviction, so an
186
+ * unreadable index fails the pass rather than passing for an empty one.
187
+ *
188
+ * Optional only for compatibility: a storage without it still hydrates and
189
+ * persists, but every destructive pass **fails** (`clear()` resolves `false`,
190
+ * a removal stays unpurged and is retried, an entity guard is kept) because
191
+ * the engine cannot tell a miss from a failed read through `get`.
192
+ */
193
+ getStrict?: (key: ReplicaRowKey) => Promise<ReplicaRow<T> | undefined>;
194
+ /**
195
+ * Delete a row. Must **reject** when the delete did not land (a closed
196
+ * IndexedDB, a quota or security error): the engine keeps the row indexed
197
+ * and the removal unpurged, so the next `remove` / `clear` retries it instead
198
+ * of letting a row the user deleted hydrate again. A missing row is a success.
199
+ */
200
+ remove: (key: ReplicaRowKey) => Promise<void>;
201
+ /**
202
+ * Write a row. Should **reject** when the write did not land: the engine only
203
+ * treats a row as holding a value after `set` resolved, so a rejected write is
204
+ * retried by the next persist rather than skipped as redundant. A storage that
205
+ * swallows failures still works, but a dropped write then survives until the
206
+ * value changes or the next session.
207
+ */
208
+ set: (key: ReplicaRowKey, row: ReplicaRow<T>) => Promise<void>;
209
+ }
210
+ /**
211
+ * Where the confirmed value of an entry came from.
212
+ * - `storage`: hydrated from the persisted projection (may be stale)
213
+ * - `server`: confirmed by a network response
214
+ * - `local`: written locally before any hydrate/replace landed
215
+ * - `seed`: a provisional local row (e.g. a list row standing in for a detail so
216
+ * an object stays resolvable). Unlike `local` it is *not* authoritative: a
217
+ * persisted `storage` value may replace it on hydrate, and it never persists —
218
+ * so it cannot overwrite the detail it is only standing in for.
219
+ */
220
+ type ReplicaSource = 'local' | 'seed' | 'server' | 'storage';
221
+ /**
222
+ * Identity partition of the persisted projection (user + workspace by default).
223
+ * `use` feeds the sync hook, `get` imperative actions, `canPersist` gates writes
224
+ * while the scope is still an optimistic guess (identity not resolved yet).
225
+ */
226
+ interface ReplicaScope {
227
+ /**
228
+ * Whether this runtime may read the persisted projection back into memory.
229
+ * Defaults to `true`. A surface that never resolves an identity (e.g. a
230
+ * public embed that does not mount the user store) opts out: its scope is
231
+ * only the last-known guess, so hydrating it could paint a different — or a
232
+ * since-invalidated — session's private rows before authorization is checked.
233
+ */
234
+ canHydrate?: () => boolean;
235
+ canPersist: () => boolean;
236
+ get: () => string;
237
+ use: () => string;
238
+ }
239
+ interface ReplicaPendingMutation<T> {
240
+ apply: (data: T) => T;
241
+ id: number;
242
+ /**
243
+ * Query the overlay was started under. An entry key can be reused for another
244
+ * query (a navigation), and an overlay only ever describes the query that
245
+ * created it — carrying it over would apply (and later persist) one query's
246
+ * row inside another.
247
+ */
248
+ query?: string;
249
+ }
250
+ interface ReplicaEntryMeta<T> {
251
+ /**
252
+ * Confirmed snapshot. Only kept while optimistic mutations are in flight —
253
+ * otherwise the view itself is the confirmed value.
254
+ */
255
+ base?: T;
256
+ /** Params of the last hydrate/replace — what `loadMore` pages with. */
257
+ params?: unknown;
258
+ pending: ReplicaPendingMutation<T>[];
259
+ /** Stable query identity beyond the key (filters, page size). */
260
+ query?: string;
261
+ /**
262
+ * Every write made while the entry is still a `seed`, in order — the seed
263
+ * writes themselves and the confirmed writes made over them. A hydrate that
264
+ * replaces the seed replays them onto the stored value: the seed's fresher
265
+ * fields (a list row's renamed title) and a confirmed edit made before
266
+ * storage answered both survive, instead of storage painting older values.
267
+ */
268
+ seedPatches?: ReplicaSeedPatch<T>[];
269
+ source: ReplicaSource;
270
+ updatedAt: number;
271
+ }
272
+ interface ReplicaSeedPatch<T> {
273
+ /**
274
+ * Replayed onto the stored value, so a seed write must merge the fields it
275
+ * owns into its input rather than ignore it (its first run sees `undefined`).
276
+ */
277
+ apply: (data: T | undefined) => T | undefined;
278
+ persist: boolean;
279
+ /** A provisional seed write: replayed, but never makes the result confirmed. */
280
+ seed?: boolean;
281
+ /**
282
+ * Source of a seed write. A later seed of the same tag supersedes this one
283
+ * (since the last confirmed write), so it must carry every field this one set.
284
+ */
285
+ tag?: string;
286
+ }
287
+ /** Bookkeeping slot a replica keeps inside its domain store. */
288
+ interface ReplicaState<T> {
289
+ entries: Record<string, ReplicaEntryMeta<T>>;
290
+ /** The scope every entry in memory belongs to. */
291
+ scope?: string;
292
+ }
293
+ interface ReplicaResource<TParams, TData, TFetched = TData, TCursor = any> {
294
+ /** Paged resources receive the page cursor (`undefined` = head page). */
295
+ fetcher?: (params: TParams, cursor?: TCursor) => Promise<TFetched>;
296
+ key: (params: TParams) => string;
297
+ /** Cap of persisted rows per scope (LRU eviction, see `DefineReplicaOptions.persist`). */
298
+ maxPersistedEntries?: number;
299
+ name: string;
300
+ /** Storage namespace — `name` + `version`, so a version bump orphans old rows. */
301
+ namespace: string;
302
+ /** Present on paged resources (`definePagedReplica`). */
303
+ paging?: ReplicaPagingConfig<any, TCursor>;
304
+ /** Whether the resource survives a reload (it has a storage). */
305
+ persisted: boolean;
306
+ /** Whether an entry key may be hydrated and persisted (see `persistKey`). */
307
+ persistKey: (key: string) => boolean;
308
+ /**
309
+ * Query identity beyond `key` (filters, page size). Persisted rows are
310
+ * stored per query, so a different query never hydrates; in memory a query
311
+ * change resets loaded pages.
312
+ */
313
+ query: (params: TParams) => string | undefined;
314
+ scope: ReplicaScope;
315
+ storage?: ReplicaStorage<TData>;
316
+ /** Row key in `storage` for these params (`key`, plus `?query` when set). */
317
+ storageKey: (params: TParams) => string;
318
+ /** Custom query-cache key of the network sync (see `DefineReplicaOptions.syncKey`). */
319
+ syncKey?: (params: TParams) => readonly unknown[];
320
+ version: number;
321
+ }
322
+ /** Persisted row key: one row per entry key and query. */
323
+ declare const replicaStorageKey: (key: string, query?: string) => string;
324
+ /**
325
+ * Whether `storageKey` is a persisted row of entry `key`, under any query.
326
+ *
327
+ * Entry keys are free-form, so a `?` prefix alone cannot tell `foo` + a query
328
+ * from a plain entry `foo?bar`. A row only counts as a variant when it is
329
+ * exactly `replicaStorageKey(key, query)` for a query this module could have
330
+ * written: the rest after `key?` must be a canonical `stableQueryKey` (it
331
+ * parses as JSON and serializes back to itself). Row keys and the index keep
332
+ * their existing flat format, so rows already persisted on devices match the
333
+ * same way.
334
+ */
335
+ declare const isReplicaStorageKeyOf: (storageKey: string, key: string) => boolean;
336
+ /** Deterministic JSON: sorted keys, `undefined` dropped. */
337
+ declare const stableQueryKey: (value: unknown) => string;
338
+ /**
339
+ * A fixed identity partition. The default for apps without accounts; an app
340
+ * with sign-in passes a scope derived from the signed-in user / workspace so
341
+ * one browser profile never mixes two identities' rows.
342
+ */
343
+ declare const staticReplicaScope: (scope?: string) => ReplicaScope;
344
+ /** Builds the storage of one resource from its namespace (`replica:<name>:v<version>`). */
345
+ type ReplicaStorageFactory<TData> = (namespace: string) => ReplicaStorage<TData> | undefined;
346
+ interface DefineReplicaOptions<TParams, TData, TFetched = TData, TCursor = any> {
347
+ /** Default network fetcher; a store binding may override it. */
348
+ fetcher?: (params: TParams, cursor?: TCursor) => Promise<TFetched>;
349
+ /** Entry identity inside one scope (memory bucket + persisted row). */
350
+ key: (params: TParams) => string;
351
+ name: string;
352
+ paging?: ReplicaPagingConfig<any, TCursor>;
353
+ /**
354
+ * Bound what the resource keeps in storage. `maxEntries` caps the persisted
355
+ * rows (storage keys: one per entry key and query) per scope; past it the
356
+ * least recently used rows are evicted — their persisted row, index entry
357
+ * and, when nothing holds it, their memory entry. Use rows (a hydrate or a
358
+ * persist, including a skipped unchanged one) count as recent, and the order
359
+ * lives in the persisted index, so it survives reloads. A row on screen (a
360
+ * mounted `useSync`, see `retain`), with pending optimistic writes or a
361
+ * `loadMore` in flight is never evicted. Unbounded by default.
362
+ */
363
+ persist?: {
364
+ maxEntries?: number;
365
+ };
366
+ /**
367
+ * Entries that never touch storage (no hydrate, no persist) — e.g. scoped
368
+ * buckets whose rows must not land under an ordinary entry. Defaults to all.
369
+ */
370
+ persistKey?: (key: string) => boolean;
371
+ /**
372
+ * Query identity beyond `key` (filters, sort, page size) — anything that
373
+ * changes the rows without changing the key. Persisted rows are kept per
374
+ * query, so a projection taken under other filters never hydrates; in memory
375
+ * a query change resets loaded pages.
376
+ */
377
+ query?: (params: TParams) => unknown;
378
+ /** Identity partition of memory and persisted rows. Defaults to one static scope. */
379
+ scope?: ReplicaScope;
380
+ /** Persisted copy of the replica. Omit for a memory-only replica (no first-frame paint). */
381
+ storage?: ReplicaStorage<TData> | ReplicaStorageFactory<TData>;
382
+ /**
383
+ * Query-cache key of the network sync, for a resource adopting an existing
384
+ * key that other code already revalidates by. Defaults to `replicaKeys.sync`.
385
+ * With a custom key, revalidate through that key — `slice.revalidate` only
386
+ * matches default keys.
387
+ */
388
+ syncKey?: (params: TParams) => readonly unknown[];
389
+ /** Bump to invalidate every persisted row written by older shapes. */
390
+ version: number;
391
+ }
392
+ declare const defineReplica: <TParams, TData, TFetched = TData, TCursor = any>(options: DefineReplicaOptions<TParams, TData, TFetched, TCursor>) => ReplicaResource<TParams, TData, TFetched, TCursor>;
393
+ interface DefinePagedReplicaOptions<TParams, TItem, TCursor> extends Omit<DefineReplicaOptions<TParams, ReplicaPagedData<TItem, TCursor>, ReplicaPageResult<TItem, TCursor>>, 'fetcher' | 'paging' | 'storage'> {
394
+ /** `cursor` is `undefined` for the head page. */
395
+ fetchPage?: (params: TParams, cursor: TCursor | undefined) => Promise<ReplicaPageResult<TItem, TCursor>>;
396
+ paging: ReplicaPagingConfig<TItem, TCursor>;
397
+ storage?: ReplicaStorage<any> | ReplicaStorageFactory<any>;
398
+ }
399
+ /**
400
+ * A resource whose value is a paged list ({@link ReplicaPagedData}). The
401
+ * store view type may extend the paged shape with domain fields.
402
+ */
403
+ declare const definePagedReplica: <TParams, TItem, TCursor = number, TData extends ReplicaPagedData<TItem, TCursor> = ReplicaPagedData<TItem, TCursor>>(options: DefinePagedReplicaOptions<TParams, TItem, TCursor>) => ReplicaResource<TParams, TData, ReplicaPageResult<TItem, TCursor>, TCursor>;
404
+ /**
405
+ * Pure transition core of a replica. It never touches the store
406
+ * or storage: it returns the next bookkeeping slot, the view writes the store
407
+ * binding must apply, and the persistence effects it must run.
408
+ *
409
+ * The VIEW (what selectors read) lives in the domain store, wherever the
410
+ * binding's lens puts it. The reducer only reads it through `readView`, so a
411
+ * domain can keep its existing state shape (e.g. `topicDataMap`).
412
+ */
413
+ type ReplicaAction<T> = {
414
+ data: T;
415
+ key: string;
416
+ /**
417
+ * Repaint a slot that answers a *different* query from the persisted page
418
+ * (a navigation). Only the caller that knows the entry has moved on
419
+ * (the requested query changed) may ask for it; by default a hydrate only
420
+ * fills an empty slot.
421
+ */
422
+ overwrite?: boolean;
423
+ params?: unknown;
424
+ query?: string;
425
+ scope: string;
426
+ type: 'hydrate';
427
+ updatedAt?: number;
428
+ } | {
429
+ /** Receives the confirmed value (base or view); `undefined` keeps it. */
430
+ data: (confirmed: T | undefined) => T | undefined;
431
+ key: string;
432
+ params?: unknown;
433
+ query?: string;
434
+ scope: string;
435
+ type: 'replace';
436
+ } | {
437
+ apply: (data: T | undefined) => T | undefined;
438
+ key: string;
439
+ persist: boolean;
440
+ scope: string;
441
+ /** Marks a provisional write (`'seed'`); defaults to the entry's own source. */
442
+ source?: ReplicaSource;
443
+ /**
444
+ * Seed writes of one source (a list refresh, a session refresh). A later
445
+ * seed write supersedes an earlier one of the same tag since the last
446
+ * confirmed write, so a remembered seed history stays bounded.
447
+ */
448
+ seedTag?: string;
449
+ type: 'update';
450
+ } | {
451
+ apply: (data: T) => T;
452
+ id: number;
453
+ key: string;
454
+ /** Query the overlay is started under (see `ReplicaPendingMutation`). */
455
+ query?: string;
456
+ scope: string;
457
+ type: 'optimistic';
458
+ } | {
459
+ confirm?: (data: T) => T;
460
+ id: number;
461
+ key: string;
462
+ scope: string;
463
+ type: 'commit';
464
+ } | {
465
+ id: number;
466
+ key: string;
467
+ scope: string;
468
+ type: 'rollback';
469
+ } | {
470
+ key: string;
471
+ scope: string;
472
+ type: 'remove';
473
+ } | {
474
+ scope: string;
475
+ type: 'resetScope';
476
+ };
477
+ type ReplicaEffect<T> = {
478
+ data: T;
479
+ key: string;
480
+ query?: string;
481
+ scope: string;
482
+ type: 'persist';
483
+ } | {
484
+ key: string;
485
+ query?: string;
486
+ scope: string;
487
+ type: 'remove';
488
+ };
489
+ type ReplicaViewWrite<T> = {
490
+ data: T | undefined;
491
+ key: string;
492
+ } | {
493
+ type: 'clear';
494
+ };
495
+ interface ReplicaTransition<T> {
496
+ effects: ReplicaEffect<T>[];
497
+ state: ReplicaState<T>;
498
+ writes: ReplicaViewWrite<T>[];
499
+ }
500
+ declare const createReplicaState: <T>() => ReplicaState<T>;
501
+ declare const replicaReducer: <T>(state: ReplicaState<T>, action: ReplicaAction<T>, readView: (key: string) => T | undefined, now?: number) => ReplicaTransition<T>;
502
+ /**
503
+ * Strict read of one row (see `ReplicaStorage.getStrict`). Rejects when the
504
+ * storage has no strict read: its `get` swallows I/O failures into a miss, so a
505
+ * destructive pass reading through it could take an unreadable row (the index)
506
+ * for an empty one and report success for rows it never saw.
507
+ */
508
+ declare const readReplicaRowStrict: <T>(storage: ReplicaStorage<T>, key: ReplicaRowKey) => Promise<ReplicaRow<T> | undefined>;
509
+ /**
510
+ * Serializes writes for one projection key. A later snapshot can never be overwritten by an
511
+ * earlier, slower IndexedDB write.
512
+ */
513
+ declare class ReplicaWriteQueue<T> {
514
+ #private;
515
+ constructor(storage: ReplicaStorage<T>);
516
+ /** Resolves `false` when storage rejected the delete (see `ReplicaStorage.remove`). */
517
+ remove: (key: ReplicaRowKey) => Promise<boolean>;
518
+ set: (key: ReplicaRowKey, value: ReplicaRow<T>) => Promise<boolean>;
519
+ /**
520
+ * Serialized read-modify-write: `fn` sees the row as of every earlier queued
521
+ * write for this key. Return a projection to write it, `null` to remove the
522
+ * row, or `undefined` to leave it untouched (a missing row stays missing).
523
+ *
524
+ * `read` picks how the row is read:
525
+ * - `'get'` (default): best-effort `get`, a failed read passes for a miss.
526
+ * - `'prefer-strict'`: `getStrict` when the storage has one, so a read failure
527
+ * fails the update (resolves `false`) instead of passing for a missing row
528
+ * that `fn` would then overwrite; `get` otherwise.
529
+ * - `'strict'`: `getStrict` only; a storage without it fails the update.
530
+ * Required for destructive passes (see `readReplicaRowStrict`).
531
+ */
532
+ update: (key: ReplicaRowKey, fn: (current: ReplicaRow<T> | undefined) => ReplicaRow<T> | null | undefined, { read }?: {
533
+ read?: 'get' | 'prefer-strict' | 'strict';
534
+ }) => Promise<boolean>;
535
+ }
536
+ /** Reserved storage key of the per-scope index of persisted rows. */
537
+ declare const REPLICA_INDEX_KEY = "__replica:index";
538
+ /**
539
+ * The engine's view of the host store. The engine never owns the rendered
540
+ * value — the host does (a Zustand slice, a signal, a plain object) — it only
541
+ * reads entries through `read` and hands every transition to `commit`.
542
+ */
543
+ interface ReplicaStorePort<TData> {
544
+ /**
545
+ * Apply the view writes and the next bookkeeping slot as ONE host update, so
546
+ * subscribers never observe a view out of step with its bookkeeping.
547
+ */
548
+ commit: (writes: ReplicaViewWrite<TData>[], state: ReplicaState<TData>, label: string) => void;
549
+ /** Current bookkeeping slot. */
550
+ getState: () => ReplicaState<TData>;
551
+ /** Enumerate loaded keys (entity propagation). Defaults to the bookkeeping entries. */
552
+ keys?: () => string[];
553
+ /** Materialized value of one entry, as the UI sees it. */
554
+ read: (key: string) => TData | undefined;
555
+ }
556
+ /**
557
+ * How entities (e.g. topics) appear inside a non-paged resource value. Paged
558
+ * resources get this for free from `paging.getId`. Build one with
559
+ * `singleEntity` (a detail value), `arrayEntity` (a plain list) or by hand for
560
+ * nested shapes (grouped lists).
561
+ */
562
+ interface ReplicaEntityAdapter<TData, TItem> {
563
+ /** Whether `data` holds the entity. */
564
+ has: (data: TData, id: string) => boolean;
565
+ /**
566
+ * Map the entity inside `data` (`fn` returning `undefined` deletes it).
567
+ * Return `data` itself when nothing changed, or `undefined` when the whole
568
+ * value goes away with the entity (a detail value).
569
+ */
570
+ map: (data: TData, id: string, fn: (item: TItem) => TItem | undefined) => TData | undefined;
571
+ }
572
+ interface ReplicaEngineOptions<TParams, TData, TFetched> {
573
+ /** Label prefix of host updates (devtools action names). Defaults to the resource name. */
574
+ actionPrefix?: string;
575
+ /** Non-paged resources: how entities sit in the value (see `linkReplicaEntity`). */
576
+ entity?: ReplicaEntityAdapter<TData, any>;
577
+ /** Overrides `resource.fetcher` when the fetch needs store context. */
578
+ fetcher?: (params: TParams, cursor?: any) => Promise<TFetched>;
579
+ /** Paged: rows that only exist client-side (kept across refreshes, never persisted). */
580
+ isClientOnly?: (item: any) => boolean;
581
+ /** Reject a persisted value that cannot serve these params. Rarely needed: rows are stored per query. */
582
+ isHydratable?: (cached: TData, params: TParams) => boolean;
583
+ /**
584
+ * Non-paged: whether a head response means the entry no longer exists. Such a
585
+ * response is applied as an explicit removal (memory + persisted row) instead
586
+ * of through `merge` — otherwise a `null` would fall back to the confirmed
587
+ * value (the reducer resolves `action.data(confirmed) ?? confirmed`), so a
588
+ * deleted aggregate would be retained and re-persisted.
589
+ */
590
+ isMissing?: (incoming: TFetched) => boolean;
591
+ /**
592
+ * Non-paged: fold a server response into the confirmed value. Return
593
+ * `undefined` to keep the current value. Defaults to "the response is the value".
594
+ */
595
+ merge?: (incoming: TFetched, confirmed: TData | undefined, params: TParams) => TData | undefined;
596
+ /** Where the engine reads and commits the view. */
597
+ port: ReplicaStorePort<TData>;
598
+ /**
599
+ * Reconcile a server response with the value currently shown before it is
600
+ * folded in — e.g. keep a row the client knows is newer, or ignore a
601
+ * response that raced a newer local state. Return `undefined` to drop the
602
+ * response entirely.
603
+ */
604
+ prepareHead?: (incoming: TFetched, current: TData | undefined, params: TParams) => TFetched | undefined;
605
+ /** Re-run the network sync of one entry (or all); wired by the fetch adapter. */
606
+ revalidate?: (key?: string) => Promise<unknown>;
607
+ /**
608
+ * Strip transient / client-only parts before persisting.
609
+ *
610
+ * - a value → persisted
611
+ * - `undefined` → skip this write (keep whatever is already stored)
612
+ * - `null` → the entry is a server-confirmed absence: drop any prior
613
+ * persisted projection, so a later hydrate cannot paint a value the server
614
+ * no longer has (the view keeps the confirmed value).
615
+ */
616
+ toPersisted?: (data: TData) => TData | null | undefined;
617
+ /** Paged: domain fields derived from params, written with every head page. */
618
+ viewFields?: (params: TParams) => Partial<TData>;
619
+ }
620
+ /** What a head response did once folded in (see `applyHead`). */
621
+ type ReplicaHeadOutcome = 'dropped' | 'missing' | 'replaced';
622
+ interface OptimisticMutationOptions<TData, TResult> {
623
+ /** Turn the server result into the confirmed value; defaults to re-applying `apply`. */
624
+ confirm?: (result: TResult) => (data: TData) => TData;
625
+ /** Revalidate the entry after the server call settles. */
626
+ revalidate?: boolean;
627
+ }
628
+ /** Handle of an optimistic overlay that is settled later (see `beginOptimistic`). */
629
+ interface ReplicaOptimisticToken<TData> {
630
+ commit: (confirm?: (data: TData) => TData) => void;
631
+ rollback: () => void;
632
+ }
633
+ /**
634
+ * The replica engine: every transition of one resource, independent of any
635
+ * UI framework or state library.
636
+ *
637
+ * It owns hydrate-if-empty, server replace, pagination, optimistic overlays
638
+ * with commit/rollback, entity propagation, scope isolation and serialized
639
+ * persistence. The host store keeps the rendered value (see
640
+ * {@link ReplicaStorePort}); fetch scheduling (when to sync, dedupe, focus
641
+ * revalidation) belongs to an adapter such as `local-replica/zustand`.
642
+ */
643
+ declare const createReplicaEngine: <TParams, TData, TFetched = TData>(resource: ReplicaResource<TParams, TData, TFetched>, options: ReplicaEngineOptions<TParams, TData, TFetched>) => {
644
+ applyHead: (params: TParams, fetched: TFetched, scope?: string) => {
645
+ changed: boolean;
646
+ outcome: ReplicaHeadOutcome;
647
+ };
648
+ beginEntityOptimistic: <TItem>(id: string, fn: (item: TItem) => TItem | undefined) => ReplicaOptimisticToken<TData>[];
649
+ beginOptimistic: (key: string, apply: (data: TData) => TData, query?: string) => ReplicaOptimisticToken<TData>;
650
+ clear: () => Promise<boolean>;
651
+ clearGeneration: (scope: string) => number;
652
+ clearScope: (scope: string) => Promise<boolean>;
653
+ removalGeneration: (scope: string, key: string) => number;
654
+ collapse: (key: string) => boolean;
655
+ dispatch: (action: ReplicaAction<TData>) => boolean;
656
+ ensureScope: (scope: string) => void;
657
+ entityKeys: (id: string) => string[];
658
+ fetcher: ((params: TParams, cursor?: any) => Promise<TFetched>) | undefined;
659
+ getConfirmed: (key: string) => TData | undefined;
660
+ hydrate: (params: TParams, scope?: string, { overwrite }?: {
661
+ overwrite?: boolean;
662
+ }) => Promise<boolean>;
663
+ insertHead: <TItem>(key: string, items: TItem[], { persist }?: {
664
+ persist?: boolean | undefined;
665
+ }) => boolean;
666
+ isMissingHead: (incoming: TFetched) => boolean;
667
+ loadMore: (key: string, fallbackParams?: TParams) => Promise<void>;
668
+ optimistic: <TResult>(key: string, apply: (data: TData) => TData, serverCall: () => Promise<TResult>, mutationOptions?: OptimisticMutationOptions<TData, TResult>) => Promise<TResult>;
669
+ patchStoredEntity: <TItem>(id: string, fn: (item: TItem) => TItem | undefined) => Promise<void>;
670
+ patchStoredRows: (fn: (data: TData) => TData | null | undefined) => Promise<void>;
671
+ persist: (key: string) => void;
672
+ remove: (key: string, scope?: string) => boolean;
673
+ removeEntity: (id: string, options?: {
674
+ persist?: boolean;
675
+ scope?: string;
676
+ } | undefined) => void;
677
+ replace: (params: TParams, fetched: TFetched, scope?: string) => boolean;
678
+ resource: ReplicaResource<TParams, TData, TFetched, any>;
679
+ retain: (key: string, queryKey?: string) => () => void;
680
+ revalidate: (key?: string) => Promise<unknown>;
681
+ update: (key: string, apply: (data: TData | undefined) => TData | undefined, { persist, seedTag, source }?: {
682
+ persist?: boolean;
683
+ seedTag?: string;
684
+ source?: ReplicaSource;
685
+ }) => boolean;
686
+ updateEntity: <TItem>(id: string, fn: (item: TItem) => TItem | undefined, options?: {
687
+ persist?: boolean;
688
+ scope?: string;
689
+ } | undefined) => void;
690
+ updateMany: (entries: {
691
+ apply: (data: TData | undefined) => TData | undefined;
692
+ key: string;
693
+ }[], { persist, seedTag, source }?: {
694
+ persist?: boolean;
695
+ seedTag?: string;
696
+ source?: ReplicaSource;
697
+ }) => boolean;
698
+ };
699
+ type ReplicaEngine<TParams, TData, TFetched = TData> = ReturnType<typeof createReplicaEngine<TParams, TData, TFetched>>;
700
+ /** The part of a slice entity propagation needs (structural, so any slice fits). */
701
+ interface ReplicaEntityTarget {
702
+ beginEntityOptimistic: <TItem>(id: string, fn: (item: TItem) => TItem | undefined) => ReplicaOptimisticToken<any>[];
703
+ /** Wipe the resource in the active scope (memory + every persisted row). */
704
+ clear?: () => Promise<boolean>;
705
+ /** Patch persisted rows whose entry is not loaded in memory. */
706
+ patchStoredEntity: <TItem>(id: string, fn: (item: TItem) => TItem | undefined) => Promise<void>;
707
+ /**
708
+ * Explicit entity removal that also guards later hydrates. Optional for
709
+ * custom targets; `linkReplicaEntity` falls back to `updateEntity`.
710
+ */
711
+ removeEntity?: (id: string, options?: {
712
+ persist?: boolean;
713
+ scope?: string;
714
+ }) => void;
715
+ revalidate: (key?: string) => Promise<unknown>;
716
+ updateEntity: <TItem>(id: string, fn: (item: TItem) => TItem | undefined, options?: {
717
+ persist?: boolean;
718
+ }) => void;
719
+ }
720
+ /**
721
+ * Link resources that hold copies of the same entity (e.g. a topic in the
722
+ * sidebar list, the management page and the detail cache).
723
+ *
724
+ * Each resource keeps exactly one store location and decides how the entity
725
+ * maps into its own shape (paged resources by `getId`, single-entity ones by
726
+ * their entity adapter). The link only fans an entity-level change out to the
727
+ * resources that currently hold it — no global event bus, no shared owner.
728
+ */
729
+ declare const linkReplicaEntity: <TItem>(targets: ReplicaEntityTarget[]) => {
730
+ clear: () => Promise<boolean>;
731
+ optimistic: <TResult>(id: string, fn: ((item: TItem) => TItem) | 'remove', serverCall: () => Promise<TResult>) => Promise<TResult>;
732
+ remove: (id: string, options?: {
733
+ persist?: boolean;
734
+ scope?: string;
735
+ }) => void;
736
+ revalidate: () => Promise<unknown[]>;
737
+ update: (id: string, fn: (item: TItem) => TItem, options?: {
738
+ persist?: boolean;
739
+ }) => void;
740
+ };
741
+ /**
742
+ * The value IS one entity, or wraps one (`{ project, members }`). Deleting the
743
+ * entity drops the whole value.
744
+ */
745
+ declare const singleEntity: <TData, TItem = TData>(getId: (item: TItem) => string, lens?: {
746
+ get: (data: TData) => TItem;
747
+ set: (data: TData, item: TItem) => TData;
748
+ }) => ReplicaEntityAdapter<TData, TItem>;
749
+ /** The value is a plain list of entities; deleting one filters it out. */
750
+ declare const arrayEntity: <TItem>(getId: (item: TItem) => string) => ReplicaEntityAdapter<TItem[], TItem>;
751
+ /**
752
+ * Keys of the queries a fetch adapter runs for a replica. Hosts that sweep
753
+ * their query cache by prefix (e.g. "refetch everything after a restore")
754
+ * match on {@link REPLICA_KEY_PREFIX}.
755
+ */
756
+ declare const REPLICA_KEY_PREFIX = "replica:";
757
+ declare const replicaKeys: {
758
+ /** One-shot read of the persisted row for `storageKey`. */
759
+ hydrate: (name: string, version: number, scope: string, storageKey: string) => readonly ['replica:hydrate', string, number, string, string];
760
+ /** Network sync of the head of one entry. */
761
+ sync: (name: string, version: number, scope: string, key: string, params: unknown) => readonly ['replica:sync', string, number, string, string, unknown];
762
+ };
763
+ /** Match a resource's sync keys, optionally narrowed to one scope and/or entry key. */
764
+ declare const isReplicaSyncKey: (queryKey: unknown, name: string, filter?: {
765
+ key?: string;
766
+ scope?: string;
767
+ }) => boolean;
768
+ /**
769
+ * Resolve once `promise` settles or `ms` elapses — never rejects, never hangs.
770
+ *
771
+ * Bounds a pre-paint hydrate in a route loader: the persisted read is usually
772
+ * sub-millisecond, and the deadline only fires on a pathological storage stall,
773
+ * so a route transition never waits longer than `ms` for it.
774
+ */
775
+ declare const settleWithin: (promise: Promise<unknown>, ms: number) => Promise<void>;
776
+ /**
777
+ * The entry storage keys this resource has a persisted row for in the active
778
+ * scope, oldest first.
779
+ *
780
+ * Storage has no key listing of its own; the engine keeps one index row per
781
+ * scope (`REPLICA_INDEX_KEY`) as it persists and drops entries, and this reads
782
+ * it. That is how code outside the owning store reaches rows an *earlier
783
+ * session* wrote — bounding how many survive a reload, or invalidating a row
784
+ * whose entry is not loaded in memory.
785
+ *
786
+ * The keys are storage keys (`key`, plus `?query` when the resource sets one),
787
+ * so a caller only matches them against entry keys when the resource leaves
788
+ * `query` unset.
789
+ */
790
+ declare const readReplicaStoredKeys: (resource: Pick<ReplicaResource<any, any, any>, 'scope' | 'storage'>) => Promise<string[]>;
791
+ /**
792
+ * An async key-value store a replica can persist into: an IndexedDB object
793
+ * store, a SQLite table behind IPC, a `Map` in tests. Values are stored as
794
+ * given (structured clone), so the store decides what it can hold.
795
+ */
796
+ interface ReplicaKeyValueStore {
797
+ /** Rejects when the delete did not land. A missing key is a success. */
798
+ delete: (key: string) => Promise<void>;
799
+ /** Rejects when the read failed; resolves `undefined` for a missing key. */
800
+ get: (key: string) => Promise<unknown>;
801
+ /** Opens the store; awaited before every operation. */
802
+ initialize?: () => Promise<void>;
803
+ /** Rejects when the write did not land. */
804
+ set: (key: string, value: unknown) => Promise<void>;
805
+ }
806
+ interface KeyValueReplicaStorageOptions {
807
+ /** Prefix of every row of this storage, usually the resource name. */
808
+ namespace: string;
809
+ store: ReplicaKeyValueStore;
810
+ }
811
+ /** Store key of one row: `<namespace>:<scope>:<queryKey>`, each part URI-encoded but the namespace. */
812
+ declare const replicaRowStoreKey: (namespace: string, { queryKey, scope }: ReplicaRowKey) => string;
813
+ /** {@link ReplicaStorage} over an async key-value store, one record per row. */
814
+ declare class KeyValueReplicaStorage<T> implements ReplicaStorage<T> {
815
+ #private;
816
+ constructor({ namespace, store }: KeyValueReplicaStorageOptions);
817
+ get: (key: ReplicaRowKey) => Promise<ReplicaRow<T> | undefined>;
818
+ /** Like `get`, but rejects when the row could not be read (see `ReplicaStorage.getStrict`). */
819
+ getStrict: (key: ReplicaRowKey) => Promise<ReplicaRow<T> | undefined>;
820
+ /**
821
+ * Rejects when the delete did not land, unlike reads: a caller that removes a
822
+ * row the user deleted must be able to retry it.
823
+ */
824
+ remove: (key: ReplicaRowKey) => Promise<void>;
825
+ /**
826
+ * Rejects when the write did not land (see `ReplicaStorage.set`): the replica
827
+ * engine only treats a row as holding a value after a confirmed write, so a
828
+ * dropped write is retried by the next persist instead of being skipped as
829
+ * redundant.
830
+ */
831
+ set: (key: ReplicaRowKey, row: ReplicaRow<T>) => Promise<void>;
832
+ }
833
+ /** Turns a row into the string `localStorage` holds, and back. */
834
+ interface ReplicaRowCodec<T> {
835
+ parse: (value: string) => ReplicaRow<T>;
836
+ stringify: (row: ReplicaRow<T>) => string;
837
+ }
838
+ interface LocalStorageReplicaStorageOptions<T> {
839
+ /** Defaults to JSON. Pass a richer codec (e.g. superjson) for `Date`, `Map` … */
840
+ codec?: ReplicaRowCodec<T>;
841
+ /** Prefix of every row of this storage, usually the resource name. */
842
+ namespace: string;
843
+ }
844
+ /**
845
+ * {@link ReplicaStorage} over `window.localStorage`. Small values only (the
846
+ * origin shares a ~5 MB quota), but the read is synchronous: `getSync` restores
847
+ * a row inside pre-paint initialization without yielding a frame. Outside a
848
+ * browser every operation is a no-op miss.
849
+ */
850
+ declare class LocalStorageReplicaStorage<T> implements ReplicaStorage<T> {
851
+ #private;
852
+ constructor({ codec, namespace }: LocalStorageReplicaStorageOptions<T>);
853
+ get: (key: ReplicaRowKey) => Promise<ReplicaRow<T> | undefined>;
854
+ /** Restore a row inside pre-paint initialization without yielding a frame. */
855
+ getSync: (key: ReplicaRowKey) => ReplicaRow<T> | undefined;
856
+ /** Like `get`, but rejects when the row could not be read or parsed (see `ReplicaStorage.getStrict`). */
857
+ getStrict: (key: ReplicaRowKey) => Promise<ReplicaRow<T> | undefined>;
858
+ /**
859
+ * Rejects when the delete did not land (e.g. storage access denied), unlike
860
+ * reads: a caller removing a row the user deleted must be able to retry it.
861
+ */
862
+ remove: (key: ReplicaRowKey) => Promise<void>;
863
+ /**
864
+ * Rejects when the write did not land (a quota error, see `ReplicaStorage.set`):
865
+ * the replica engine only treats a row as holding a value after a confirmed
866
+ * write.
867
+ */
868
+ set: (key: ReplicaRowKey, row: ReplicaRow<T>) => Promise<void>;
869
+ }
870
+ export { ReplicaPageInfo as $, ReplicaTransition as A, replicaStorageKey as B, ReplicaStorePort as C, readReplicaRowStrict as D, ReplicaWriteQueue as E, DefineReplicaOptions as F, ReplicaResource as G, staticReplicaScope as H, ReplicaStorageFactory as I, ReplicaScope as J, ReplicaRow as K, definePagedReplica as L, createReplicaState as M, replicaReducer as N, ReplicaAction as O, DefinePagedReplicaOptions as P, ReplicaStorage as Q, defineReplica as R, ReplicaOptimisticToken as S, REPLICA_INDEX_KEY as T, ReplicaEntryMeta as U, stableQueryKey as V, ReplicaPendingMutation as W, ReplicaSource as X, ReplicaSeedPatch as Y, ReplicaState as Z, OptimisticMutationOptions as _, KeyValueReplicaStorageOptions as a, ReplicaPagingMode as at, ReplicaEntityAdapter as b, readReplicaStoredKeys as c, collapseToHead as ct, isReplicaSyncKey as d, insertHeadItems as dt, ReplicaPageResult as et, replicaKeys as f, joinPages as ft, linkReplicaEntity as g, ReplicaEntityTarget as h, toPersistedPage as ht, KeyValueReplicaStorage as i, ReplicaPagingDirection as it, ReplicaViewWrite as j, ReplicaEffect as k, settleWithin as l, getNextPageCursor as lt, singleEntity as m, pagingFor as mt, LocalStorageReplicaStorageOptions as n, ReplicaPagingConfig as nt, ReplicaKeyValueStore as o, applyHeadPage as ot, arrayEntity as p, mapPagedItem as pt, ReplicaRowKey as q, ReplicaRowCodec as r, ReplicaPagingContext as rt, replicaRowStoreKey as s, applyNextPage as st, LocalStorageReplicaStorage as t, ReplicaPagedData as tt, REPLICA_KEY_PREFIX as u, hasPagedItem as ut, ReplicaEngine as v, createReplicaEngine as w, ReplicaHeadOutcome as x, ReplicaEngineOptions as y, isReplicaStorageKeyOf as z };
871
+ //# sourceMappingURL=index-CFbCHM7Q.d.ts.map