@interop/was-react 0.19.0 → 0.20.0

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 (60) 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 +69 -40
  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/entityStore.d.ts.map +1 -1
  33. package/dist/storage/entityStore.js +3 -0
  34. package/dist/storage/entityStore.js.map +1 -1
  35. package/dist/storage/publicUrl.js +3 -3
  36. package/dist/storage/storageContext.d.ts +175 -0
  37. package/dist/storage/storageContext.d.ts.map +1 -0
  38. package/dist/storage/storageContext.js +327 -0
  39. package/dist/storage/storageContext.js.map +1 -0
  40. package/dist/storage/storageManager.d.ts +57 -76
  41. package/dist/storage/storageManager.d.ts.map +1 -1
  42. package/dist/storage/storageManager.js +55 -150
  43. package/dist/storage/storageManager.js.map +1 -1
  44. package/dist/storage/syncController.d.ts +10 -1
  45. package/dist/storage/syncController.d.ts.map +1 -1
  46. package/dist/storage/syncController.js +12 -4
  47. package/dist/storage/syncController.js.map +1 -1
  48. package/dist/storage/syncStatusStore.d.ts +23 -2
  49. package/dist/storage/syncStatusStore.d.ts.map +1 -1
  50. package/dist/storage/syncStatusStore.js +22 -13
  51. package/dist/storage/syncStatusStore.js.map +1 -1
  52. package/dist/storage/writerId.d.ts +36 -0
  53. package/dist/storage/writerId.d.ts.map +1 -0
  54. package/dist/storage/writerId.js +76 -0
  55. package/dist/storage/writerId.js.map +1 -0
  56. package/package.json +9 -9
  57. package/dist/storage/rehydrate.d.ts +0 -64
  58. package/dist/storage/rehydrate.d.ts.map +0 -1
  59. package/dist/storage/rehydrate.js +0 -132
  60. package/dist/storage/rehydrate.js.map +0 -1
@@ -0,0 +1,175 @@
1
+ import type { StoreRegistry } from '../config.js';
2
+ import type { Json } from '../sync/index.js';
3
+ import type { LwwFields } from '../sync/lww.js';
4
+ import type { LocalStore } from './localStore.js';
5
+ import { type SyncStatusStore } from './syncStatusStore.js';
6
+ import type { WasRemoteStore } from './wasRemoteStore.js';
7
+ export declare class StorageContext {
8
+ #private;
9
+ /**
10
+ * The app's per-collection hydrate/patch handlers; the change patches and the
11
+ * scheduled re-hydrates route on it.
12
+ */
13
+ readonly registry: StoreRegistry;
14
+ /**
15
+ * This session's per-collection replication statuses.
16
+ */
17
+ readonly syncStatus: SyncStatusStore;
18
+ /**
19
+ * @param options {object}
20
+ * @param options.registry {StoreRegistry}
21
+ * @param options.writerId {string} the resolved per-install writer id (see
22
+ * `getWriterId`), the value every stamp of this session carries
23
+ */
24
+ constructor({ registry, writerId }: {
25
+ registry: StoreRegistry;
26
+ writerId: string;
27
+ });
28
+ /**
29
+ * The writer id this session stamps with: an unkeyed, clearable attribution
30
+ * label, never an identity.
31
+ *
32
+ * @returns {string}
33
+ */
34
+ get writerId(): string;
35
+ /**
36
+ * Replaces the in-memory writer id with a fresh one (the clear-data wipe,
37
+ * after `clearPersistedWriterId` removed the persisted one). The session
38
+ * keeps running over its new anonymous replica and its write verbs still
39
+ * have to stamp; nothing persists the new id, so the next run resolves and
40
+ * stores an id of its own.
41
+ *
42
+ * @returns {string} the new id
43
+ */
44
+ resetWriterId(): string;
45
+ /**
46
+ * Stamps a payload with fresh last-write-wins fields: the current instant as
47
+ * `updatedAt` and this session's writer id. Any values the caller supplied
48
+ * are overwritten -- a stamp must describe THIS write, or a hydrated doc's
49
+ * older `updatedAt` would ride a later edit and lose the conflict.
50
+ *
51
+ * @param payload {object}
52
+ * @returns {object} the payload with the LWW fields set
53
+ */
54
+ stampLww<T extends {
55
+ id: string;
56
+ }>(payload: T): T & LwwFields;
57
+ /**
58
+ * Installs the opened replica and makes this the process's active context
59
+ * (the one the app-facing facades and the entity-store verbs resolve to).
60
+ * Throws if ANOTHER context still has a replica attached: two live sessions
61
+ * in one process would write into each other's entity stores.
62
+ *
63
+ * @param store {LocalStore}
64
+ * @returns {void}
65
+ */
66
+ attachStore(store: LocalStore): void;
67
+ /**
68
+ * Releases the replica (the caller closes or deletes it) and cancels every
69
+ * pending re-hydrate; anything still in flight against the old replica sees
70
+ * the generation change and ends as a no-op.
71
+ *
72
+ * @returns {LocalStore | null} the replica that was attached, if any
73
+ */
74
+ detachStore(): LocalStore | null;
75
+ /**
76
+ * Whether a replica is attached.
77
+ *
78
+ * @returns {boolean}
79
+ */
80
+ hasStore(): boolean;
81
+ /**
82
+ * The attached replica, or throws if none is open.
83
+ *
84
+ * @returns {LocalStore}
85
+ */
86
+ requireStore(): LocalStore;
87
+ /**
88
+ * Runs `op` against the replica attached now and resolves with its result
89
+ * only if that same replica is still attached when `op` settles. Resolves
90
+ * `undefined` when no replica is attached, or when it was detached or
91
+ * swapped meanwhile -- including when `op` rejected after the swap, since a
92
+ * read torn by its own teardown is expected noise, not an error. A rejection
93
+ * against a replica that is still attached propagates.
94
+ *
95
+ * @param op {(store: LocalStore) => Promise<T>}
96
+ * @returns {Promise<T | undefined>}
97
+ */
98
+ whileAttached<T>(op: (store: LocalStore) => Promise<T>): Promise<T | undefined>;
99
+ /**
100
+ * Installs the connected session's delegated remote store (once background
101
+ * sync has bootstrapped it from the granted zcaps).
102
+ *
103
+ * @param store {WasRemoteStore}
104
+ * @returns {void}
105
+ */
106
+ attachRemoteStore(store: WasRemoteStore): void;
107
+ /**
108
+ * Releases the remote store (logout / sync teardown).
109
+ *
110
+ * @returns {void}
111
+ */
112
+ detachRemoteStore(): void;
113
+ /**
114
+ * Whether a connected session's remote store is available.
115
+ *
116
+ * @returns {boolean}
117
+ */
118
+ hasRemoteStore(): boolean;
119
+ /**
120
+ * The connected session's remote store, or throws while no wallet-connected
121
+ * session is active (local-only mode, or sync has not bootstrapped yet).
122
+ *
123
+ * @returns {WasRemoteStore}
124
+ */
125
+ requireRemoteStore(): WasRemoteStore;
126
+ /**
127
+ * Hydrates every registered store from the attached replica.
128
+ *
129
+ * @returns {Promise<void>}
130
+ */
131
+ hydrateAll(): Promise<void>;
132
+ /**
133
+ * Empties every registered store (logout).
134
+ *
135
+ * @returns {void}
136
+ */
137
+ clearEntityStores(): void;
138
+ /**
139
+ * Patches ONE store from a single RxDB change event (per-doc, no
140
+ * whole-collection re-hydrate): decrypt the changed envelope, then upsert the
141
+ * payload (INSERT / UPDATE, including conflict-resolved rows) or drop it
142
+ * (DELETE / tombstone). The `uuid -> envelopeId` index is kept in step so a
143
+ * later local edit of a remotely-created doc still finds its envelope. Falls
144
+ * back to a debounced whole-collection re-hydrate if the envelope is missing
145
+ * or fails to decrypt.
146
+ *
147
+ * Fired floating off the RxDB change stream, so a logout/login teardown can
148
+ * detach or swap the replica while the decrypt is in flight; the decrypt runs
149
+ * under {@link StorageContext.whileAttached}, so an event that outlives its
150
+ * replica is dropped rather than patched into the next session's stores.
151
+ *
152
+ * @param collectionKey {string}
153
+ * @param event {object} an RxDB change event (operation + documentData)
154
+ * @returns {Promise<void>}
155
+ */
156
+ patchFromChange(collectionKey: string, event: {
157
+ operation: string;
158
+ documentData?: {
159
+ id: string;
160
+ data?: Json;
161
+ _deleted?: boolean;
162
+ };
163
+ }): Promise<void>;
164
+ /**
165
+ * Schedules a debounced re-hydrate of one collection's store after a pull.
166
+ * A no-op without an attached replica, and the timer itself is bound to the
167
+ * replica attached now: a detach cancels it, and one that still fires after
168
+ * a swap finds the generation changed and does nothing.
169
+ *
170
+ * @param collectionKey {string}
171
+ * @returns {void}
172
+ */
173
+ scheduleRehydrate(collectionKey: string): void;
174
+ }
175
+ //# sourceMappingURL=storageContext.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storageContext.d.ts","sourceRoot":"","sources":["../../src/storage/storageContext.ts"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AACjD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAA;AAC5C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC/C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAEjD,OAAO,EAEL,KAAK,eAAe,EACrB,MAAM,sBAAsB,CAAA;AAC7B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AAOzD,qBAAa,cAAc;;IACzB;;;OAGG;IACH,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAA;IAChC;;OAEG;IACH,QAAQ,CAAC,UAAU,EAAE,eAAe,CAA0B;IAU9D;;;;;OAKG;gBACS,EACV,QAAQ,EACR,QAAQ,EACT,EAAE;QACD,QAAQ,EAAE,aAAa,CAAA;QACvB,QAAQ,EAAE,MAAM,CAAA;KACjB;IAKD;;;;;OAKG;IACH,IAAI,QAAQ,IAAI,MAAM,CAErB;IAED;;;;;;;;OAQG;IACH,aAAa,IAAI,MAAM;IAKvB;;;;;;;;OAQG;IACH,QAAQ,CAAC,CAAC,SAAS;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,EAAE,OAAO,EAAE,CAAC,GAAG,CAAC,GAAG,SAAS;IAQ7D;;;;;;;;OAQG;IACH,WAAW,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI;IAMpC;;;;;;OAMG;IACH,WAAW,IAAI,UAAU,GAAG,IAAI;IAWhC;;;;OAIG;IACH,QAAQ,IAAI,OAAO;IAInB;;;;OAIG;IACH,YAAY,IAAI,UAAU;IAO1B;;;;;;;;;;OAUG;IACG,aAAa,CAAC,CAAC,EACnB,EAAE,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,OAAO,CAAC,CAAC,CAAC,GACpC,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC;IAkBzB;;;;;;OAMG;IACH,iBAAiB,CAAC,KAAK,EAAE,cAAc,GAAG,IAAI;IAI9C;;;;OAIG;IACH,iBAAiB,IAAI,IAAI;IAIzB;;;;OAIG;IACH,cAAc,IAAI,OAAO;IAIzB;;;;;OAKG;IACH,kBAAkB,IAAI,cAAc;IASpC;;;;OAIG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAMjC;;;;OAIG;IACH,iBAAiB,IAAI,IAAI;IAMzB;;;;;;;;;;;;;;;;;OAiBG;IACG,eAAe,CACnB,aAAa,EAAE,MAAM,EACrB,KAAK,EAAE;QACL,SAAS,EAAE,MAAM,CAAA;QACjB,YAAY,CAAC,EAAE;YAAE,EAAE,EAAE,MAAM,CAAC;YAAC,IAAI,CAAC,EAAE,IAAI,CAAC;YAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;SAAE,CAAA;KAC/D,GACA,OAAO,CAAC,IAAI,CAAC;IAqDhB;;;;;;;;OAQG;IACH,iBAAiB,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI;CAqB/C"}
@@ -0,0 +1,327 @@
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 } 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) and cancels every
111
+ * pending re-hydrate; anything still in flight against the old replica sees
112
+ * the generation change and ends as a no-op.
113
+ *
114
+ * @returns {LocalStore | null} the replica that was attached, if any
115
+ */
116
+ detachStore() {
117
+ for (const timer of this.#rehydrateTimers.values()) {
118
+ clearTimeout(timer);
119
+ }
120
+ this.#rehydrateTimers.clear();
121
+ const store = this.#localStore;
122
+ this.#localStore = null;
123
+ this.#generation += 1;
124
+ return store;
125
+ }
126
+ /**
127
+ * Whether a replica is attached.
128
+ *
129
+ * @returns {boolean}
130
+ */
131
+ hasStore() {
132
+ return this.#localStore !== null;
133
+ }
134
+ /**
135
+ * The attached replica, or throws if none is open.
136
+ *
137
+ * @returns {LocalStore}
138
+ */
139
+ requireStore() {
140
+ if (!this.#localStore) {
141
+ throw new Error('LocalStore is not initialized; open it first.');
142
+ }
143
+ return this.#localStore;
144
+ }
145
+ /**
146
+ * Runs `op` against the replica attached now and resolves with its result
147
+ * only if that same replica is still attached when `op` settles. Resolves
148
+ * `undefined` when no replica is attached, or when it was detached or
149
+ * swapped meanwhile -- including when `op` rejected after the swap, since a
150
+ * read torn by its own teardown is expected noise, not an error. A rejection
151
+ * against a replica that is still attached propagates.
152
+ *
153
+ * @param op {(store: LocalStore) => Promise<T>}
154
+ * @returns {Promise<T | undefined>}
155
+ */
156
+ async whileAttached(op) {
157
+ const store = this.#localStore;
158
+ if (!store) {
159
+ return undefined;
160
+ }
161
+ const generation = this.#generation;
162
+ let result;
163
+ try {
164
+ result = await op(store);
165
+ }
166
+ catch (err) {
167
+ if (this.#generation !== generation) {
168
+ return undefined;
169
+ }
170
+ throw err;
171
+ }
172
+ return this.#generation === generation ? result : undefined;
173
+ }
174
+ /**
175
+ * Installs the connected session's delegated remote store (once background
176
+ * sync has bootstrapped it from the granted zcaps).
177
+ *
178
+ * @param store {WasRemoteStore}
179
+ * @returns {void}
180
+ */
181
+ attachRemoteStore(store) {
182
+ this.#remoteStore = store;
183
+ }
184
+ /**
185
+ * Releases the remote store (logout / sync teardown).
186
+ *
187
+ * @returns {void}
188
+ */
189
+ detachRemoteStore() {
190
+ this.#remoteStore = null;
191
+ }
192
+ /**
193
+ * Whether a connected session's remote store is available.
194
+ *
195
+ * @returns {boolean}
196
+ */
197
+ hasRemoteStore() {
198
+ return this.#remoteStore !== null;
199
+ }
200
+ /**
201
+ * The connected session's remote store, or throws while no wallet-connected
202
+ * session is active (local-only mode, or sync has not bootstrapped yet).
203
+ *
204
+ * @returns {WasRemoteStore}
205
+ */
206
+ requireRemoteStore() {
207
+ if (!this.#remoteStore) {
208
+ throw new Error('No WAS remote store is available; connect a wallet session first.');
209
+ }
210
+ return this.#remoteStore;
211
+ }
212
+ /**
213
+ * Hydrates every registered store from the attached replica.
214
+ *
215
+ * @returns {Promise<void>}
216
+ */
217
+ async hydrateAll() {
218
+ await Promise.all(Object.values(this.registry).map(entry => entry.hydrate()));
219
+ }
220
+ /**
221
+ * Empties every registered store (logout).
222
+ *
223
+ * @returns {void}
224
+ */
225
+ clearEntityStores() {
226
+ for (const entry of Object.values(this.registry)) {
227
+ entry.clear();
228
+ }
229
+ }
230
+ /**
231
+ * Patches ONE store from a single RxDB change event (per-doc, no
232
+ * whole-collection re-hydrate): decrypt the changed envelope, then upsert the
233
+ * payload (INSERT / UPDATE, including conflict-resolved rows) or drop it
234
+ * (DELETE / tombstone). The `uuid -> envelopeId` index is kept in step so a
235
+ * later local edit of a remotely-created doc still finds its envelope. Falls
236
+ * back to a debounced whole-collection re-hydrate if the envelope is missing
237
+ * or fails to decrypt.
238
+ *
239
+ * Fired floating off the RxDB change stream, so a logout/login teardown can
240
+ * detach or swap the replica while the decrypt is in flight; the decrypt runs
241
+ * under {@link StorageContext.whileAttached}, so an event that outlives its
242
+ * replica is dropped rather than patched into the next session's stores.
243
+ *
244
+ * @param collectionKey {string}
245
+ * @param event {object} an RxDB change event (operation + documentData)
246
+ * @returns {Promise<void>}
247
+ */
248
+ async patchFromChange(collectionKey, event) {
249
+ const entry = this.registry[collectionKey];
250
+ if (!entry) {
251
+ return;
252
+ }
253
+ const row = event.documentData;
254
+ const envelope = row?.data;
255
+ const deleted = event.operation === 'DELETE' || row?._deleted === true;
256
+ if (!row || envelope === undefined) {
257
+ this.scheduleRehydrate(collectionKey);
258
+ return;
259
+ }
260
+ // `null` marks a decrypt failure against a still-attached replica (fall
261
+ // back to a re-hydrate); `undefined` marks a replica gone meanwhile (drop).
262
+ const decrypted = await this.whileAttached(async (store) => {
263
+ try {
264
+ return {
265
+ store,
266
+ payload: await store.decryptEnvelope(collectionKey, envelope)
267
+ };
268
+ }
269
+ catch {
270
+ return null;
271
+ }
272
+ });
273
+ if (decrypted === undefined) {
274
+ return;
275
+ }
276
+ if (decrypted === null) {
277
+ this.scheduleRehydrate(collectionKey);
278
+ return;
279
+ }
280
+ const { store, payload } = decrypted;
281
+ if (deleted) {
282
+ // Only honor a tombstone for the envelope the entity currently lives in.
283
+ // A delete of a DIFFERENT envelope that decrypts to the same logical id
284
+ // is a stale duplicate being cleaned up (a reconciled singleton loser,
285
+ // or the pre-resurrection row of a locally re-created doc) -- dropping
286
+ // the live doc for it would undo the reconciliation/resurrection.
287
+ const mapped = store.envelopeIdFor(collectionKey, payload.id);
288
+ if (mapped !== undefined && mapped !== row.id) {
289
+ return;
290
+ }
291
+ store.forgetEnvelope(collectionKey, payload.id);
292
+ entry.drop(payload.id);
293
+ }
294
+ else {
295
+ store.rememberEnvelope(collectionKey, payload.id, row.id);
296
+ entry.upsert(payload);
297
+ }
298
+ }
299
+ /**
300
+ * Schedules a debounced re-hydrate of one collection's store after a pull.
301
+ * A no-op without an attached replica, and the timer itself is bound to the
302
+ * replica attached now: a detach cancels it, and one that still fires after
303
+ * a swap finds the generation changed and does nothing.
304
+ *
305
+ * @param collectionKey {string}
306
+ * @returns {void}
307
+ */
308
+ scheduleRehydrate(collectionKey) {
309
+ const entry = this.registry[collectionKey];
310
+ if (!entry || !this.#localStore) {
311
+ return;
312
+ }
313
+ const generation = this.#generation;
314
+ const existing = this.#rehydrateTimers.get(collectionKey);
315
+ if (existing) {
316
+ clearTimeout(existing);
317
+ }
318
+ this.#rehydrateTimers.set(collectionKey, setTimeout(() => {
319
+ this.#rehydrateTimers.delete(collectionKey);
320
+ if (this.#generation !== generation) {
321
+ return;
322
+ }
323
+ void entry.hydrate();
324
+ }, REHYDRATE_DEBOUNCE_MS));
325
+ }
326
+ }
327
+ //# 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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"}