@interop/was-react 0.18.1 → 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 (71) hide show
  1. package/README.md +4 -3
  2. package/dist/auth/verifyResponse.d.ts.map +1 -1
  3. package/dist/auth/verifyResponse.js +14 -7
  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 +5 -4
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +10 -8
  16. package/dist/index.js.map +1 -1
  17. package/dist/mui/ClearDataDialog.d.ts.map +1 -1
  18. package/dist/mui/ClearDataDialog.js +14 -12
  19. package/dist/mui/ClearDataDialog.js.map +1 -1
  20. package/dist/mui/LogoutDialog.d.ts.map +1 -1
  21. package/dist/mui/LogoutDialog.js +12 -8
  22. package/dist/mui/LogoutDialog.js.map +1 -1
  23. package/dist/react/hooks.d.ts +11 -7
  24. package/dist/react/hooks.d.ts.map +1 -1
  25. package/dist/react/hooks.js +14 -8
  26. package/dist/react/hooks.js.map +1 -1
  27. package/dist/session/authStore.d.ts +30 -9
  28. package/dist/session/authStore.d.ts.map +1 -1
  29. package/dist/session/authStore.js +147 -48
  30. package/dist/session/authStore.js.map +1 -1
  31. package/dist/session/index.d.ts +1 -0
  32. package/dist/session/index.d.ts.map +1 -1
  33. package/dist/session/index.js +1 -0
  34. package/dist/session/index.js.map +1 -1
  35. package/dist/session/localWipe.d.ts +144 -0
  36. package/dist/session/localWipe.d.ts.map +1 -0
  37. package/dist/session/localWipe.js +190 -0
  38. package/dist/session/localWipe.js.map +1 -0
  39. package/dist/storage/adopt.d.ts +4 -1
  40. package/dist/storage/adopt.d.ts.map +1 -1
  41. package/dist/storage/adopt.js +4 -3
  42. package/dist/storage/adopt.js.map +1 -1
  43. package/dist/storage/entityStore.d.ts.map +1 -1
  44. package/dist/storage/entityStore.js +3 -0
  45. package/dist/storage/entityStore.js.map +1 -1
  46. package/dist/storage/publicUrl.js +3 -3
  47. package/dist/storage/storageContext.d.ts +175 -0
  48. package/dist/storage/storageContext.d.ts.map +1 -0
  49. package/dist/storage/storageContext.js +327 -0
  50. package/dist/storage/storageContext.js.map +1 -0
  51. package/dist/storage/storageManager.d.ts +57 -60
  52. package/dist/storage/storageManager.d.ts.map +1 -1
  53. package/dist/storage/storageManager.js +55 -125
  54. package/dist/storage/storageManager.js.map +1 -1
  55. package/dist/storage/syncController.d.ts +10 -1
  56. package/dist/storage/syncController.d.ts.map +1 -1
  57. package/dist/storage/syncController.js +12 -4
  58. package/dist/storage/syncController.js.map +1 -1
  59. package/dist/storage/syncStatusStore.d.ts +23 -2
  60. package/dist/storage/syncStatusStore.d.ts.map +1 -1
  61. package/dist/storage/syncStatusStore.js +22 -13
  62. package/dist/storage/syncStatusStore.js.map +1 -1
  63. package/dist/storage/writerId.d.ts +36 -0
  64. package/dist/storage/writerId.d.ts.map +1 -0
  65. package/dist/storage/writerId.js +76 -0
  66. package/dist/storage/writerId.js.map +1 -0
  67. package/package.json +13 -13
  68. package/dist/storage/rehydrate.d.ts +0 -64
  69. package/dist/storage/rehydrate.d.ts.map +0 -1
  70. package/dist/storage/rehydrate.js +0 -132
  71. package/dist/storage/rehydrate.js.map +0 -1
@@ -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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"}
@@ -1,99 +1,96 @@
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 (a torn-down session); a
38
+ * no-op for any other context.
13
39
  *
14
- * @returns {LocalStore}
40
+ * @param context {StorageContext}
41
+ * @returns {void}
15
42
  */
16
- export declare function requireStore(): LocalStore;
43
+ export declare function deactivateStorageContext(context: StorageContext): void;
17
44
  /**
18
- * Whether the store has been opened.
45
+ * Whether a storage context is active.
19
46
  *
20
47
  * @returns {boolean}
21
48
  */
22
- export declare function hasStore(): boolean;
49
+ export declare function hasStorageContext(): boolean;
23
50
  /**
24
- * Releases the held store reference (logout; the caller closes the db).
51
+ * The active storage context, or throws if no session store has been created.
25
52
  *
26
- * @returns {void}
53
+ * @returns {StorageContext}
27
54
  */
28
- export declare function clearLocalStore(): void;
55
+ export declare function requireStorageContext(): StorageContext;
29
56
  /**
30
- * Installs the per-session delegated remote store (set once background sync
31
- * has bootstrapped it from the granted zcaps).
57
+ * The active session's opened replica, or throws if none is open.
32
58
  *
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}
59
+ * @returns {LocalStore}
42
60
  */
43
- export declare function requireRemoteStore(): WasRemoteStore;
61
+ export declare function requireStore(): LocalStore;
44
62
  /**
45
- * Whether a connected session's remote store is available.
63
+ * Whether the active session has a replica open.
46
64
  *
47
65
  * @returns {boolean}
48
66
  */
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;
67
+ export declare function hasStore(): boolean;
56
68
  /**
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.
69
+ * The active session's remote store, or throws while no wallet-connected
70
+ * session is active.
61
71
  *
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.
66
- *
67
- * @param [options] {object}
68
- * @param [options.storageKeyPrefix] {string} the localStorage key prefix
69
- * (defaults to {@link DEFAULT_STORAGE_KEY_PREFIX})
70
- * @returns {string}
72
+ * @returns {WasRemoteStore}
71
73
  */
72
- export declare function getWriterId({ storageKeyPrefix }?: {
73
- storageKeyPrefix?: string;
74
- }): string;
74
+ export declare function requireRemoteStore(): WasRemoteStore;
75
75
  /**
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).
76
+ * Whether the active session has a remote store available.
78
77
  *
79
- * @param id {string}
80
- * @returns {void}
78
+ * @returns {boolean}
81
79
  */
82
- export declare function setWriterId(id: string): void;
80
+ export declare function hasRemoteStore(): boolean;
83
81
  /**
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.
82
+ * The active session's writer id, or throws if no session store exists yet.
83
+ * Deliberately never falls back to `getWriterId`: that would resolve under the
84
+ * DEFAULT key prefix, so an app with a custom `storageKeyPrefix` would silently
85
+ * stamp a second writer id.
88
86
  *
89
87
  * @returns {string}
90
88
  */
91
89
  export declare function requireWriterId(): string;
92
90
  /**
93
- * Stamps a payload with fresh last-write-wins fields: the current instant as
94
- * `updatedAt` and the session's resolved writer id. Any values the caller
95
- * supplied are overwritten -- a stamp must describe THIS write, or a hydrated
96
- * doc's older `updatedAt` would ride a later edit and lose the conflict.
91
+ * Stamps a payload with fresh last-write-wins fields under the active session
92
+ * (see {@link StorageContext.stampLww}); the entity-store write verbs call it
93
+ * on every write, and an app writing through `LocalStore` directly does too.
97
94
  *
98
95
  * @param payload {object}
99
96
  * @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":"AAkBA,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;;;;;;;;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;;;;;;GAMG;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"}