local-replica 0.0.1 → 0.0.3

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.
package/dist/index.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- import { $ as ReplicaPageInfo, A as ReplicaTransition, B as replicaStorageKey, C as ReplicaStorePort, D as readReplicaRowStrict, E as ReplicaWriteQueue, F as DefineReplicaOptions, G as ReplicaResource, H as staticReplicaScope, I as ReplicaStorageFactory, J as ReplicaScope, K as ReplicaRow, L as definePagedReplica, M as createReplicaState, N as replicaReducer, O as ReplicaAction, P as DefinePagedReplicaOptions, Q as ReplicaStorage, R as defineReplica, S as ReplicaOptimisticToken, T as REPLICA_INDEX_KEY, U as ReplicaEntryMeta, V as stableQueryKey, W as ReplicaPendingMutation, X as ReplicaSource, Y as ReplicaSeedPatch, Z as ReplicaState, _ as OptimisticMutationOptions, a as KeyValueReplicaStorageOptions, at as ReplicaPagingMode, b as ReplicaEntityAdapter, c as readReplicaStoredKeys, ct as collapseToHead, d as isReplicaSyncKey, dt as insertHeadItems, et as ReplicaPageResult, f as replicaKeys, ft as joinPages, g as linkReplicaEntity, h as ReplicaEntityTarget, ht as toPersistedPage, i as KeyValueReplicaStorage, it as ReplicaPagingDirection, j as ReplicaViewWrite, k as ReplicaEffect, l as settleWithin, lt as getNextPageCursor, m as singleEntity, mt as pagingFor, n as LocalStorageReplicaStorageOptions, nt as ReplicaPagingConfig, o as ReplicaKeyValueStore, ot as applyHeadPage, p as arrayEntity, pt as mapPagedItem, q as ReplicaRowKey, r as ReplicaRowCodec, rt as ReplicaPagingContext, s as replicaRowStoreKey, st as applyNextPage, t as LocalStorageReplicaStorage, tt as ReplicaPagedData, u as REPLICA_KEY_PREFIX, ut as hasPagedItem, v as ReplicaEngine, w as createReplicaEngine, x as ReplicaHeadOutcome, y as ReplicaEngineOptions, z as isReplicaStorageKeyOf } from "./index-CFbCHM7Q.js";
2
- export { DefinePagedReplicaOptions, DefineReplicaOptions, KeyValueReplicaStorage, KeyValueReplicaStorageOptions, LocalStorageReplicaStorage, LocalStorageReplicaStorageOptions, OptimisticMutationOptions, REPLICA_INDEX_KEY, REPLICA_KEY_PREFIX, ReplicaAction, ReplicaEffect, ReplicaEngine, ReplicaEngineOptions, ReplicaEntityAdapter, ReplicaEntityTarget, type ReplicaEntryMeta, ReplicaHeadOutcome, ReplicaKeyValueStore, ReplicaOptimisticToken, ReplicaPageInfo, ReplicaPageResult, ReplicaPagedData, ReplicaPagingConfig, ReplicaPagingContext, ReplicaPagingDirection, ReplicaPagingMode, type ReplicaPendingMutation, type ReplicaResource, type ReplicaRow, ReplicaRowCodec, type ReplicaRowKey, type ReplicaScope, type ReplicaSeedPatch, type ReplicaSource, type ReplicaState, type ReplicaStorage, ReplicaStorageFactory, ReplicaStorePort, ReplicaTransition, ReplicaViewWrite, ReplicaWriteQueue, applyHeadPage, applyNextPage, arrayEntity, collapseToHead, createReplicaEngine, createReplicaState, definePagedReplica, defineReplica, getNextPageCursor, hasPagedItem, insertHeadItems, isReplicaStorageKeyOf, isReplicaSyncKey, joinPages, linkReplicaEntity, mapPagedItem, pagingFor, readReplicaRowStrict, readReplicaStoredKeys, replicaKeys, replicaReducer, replicaRowStoreKey, replicaStorageKey, settleWithin, singleEntity, stableQueryKey, staticReplicaScope, toPersistedPage };
1
+ import { $ as DefinePagedReplicaOptions, A as ReplicaEngineOptions, B as replicaReducer, C as joinPages, D as ReplicaEngine, E as toPersistedPage, F as ReplicaAction, G as ReplicaState, H as ReplicaPendingMutation, I as ReplicaEffect, J as stableQueryKey, K as isReplicaStorageKeyOf, L as ReplicaTransition, M as ReplicaOptimisticToken, N as ReplicaStorePort, O as createReplicaEngine, P as ReplicaEntityAdapter, Q as replicaKeys, R as ReplicaViewWrite, S as insertHeadItems, T as pagingFor, U as ReplicaSeedPatch, V as ReplicaEntryMeta, W as ReplicaSource, X as REPLICA_KEY_PREFIX, Y as staticReplicaScope, Z as isReplicaSyncKey, _ as applyHeadPage, a as KeyValueReplicaStorageOptions, at as ReplicaScope, b as getNextPageCursor, c as settleWithin, ct as ReplicaPagedData, d as ReplicaWriteQueue, dt as ReplicaPagingDirection, et as DefineReplicaOptions, f as readReplicaRowStrict, ft as ReplicaPagingMode, g as singleEntity, h as arrayEntity, ht as ReplicaStorage, i as KeyValueReplicaStorage, it as ReplicaResource, j as ReplicaHeadOutcome, k as OptimisticMutationOptions, l as readReplicaStoredKeys, lt as ReplicaPagingConfig, m as linkReplicaEntity, mt as ReplicaRowKey, n as LocalStorageReplicaStorageOptions, nt as definePagedReplica, o as ReplicaKeyValueStore, ot as ReplicaPageInfo, p as ReplicaEntityTarget, pt as ReplicaRow, q as replicaStorageKey, r as ReplicaRowCodec, rt as defineReplica, s as replicaRowStoreKey, st as ReplicaPageResult, t as LocalStorageReplicaStorage, tt as ReplicaStorageFactory, u as REPLICA_INDEX_KEY, ut as ReplicaPagingContext, v as applyNextPage, w as mapPagedItem, x as hasPagedItem, y as collapseToHead, z as createReplicaState } from "./index-DsElitHD.js";
2
+ export { DefinePagedReplicaOptions, DefineReplicaOptions, KeyValueReplicaStorage, KeyValueReplicaStorageOptions, LocalStorageReplicaStorage, LocalStorageReplicaStorageOptions, type OptimisticMutationOptions, REPLICA_INDEX_KEY, REPLICA_KEY_PREFIX, ReplicaAction, ReplicaEffect, ReplicaEngine, type ReplicaEngineOptions, type ReplicaEntityAdapter, ReplicaEntityTarget, type ReplicaEntryMeta, type ReplicaHeadOutcome, ReplicaKeyValueStore, type ReplicaOptimisticToken, type ReplicaPageInfo, type ReplicaPageResult, type ReplicaPagedData, type ReplicaPagingConfig, type ReplicaPagingContext, type ReplicaPagingDirection, type ReplicaPagingMode, type ReplicaPendingMutation, type ReplicaResource, type ReplicaRow, ReplicaRowCodec, type ReplicaRowKey, type ReplicaScope, type ReplicaSeedPatch, type ReplicaSource, type ReplicaState, type ReplicaStorage, ReplicaStorageFactory, type ReplicaStorePort, ReplicaTransition, ReplicaViewWrite, ReplicaWriteQueue, applyHeadPage, applyNextPage, arrayEntity, collapseToHead, createReplicaEngine, createReplicaState, definePagedReplica, defineReplica, getNextPageCursor, hasPagedItem, insertHeadItems, isReplicaStorageKeyOf, isReplicaSyncKey, joinPages, linkReplicaEntity, mapPagedItem, pagingFor, readReplicaRowStrict, readReplicaStoredKeys, replicaKeys, replicaReducer, replicaRowStoreKey, replicaStorageKey, settleWithin, singleEntity, stableQueryKey, staticReplicaScope, toPersistedPage };
package/dist/index.js CHANGED
@@ -1,5 +1,83 @@
1
- import { C as isReplicaStorageKeyOf, E as staticReplicaScope, S as defineReplica, T as stableQueryKey, _ as joinPages, b as toPersistedPage, c as REPLICA_INDEX_KEY, d as applyHeadPage, f as applyNextPage, g as insertHeadItems, h as hasPagedItem, i as createReplicaEngine, l as ReplicaWriteQueue, m as getNextPageCursor, n as isReplicaSyncKey, o as createReplicaState, p as collapseToHead, r as replicaKeys, s as replicaReducer, t as REPLICA_KEY_PREFIX, u as readReplicaRowStrict, v as mapPagedItem, w as replicaStorageKey, x as definePagedReplica, y as pagingFor } from "./keys-BTsqwfEa.js";
2
- // src/core/entity.ts
1
+ import { S as stableQueryKey, _ as REPLICA_KEY_PREFIX, a as ReplicaWriteQueue, b as isReplicaStorageKeyOf, c as applyNextPage, d as hasPagedItem, f as insertHeadItems, g as toPersistedPage, h as pagingFor, i as REPLICA_INDEX_KEY, l as collapseToHead, m as mapPagedItem, n as createReplicaState, o as readReplicaRowStrict, p as joinPages, r as replicaReducer, s as applyHeadPage, t as createReplicaEngine, u as getNextPageCursor, v as isReplicaSyncKey, x as replicaStorageKey, y as replicaKeys } from "./createReplicaEngine-CdR-URHo.js";
2
+ // src/core/resource/scope.ts
3
+ /**
4
+ * A fixed identity partition. The default for apps without accounts; an app
5
+ * with sign-in passes a scope derived from the signed-in user / workspace so
6
+ * one browser profile never mixes two identities' rows.
7
+ */
8
+ const staticReplicaScope = (scope = "default") => ({
9
+ canPersist: () => true,
10
+ get: () => scope,
11
+ use: () => scope
12
+ });
13
+ // src/core/resource/defineReplica.ts
14
+ const DEFAULT_SCOPE = staticReplicaScope();
15
+ const defineReplica = (options) => {
16
+ const namespace = `replica:${options.name}:v${options.version}`;
17
+ const storage = typeof options.storage === "function" ? options.storage(namespace) : options.storage;
18
+ return {
19
+ fetcher: options.fetcher,
20
+ key: options.key,
21
+ maxPersistedEntries: options.persist?.maxEntries,
22
+ name: options.name,
23
+ namespace,
24
+ paging: options.paging,
25
+ persistKey: options.persistKey ?? (() => true),
26
+ persisted: !!storage,
27
+ query: (params) => options.query ? stableQueryKey(options.query(params)) : void 0,
28
+ storageKey: (params) => replicaStorageKey(options.key(params), options.query ? stableQueryKey(options.query(params)) : void 0),
29
+ scope: options.scope ?? DEFAULT_SCOPE,
30
+ syncKey: options.syncKey,
31
+ storage,
32
+ version: options.version
33
+ };
34
+ };
35
+ /**
36
+ * A resource whose value is a paged list ({@link ReplicaPagedData}). The
37
+ * store view type may extend the paged shape with domain fields.
38
+ */
39
+ const definePagedReplica = (options) => defineReplica({
40
+ ...options,
41
+ fetcher: options.fetchPage,
42
+ storage: options.storage
43
+ });
44
+ // src/core/entity/adapters.ts
45
+ /**
46
+ * The value IS one entity, or wraps one (`{ project, members }`). Deleting the
47
+ * entity drops the whole value.
48
+ */
49
+ const singleEntity = (getId, lens) => {
50
+ const pick = (data) => lens ? lens.get(data) : data;
51
+ return {
52
+ has: (data, id) => getId(pick(data)) === id,
53
+ map: (data, _id, fn) => {
54
+ const current = pick(data);
55
+ const next = fn(current);
56
+ if (next === void 0) return void 0;
57
+ if (next === current) return data;
58
+ return lens ? lens.set(data, next) : next;
59
+ }
60
+ };
61
+ };
62
+ /** The value is a plain list of entities; deleting one filters it out. */
63
+ const arrayEntity = (getId) => ({
64
+ has: (data, id) => data.some((item) => getId(item) === id),
65
+ map: (data, id, fn) => {
66
+ let changed = false;
67
+ const next = [];
68
+ for (const item of data) {
69
+ if (getId(item) !== id) {
70
+ next.push(item);
71
+ continue;
72
+ }
73
+ const mapped = fn(item);
74
+ if (mapped !== item) changed = true;
75
+ if (mapped !== void 0) next.push(mapped);
76
+ }
77
+ return changed ? next : data;
78
+ }
79
+ });
80
+ // src/core/entity/link.ts
3
81
  /**
4
82
  * Link resources that hold copies of the same entity (e.g. a topic in the
5
83
  * sidebar list, the management page and the detail cache).
@@ -64,59 +142,7 @@ const linkReplicaEntity = (targets) => {
64
142
  update
65
143
  };
66
144
  };
67
- // src/core/entityAdapters.ts
68
- /**
69
- * The value IS one entity, or wraps one (`{ project, members }`). Deleting the
70
- * entity drops the whole value.
71
- */
72
- const singleEntity = (getId, lens) => {
73
- const pick = (data) => lens ? lens.get(data) : data;
74
- return {
75
- has: (data, id) => getId(pick(data)) === id,
76
- map: (data, _id, fn) => {
77
- const current = pick(data);
78
- const next = fn(current);
79
- if (next === void 0) return void 0;
80
- if (next === current) return data;
81
- return lens ? lens.set(data, next) : next;
82
- }
83
- };
84
- };
85
- /** The value is a plain list of entities; deleting one filters it out. */
86
- const arrayEntity = (getId) => ({
87
- has: (data, id) => data.some((item) => getId(item) === id),
88
- map: (data, id, fn) => {
89
- let changed = false;
90
- const next = [];
91
- for (const item of data) {
92
- if (getId(item) !== id) {
93
- next.push(item);
94
- continue;
95
- }
96
- const mapped = fn(item);
97
- if (mapped !== item) changed = true;
98
- if (mapped !== void 0) next.push(mapped);
99
- }
100
- return changed ? next : data;
101
- }
102
- });
103
- // src/core/settle.ts
104
- /**
105
- * Resolve once `promise` settles or `ms` elapses — never rejects, never hangs.
106
- *
107
- * Bounds a pre-paint hydrate in a route loader: the persisted read is usually
108
- * sub-millisecond, and the deadline only fires on a pathological storage stall,
109
- * so a route transition never waits longer than `ms` for it.
110
- */
111
- const settleWithin = (promise, ms) => new Promise((resolve) => {
112
- const finish = () => {
113
- clearTimeout(timer);
114
- resolve();
115
- };
116
- const timer = setTimeout(finish, ms);
117
- Promise.resolve(promise).then(finish, finish);
118
- });
119
- // src/core/storedKeys.ts
145
+ // src/core/persistence/storedKeys.ts
120
146
  /**
121
147
  * The entry storage keys this resource has a persisted row for in the active
122
148
  * scope, oldest first.
@@ -137,6 +163,22 @@ const readReplicaStoredKeys = async (resource) => {
137
163
  scope: resource.scope.get()
138
164
  }))?.data ?? [];
139
165
  };
166
+ // src/core/utils/settleWithin.ts
167
+ /**
168
+ * Resolve once `promise` settles or `ms` elapses — never rejects, never hangs.
169
+ *
170
+ * Bounds a pre-paint hydrate in a route loader: the persisted read is usually
171
+ * sub-millisecond, and the deadline only fires on a pathological storage stall,
172
+ * so a route transition never waits longer than `ms` for it.
173
+ */
174
+ const settleWithin = (promise, ms) => new Promise((resolve) => {
175
+ const finish = () => {
176
+ clearTimeout(timer);
177
+ resolve();
178
+ };
179
+ const timer = setTimeout(finish, ms);
180
+ Promise.resolve(promise).then(finish, finish);
181
+ });
140
182
  // src/storage/keyValue.ts
141
183
  /** Store key of one row: `<namespace>:<scope>:<queryKey>`, each part URI-encoded but the namespace. */
142
184
  const replicaRowStoreKey = (namespace, { queryKey, scope }) => `${namespace}:${encodeURIComponent(scope)}:${encodeURIComponent(queryKey)}`;
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/core/entity.ts","../src/core/entityAdapters.ts","../src/core/settle.ts","../src/core/storedKeys.ts","../src/storage/keyValue.ts","../src/storage/localStorage.ts"],"sourcesContent":["import type { ReplicaOptimisticToken } from './engine';\n\n/** The part of a slice entity propagation needs (structural, so any slice fits). */\nexport interface ReplicaEntityTarget {\n beginEntityOptimistic: <TItem>(\n id: string,\n fn: (item: TItem) => TItem | undefined,\n ) => ReplicaOptimisticToken<any>[];\n /** Wipe the resource in the active scope (memory + every persisted row). */\n clear?: () => Promise<boolean>;\n /** Patch persisted rows whose entry is not loaded in memory. */\n patchStoredEntity: <TItem>(id: string, fn: (item: TItem) => TItem | undefined) => Promise<void>;\n /**\n * Explicit entity removal that also guards later hydrates. Optional for\n * custom targets; `linkReplicaEntity` falls back to `updateEntity`.\n */\n removeEntity?: (id: string, options?: { persist?: boolean; scope?: string }) => void;\n revalidate: (key?: string) => Promise<unknown>;\n updateEntity: <TItem>(\n id: string,\n fn: (item: TItem) => TItem | undefined,\n options?: { persist?: boolean },\n ) => void;\n}\n\n/**\n * Link resources that hold copies of the same entity (e.g. a topic in the\n * sidebar list, the management page and the detail cache).\n *\n * Each resource keeps exactly one store location and decides how the entity\n * maps into its own shape (paged resources by `getId`, single-entity ones by\n * their entity adapter). The link only fans an entity-level change out to the\n * resources that currently hold it — no global event bus, no shared owner.\n */\nexport const linkReplicaEntity = <TItem>(targets: ReplicaEntityTarget[]) => {\n /**\n * Confirmed patch everywhere the entity is loaded, and (when persisted, the\n * default) in persisted rows of entries that are not loaded.\n */\n const update = (\n id: string,\n fn: (item: TItem) => TItem,\n options?: { persist?: boolean },\n ): void => {\n for (const target of targets) target.updateEntity<TItem>(id, fn, options);\n };\n\n /**\n * Confirmed removal everywhere (list rows dropped, single-entity values\n * removed). A value hydrated later — even one whose read was already in\n * flight — comes back without the entity. With `scope` set to a scope left\n * behind, only that scope's persisted rows lose the entity; the view on\n * screen belongs to another scope and is left alone.\n */\n const remove = (id: string, options?: { persist?: boolean; scope?: string }): void => {\n for (const target of targets) {\n if (target.removeEntity) target.removeEntity(id, options);\n else if (!options?.scope) target.updateEntity<TItem>(id, () => undefined, options);\n }\n };\n\n /**\n * Wipe every linked resource in the active scope (e.g. \"delete all\"): memory,\n * every persisted row and reads in flight. Resolves `true` once every\n * persisted row is gone.\n */\n const clear = async (): Promise<boolean> => {\n const purged = await Promise.all(targets.map((target) => target.clear?.() ?? true));\n return purged.every(Boolean);\n };\n\n /**\n * One server call, one overlay per resource holding the entity: all commit\n * together or all roll back together (then rethrow). Pass `'remove'` for an\n * optimistic delete.\n */\n const optimistic = async <TResult>(\n id: string,\n fn: ((item: TItem) => TItem) | 'remove',\n serverCall: () => Promise<TResult>,\n ): Promise<TResult> => {\n const apply = fn === 'remove' ? () => undefined : fn;\n const tokens = targets.flatMap((target) => target.beginEntityOptimistic<TItem>(id, apply));\n try {\n const result = await serverCall();\n for (const token of tokens) token.commit();\n // `remove` / `update` also patch persisted rows that are not loaded.\n if (fn === 'remove') remove(id);\n else for (const target of targets) void target.patchStoredEntity<TItem>(id, fn);\n return result;\n } catch (error) {\n for (const token of tokens) token.rollback();\n throw error;\n }\n };\n\n const revalidate = () => Promise.all(targets.map((target) => target.revalidate()));\n\n return { clear, optimistic, remove, revalidate, update };\n};\n","import type { ReplicaEntityAdapter } from './engine';\n\n/**\n * The value IS one entity, or wraps one (`{ project, members }`). Deleting the\n * entity drops the whole value.\n */\nexport const singleEntity = <TData, TItem = TData>(\n getId: (item: TItem) => string,\n lens?: { get: (data: TData) => TItem; set: (data: TData, item: TItem) => TData },\n): ReplicaEntityAdapter<TData, TItem> => {\n const pick = (data: TData) => (lens ? lens.get(data) : (data as unknown as TItem));\n return {\n has: (data, id) => getId(pick(data)) === id,\n map: (data, _id, fn) => {\n const current = pick(data);\n const next = fn(current);\n if (next === undefined) return undefined;\n if (next === current) return data;\n return lens ? lens.set(data, next) : (next as unknown as TData);\n },\n };\n};\n\n/** The value is a plain list of entities; deleting one filters it out. */\nexport const arrayEntity = <TItem>(\n getId: (item: TItem) => string,\n): ReplicaEntityAdapter<TItem[], TItem> => ({\n has: (data, id) => data.some((item) => getId(item) === id),\n map: (data, id, fn) => {\n let changed = false;\n const next: TItem[] = [];\n for (const item of data) {\n if (getId(item) !== id) {\n next.push(item);\n continue;\n }\n const mapped = fn(item);\n if (mapped !== item) changed = true;\n if (mapped !== undefined) next.push(mapped);\n }\n return changed ? next : data;\n },\n});\n","/**\n * Resolve once `promise` settles or `ms` elapses — never rejects, never hangs.\n *\n * Bounds a pre-paint hydrate in a route loader: the persisted read is usually\n * sub-millisecond, and the deadline only fires on a pathological storage stall,\n * so a route transition never waits longer than `ms` for it.\n */\nexport const settleWithin = (promise: Promise<unknown>, ms: number): Promise<void> =>\n new Promise((resolve) => {\n const finish = () => {\n clearTimeout(timer);\n resolve();\n };\n const timer = setTimeout(finish, ms);\n void Promise.resolve(promise).then(finish, finish);\n });\n","import { REPLICA_INDEX_KEY } from './engine';\nimport type { ReplicaResource } from './types';\n\n/**\n * The entry storage keys this resource has a persisted row for in the active\n * scope, oldest first.\n *\n * Storage has no key listing of its own; the engine keeps one index row per\n * scope (`REPLICA_INDEX_KEY`) as it persists and drops entries, and this reads\n * it. That is how code outside the owning store reaches rows an *earlier\n * session* wrote — bounding how many survive a reload, or invalidating a row\n * whose entry is not loaded in memory.\n *\n * The keys are storage keys (`key`, plus `?query` when the resource sets one),\n * so a caller only matches them against entry keys when the resource leaves\n * `query` unset.\n */\nexport const readReplicaStoredKeys = async (\n resource: Pick<ReplicaResource<any, any, any>, 'scope' | 'storage'>,\n): Promise<string[]> => {\n const row = await resource.storage?.get({\n queryKey: REPLICA_INDEX_KEY,\n scope: resource.scope.get(),\n });\n return (row?.data as unknown as string[] | undefined) ?? [];\n};\n","import type { ReplicaRow, ReplicaRowKey, ReplicaStorage } from '../core/types';\n\n/**\n * An async key-value store a replica can persist into: an IndexedDB object\n * store, a SQLite table behind IPC, a `Map` in tests. Values are stored as\n * given (structured clone), so the store decides what it can hold.\n */\nexport interface ReplicaKeyValueStore {\n /** Rejects when the delete did not land. A missing key is a success. */\n delete: (key: string) => Promise<void>;\n /** Rejects when the read failed; resolves `undefined` for a missing key. */\n get: (key: string) => Promise<unknown>;\n /** Opens the store; awaited before every operation. */\n initialize?: () => Promise<void>;\n /** Rejects when the write did not land. */\n set: (key: string, value: unknown) => Promise<void>;\n}\n\nexport interface KeyValueReplicaStorageOptions {\n /** Prefix of every row of this storage, usually the resource name. */\n namespace: string;\n store: ReplicaKeyValueStore;\n}\n\n/** Store key of one row: `<namespace>:<scope>:<queryKey>`, each part URI-encoded but the namespace. */\nexport const replicaRowStoreKey = (namespace: string, { queryKey, scope }: ReplicaRowKey) =>\n `${namespace}:${encodeURIComponent(scope)}:${encodeURIComponent(queryKey)}`;\n\n/** {@link ReplicaStorage} over an async key-value store, one record per row. */\nexport class KeyValueReplicaStorage<T> implements ReplicaStorage<T> {\n readonly #namespace: string;\n readonly #store: ReplicaKeyValueStore;\n\n constructor({ namespace, store }: KeyValueReplicaStorageOptions) {\n this.#namespace = namespace;\n this.#store = store;\n }\n\n #key = (key: ReplicaRowKey) => replicaRowStoreKey(this.#namespace, key);\n\n get = async (key: ReplicaRowKey): Promise<ReplicaRow<T> | undefined> => {\n try {\n return await this.getStrict(key);\n } catch {\n return undefined;\n }\n };\n\n /** Like `get`, but rejects when the row could not be read (see `ReplicaStorage.getStrict`). */\n getStrict = async (key: ReplicaRowKey): Promise<ReplicaRow<T> | undefined> => {\n await this.#store.initialize?.();\n return (await this.#store.get(this.#key(key))) as ReplicaRow<T> | undefined;\n };\n\n /**\n * Rejects when the delete did not land, unlike reads: a caller that removes a\n * row the user deleted must be able to retry it.\n */\n remove = async (key: ReplicaRowKey): Promise<void> => {\n await this.#store.initialize?.();\n await this.#store.delete(this.#key(key));\n };\n\n /**\n * Rejects when the write did not land (see `ReplicaStorage.set`): the replica\n * engine only treats a row as holding a value after a confirmed write, so a\n * dropped write is retried by the next persist instead of being skipped as\n * redundant.\n */\n set = async (key: ReplicaRowKey, row: ReplicaRow<T>): Promise<void> => {\n await this.#store.initialize?.();\n await this.#store.set(this.#key(key), row);\n };\n}\n","import type { ReplicaRow, ReplicaRowKey, ReplicaStorage } from '../core/types';\nimport { replicaRowStoreKey } from './keyValue';\n\n/** Turns a row into the string `localStorage` holds, and back. */\nexport interface ReplicaRowCodec<T> {\n parse: (value: string) => ReplicaRow<T>;\n stringify: (row: ReplicaRow<T>) => string;\n}\n\nexport interface LocalStorageReplicaStorageOptions<T> {\n /** Defaults to JSON. Pass a richer codec (e.g. superjson) for `Date`, `Map` … */\n codec?: ReplicaRowCodec<T>;\n /** Prefix of every row of this storage, usually the resource name. */\n namespace: string;\n}\n\nconst jsonCodec: ReplicaRowCodec<any> = { parse: JSON.parse, stringify: JSON.stringify };\n\n/**\n * {@link ReplicaStorage} over `window.localStorage`. Small values only (the\n * origin shares a ~5 MB quota), but the read is synchronous: `getSync` restores\n * a row inside pre-paint initialization without yielding a frame. Outside a\n * browser every operation is a no-op miss.\n */\nexport class LocalStorageReplicaStorage<T> implements ReplicaStorage<T> {\n readonly #codec: ReplicaRowCodec<T>;\n readonly #namespace: string;\n\n constructor({ codec = jsonCodec, namespace }: LocalStorageReplicaStorageOptions<T>) {\n this.#codec = codec;\n this.#namespace = namespace;\n }\n\n #key = (key: ReplicaRowKey) => replicaRowStoreKey(this.#namespace, key);\n\n get = async (key: ReplicaRowKey): Promise<ReplicaRow<T> | undefined> => this.getSync(key);\n\n /** Restore a row inside pre-paint initialization without yielding a frame. */\n getSync = (key: ReplicaRowKey): ReplicaRow<T> | undefined => {\n if (typeof window === 'undefined') return undefined;\n\n try {\n const value = localStorage.getItem(this.#key(key));\n return value ? this.#codec.parse(value) : undefined;\n } catch {\n return undefined;\n }\n };\n\n /** Like `get`, but rejects when the row could not be read or parsed (see `ReplicaStorage.getStrict`). */\n getStrict = async (key: ReplicaRowKey): Promise<ReplicaRow<T> | undefined> => {\n if (typeof window === 'undefined') return undefined;\n\n const value = localStorage.getItem(this.#key(key));\n return value ? this.#codec.parse(value) : undefined;\n };\n\n /**\n * Rejects when the delete did not land (e.g. storage access denied), unlike\n * reads: a caller removing a row the user deleted must be able to retry it.\n */\n remove = async (key: ReplicaRowKey): Promise<void> => {\n if (typeof window === 'undefined') return;\n\n localStorage.removeItem(this.#key(key));\n };\n\n /**\n * Rejects when the write did not land (a quota error, see `ReplicaStorage.set`):\n * the replica engine only treats a row as holding a value after a confirmed\n * write.\n */\n set = async (key: ReplicaRowKey, row: ReplicaRow<T>): Promise<void> => {\n if (typeof window === 'undefined') return;\n\n localStorage.setItem(this.#key(key), this.#codec.stringify(row));\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/core/resource/scope.ts","../src/core/resource/defineReplica.ts","../src/core/entity/adapters.ts","../src/core/entity/link.ts","../src/core/persistence/storedKeys.ts","../src/core/utils/settleWithin.ts","../src/storage/keyValue.ts","../src/storage/localStorage.ts"],"sourcesContent":["import type { ReplicaScope } from './types';\n\n/**\n * A fixed identity partition. The default for apps without accounts; an app\n * with sign-in passes a scope derived from the signed-in user / workspace so\n * one browser profile never mixes two identities' rows.\n */\nexport const staticReplicaScope = (scope = 'default'): ReplicaScope => ({\n canPersist: () => true,\n get: () => scope,\n use: () => scope,\n});\n","import type { ReplicaStorage } from '../../storage/types';\nimport type { ReplicaPagedData, ReplicaPageResult, ReplicaPagingConfig } from '../paging/types';\nimport { staticReplicaScope } from './scope';\nimport { replicaStorageKey, stableQueryKey } from './storageKey';\nimport type { ReplicaResource, ReplicaScope } from './types';\n\nconst DEFAULT_SCOPE = staticReplicaScope();\n\n/** Builds the storage of one resource from its namespace (`replica:<name>:v<version>`). */\nexport type ReplicaStorageFactory<TData> = (namespace: string) => ReplicaStorage<TData> | undefined;\n\nexport interface DefineReplicaOptions<TParams, TData, TFetched = TData, TCursor = any> {\n /** Default network fetcher; a store binding may override it. */\n fetcher?: (params: TParams, cursor?: TCursor) => Promise<TFetched>;\n /** Entry identity inside one scope (memory bucket + persisted row). */\n key: (params: TParams) => string;\n name: string;\n paging?: ReplicaPagingConfig<any, TCursor>;\n /**\n * Bound what the resource keeps in storage. `maxEntries` caps the persisted\n * rows (storage keys: one per entry key and query) per scope; past it the\n * least recently used rows are evicted — their persisted row, index entry\n * and, when nothing holds it, their memory entry. Use rows (a hydrate or a\n * persist, including a skipped unchanged one) count as recent, and the order\n * lives in the persisted index, so it survives reloads. A row on screen (a\n * mounted `useSync`, see `retain`), with pending optimistic writes or a\n * `loadMore` in flight is never evicted. Unbounded by default.\n */\n persist?: { maxEntries?: number };\n /**\n * Entries that never touch storage (no hydrate, no persist) — e.g. scoped\n * buckets whose rows must not land under an ordinary entry. Defaults to all.\n */\n persistKey?: (key: string) => boolean;\n /**\n * Query identity beyond `key` (filters, sort, page size) — anything that\n * changes the rows without changing the key. Persisted rows are kept per\n * query, so a projection taken under other filters never hydrates; in memory\n * a query change resets loaded pages.\n */\n query?: (params: TParams) => unknown;\n /** Identity partition of memory and persisted rows. Defaults to one static scope. */\n scope?: ReplicaScope;\n /** Persisted copy of the replica. Omit for a memory-only replica (no first-frame paint). */\n storage?: ReplicaStorage<TData> | ReplicaStorageFactory<TData>;\n /**\n * Query-cache key of the network sync, for a resource adopting an existing\n * key that other code already revalidates by. Defaults to `replicaKeys.sync`.\n * With a custom key, revalidate through that key — `slice.revalidate` only\n * matches default keys.\n */\n syncKey?: (params: TParams) => readonly unknown[];\n /** Bump to invalidate every persisted row written by older shapes. */\n version: number;\n}\n\nexport const defineReplica = <TParams, TData, TFetched = TData, TCursor = any>(\n options: DefineReplicaOptions<TParams, TData, TFetched, TCursor>,\n): ReplicaResource<TParams, TData, TFetched, TCursor> => {\n const namespace = `replica:${options.name}:v${options.version}`;\n const storage =\n typeof options.storage === 'function' ? options.storage(namespace) : options.storage;\n\n return {\n fetcher: options.fetcher,\n key: options.key,\n maxPersistedEntries: options.persist?.maxEntries,\n name: options.name,\n namespace,\n paging: options.paging,\n persistKey: options.persistKey ?? (() => true),\n persisted: !!storage,\n query: (params) => (options.query ? stableQueryKey(options.query(params)) : undefined),\n storageKey: (params) =>\n replicaStorageKey(\n options.key(params),\n options.query ? stableQueryKey(options.query(params)) : undefined,\n ),\n scope: options.scope ?? DEFAULT_SCOPE,\n syncKey: options.syncKey,\n storage,\n version: options.version,\n };\n};\n\nexport interface DefinePagedReplicaOptions<TParams, TItem, TCursor> extends Omit<\n DefineReplicaOptions<\n TParams,\n ReplicaPagedData<TItem, TCursor>,\n ReplicaPageResult<TItem, TCursor>\n >,\n 'fetcher' | 'paging' | 'storage'\n> {\n /** `cursor` is `undefined` for the head page. */\n fetchPage?: (\n params: TParams,\n cursor: TCursor | undefined,\n ) => Promise<ReplicaPageResult<TItem, TCursor>>;\n paging: ReplicaPagingConfig<TItem, TCursor>;\n storage?: ReplicaStorage<any> | ReplicaStorageFactory<any>;\n}\n\n/**\n * A resource whose value is a paged list ({@link ReplicaPagedData}). The\n * store view type may extend the paged shape with domain fields.\n */\nexport const definePagedReplica = <\n TParams,\n TItem,\n TCursor = number,\n TData extends ReplicaPagedData<TItem, TCursor> = ReplicaPagedData<TItem, TCursor>,\n>(\n options: DefinePagedReplicaOptions<TParams, TItem, TCursor>,\n): ReplicaResource<TParams, TData, ReplicaPageResult<TItem, TCursor>, TCursor> =>\n defineReplica<TParams, TData, ReplicaPageResult<TItem, TCursor>, TCursor>({\n ...options,\n fetcher: options.fetchPage,\n storage: options.storage as ReplicaStorage<TData> | ReplicaStorageFactory<TData> | undefined,\n });\n","import type { ReplicaEntityAdapter } from './types';\n\n/**\n * The value IS one entity, or wraps one (`{ project, members }`). Deleting the\n * entity drops the whole value.\n */\nexport const singleEntity = <TData, TItem = TData>(\n getId: (item: TItem) => string,\n lens?: { get: (data: TData) => TItem; set: (data: TData, item: TItem) => TData },\n): ReplicaEntityAdapter<TData, TItem> => {\n const pick = (data: TData) => (lens ? lens.get(data) : (data as unknown as TItem));\n return {\n has: (data, id) => getId(pick(data)) === id,\n map: (data, _id, fn) => {\n const current = pick(data);\n const next = fn(current);\n if (next === undefined) return undefined;\n if (next === current) return data;\n return lens ? lens.set(data, next) : (next as unknown as TData);\n },\n };\n};\n\n/** The value is a plain list of entities; deleting one filters it out. */\nexport const arrayEntity = <TItem>(\n getId: (item: TItem) => string,\n): ReplicaEntityAdapter<TItem[], TItem> => ({\n has: (data, id) => data.some((item) => getId(item) === id),\n map: (data, id, fn) => {\n let changed = false;\n const next: TItem[] = [];\n for (const item of data) {\n if (getId(item) !== id) {\n next.push(item);\n continue;\n }\n const mapped = fn(item);\n if (mapped !== item) changed = true;\n if (mapped !== undefined) next.push(mapped);\n }\n return changed ? next : data;\n },\n});\n","import type { ReplicaOptimisticToken } from '../engine/types';\n\n/** The part of a slice entity propagation needs (structural, so any slice fits). */\nexport interface ReplicaEntityTarget {\n beginEntityOptimistic: <TItem>(\n id: string,\n fn: (item: TItem) => TItem | undefined,\n ) => ReplicaOptimisticToken<any>[];\n /** Wipe the resource in the active scope (memory + every persisted row). */\n clear?: () => Promise<boolean>;\n /** Patch persisted rows whose entry is not loaded in memory. */\n patchStoredEntity: <TItem>(id: string, fn: (item: TItem) => TItem | undefined) => Promise<void>;\n /**\n * Explicit entity removal that also guards later hydrates. Optional for\n * custom targets; `linkReplicaEntity` falls back to `updateEntity`.\n */\n removeEntity?: (id: string, options?: { persist?: boolean; scope?: string }) => void;\n revalidate: (key?: string) => Promise<unknown>;\n updateEntity: <TItem>(\n id: string,\n fn: (item: TItem) => TItem | undefined,\n options?: { persist?: boolean },\n ) => void;\n}\n\n/**\n * Link resources that hold copies of the same entity (e.g. a topic in the\n * sidebar list, the management page and the detail cache).\n *\n * Each resource keeps exactly one store location and decides how the entity\n * maps into its own shape (paged resources by `getId`, single-entity ones by\n * their entity adapter). The link only fans an entity-level change out to the\n * resources that currently hold it — no global event bus, no shared owner.\n */\nexport const linkReplicaEntity = <TItem>(targets: ReplicaEntityTarget[]) => {\n /**\n * Confirmed patch everywhere the entity is loaded, and (when persisted, the\n * default) in persisted rows of entries that are not loaded.\n */\n const update = (\n id: string,\n fn: (item: TItem) => TItem,\n options?: { persist?: boolean },\n ): void => {\n for (const target of targets) target.updateEntity<TItem>(id, fn, options);\n };\n\n /**\n * Confirmed removal everywhere (list rows dropped, single-entity values\n * removed). A value hydrated later — even one whose read was already in\n * flight — comes back without the entity. With `scope` set to a scope left\n * behind, only that scope's persisted rows lose the entity; the view on\n * screen belongs to another scope and is left alone.\n */\n const remove = (id: string, options?: { persist?: boolean; scope?: string }): void => {\n for (const target of targets) {\n if (target.removeEntity) target.removeEntity(id, options);\n else if (!options?.scope) target.updateEntity<TItem>(id, () => undefined, options);\n }\n };\n\n /**\n * Wipe every linked resource in the active scope (e.g. \"delete all\"): memory,\n * every persisted row and reads in flight. Resolves `true` once every\n * persisted row is gone.\n */\n const clear = async (): Promise<boolean> => {\n const purged = await Promise.all(targets.map((target) => target.clear?.() ?? true));\n return purged.every(Boolean);\n };\n\n /**\n * One server call, one overlay per resource holding the entity: all commit\n * together or all roll back together (then rethrow). Pass `'remove'` for an\n * optimistic delete.\n */\n const optimistic = async <TResult>(\n id: string,\n fn: ((item: TItem) => TItem) | 'remove',\n serverCall: () => Promise<TResult>,\n ): Promise<TResult> => {\n const apply = fn === 'remove' ? () => undefined : fn;\n const tokens = targets.flatMap((target) => target.beginEntityOptimistic<TItem>(id, apply));\n try {\n const result = await serverCall();\n for (const token of tokens) token.commit();\n // `remove` / `update` also patch persisted rows that are not loaded.\n if (fn === 'remove') remove(id);\n else for (const target of targets) void target.patchStoredEntity<TItem>(id, fn);\n return result;\n } catch (error) {\n for (const token of tokens) token.rollback();\n throw error;\n }\n };\n\n const revalidate = () => Promise.all(targets.map((target) => target.revalidate()));\n\n return { clear, optimistic, remove, revalidate, update };\n};\n","import type { ReplicaResource } from '../resource/types';\nimport { REPLICA_INDEX_KEY } from './persistedRows';\n\n/**\n * The entry storage keys this resource has a persisted row for in the active\n * scope, oldest first.\n *\n * Storage has no key listing of its own; the engine keeps one index row per\n * scope (`REPLICA_INDEX_KEY`) as it persists and drops entries, and this reads\n * it. That is how code outside the owning store reaches rows an *earlier\n * session* wrote — bounding how many survive a reload, or invalidating a row\n * whose entry is not loaded in memory.\n *\n * The keys are storage keys (`key`, plus `?query` when the resource sets one),\n * so a caller only matches them against entry keys when the resource leaves\n * `query` unset.\n */\nexport const readReplicaStoredKeys = async (\n resource: Pick<ReplicaResource<any, any, any>, 'scope' | 'storage'>,\n): Promise<string[]> => {\n const row = await resource.storage?.get({\n queryKey: REPLICA_INDEX_KEY,\n scope: resource.scope.get(),\n });\n return (row?.data as unknown as string[] | undefined) ?? [];\n};\n","/**\n * Resolve once `promise` settles or `ms` elapses — never rejects, never hangs.\n *\n * Bounds a pre-paint hydrate in a route loader: the persisted read is usually\n * sub-millisecond, and the deadline only fires on a pathological storage stall,\n * so a route transition never waits longer than `ms` for it.\n */\nexport const settleWithin = (promise: Promise<unknown>, ms: number): Promise<void> =>\n new Promise((resolve) => {\n const finish = () => {\n clearTimeout(timer);\n resolve();\n };\n const timer = setTimeout(finish, ms);\n void Promise.resolve(promise).then(finish, finish);\n });\n","import type { ReplicaRow, ReplicaRowKey, ReplicaStorage } from './types';\n\n/**\n * An async key-value store a replica can persist into: an IndexedDB object\n * store, a SQLite table behind IPC, a `Map` in tests. Values are stored as\n * given (structured clone), so the store decides what it can hold.\n */\nexport interface ReplicaKeyValueStore {\n /** Rejects when the delete did not land. A missing key is a success. */\n delete: (key: string) => Promise<void>;\n /** Rejects when the read failed; resolves `undefined` for a missing key. */\n get: (key: string) => Promise<unknown>;\n /** Opens the store; awaited before every operation. */\n initialize?: () => Promise<void>;\n /** Rejects when the write did not land. */\n set: (key: string, value: unknown) => Promise<void>;\n}\n\nexport interface KeyValueReplicaStorageOptions {\n /** Prefix of every row of this storage, usually the resource name. */\n namespace: string;\n store: ReplicaKeyValueStore;\n}\n\n/** Store key of one row: `<namespace>:<scope>:<queryKey>`, each part URI-encoded but the namespace. */\nexport const replicaRowStoreKey = (namespace: string, { queryKey, scope }: ReplicaRowKey) =>\n `${namespace}:${encodeURIComponent(scope)}:${encodeURIComponent(queryKey)}`;\n\n/** {@link ReplicaStorage} over an async key-value store, one record per row. */\nexport class KeyValueReplicaStorage<T> implements ReplicaStorage<T> {\n readonly #namespace: string;\n readonly #store: ReplicaKeyValueStore;\n\n constructor({ namespace, store }: KeyValueReplicaStorageOptions) {\n this.#namespace = namespace;\n this.#store = store;\n }\n\n #key = (key: ReplicaRowKey) => replicaRowStoreKey(this.#namespace, key);\n\n get = async (key: ReplicaRowKey): Promise<ReplicaRow<T> | undefined> => {\n try {\n return await this.getStrict(key);\n } catch {\n return undefined;\n }\n };\n\n /** Like `get`, but rejects when the row could not be read (see `ReplicaStorage.getStrict`). */\n getStrict = async (key: ReplicaRowKey): Promise<ReplicaRow<T> | undefined> => {\n await this.#store.initialize?.();\n return (await this.#store.get(this.#key(key))) as ReplicaRow<T> | undefined;\n };\n\n /**\n * Rejects when the delete did not land, unlike reads: a caller that removes a\n * row the user deleted must be able to retry it.\n */\n remove = async (key: ReplicaRowKey): Promise<void> => {\n await this.#store.initialize?.();\n await this.#store.delete(this.#key(key));\n };\n\n /**\n * Rejects when the write did not land (see `ReplicaStorage.set`): the replica\n * engine only treats a row as holding a value after a confirmed write, so a\n * dropped write is retried by the next persist instead of being skipped as\n * redundant.\n */\n set = async (key: ReplicaRowKey, row: ReplicaRow<T>): Promise<void> => {\n await this.#store.initialize?.();\n await this.#store.set(this.#key(key), row);\n };\n}\n","import { replicaRowStoreKey } from './keyValue';\nimport type { ReplicaRow, ReplicaRowKey, ReplicaStorage } from './types';\n\n/** Turns a row into the string `localStorage` holds, and back. */\nexport interface ReplicaRowCodec<T> {\n parse: (value: string) => ReplicaRow<T>;\n stringify: (row: ReplicaRow<T>) => string;\n}\n\nexport interface LocalStorageReplicaStorageOptions<T> {\n /** Defaults to JSON. Pass a richer codec (e.g. superjson) for `Date`, `Map` … */\n codec?: ReplicaRowCodec<T>;\n /** Prefix of every row of this storage, usually the resource name. */\n namespace: string;\n}\n\nconst jsonCodec: ReplicaRowCodec<any> = { parse: JSON.parse, stringify: JSON.stringify };\n\n/**\n * {@link ReplicaStorage} over `window.localStorage`. Small values only (the\n * origin shares a ~5 MB quota), but the read is synchronous: `getSync` restores\n * a row inside pre-paint initialization without yielding a frame. Outside a\n * browser every operation is a no-op miss.\n */\nexport class LocalStorageReplicaStorage<T> implements ReplicaStorage<T> {\n readonly #codec: ReplicaRowCodec<T>;\n readonly #namespace: string;\n\n constructor({ codec = jsonCodec, namespace }: LocalStorageReplicaStorageOptions<T>) {\n this.#codec = codec;\n this.#namespace = namespace;\n }\n\n #key = (key: ReplicaRowKey) => replicaRowStoreKey(this.#namespace, key);\n\n get = async (key: ReplicaRowKey): Promise<ReplicaRow<T> | undefined> => this.getSync(key);\n\n /** Restore a row inside pre-paint initialization without yielding a frame. */\n getSync = (key: ReplicaRowKey): ReplicaRow<T> | undefined => {\n if (typeof window === 'undefined') return undefined;\n\n try {\n const value = localStorage.getItem(this.#key(key));\n return value ? this.#codec.parse(value) : undefined;\n } catch {\n return undefined;\n }\n };\n\n /** Like `get`, but rejects when the row could not be read or parsed (see `ReplicaStorage.getStrict`). */\n getStrict = async (key: ReplicaRowKey): Promise<ReplicaRow<T> | undefined> => {\n if (typeof window === 'undefined') return undefined;\n\n const value = localStorage.getItem(this.#key(key));\n return value ? this.#codec.parse(value) : undefined;\n };\n\n /**\n * Rejects when the delete did not land (e.g. storage access denied), unlike\n * reads: a caller removing a row the user deleted must be able to retry it.\n */\n remove = async (key: ReplicaRowKey): Promise<void> => {\n if (typeof window === 'undefined') return;\n\n localStorage.removeItem(this.#key(key));\n };\n\n /**\n * Rejects when the write did not land (a quota error, see `ReplicaStorage.set`):\n * the replica engine only treats a row as holding a value after a confirmed\n * write.\n */\n set = async (key: ReplicaRowKey, row: ReplicaRow<T>): Promise<void> => {\n if (typeof window === 'undefined') return;\n\n localStorage.setItem(this.#key(key), this.#codec.stringify(row));\n };\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { G as ReplicaResource, O as ReplicaAction, S as ReplicaOptimisticToken, X as ReplicaSource, _ as OptimisticMutationOptions, tt as ReplicaPagedData, x as ReplicaHeadOutcome, y as ReplicaEngineOptions } from "../index-CFbCHM7Q.js";
1
+ import { A as ReplicaEngineOptions, F as ReplicaAction, M as ReplicaOptimisticToken, W as ReplicaSource, ct as ReplicaPagedData, it as ReplicaResource, j as ReplicaHeadOutcome, k as OptimisticMutationOptions } from "../index-DsElitHD.js";
2
2
  /**
3
3
  * Scheduling knobs a sync can ask its driver for. Named after SWR's options;
4
4
  * a driver maps them onto its own cache (or ignores what it cannot honor).
@@ -86,12 +86,10 @@ export declare const createSWRDriver: ({ mutate, useSWR }: {
86
86
  mutate: (...args: [match: (key: any) => boolean] | [key: unknown, data: unknown, options: object]) => Promise<unknown>;
87
87
  useSWR: SWRLikeHook;
88
88
  }) => ReplicaSyncDriver;
89
- /** Zustand `setState`, with the devtools action label. */
90
- type Setter<TStore> = (partial: Partial<TStore>, replace?: false, action?: any) => void;
91
89
  /**
92
90
  * One `key` write in a batched lens update (see {@link ReplicaLens.setMany}).
93
91
  */
94
- export interface ReplicaViewSet<TData> {
92
+ interface ReplicaViewSet<TData> {
95
93
  data: TData | undefined;
96
94
  key: string;
97
95
  }
@@ -99,7 +97,7 @@ export interface ReplicaViewSet<TData> {
99
97
  * Where the materialized value lives in the domain store. Selectors keep
100
98
  * reading this location; the slice is the only writer.
101
99
  */
102
- export interface ReplicaLens<TStore, TData> {
100
+ interface ReplicaLens<TStore, TData> {
103
101
  clear: (state: TStore) => Partial<TStore>;
104
102
  get: (state: TStore, key: string) => TData | undefined;
105
103
  /** Enumerate loaded keys (needed for entity propagation). */
@@ -112,7 +110,29 @@ export interface ReplicaLens<TStore, TData> {
112
110
  */
113
111
  setMany?: (state: TStore, entries: ReplicaViewSet<TData>[]) => Partial<TStore>;
114
112
  }
115
- export interface CreateReplicaSliceOptions<TStore, TParams, TData, TFetched> extends Omit<ReplicaEngineOptions<TParams, TData, TFetched>, 'port' | 'revalidate'> {
113
+ /** Lens for the common case: a `Record<key, TData>` field on the store. */
114
+ export declare const recordLens: <TStore, TData>(field: keyof TStore & string) => ReplicaLens<TStore, TData>;
115
+ /**
116
+ * Lens for a paged view split across store fields: the rows stay where
117
+ * existing readers expect them (`itemsField[key]`, a plain array), the paging
118
+ * bookkeeping lives in `metaField[key]`, and `derive` recomputes fields built
119
+ * from the rows (e.g. a parsed display list) in the same commit — so a derived
120
+ * field is never one frame behind its rows.
121
+ *
122
+ * Rows seeded without bookkeeping (by code outside the replica) read as a
123
+ * single head page.
124
+ */
125
+ export declare const splitPagedLens: <TStore, TItem, TCursor = unknown>({ clearDerived, derive, itemsField, metaField }: {
126
+ /** Derived fields to reset on a scope change. */
127
+ clearDerived?: () => Partial<TStore>;
128
+ /** Fields computed from an entry's rows (`undefined` = the entry is gone). */
129
+ derive?: (state: TStore, key: string, items: TItem[] | undefined) => Partial<TStore>;
130
+ itemsField: keyof TStore & string;
131
+ metaField: keyof TStore & string;
132
+ }) => ReplicaLens<TStore, ReplicaPagedData<TItem, TCursor>>;
133
+ /** Zustand `setState`, with the devtools action label. */
134
+ type Setter<TStore> = (partial: Partial<TStore>, replace?: false, action?: any) => void;
135
+ interface CreateReplicaSliceOptions<TStore, TParams, TData, TFetched> extends Omit<ReplicaEngineOptions<TParams, TData, TFetched>, 'port' | 'revalidate'> {
116
136
  /** Query cache that schedules fetches (see `createSWRDriver`). */
117
137
  driver: ReplicaSyncDriver;
118
138
  /**
@@ -133,7 +153,7 @@ export interface CreateReplicaSliceOptions<TStore, TParams, TData, TFetched> ext
133
153
  * when the request was issued, which may differ from the scope active when the
134
154
  * callback runs (an identity / workspace switch in between).
135
155
  */
136
- export interface ReplicaSyncErrorContext {
156
+ interface ReplicaSyncErrorContext {
137
157
  /**
138
158
  * Whether the captured `scope` is still the one the store is on. A failure
139
159
  * with `isActive: false` belongs to an identity / workspace the store has
@@ -147,7 +167,7 @@ export interface ReplicaSyncErrorContext {
147
167
  scope: string;
148
168
  }
149
169
  /** Where a settled head response was captured, for {@link ReplicaEffects}. */
150
- export interface ReplicaSettleContext {
170
+ interface ReplicaSettleContext {
151
171
  key: string;
152
172
  /** The cache scope the request was captured under (the active one, except for `onPurge`). */
153
173
  scope: string;
@@ -164,7 +184,7 @@ export interface ReplicaSettleContext {
164
184
  * query, a newer response already applied) and, for `onApplied` / `onMissing`,
165
185
  * only when its captured scope is still the active one.
166
186
  */
167
- export interface ReplicaEffects<TParams, TFetched> {
187
+ interface ReplicaEffects<TParams, TFetched> {
168
188
  /**
169
189
  * A response was folded into the active scope (`prepareHead` kept it and it
170
190
  * is not a terminal absence). Runs even when the value did not change.
@@ -189,7 +209,7 @@ export interface ReplicaEffects<TParams, TFetched> {
189
209
  */
190
210
  onPurge?: (params: TParams, context: ReplicaSettleContext) => void;
191
211
  }
192
- export interface ReplicaFetchOptions {
212
+ interface ReplicaFetchOptions {
193
213
  /**
194
214
  * Start a new request even when one for the same entry is in flight, and
195
215
  * retire every request started before it: a late response of an older one
@@ -208,7 +228,7 @@ export interface ReplicaFetchOptions {
208
228
  */
209
229
  scope?: string;
210
230
  }
211
- export interface ReplicaFetchResult<TFetched> {
231
+ interface ReplicaFetchResult<TFetched> {
212
232
  /**
213
233
  * Whether the response was settled into the active scope: folded in, or
214
234
  * applied as a terminal absence. `false` when a guard dropped it (scope
@@ -220,7 +240,7 @@ export interface ReplicaFetchResult<TFetched> {
220
240
  /** The response was a terminal absence (`isMissing`), in whichever scope. */
221
241
  missing: boolean;
222
242
  }
223
- export interface ReplicaSyncOptions<TFetched = unknown, TData = unknown> extends ReplicaSyncSchedule {
243
+ interface ReplicaSyncOptions<TFetched = unknown, TData = unknown> extends ReplicaSyncSchedule {
224
244
  enabled?: boolean;
225
245
  /**
226
246
  * Side effects of a failed fetch (error side-maps); the store view is left as is.
@@ -244,20 +264,7 @@ export interface ReplicaSyncOptions<TFetched = unknown, TData = unknown> extends
244
264
  */
245
265
  onSuccess?: (data: TFetched) => void;
246
266
  }
247
- export interface ReplicaSyncResult {
248
- error: unknown;
249
- /** The persisted row has been read (or there is nothing to read). */
250
- isHydrated: boolean;
251
- /** A network request is in flight. Never a reason to hide store data. */
252
- isValidating: boolean;
253
- /** Re-run the network sync for this entry. */
254
- revalidate: () => Promise<unknown>;
255
- }
256
- /** `slice.fetch` was asked to fetch for a scope that is no longer active. */
257
- export declare class ReplicaScopeChangedError extends Error {
258
- constructor(name: string, scope: string);
259
- }
260
- export interface ReplicaSyncResult {
267
+ interface ReplicaSyncResult {
261
268
  error: unknown;
262
269
  /** The persisted row has been read (or there is nothing to read). */
263
270
  isHydrated: boolean;
@@ -338,25 +345,9 @@ export declare const createReplicaSlice: <TStore, TParams, TData, TFetched = TDa
338
345
  useSync: (params: TParams | null | undefined, { enabled, onError, onHydrated, onSuccess, ...schedule }?: ReplicaSyncOptions<TFetched, TData>) => ReplicaSyncResult;
339
346
  };
340
347
  export type ReplicaSlice<TStore, TParams, TData, TFetched = TData> = ReturnType<typeof createReplicaSlice<TStore, TParams, TData, TFetched>>;
341
- /** Lens for the common case: a `Record<key, TData>` field on the store. */
342
- export declare const recordLens: <TStore, TData>(field: keyof TStore & string) => ReplicaLens<TStore, TData>;
343
- /**
344
- * Lens for a paged view split across store fields: the rows stay where
345
- * existing readers expect them (`itemsField[key]`, a plain array), the paging
346
- * bookkeeping lives in `metaField[key]`, and `derive` recomputes fields built
347
- * from the rows (e.g. a parsed display list) in the same commit — so a derived
348
- * field is never one frame behind its rows.
349
- *
350
- * Rows seeded without bookkeeping (by code outside the replica) read as a
351
- * single head page.
352
- */
353
- export declare const splitPagedLens: <TStore, TItem, TCursor = unknown>({ clearDerived, derive, itemsField, metaField }: {
354
- /** Derived fields to reset on a scope change. */
355
- clearDerived?: () => Partial<TStore>;
356
- /** Fields computed from an entry's rows (`undefined` = the entry is gone). */
357
- derive?: (state: TStore, key: string, items: TItem[] | undefined) => Partial<TStore>;
358
- itemsField: keyof TStore & string;
359
- metaField: keyof TStore & string;
360
- }) => ReplicaLens<TStore, ReplicaPagedData<TItem, TCursor>>;
361
- export {};
348
+ /** `slice.fetch` was asked to fetch for a scope that is no longer active. */
349
+ export declare class ReplicaScopeChangedError extends Error {
350
+ constructor(name: string, scope: string);
351
+ }
352
+ export type { CreateReplicaSliceOptions, ReplicaEffects, ReplicaFetchOptions, ReplicaFetchResult, ReplicaLens, ReplicaSettleContext, ReplicaSyncErrorContext, ReplicaSyncOptions, ReplicaSyncResult, ReplicaViewSet };
362
353
  //# sourceMappingURL=index.d.ts.map