@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.
- package/README.md +4 -3
- package/dist/auth/verifyResponse.d.ts.map +1 -1
- package/dist/auth/verifyResponse.js +14 -7
- package/dist/auth/verifyResponse.js.map +1 -1
- package/dist/identity/agents.d.ts +5 -0
- package/dist/identity/agents.d.ts.map +1 -1
- package/dist/identity/agents.js +10 -3
- package/dist/identity/agents.js.map +1 -1
- package/dist/identity/seedCredential.d.ts +5 -1
- package/dist/identity/seedCredential.d.ts.map +1 -1
- package/dist/identity/seedCredential.js +15 -28
- package/dist/identity/seedCredential.js.map +1 -1
- package/dist/index.d.ts +5 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -8
- package/dist/index.js.map +1 -1
- package/dist/mui/ClearDataDialog.d.ts.map +1 -1
- package/dist/mui/ClearDataDialog.js +14 -12
- package/dist/mui/ClearDataDialog.js.map +1 -1
- package/dist/mui/LogoutDialog.d.ts.map +1 -1
- package/dist/mui/LogoutDialog.js +12 -8
- package/dist/mui/LogoutDialog.js.map +1 -1
- package/dist/react/hooks.d.ts +11 -7
- package/dist/react/hooks.d.ts.map +1 -1
- package/dist/react/hooks.js +14 -8
- package/dist/react/hooks.js.map +1 -1
- package/dist/session/authStore.d.ts +30 -9
- package/dist/session/authStore.d.ts.map +1 -1
- package/dist/session/authStore.js +147 -48
- package/dist/session/authStore.js.map +1 -1
- package/dist/session/index.d.ts +1 -0
- package/dist/session/index.d.ts.map +1 -1
- package/dist/session/index.js +1 -0
- package/dist/session/index.js.map +1 -1
- package/dist/session/localWipe.d.ts +144 -0
- package/dist/session/localWipe.d.ts.map +1 -0
- package/dist/session/localWipe.js +190 -0
- package/dist/session/localWipe.js.map +1 -0
- package/dist/storage/adopt.d.ts +4 -1
- package/dist/storage/adopt.d.ts.map +1 -1
- package/dist/storage/adopt.js +4 -3
- package/dist/storage/adopt.js.map +1 -1
- package/dist/storage/entityStore.d.ts.map +1 -1
- package/dist/storage/entityStore.js +3 -0
- package/dist/storage/entityStore.js.map +1 -1
- package/dist/storage/publicUrl.js +3 -3
- package/dist/storage/storageContext.d.ts +175 -0
- package/dist/storage/storageContext.d.ts.map +1 -0
- package/dist/storage/storageContext.js +327 -0
- package/dist/storage/storageContext.js.map +1 -0
- package/dist/storage/storageManager.d.ts +57 -60
- package/dist/storage/storageManager.d.ts.map +1 -1
- package/dist/storage/storageManager.js +55 -125
- package/dist/storage/storageManager.js.map +1 -1
- package/dist/storage/syncController.d.ts +10 -1
- package/dist/storage/syncController.d.ts.map +1 -1
- package/dist/storage/syncController.js +12 -4
- package/dist/storage/syncController.js.map +1 -1
- package/dist/storage/syncStatusStore.d.ts +23 -2
- package/dist/storage/syncStatusStore.d.ts.map +1 -1
- package/dist/storage/syncStatusStore.js +22 -13
- package/dist/storage/syncStatusStore.js.map +1 -1
- package/dist/storage/writerId.d.ts +36 -0
- package/dist/storage/writerId.d.ts.map +1 -0
- package/dist/storage/writerId.js +76 -0
- package/dist/storage/writerId.js.map +1 -0
- package/package.json +13 -13
- package/dist/storage/rehydrate.d.ts +0 -64
- package/dist/storage/rehydrate.d.ts.map +0 -1
- package/dist/storage/rehydrate.js +0 -132
- 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
|
-
*
|
|
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
|
|
32
|
+
* @param context {StorageContext}
|
|
8
33
|
* @returns {void}
|
|
9
34
|
*/
|
|
10
|
-
export declare function
|
|
35
|
+
export declare function activateStorageContext(context: StorageContext): void;
|
|
11
36
|
/**
|
|
12
|
-
*
|
|
37
|
+
* Releases the active pointer if `context` holds it (a torn-down session); a
|
|
38
|
+
* no-op for any other context.
|
|
13
39
|
*
|
|
14
|
-
* @
|
|
40
|
+
* @param context {StorageContext}
|
|
41
|
+
* @returns {void}
|
|
15
42
|
*/
|
|
16
|
-
export declare function
|
|
43
|
+
export declare function deactivateStorageContext(context: StorageContext): void;
|
|
17
44
|
/**
|
|
18
|
-
* Whether
|
|
45
|
+
* Whether a storage context is active.
|
|
19
46
|
*
|
|
20
47
|
* @returns {boolean}
|
|
21
48
|
*/
|
|
22
|
-
export declare function
|
|
49
|
+
export declare function hasStorageContext(): boolean;
|
|
23
50
|
/**
|
|
24
|
-
*
|
|
51
|
+
* The active storage context, or throws if no session store has been created.
|
|
25
52
|
*
|
|
26
|
-
* @returns {
|
|
53
|
+
* @returns {StorageContext}
|
|
27
54
|
*/
|
|
28
|
-
export declare function
|
|
55
|
+
export declare function requireStorageContext(): StorageContext;
|
|
29
56
|
/**
|
|
30
|
-
*
|
|
31
|
-
* has bootstrapped it from the granted zcaps).
|
|
57
|
+
* The active session's opened replica, or throws if none is open.
|
|
32
58
|
*
|
|
33
|
-
* @
|
|
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
|
|
61
|
+
export declare function requireStore(): LocalStore;
|
|
44
62
|
/**
|
|
45
|
-
* Whether
|
|
63
|
+
* Whether the active session has a replica open.
|
|
46
64
|
*
|
|
47
65
|
* @returns {boolean}
|
|
48
66
|
*/
|
|
49
|
-
export declare function
|
|
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
|
-
*
|
|
58
|
-
*
|
|
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
|
-
*
|
|
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
|
|
73
|
-
storageKeyPrefix?: string;
|
|
74
|
-
}): string;
|
|
74
|
+
export declare function requireRemoteStore(): WasRemoteStore;
|
|
75
75
|
/**
|
|
76
|
-
*
|
|
77
|
-
* under the app's configured `storageKeyPrefix`, before any replica opens).
|
|
76
|
+
* Whether the active session has a remote store available.
|
|
78
77
|
*
|
|
79
|
-
* @
|
|
80
|
-
* @returns {void}
|
|
78
|
+
* @returns {boolean}
|
|
81
79
|
*/
|
|
82
|
-
export declare function
|
|
80
|
+
export declare function hasRemoteStore(): boolean;
|
|
83
81
|
/**
|
|
84
|
-
* The session's
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
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
|
|
94
|
-
*
|
|
95
|
-
*
|
|
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":"
|
|
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"}
|