@interop/was-react 0.19.0 → 0.20.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.
Files changed (72) hide show
  1. package/README.md +4 -3
  2. package/dist/auth/verifyResponse.d.ts.map +1 -1
  3. package/dist/auth/verifyResponse.js +10 -5
  4. package/dist/auth/verifyResponse.js.map +1 -1
  5. package/dist/identity/agents.d.ts +5 -0
  6. package/dist/identity/agents.d.ts.map +1 -1
  7. package/dist/identity/agents.js +10 -3
  8. package/dist/identity/agents.js.map +1 -1
  9. package/dist/identity/seedCredential.d.ts +5 -1
  10. package/dist/identity/seedCredential.d.ts.map +1 -1
  11. package/dist/identity/seedCredential.js +15 -28
  12. package/dist/identity/seedCredential.js.map +1 -1
  13. package/dist/index.d.ts +4 -3
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +7 -6
  16. package/dist/index.js.map +1 -1
  17. package/dist/react/hooks.d.ts +3 -2
  18. package/dist/react/hooks.d.ts.map +1 -1
  19. package/dist/react/hooks.js +8 -4
  20. package/dist/react/hooks.js.map +1 -1
  21. package/dist/session/authStore.d.ts +9 -0
  22. package/dist/session/authStore.d.ts.map +1 -1
  23. package/dist/session/authStore.js +113 -47
  24. package/dist/session/authStore.js.map +1 -1
  25. package/dist/session/localWipe.d.ts +1 -1
  26. package/dist/session/localWipe.js +2 -2
  27. package/dist/session/localWipe.js.map +1 -1
  28. package/dist/storage/adopt.d.ts +4 -1
  29. package/dist/storage/adopt.d.ts.map +1 -1
  30. package/dist/storage/adopt.js +4 -3
  31. package/dist/storage/adopt.js.map +1 -1
  32. package/dist/storage/descriptorManager.d.ts +1 -0
  33. package/dist/storage/descriptorManager.d.ts.map +1 -1
  34. package/dist/storage/descriptorManager.js +39 -21
  35. package/dist/storage/descriptorManager.js.map +1 -1
  36. package/dist/storage/entityStore.d.ts.map +1 -1
  37. package/dist/storage/entityStore.js +3 -0
  38. package/dist/storage/entityStore.js.map +1 -1
  39. package/dist/storage/publicUrl.js +3 -3
  40. package/dist/storage/storageContext.d.ts +178 -0
  41. package/dist/storage/storageContext.d.ts.map +1 -0
  42. package/dist/storage/storageContext.js +331 -0
  43. package/dist/storage/storageContext.js.map +1 -0
  44. package/dist/storage/storageManager.d.ts +61 -76
  45. package/dist/storage/storageManager.d.ts.map +1 -1
  46. package/dist/storage/storageManager.js +59 -150
  47. package/dist/storage/storageManager.js.map +1 -1
  48. package/dist/storage/syncController.d.ts +10 -1
  49. package/dist/storage/syncController.d.ts.map +1 -1
  50. package/dist/storage/syncController.js +12 -4
  51. package/dist/storage/syncController.js.map +1 -1
  52. package/dist/storage/syncStatusStore.d.ts +23 -2
  53. package/dist/storage/syncStatusStore.d.ts.map +1 -1
  54. package/dist/storage/syncStatusStore.js +22 -13
  55. package/dist/storage/syncStatusStore.js.map +1 -1
  56. package/dist/storage/wasRemoteStore.d.ts +24 -10
  57. package/dist/storage/wasRemoteStore.d.ts.map +1 -1
  58. package/dist/storage/wasRemoteStore.js +91 -36
  59. package/dist/storage/wasRemoteStore.js.map +1 -1
  60. package/dist/storage/wasSync.d.ts +24 -9
  61. package/dist/storage/wasSync.d.ts.map +1 -1
  62. package/dist/storage/wasSync.js +52 -21
  63. package/dist/storage/wasSync.js.map +1 -1
  64. package/dist/storage/writerId.d.ts +36 -0
  65. package/dist/storage/writerId.d.ts.map +1 -0
  66. package/dist/storage/writerId.js +76 -0
  67. package/dist/storage/writerId.js.map +1 -0
  68. package/package.json +9 -9
  69. package/dist/storage/rehydrate.d.ts +0 -64
  70. package/dist/storage/rehydrate.d.ts.map +0 -1
  71. package/dist/storage/rehydrate.js +0 -132
  72. package/dist/storage/rehydrate.js.map +0 -1
@@ -0,0 +1,331 @@
1
+ /*!
2
+ * Copyright (c) 2026 Interop Alliance. All rights reserved.
3
+ */
4
+ /**
5
+ * The session-scoped storage context: everything a session's storage layer
6
+ * shares across its async consumers, bound to one store registry and owned by
7
+ * one session auth store. It holds the open {@link LocalStore} replica, the
8
+ * connected session's {@link WasRemoteStore}, the per-install writer id the
9
+ * write verbs stamp with, the sync status store, and the debounced re-hydrate
10
+ * timers -- the values that used to be module globals, so that two providers
11
+ * in one process no longer clobber each other and a consumer never has to
12
+ * invent its own "did the holder change under me" guard.
13
+ *
14
+ * Staleness is one mechanism, a generation counter: every attach and detach of
15
+ * the replica bumps it. {@link StorageContext.whileAttached} runs an async
16
+ * operation against the replica attached at its start and reports the result
17
+ * only if the same replica is still attached when it settles; the scheduled
18
+ * re-hydrates and the per-doc change patches are built on it. A consumer that
19
+ * outlives its replica therefore ends as a silent no-op rather than as an
20
+ * unhandled rejection or -- worse -- a write into the NEXT session's replica.
21
+ */
22
+ import { uuidv7 } from 'uuidv7';
23
+ import { activateStorageContext, deactivateStorageContext } from './storageManager.js';
24
+ import { createSyncStatusStore } from './syncStatusStore.js';
25
+ /**
26
+ * The debounce window coalescing a pull burst into one re-hydrate.
27
+ */
28
+ const REHYDRATE_DEBOUNCE_MS = 50;
29
+ export class StorageContext {
30
+ /**
31
+ * The app's per-collection hydrate/patch handlers; the change patches and the
32
+ * scheduled re-hydrates route on it.
33
+ */
34
+ registry;
35
+ /**
36
+ * This session's per-collection replication statuses.
37
+ */
38
+ syncStatus = createSyncStatusStore();
39
+ #writerId;
40
+ #localStore = null;
41
+ #remoteStore = null;
42
+ #generation = 0;
43
+ /**
44
+ * Per-collection debounce timers coalescing a pull burst into one hydrate.
45
+ */
46
+ #rehydrateTimers = new Map();
47
+ /**
48
+ * @param options {object}
49
+ * @param options.registry {StoreRegistry}
50
+ * @param options.writerId {string} the resolved per-install writer id (see
51
+ * `getWriterId`), the value every stamp of this session carries
52
+ */
53
+ constructor({ registry, writerId }) {
54
+ this.registry = registry;
55
+ this.#writerId = writerId;
56
+ }
57
+ /**
58
+ * The writer id this session stamps with: an unkeyed, clearable attribution
59
+ * label, never an identity.
60
+ *
61
+ * @returns {string}
62
+ */
63
+ get writerId() {
64
+ return this.#writerId;
65
+ }
66
+ /**
67
+ * Replaces the in-memory writer id with a fresh one (the clear-data wipe,
68
+ * after `clearPersistedWriterId` removed the persisted one). The session
69
+ * keeps running over its new anonymous replica and its write verbs still
70
+ * have to stamp; nothing persists the new id, so the next run resolves and
71
+ * stores an id of its own.
72
+ *
73
+ * @returns {string} the new id
74
+ */
75
+ resetWriterId() {
76
+ this.#writerId = uuidv7();
77
+ return this.#writerId;
78
+ }
79
+ /**
80
+ * Stamps a payload with fresh last-write-wins fields: the current instant as
81
+ * `updatedAt` and this session's writer id. Any values the caller supplied
82
+ * are overwritten -- a stamp must describe THIS write, or a hydrated doc's
83
+ * older `updatedAt` would ride a later edit and lose the conflict.
84
+ *
85
+ * @param payload {object}
86
+ * @returns {object} the payload with the LWW fields set
87
+ */
88
+ stampLww(payload) {
89
+ return {
90
+ ...payload,
91
+ updatedAt: new Date().toISOString(),
92
+ writerId: this.#writerId
93
+ };
94
+ }
95
+ /**
96
+ * Installs the opened replica and makes this the process's active context
97
+ * (the one the app-facing facades and the entity-store verbs resolve to).
98
+ * Throws if ANOTHER context still has a replica attached: two live sessions
99
+ * in one process would write into each other's entity stores.
100
+ *
101
+ * @param store {LocalStore}
102
+ * @returns {void}
103
+ */
104
+ attachStore(store) {
105
+ activateStorageContext(this);
106
+ this.#localStore = store;
107
+ this.#generation += 1;
108
+ }
109
+ /**
110
+ * Releases the replica (the caller closes or deletes it), cancels every
111
+ * pending re-hydrate, and releases the process-wide active pointer when this
112
+ * context holds it -- the mirror of {@link attachStore}'s claim. Anything
113
+ * still in flight against the old replica sees the generation change and
114
+ * ends as a no-op; the facades throw until the next attach claims a live
115
+ * context, rather than resolving a retired one.
116
+ *
117
+ * @returns {LocalStore | null} the replica that was attached, if any
118
+ */
119
+ detachStore() {
120
+ for (const timer of this.#rehydrateTimers.values()) {
121
+ clearTimeout(timer);
122
+ }
123
+ this.#rehydrateTimers.clear();
124
+ const store = this.#localStore;
125
+ this.#localStore = null;
126
+ this.#generation += 1;
127
+ deactivateStorageContext(this);
128
+ return store;
129
+ }
130
+ /**
131
+ * Whether a replica is attached.
132
+ *
133
+ * @returns {boolean}
134
+ */
135
+ hasStore() {
136
+ return this.#localStore !== null;
137
+ }
138
+ /**
139
+ * The attached replica, or throws if none is open.
140
+ *
141
+ * @returns {LocalStore}
142
+ */
143
+ requireStore() {
144
+ if (!this.#localStore) {
145
+ throw new Error('LocalStore is not initialized; open it first.');
146
+ }
147
+ return this.#localStore;
148
+ }
149
+ /**
150
+ * Runs `op` against the replica attached now and resolves with its result
151
+ * only if that same replica is still attached when `op` settles. Resolves
152
+ * `undefined` when no replica is attached, or when it was detached or
153
+ * swapped meanwhile -- including when `op` rejected after the swap, since a
154
+ * read torn by its own teardown is expected noise, not an error. A rejection
155
+ * against a replica that is still attached propagates.
156
+ *
157
+ * @param op {(store: LocalStore) => Promise<T>}
158
+ * @returns {Promise<T | undefined>}
159
+ */
160
+ async whileAttached(op) {
161
+ const store = this.#localStore;
162
+ if (!store) {
163
+ return undefined;
164
+ }
165
+ const generation = this.#generation;
166
+ let result;
167
+ try {
168
+ result = await op(store);
169
+ }
170
+ catch (err) {
171
+ if (this.#generation !== generation) {
172
+ return undefined;
173
+ }
174
+ throw err;
175
+ }
176
+ return this.#generation === generation ? result : undefined;
177
+ }
178
+ /**
179
+ * Installs the connected session's delegated remote store (once background
180
+ * sync has bootstrapped it from the granted zcaps).
181
+ *
182
+ * @param store {WasRemoteStore}
183
+ * @returns {void}
184
+ */
185
+ attachRemoteStore(store) {
186
+ this.#remoteStore = store;
187
+ }
188
+ /**
189
+ * Releases the remote store (logout / sync teardown).
190
+ *
191
+ * @returns {void}
192
+ */
193
+ detachRemoteStore() {
194
+ this.#remoteStore = null;
195
+ }
196
+ /**
197
+ * Whether a connected session's remote store is available.
198
+ *
199
+ * @returns {boolean}
200
+ */
201
+ hasRemoteStore() {
202
+ return this.#remoteStore !== null;
203
+ }
204
+ /**
205
+ * The connected session's remote store, or throws while no wallet-connected
206
+ * session is active (local-only mode, or sync has not bootstrapped yet).
207
+ *
208
+ * @returns {WasRemoteStore}
209
+ */
210
+ requireRemoteStore() {
211
+ if (!this.#remoteStore) {
212
+ throw new Error('No WAS remote store is available; connect a wallet session first.');
213
+ }
214
+ return this.#remoteStore;
215
+ }
216
+ /**
217
+ * Hydrates every registered store from the attached replica.
218
+ *
219
+ * @returns {Promise<void>}
220
+ */
221
+ async hydrateAll() {
222
+ await Promise.all(Object.values(this.registry).map(entry => entry.hydrate()));
223
+ }
224
+ /**
225
+ * Empties every registered store (logout).
226
+ *
227
+ * @returns {void}
228
+ */
229
+ clearEntityStores() {
230
+ for (const entry of Object.values(this.registry)) {
231
+ entry.clear();
232
+ }
233
+ }
234
+ /**
235
+ * Patches ONE store from a single RxDB change event (per-doc, no
236
+ * whole-collection re-hydrate): decrypt the changed envelope, then upsert the
237
+ * payload (INSERT / UPDATE, including conflict-resolved rows) or drop it
238
+ * (DELETE / tombstone). The `uuid -> envelopeId` index is kept in step so a
239
+ * later local edit of a remotely-created doc still finds its envelope. Falls
240
+ * back to a debounced whole-collection re-hydrate if the envelope is missing
241
+ * or fails to decrypt.
242
+ *
243
+ * Fired floating off the RxDB change stream, so a logout/login teardown can
244
+ * detach or swap the replica while the decrypt is in flight; the decrypt runs
245
+ * under {@link StorageContext.whileAttached}, so an event that outlives its
246
+ * replica is dropped rather than patched into the next session's stores.
247
+ *
248
+ * @param collectionKey {string}
249
+ * @param event {object} an RxDB change event (operation + documentData)
250
+ * @returns {Promise<void>}
251
+ */
252
+ async patchFromChange(collectionKey, event) {
253
+ const entry = this.registry[collectionKey];
254
+ if (!entry) {
255
+ return;
256
+ }
257
+ const row = event.documentData;
258
+ const envelope = row?.data;
259
+ const deleted = event.operation === 'DELETE' || row?._deleted === true;
260
+ if (!row || envelope === undefined) {
261
+ this.scheduleRehydrate(collectionKey);
262
+ return;
263
+ }
264
+ // `null` marks a decrypt failure against a still-attached replica (fall
265
+ // back to a re-hydrate); `undefined` marks a replica gone meanwhile (drop).
266
+ const decrypted = await this.whileAttached(async (store) => {
267
+ try {
268
+ return {
269
+ store,
270
+ payload: await store.decryptEnvelope(collectionKey, envelope)
271
+ };
272
+ }
273
+ catch {
274
+ return null;
275
+ }
276
+ });
277
+ if (decrypted === undefined) {
278
+ return;
279
+ }
280
+ if (decrypted === null) {
281
+ this.scheduleRehydrate(collectionKey);
282
+ return;
283
+ }
284
+ const { store, payload } = decrypted;
285
+ if (deleted) {
286
+ // Only honor a tombstone for the envelope the entity currently lives in.
287
+ // A delete of a DIFFERENT envelope that decrypts to the same logical id
288
+ // is a stale duplicate being cleaned up (a reconciled singleton loser,
289
+ // or the pre-resurrection row of a locally re-created doc) -- dropping
290
+ // the live doc for it would undo the reconciliation/resurrection.
291
+ const mapped = store.envelopeIdFor(collectionKey, payload.id);
292
+ if (mapped !== undefined && mapped !== row.id) {
293
+ return;
294
+ }
295
+ store.forgetEnvelope(collectionKey, payload.id);
296
+ entry.drop(payload.id);
297
+ }
298
+ else {
299
+ store.rememberEnvelope(collectionKey, payload.id, row.id);
300
+ entry.upsert(payload);
301
+ }
302
+ }
303
+ /**
304
+ * Schedules a debounced re-hydrate of one collection's store after a pull.
305
+ * A no-op without an attached replica, and the timer itself is bound to the
306
+ * replica attached now: a detach cancels it, and one that still fires after
307
+ * a swap finds the generation changed and does nothing.
308
+ *
309
+ * @param collectionKey {string}
310
+ * @returns {void}
311
+ */
312
+ scheduleRehydrate(collectionKey) {
313
+ const entry = this.registry[collectionKey];
314
+ if (!entry || !this.#localStore) {
315
+ return;
316
+ }
317
+ const generation = this.#generation;
318
+ const existing = this.#rehydrateTimers.get(collectionKey);
319
+ if (existing) {
320
+ clearTimeout(existing);
321
+ }
322
+ this.#rehydrateTimers.set(collectionKey, setTimeout(() => {
323
+ this.#rehydrateTimers.delete(collectionKey);
324
+ if (this.#generation !== generation) {
325
+ return;
326
+ }
327
+ void entry.hydrate();
328
+ }, REHYDRATE_DEBOUNCE_MS));
329
+ }
330
+ }
331
+ //# sourceMappingURL=storageContext.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storageContext.js","sourceRoot":"","sources":["../../src/storage/storageContext.ts"],"names":[],"mappings":"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"}
@@ -1,115 +1,100 @@
1
+ /*!
2
+ * Copyright (c) 2026 Interop Alliance. All rights reserved.
3
+ */
4
+ /**
5
+ * The storage manager: the one remaining process-wide pointer, to the ACTIVE
6
+ * {@link StorageContext}, plus the app-facing facades over it. Entity stores
7
+ * are created at module level, before any session exists, so their verbs
8
+ * cannot hold a context of their own; they reach the live session through
9
+ * {@link requireStore} / {@link requireRemoteStore} / {@link stampLww} here,
10
+ * which resolve to whichever context is active. Keeping the facade free of
11
+ * store imports (no cycle) also lets the session store own the init/hydrate
12
+ * ordering.
13
+ *
14
+ * Activation is deliberate about the two-providers case: a context with a
15
+ * replica attached is live, and a live context is never replaced -- a second
16
+ * session attaching a replica of its own throws instead of silently taking the
17
+ * facades over. A context without a replica (created but not booted, or torn
18
+ * down) is inert and is replaced without complaint, which is what a React
19
+ * dev-mode double `useState` initializer or a test's sequence of stores needs.
20
+ */
1
21
  import type { LwwFields } from '../sync/lww.js';
2
22
  import type { LocalStore } from './localStore.js';
23
+ import type { StorageContext } from './storageContext.js';
3
24
  import type { WasRemoteStore } from './wasRemoteStore.js';
4
25
  /**
5
- * Installs the opened store (called once by the app bootstrap).
26
+ * Makes `context` the active one (called by the session store at creation, so
27
+ * the facades resolve before any replica opens, again right before it opens a
28
+ * replica, and by {@link StorageContext.attachStore}). Idempotent for the
29
+ * context already active; throws while a DIFFERENT context still has a replica
30
+ * attached.
6
31
  *
7
- * @param store {LocalStore}
32
+ * @param context {StorageContext}
8
33
  * @returns {void}
9
34
  */
10
- export declare function setLocalStore(store: LocalStore): void;
35
+ export declare function activateStorageContext(context: StorageContext): void;
11
36
  /**
12
- * The opened store, or throws if the app has not bootstrapped yet.
37
+ * Releases the active pointer if `context` holds it (called by
38
+ * `StorageContext.detachStore`, so every path that detaches a replica --
39
+ * `destroy`, logout, clear-data, the connected activation's fallback to
40
+ * `local` -- releases it and re-claims on re-attach); a no-op for any other
41
+ * context. Until the next claim the facades throw rather than resolve a
42
+ * retired session.
13
43
  *
14
- * @returns {LocalStore}
44
+ * @param context {StorageContext}
45
+ * @returns {void}
15
46
  */
16
- export declare function requireStore(): LocalStore;
47
+ export declare function deactivateStorageContext(context: StorageContext): void;
17
48
  /**
18
- * Whether the store has been opened.
49
+ * Whether a storage context is active.
19
50
  *
20
51
  * @returns {boolean}
21
52
  */
22
- export declare function hasStore(): boolean;
53
+ export declare function hasStorageContext(): boolean;
23
54
  /**
24
- * Releases the held store reference (logout; the caller closes the db).
55
+ * The active storage context, or throws if no session store has been created.
25
56
  *
26
- * @returns {void}
57
+ * @returns {StorageContext}
27
58
  */
28
- export declare function clearLocalStore(): void;
59
+ export declare function requireStorageContext(): StorageContext;
29
60
  /**
30
- * Installs the per-session delegated remote store (set once background sync
31
- * has bootstrapped it from the granted zcaps).
61
+ * The active session's opened replica, or throws if none is open.
32
62
  *
33
- * @param store {WasRemoteStore}
34
- * @returns {void}
35
- */
36
- export declare function setRemoteStore(store: WasRemoteStore): void;
37
- /**
38
- * The connected session's remote store, or throws while no wallet-connected
39
- * session is active (local-only mode, or sync has not bootstrapped yet).
40
- *
41
- * @returns {WasRemoteStore}
63
+ * @returns {LocalStore}
42
64
  */
43
- export declare function requireRemoteStore(): WasRemoteStore;
65
+ export declare function requireStore(): LocalStore;
44
66
  /**
45
- * Whether a connected session's remote store is available.
67
+ * Whether the active session has a replica open.
46
68
  *
47
69
  * @returns {boolean}
48
70
  */
49
- export declare function hasRemoteStore(): boolean;
50
- /**
51
- * Releases the held remote store reference (logout / sync teardown).
52
- *
53
- * @returns {void}
54
- */
55
- export declare function clearRemoteStore(): void;
71
+ export declare function hasStore(): boolean;
56
72
  /**
57
- * A stable per-install writer id (the last-write-wins tiebreak stamped into
58
- * every payload), persisted in localStorage under `<prefix>writerId`. In an
59
- * environment without `localStorage` it falls back to a process-stable
60
- * unpersisted id instead of throwing.
61
- *
62
- * The id is an unkeyed, clearable attribution label -- never an identity. On a
63
- * miss it adopts a value left under the pre-rename `<prefix>clientId` key and
64
- * removes the old one, so an existing install keeps stamping the same id
65
- * across the rename rather than looking like a second writer.
73
+ * The active session's remote store, or throws while no wallet-connected
74
+ * session is active.
66
75
  *
67
- * @param [options] {object}
68
- * @param [options.storageKeyPrefix] {string} the localStorage key prefix
69
- * (defaults to {@link DEFAULT_STORAGE_KEY_PREFIX})
70
- * @returns {string}
76
+ * @returns {WasRemoteStore}
71
77
  */
72
- export declare function getWriterId({ storageKeyPrefix }?: {
73
- storageKeyPrefix?: string;
74
- }): string;
78
+ export declare function requireRemoteStore(): WasRemoteStore;
75
79
  /**
76
- * Installs the session's resolved writer id (called once by the session store,
77
- * under the app's configured `storageKeyPrefix`, before any replica opens).
80
+ * Whether the active session has a remote store available.
78
81
  *
79
- * @param id {string}
80
- * @returns {void}
82
+ * @returns {boolean}
81
83
  */
82
- export declare function setWriterId(id: string): void;
84
+ export declare function hasRemoteStore(): boolean;
83
85
  /**
84
- * The session's resolved writer id, or throws if it has not been installed
85
- * yet. Deliberately never falls back to {@link getWriterId}: that would resolve
86
- * under the DEFAULT key prefix, so an app with a custom `storageKeyPrefix`
87
- * would silently stamp a second writer id.
86
+ * The active session's writer id, or throws if no session store exists yet.
87
+ * Deliberately never falls back to `getWriterId`: that would resolve under the
88
+ * DEFAULT key prefix, so an app with a custom `storageKeyPrefix` would silently
89
+ * stamp a second writer id.
88
90
  *
89
91
  * @returns {string}
90
92
  */
91
93
  export declare function requireWriterId(): string;
92
94
  /**
93
- * Clears the persisted writer id (the clear-data grade of the wipe): removes
94
- * both the current key and the pre-rename one, so nothing this library wrote
95
- * survives in localStorage. A fresh id is installed in memory rather than the
96
- * resolved one being dropped, because the session keeps running over its new
97
- * anonymous replica and its write verbs still have to stamp; nothing persists
98
- * it, so the next run resolves and stores an id of its own.
99
- *
100
- * @param [options] {object}
101
- * @param [options.storageKeyPrefix] {string} the localStorage key prefix
102
- * (defaults to {@link DEFAULT_STORAGE_KEY_PREFIX})
103
- * @returns {void}
104
- */
105
- export declare function clearWriterId({ storageKeyPrefix }?: {
106
- storageKeyPrefix?: string;
107
- }): void;
108
- /**
109
- * Stamps a payload with fresh last-write-wins fields: the current instant as
110
- * `updatedAt` and the session's resolved writer id. Any values the caller
111
- * supplied are overwritten -- a stamp must describe THIS write, or a hydrated
112
- * doc's older `updatedAt` would ride a later edit and lose the conflict.
95
+ * Stamps a payload with fresh last-write-wins fields under the active session
96
+ * (see {@link StorageContext.stampLww}); the entity-store write verbs call it
97
+ * on every write, and an app writing through `LocalStore` directly does too.
113
98
  *
114
99
  * @param payload {object}
115
100
  * @returns {object} the payload with the LWW fields set
@@ -1 +1 @@
1
- {"version":3,"file":"storageManager.d.ts","sourceRoot":"","sources":["../../src/storage/storageManager.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC/C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AACjD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AAKzD;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,CAErD;AAED;;;;GAIG;AACH,wBAAgB,YAAY,IAAI,UAAU,CAKzC;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,IAAI,OAAO,CAElC;AAED;;;;GAIG;AACH,wBAAgB,eAAe,IAAI,IAAI,CAEtC;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,cAAc,GAAG,IAAI,CAE1D;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,IAAI,cAAc,CAOnD;AAED;;;;GAIG;AACH,wBAAgB,cAAc,IAAI,OAAO,CAExC;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,IAAI,IAAI,CAEvC;AASD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,WAAW,CAAC,EAC1B,gBAA6C,EAC9C,GAAE;IAAE,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,MAAM,CAe7C;AAID;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAE5C;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,IAAI,MAAM,CAOxC;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CAAC,EAC5B,gBAA6C,EAC9C,GAAE;IAAE,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,IAAI,CAS3C;AAED;;;;;;;;GAQG;AACH,wBAAgB,QAAQ,CAAC,CAAC,SAAS;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,EAAE,OAAO,EAAE,CAAC,GAAG,CAAC,GAAG,SAAS,CAM5E"}
1
+ {"version":3,"file":"storageManager.d.ts","sourceRoot":"","sources":["../../src/storage/storageManager.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC/C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AACjD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AACzD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AAIzD;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,cAAc,GAAG,IAAI,CAQpE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,cAAc,GAAG,IAAI,CAItE;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,IAAI,OAAO,CAE3C;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,IAAI,cAAc,CAOtD;AAED;;;;GAIG;AACH,wBAAgB,YAAY,IAAI,UAAU,CAEzC;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,IAAI,OAAO,CAElC;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,IAAI,cAAc,CAEnD;AAED;;;;GAIG;AACH,wBAAgB,cAAc,IAAI,OAAO,CAExC;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,IAAI,MAAM,CAExC;AAED;;;;;;;GAOG;AACH,wBAAgB,QAAQ,CAAC,CAAC,SAAS;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,EAAE,OAAO,EAAE,CAAC,GAAG,CAAC,GAAG,SAAS,CAE5E"}