@interop/was-react 0.1.2 → 0.1.4
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 +9 -1
- package/dist/auth/chapi.d.ts +3 -1
- package/dist/auth/chapi.d.ts.map +1 -1
- package/dist/auth/chapi.js +6 -2
- package/dist/auth/chapi.js.map +1 -1
- package/dist/auth/loginFlow.d.ts +30 -10
- package/dist/auth/loginFlow.d.ts.map +1 -1
- package/dist/auth/loginFlow.js +3 -1
- package/dist/auth/loginFlow.js.map +1 -1
- package/dist/auth/loginRequest.d.ts +9 -3
- package/dist/auth/loginRequest.d.ts.map +1 -1
- package/dist/auth/loginRequest.js +9 -3
- package/dist/auth/loginRequest.js.map +1 -1
- package/dist/auth/verifyResponse.d.ts +9 -3
- package/dist/auth/verifyResponse.d.ts.map +1 -1
- package/dist/auth/verifyResponse.js +6 -2
- package/dist/auth/verifyResponse.js.map +1 -1
- package/dist/config.d.ts +60 -20
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +12 -4
- package/dist/config.js.map +1 -1
- package/dist/dev/cli.d.ts.map +1 -1
- package/dist/dev/cli.js +4 -7
- package/dist/dev/cli.js.map +1 -1
- package/dist/dev/provisionDevGrants.d.ts +15 -5
- package/dist/dev/provisionDevGrants.d.ts.map +1 -1
- package/dist/dev/provisionDevGrants.js +2 -2
- package/dist/dev/provisionDevGrants.js.map +1 -1
- package/dist/grants.d.ts +7 -19
- package/dist/grants.d.ts.map +1 -1
- package/dist/grants.js +28 -7
- package/dist/grants.js.map +1 -1
- package/dist/identity/appSession.d.ts +9 -3
- package/dist/identity/appSession.d.ts.map +1 -1
- package/dist/identity/appSession.js +3 -1
- package/dist/identity/appSession.js.map +1 -1
- package/dist/identity/documentLoader.d.ts +3 -1
- package/dist/identity/documentLoader.d.ts.map +1 -1
- package/dist/identity/documentLoader.js +6 -2
- package/dist/identity/documentLoader.js.map +1 -1
- package/dist/identity/seedCredential.d.ts +9 -3
- package/dist/identity/seedCredential.d.ts.map +1 -1
- package/dist/identity/seedCredential.js +9 -15
- package/dist/identity/seedCredential.js.map +1 -1
- package/dist/identity/seedStore.d.ts +18 -6
- package/dist/identity/seedStore.d.ts.map +1 -1
- package/dist/identity/seedStore.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/react/WasSessionProvider.d.ts.map +1 -1
- package/dist/react/WasSessionProvider.js +10 -1
- package/dist/react/WasSessionProvider.js.map +1 -1
- package/dist/react/hooks.d.ts +10 -5
- package/dist/react/hooks.d.ts.map +1 -1
- package/dist/react/hooks.js +6 -30
- package/dist/react/hooks.js.map +1 -1
- package/dist/session/authStore.d.ts +29 -7
- package/dist/session/authStore.d.ts.map +1 -1
- package/dist/session/authStore.js +125 -64
- package/dist/session/authStore.js.map +1 -1
- package/dist/storage/entityStore.d.ts +32 -9
- package/dist/storage/entityStore.d.ts.map +1 -1
- package/dist/storage/entityStore.js +88 -48
- package/dist/storage/entityStore.js.map +1 -1
- package/dist/storage/localStore.d.ts +53 -0
- package/dist/storage/localStore.d.ts.map +1 -1
- package/dist/storage/localStore.js +140 -12
- package/dist/storage/localStore.js.map +1 -1
- package/dist/storage/rehydrate.d.ts +8 -0
- package/dist/storage/rehydrate.d.ts.map +1 -1
- package/dist/storage/rehydrate.js +31 -2
- package/dist/storage/rehydrate.js.map +1 -1
- package/dist/storage/syncController.d.ts +7 -3
- package/dist/storage/syncController.d.ts.map +1 -1
- package/dist/storage/syncController.js +28 -15
- package/dist/storage/syncController.js.map +1 -1
- package/dist/storage/syncStatusStore.d.ts +26 -1
- package/dist/storage/syncStatusStore.d.ts.map +1 -1
- package/dist/storage/syncStatusStore.js +40 -0
- package/dist/storage/syncStatusStore.js.map +1 -1
- package/dist/storage/wasRemoteStore.d.ts +3 -1
- package/dist/storage/wasRemoteStore.d.ts.map +1 -1
- package/dist/storage/wasRemoteStore.js +3 -3
- package/dist/storage/wasRemoteStore.js.map +1 -1
- package/dist/sync/feedMasterPort.d.ts +21 -19
- package/dist/sync/feedMasterPort.d.ts.map +1 -1
- package/dist/sync/feedMasterPort.js +18 -5
- package/dist/sync/feedMasterPort.js.map +1 -1
- package/dist/sync/index.d.ts +3 -3
- package/dist/sync/index.d.ts.map +1 -1
- package/dist/sync/index.js +2 -2
- package/dist/sync/index.js.map +1 -1
- package/dist/sync/lwwConflictHandler.d.ts +35 -2
- package/dist/sync/lwwConflictHandler.d.ts.map +1 -1
- package/dist/sync/lwwConflictHandler.js +47 -9
- package/dist/sync/lwwConflictHandler.js.map +1 -1
- package/dist/sync/pushWrites.d.ts +27 -8
- package/dist/sync/pushWrites.d.ts.map +1 -1
- package/dist/sync/pushWrites.js +43 -15
- package/dist/sync/pushWrites.js.map +1 -1
- package/dist/sync/types.d.ts +49 -16
- package/dist/sync/types.d.ts.map +1 -1
- package/dist/sync/types.js +17 -0
- package/dist/sync/types.js.map +1 -1
- package/dist/sync/wasReplication.d.ts.map +1 -1
- package/dist/sync/wasReplication.js +38 -1
- package/dist/sync/wasReplication.js.map +1 -1
- package/dist/sync/wasSyncPort.d.ts +29 -6
- package/dist/sync/wasSyncPort.d.ts.map +1 -1
- package/dist/sync/wasSyncPort.js +105 -101
- package/dist/sync/wasSyncPort.js.map +1 -1
- package/package.json +4 -3
- package/dist/auth/verifyResponse.test.d.ts +0 -2
- package/dist/auth/verifyResponse.test.d.ts.map +0 -1
- package/dist/auth/verifyResponse.test.js +0 -282
- package/dist/auth/verifyResponse.test.js.map +0 -1
- package/dist/dev/cli.test.d.ts +0 -2
- package/dist/dev/cli.test.d.ts.map +0 -1
- package/dist/dev/cli.test.js +0 -39
- package/dist/dev/cli.test.js.map +0 -1
- package/dist/grants.test.d.ts +0 -2
- package/dist/grants.test.d.ts.map +0 -1
- package/dist/grants.test.js +0 -79
- package/dist/grants.test.js.map +0 -1
- package/dist/identity/seedCredential.test.d.ts +0 -2
- package/dist/identity/seedCredential.test.d.ts.map +0 -1
- package/dist/identity/seedCredential.test.js +0 -205
- package/dist/identity/seedCredential.test.js.map +0 -1
- package/dist/storage/rehydrate.test.d.ts +0 -2
- package/dist/storage/rehydrate.test.d.ts.map +0 -1
- package/dist/storage/rehydrate.test.js +0 -172
- package/dist/storage/rehydrate.test.js.map +0 -1
- package/dist/sync/changesQuery.test.d.ts +0 -2
- package/dist/sync/changesQuery.test.d.ts.map +0 -1
- package/dist/sync/changesQuery.test.js +0 -145
- package/dist/sync/changesQuery.test.js.map +0 -1
- package/dist/sync/lww.test.d.ts +0 -2
- package/dist/sync/lww.test.d.ts.map +0 -1
- package/dist/sync/lww.test.js +0 -28
- package/dist/sync/lww.test.js.map +0 -1
- package/dist/sync/lwwConflictHandler.test.d.ts +0 -2
- package/dist/sync/lwwConflictHandler.test.d.ts.map +0 -1
- package/dist/sync/lwwConflictHandler.test.js +0 -78
- package/dist/sync/lwwConflictHandler.test.js.map +0 -1
- package/dist/sync/pushWrites.test.d.ts +0 -2
- package/dist/sync/pushWrites.test.d.ts.map +0 -1
- package/dist/sync/pushWrites.test.js +0 -287
- package/dist/sync/pushWrites.test.js.map +0 -1
|
@@ -26,6 +26,19 @@ import { type Json, type SyncedDoc } from '../sync/index.js';
|
|
|
26
26
|
type EntityPayload = {
|
|
27
27
|
id: string;
|
|
28
28
|
};
|
|
29
|
+
/**
|
|
30
|
+
* A stable, RxDB-safe database name per controller DID (FNV-1a hex), so two
|
|
31
|
+
* wallet users on one browser never collide on the same local database.
|
|
32
|
+
*
|
|
33
|
+
* @param options {object}
|
|
34
|
+
* @param options.dbName {string} the app's base database name
|
|
35
|
+
* @param options.controllerDid {string} the session controller DID
|
|
36
|
+
* @returns {string}
|
|
37
|
+
*/
|
|
38
|
+
export declare function dbNameForController({ dbName, controllerDid }: {
|
|
39
|
+
dbName: string;
|
|
40
|
+
controllerDid: string;
|
|
41
|
+
}): string;
|
|
29
42
|
/**
|
|
30
43
|
* The local encrypted store. Construct via {@link LocalStore.init}, which
|
|
31
44
|
* derives the per-collection ciphers from the master seed and opens RxDB.
|
|
@@ -80,6 +93,17 @@ export declare class LocalStore {
|
|
|
80
93
|
* @returns {Promise<void>}
|
|
81
94
|
*/
|
|
82
95
|
updateEntity(key: string, payload: EntityPayload): Promise<void>;
|
|
96
|
+
/**
|
|
97
|
+
* Inserts the entity if the collection has no row for its uuid yet, otherwise
|
|
98
|
+
* re-encrypts it in place. The hydration index is the source of truth for
|
|
99
|
+
* existence, so callers (e.g. an app's singleton collection) need not track
|
|
100
|
+
* an insert-vs-update flag of their own.
|
|
101
|
+
*
|
|
102
|
+
* @param key {string}
|
|
103
|
+
* @param payload {EntityPayload}
|
|
104
|
+
* @returns {Promise<void>}
|
|
105
|
+
*/
|
|
106
|
+
upsertEntity(key: string, payload: EntityPayload): Promise<void>;
|
|
83
107
|
/**
|
|
84
108
|
* Tombstones the entity's row (RxDB soft delete) so the deletion replicates.
|
|
85
109
|
*
|
|
@@ -96,6 +120,24 @@ export declare class LocalStore {
|
|
|
96
120
|
* @returns {Promise<T[]>}
|
|
97
121
|
*/
|
|
98
122
|
listEntities<T extends EntityPayload>(key: string): Promise<T[]>;
|
|
123
|
+
/**
|
|
124
|
+
* Hydrates a singleton collection (at most one logical entity, e.g. an app's
|
|
125
|
+
* current-selection doc) and reconciles any duplicates. Two devices that each
|
|
126
|
+
* created the singleton before syncing produce distinct envelope rows that all
|
|
127
|
+
* decrypt to the same logical id; because LWW conflict resolution is
|
|
128
|
+
* per-envelope-id, those duplicates never reconcile on their own. This keeps
|
|
129
|
+
* the last-writer-wins winner (payload `updatedAt`, `deviceId` tiebreak) and
|
|
130
|
+
* tombstones the losers so the deletion replicates and the space converges on
|
|
131
|
+
* one row. Returns the winning payload, or `null` when the collection is empty.
|
|
132
|
+
*
|
|
133
|
+
* @param key {string}
|
|
134
|
+
* @returns {Promise<T | null>}
|
|
135
|
+
*/
|
|
136
|
+
hydrateSingleton<T extends {
|
|
137
|
+
id: string;
|
|
138
|
+
updatedAt: string;
|
|
139
|
+
deviceId: string;
|
|
140
|
+
}>(key: string): Promise<T | null>;
|
|
99
141
|
/**
|
|
100
142
|
* Decrypts a single EDV envelope into its plaintext payload, for per-doc
|
|
101
143
|
* reactive patching of a pulled remote change (without a whole-collection
|
|
@@ -125,6 +167,17 @@ export declare class LocalStore {
|
|
|
125
167
|
* @returns {void}
|
|
126
168
|
*/
|
|
127
169
|
forgetEnvelope(key: string, uuid: string): void;
|
|
170
|
+
/**
|
|
171
|
+
* The envelope id the hydration index currently maps a logical uuid to, or
|
|
172
|
+
* `undefined` when unknown (not yet hydrated, or no such entity). Lets the
|
|
173
|
+
* sync patch path tell a tombstone for the LIVE envelope apart from one for a
|
|
174
|
+
* stale duplicate (a reconciled singleton loser or a pre-resurrection row).
|
|
175
|
+
*
|
|
176
|
+
* @param key {string}
|
|
177
|
+
* @param uuid {string}
|
|
178
|
+
* @returns {string | undefined}
|
|
179
|
+
*/
|
|
180
|
+
envelopeIdFor(key: string, uuid: string): string | undefined;
|
|
128
181
|
/**
|
|
129
182
|
* The live RxDB collection handle, for reactive subscriptions and the sync
|
|
130
183
|
* controller.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"localStore.d.ts","sourceRoot":"","sources":["../../src/storage/localStore.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,EAEL,KAAK,YAAY,EAEjB,KAAK,SAAS,EACf,MAAM,mBAAmB,CAAA;AAE1B,OAAO,EAAmB,KAAK,mBAAmB,EAAE,MAAM,cAAc,CAAA;AACxE,OAAO,
|
|
1
|
+
{"version":3,"file":"localStore.d.ts","sourceRoot":"","sources":["../../src/storage/localStore.ts"],"names":[],"mappings":"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"}
|
|
@@ -23,8 +23,25 @@
|
|
|
23
23
|
import { createRxDatabase } from 'rxdb/plugins/core';
|
|
24
24
|
import { getRxStorageDexie } from 'rxdb/plugins/storage-dexie';
|
|
25
25
|
import { DEFAULT_DB_NAME } from '../config.js';
|
|
26
|
-
import { syncedDocSchema, createDocCipher, makeLwwConflictHandler } from '../sync/index.js';
|
|
26
|
+
import { syncedDocSchema, createDocCipher, makeLwwConflictHandler, remotePayloadWins } from '../sync/index.js';
|
|
27
27
|
import { deriveCollectionKeys } from '../identity/agents.js';
|
|
28
|
+
/**
|
|
29
|
+
* A stable, RxDB-safe database name per controller DID (FNV-1a hex), so two
|
|
30
|
+
* wallet users on one browser never collide on the same local database.
|
|
31
|
+
*
|
|
32
|
+
* @param options {object}
|
|
33
|
+
* @param options.dbName {string} the app's base database name
|
|
34
|
+
* @param options.controllerDid {string} the session controller DID
|
|
35
|
+
* @returns {string}
|
|
36
|
+
*/
|
|
37
|
+
export function dbNameForController({ dbName, controllerDid }) {
|
|
38
|
+
let hash = 0x811c9dc5;
|
|
39
|
+
for (let index = 0; index < controllerDid.length; index++) {
|
|
40
|
+
hash ^= controllerDid.charCodeAt(index);
|
|
41
|
+
hash = Math.imul(hash, 0x01000193) >>> 0;
|
|
42
|
+
}
|
|
43
|
+
return `${dbName}-${hash.toString(16).padStart(8, '0')}`;
|
|
44
|
+
}
|
|
28
45
|
/**
|
|
29
46
|
* The local encrypted store. Construct via {@link LocalStore.init}, which
|
|
30
47
|
* derives the per-collection ciphers from the master seed and opens RxDB.
|
|
@@ -121,15 +138,22 @@ export class LocalStore {
|
|
|
121
138
|
const index = new Map();
|
|
122
139
|
const cipher = this._cipher(key);
|
|
123
140
|
const docs = await this._collection(key).find().exec();
|
|
124
|
-
|
|
141
|
+
// Decrypt rows concurrently: index building has no ordering dependency, so
|
|
142
|
+
// serializing the per-row WebCrypto work would only stall the unlock path.
|
|
143
|
+
const entries = await Promise.all(docs.map(async (doc) => {
|
|
125
144
|
const { id: envelopeId, data } = doc.toMutableJSON();
|
|
126
145
|
if (data === undefined) {
|
|
127
|
-
|
|
146
|
+
return null;
|
|
128
147
|
}
|
|
129
148
|
const payload = (await cipher.decrypt({
|
|
130
149
|
envelope: data
|
|
131
150
|
}));
|
|
132
|
-
|
|
151
|
+
return { envelopeId, uuid: payload.id };
|
|
152
|
+
}));
|
|
153
|
+
for (const entry of entries) {
|
|
154
|
+
if (entry !== null) {
|
|
155
|
+
index.set(entry.uuid, entry.envelopeId);
|
|
156
|
+
}
|
|
133
157
|
}
|
|
134
158
|
this._index[key] = index;
|
|
135
159
|
return index;
|
|
@@ -167,16 +191,27 @@ export class LocalStore {
|
|
|
167
191
|
async updateEntity(key, payload) {
|
|
168
192
|
const index = await this._ensureIndex(key);
|
|
169
193
|
const envelopeId = index.get(payload.id);
|
|
194
|
+
// The entity's envelope may be gone -- another device deleted it and the
|
|
195
|
+
// tombstone was pulled (which forgets the index entry), or its row was
|
|
196
|
+
// otherwise removed. Rather than throwing (which loses the edit), resurrect
|
|
197
|
+
// the entity as a fresh create. This matches the mutable-head LWW rule the
|
|
198
|
+
// conflict handler already applies: a live local edit beats a remote
|
|
199
|
+
// tombstone, so re-asserting the payload under a new envelope is correct.
|
|
170
200
|
if (!envelopeId) {
|
|
171
|
-
|
|
201
|
+
await this.insertEntity(key, payload);
|
|
202
|
+
return;
|
|
172
203
|
}
|
|
173
204
|
const doc = await this._collection(key).findOne(envelopeId).exec();
|
|
174
205
|
if (!doc) {
|
|
175
|
-
|
|
206
|
+
index.delete(payload.id);
|
|
207
|
+
await this.insertEntity(key, payload);
|
|
208
|
+
return;
|
|
176
209
|
}
|
|
177
210
|
const current = doc.toMutableJSON().data;
|
|
178
211
|
if (current === undefined) {
|
|
179
|
-
|
|
212
|
+
index.delete(payload.id);
|
|
213
|
+
await this.insertEntity(key, payload);
|
|
214
|
+
return;
|
|
180
215
|
}
|
|
181
216
|
const cipher = this._cipher(key);
|
|
182
217
|
const { envelope } = await cipher.encryptUpdate({
|
|
@@ -189,6 +224,25 @@ export class LocalStore {
|
|
|
189
224
|
updatedAt: new Date().toISOString()
|
|
190
225
|
});
|
|
191
226
|
}
|
|
227
|
+
/**
|
|
228
|
+
* Inserts the entity if the collection has no row for its uuid yet, otherwise
|
|
229
|
+
* re-encrypts it in place. The hydration index is the source of truth for
|
|
230
|
+
* existence, so callers (e.g. an app's singleton collection) need not track
|
|
231
|
+
* an insert-vs-update flag of their own.
|
|
232
|
+
*
|
|
233
|
+
* @param key {string}
|
|
234
|
+
* @param payload {EntityPayload}
|
|
235
|
+
* @returns {Promise<void>}
|
|
236
|
+
*/
|
|
237
|
+
async upsertEntity(key, payload) {
|
|
238
|
+
const index = await this._ensureIndex(key);
|
|
239
|
+
if (index.has(payload.id)) {
|
|
240
|
+
await this.updateEntity(key, payload);
|
|
241
|
+
}
|
|
242
|
+
else {
|
|
243
|
+
await this.insertEntity(key, payload);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
192
246
|
/**
|
|
193
247
|
* Tombstones the entity's row (RxDB soft delete) so the deletion replicates.
|
|
194
248
|
*
|
|
@@ -219,19 +273,80 @@ export class LocalStore {
|
|
|
219
273
|
const cipher = this._cipher(key);
|
|
220
274
|
const index = new Map();
|
|
221
275
|
const docs = await this._collection(key).find().exec();
|
|
222
|
-
|
|
223
|
-
|
|
276
|
+
// Decrypt every row concurrently (the unlock hot path): the store is keyed
|
|
277
|
+
// by logical uuid, so payload order does not matter and serializing the
|
|
278
|
+
// per-row WebCrypto work would only add latency.
|
|
279
|
+
const decoded = await Promise.all(docs.map(async (doc) => {
|
|
224
280
|
const { id: envelopeId, data } = doc.toMutableJSON();
|
|
225
281
|
if (data === undefined) {
|
|
226
|
-
|
|
282
|
+
return null;
|
|
227
283
|
}
|
|
228
284
|
const payload = (await cipher.decrypt({ envelope: data }));
|
|
229
|
-
|
|
230
|
-
|
|
285
|
+
return { envelopeId, payload };
|
|
286
|
+
}));
|
|
287
|
+
const payloads = [];
|
|
288
|
+
for (const entry of decoded) {
|
|
289
|
+
if (entry === null) {
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
index.set(entry.payload.id, entry.envelopeId);
|
|
293
|
+
payloads.push(entry.payload);
|
|
231
294
|
}
|
|
232
295
|
this._index[key] = index;
|
|
233
296
|
return payloads;
|
|
234
297
|
}
|
|
298
|
+
/**
|
|
299
|
+
* Hydrates a singleton collection (at most one logical entity, e.g. an app's
|
|
300
|
+
* current-selection doc) and reconciles any duplicates. Two devices that each
|
|
301
|
+
* created the singleton before syncing produce distinct envelope rows that all
|
|
302
|
+
* decrypt to the same logical id; because LWW conflict resolution is
|
|
303
|
+
* per-envelope-id, those duplicates never reconcile on their own. This keeps
|
|
304
|
+
* the last-writer-wins winner (payload `updatedAt`, `deviceId` tiebreak) and
|
|
305
|
+
* tombstones the losers so the deletion replicates and the space converges on
|
|
306
|
+
* one row. Returns the winning payload, or `null` when the collection is empty.
|
|
307
|
+
*
|
|
308
|
+
* @param key {string}
|
|
309
|
+
* @returns {Promise<T | null>}
|
|
310
|
+
*/
|
|
311
|
+
async hydrateSingleton(key) {
|
|
312
|
+
const cipher = this._cipher(key);
|
|
313
|
+
const collection = this._collection(key);
|
|
314
|
+
const rows = await collection.find().exec();
|
|
315
|
+
const decoded = [];
|
|
316
|
+
for (const row of rows) {
|
|
317
|
+
const { id: envelopeId, data } = row.toMutableJSON();
|
|
318
|
+
if (data === undefined) {
|
|
319
|
+
continue;
|
|
320
|
+
}
|
|
321
|
+
const payload = (await cipher.decrypt({ envelope: data }));
|
|
322
|
+
decoded.push({ envelopeId, payload });
|
|
323
|
+
}
|
|
324
|
+
const index = new Map();
|
|
325
|
+
this._index[key] = index;
|
|
326
|
+
if (decoded.length === 0) {
|
|
327
|
+
return null;
|
|
328
|
+
}
|
|
329
|
+
let winner = decoded[0];
|
|
330
|
+
for (const entry of decoded) {
|
|
331
|
+
if (entry === winner) {
|
|
332
|
+
continue;
|
|
333
|
+
}
|
|
334
|
+
if (remotePayloadWins(entry.payload, winner.payload)) {
|
|
335
|
+
winner = entry;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
for (const entry of decoded) {
|
|
339
|
+
if (entry.envelopeId === winner.envelopeId) {
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
const doc = await collection.findOne(entry.envelopeId).exec();
|
|
343
|
+
if (doc) {
|
|
344
|
+
await doc.remove();
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
index.set(winner.payload.id, winner.envelopeId);
|
|
348
|
+
return winner.payload;
|
|
349
|
+
}
|
|
235
350
|
/**
|
|
236
351
|
* Decrypts a single EDV envelope into its plaintext payload, for per-doc
|
|
237
352
|
* reactive patching of a pulled remote change (without a whole-collection
|
|
@@ -267,6 +382,19 @@ export class LocalStore {
|
|
|
267
382
|
forgetEnvelope(key, uuid) {
|
|
268
383
|
this._index[key]?.delete(uuid);
|
|
269
384
|
}
|
|
385
|
+
/**
|
|
386
|
+
* The envelope id the hydration index currently maps a logical uuid to, or
|
|
387
|
+
* `undefined` when unknown (not yet hydrated, or no such entity). Lets the
|
|
388
|
+
* sync patch path tell a tombstone for the LIVE envelope apart from one for a
|
|
389
|
+
* stale duplicate (a reconciled singleton loser or a pre-resurrection row).
|
|
390
|
+
*
|
|
391
|
+
* @param key {string}
|
|
392
|
+
* @param uuid {string}
|
|
393
|
+
* @returns {string | undefined}
|
|
394
|
+
*/
|
|
395
|
+
envelopeIdFor(key, uuid) {
|
|
396
|
+
return this._index[key]?.get(uuid);
|
|
397
|
+
}
|
|
270
398
|
/**
|
|
271
399
|
* The live RxDB collection handle, for reactive subscriptions and the sync
|
|
272
400
|
* controller.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"localStore.js","sourceRoot":"","sources":["../../src/storage/localStore.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,EACL,gBAAgB,EAIjB,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAA;AAC9D,OAAO,EAAE,eAAe,EAA4B,MAAM,cAAc,CAAA;AACxE,OAAO,EACL,eAAe,EACf,eAAe,EACf,sBAAsB,
|
|
1
|
+
{"version":3,"file":"localStore.js","sourceRoot":"","sources":["../../src/storage/localStore.ts"],"names":[],"mappings":"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"}
|
|
@@ -53,4 +53,12 @@ export declare function patchFromChange(registry: StoreRegistry, collectionKey:
|
|
|
53
53
|
* @returns {void}
|
|
54
54
|
*/
|
|
55
55
|
export declare function scheduleRehydrate(registry: StoreRegistry, collectionKey: string): void;
|
|
56
|
+
/**
|
|
57
|
+
* Cancels every pending debounced re-hydrate. Called on logout so a timer that
|
|
58
|
+
* fires after the LocalStore is torn down does not reach a `requireStore()` that
|
|
59
|
+
* throws (an unhandled rejection).
|
|
60
|
+
*
|
|
61
|
+
* @returns {void}
|
|
62
|
+
*/
|
|
63
|
+
export declare function cancelScheduledRehydrates(): void;
|
|
56
64
|
//# sourceMappingURL=rehydrate.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rehydrate.d.ts","sourceRoot":"","sources":["../../src/storage/rehydrate.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;GAMG;AACH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAA;AAC5C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAGjD;;;;;GAKG;AACH,wBAAsB,UAAU,CAAC,QAAQ,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAEvE;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,aAAa,GAAG,IAAI,CAIlE;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,eAAe,CACnC,QAAQ,EAAE,aAAa,EACvB,aAAa,EAAE,MAAM,EACrB,KAAK,EAAE;IACL,SAAS,EAAE,MAAM,CAAA;IACjB,YAAY,CAAC,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,IAAI,CAAC;QAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE,CAAA;CAC/D,GACA,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"rehydrate.d.ts","sourceRoot":"","sources":["../../src/storage/rehydrate.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;GAMG;AACH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAA;AAC5C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAGjD;;;;;GAKG;AACH,wBAAsB,UAAU,CAAC,QAAQ,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAEvE;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,aAAa,GAAG,IAAI,CAIlE;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,eAAe,CACnC,QAAQ,EAAE,aAAa,EACvB,aAAa,EAAE,MAAM,EACrB,KAAK,EAAE;IACL,SAAS,EAAE,MAAM,CAAA;IACjB,YAAY,CAAC,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,IAAI,CAAC;QAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE,CAAA;CAC/D,GACA,OAAO,CAAC,IAAI,CAAC,CAsCf;AAOD;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,aAAa,EACvB,aAAa,EAAE,MAAM,GACpB,IAAI,CAqBN;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,IAAI,IAAI,CAKhD"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { requireStore } from './storageManager.js';
|
|
1
|
+
import { hasStore, requireStore } from './storageManager.js';
|
|
2
2
|
/**
|
|
3
3
|
* Hydrates every registered store from the (already opened) localStore.
|
|
4
4
|
*
|
|
@@ -53,6 +53,15 @@ export async function patchFromChange(registry, collectionKey, event) {
|
|
|
53
53
|
return;
|
|
54
54
|
}
|
|
55
55
|
if (deleted) {
|
|
56
|
+
// Only honor a tombstone for the envelope the entity currently lives in.
|
|
57
|
+
// A delete of a DIFFERENT envelope that decrypts to the same logical id is
|
|
58
|
+
// a stale duplicate being cleaned up (a reconciled singleton loser, or the
|
|
59
|
+
// pre-resurrection row of a locally re-created doc) -- dropping the live
|
|
60
|
+
// doc for it would undo the reconciliation/resurrection.
|
|
61
|
+
const mapped = requireStore().envelopeIdFor(collectionKey, payload.id);
|
|
62
|
+
if (mapped !== undefined && mapped !== row.id) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
56
65
|
requireStore().forgetEnvelope(collectionKey, payload.id);
|
|
57
66
|
entry.drop(payload.id);
|
|
58
67
|
}
|
|
@@ -61,7 +70,9 @@ export async function patchFromChange(registry, collectionKey, event) {
|
|
|
61
70
|
entry.upsert(payload);
|
|
62
71
|
}
|
|
63
72
|
}
|
|
64
|
-
/**
|
|
73
|
+
/**
|
|
74
|
+
* Per-collection debounce timers coalescing a pull burst into one hydrate.
|
|
75
|
+
*/
|
|
65
76
|
const rehydrateTimers = new Map();
|
|
66
77
|
/**
|
|
67
78
|
* Schedules a debounced re-hydrate of one collection's store after a pull.
|
|
@@ -81,7 +92,25 @@ export function scheduleRehydrate(registry, collectionKey) {
|
|
|
81
92
|
}
|
|
82
93
|
rehydrateTimers.set(collectionKey, setTimeout(() => {
|
|
83
94
|
rehydrateTimers.delete(collectionKey);
|
|
95
|
+
// A timer that outlived logout must not touch a torn-down store: hydrate
|
|
96
|
+
// reaches for `requireStore()`, which throws once the store is cleared.
|
|
97
|
+
if (!hasStore()) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
84
100
|
void entry.hydrate();
|
|
85
101
|
}, 50));
|
|
86
102
|
}
|
|
103
|
+
/**
|
|
104
|
+
* Cancels every pending debounced re-hydrate. Called on logout so a timer that
|
|
105
|
+
* fires after the LocalStore is torn down does not reach a `requireStore()` that
|
|
106
|
+
* throws (an unhandled rejection).
|
|
107
|
+
*
|
|
108
|
+
* @returns {void}
|
|
109
|
+
*/
|
|
110
|
+
export function cancelScheduledRehydrates() {
|
|
111
|
+
for (const timer of rehydrateTimers.values()) {
|
|
112
|
+
clearTimeout(timer);
|
|
113
|
+
}
|
|
114
|
+
rehydrateTimers.clear();
|
|
115
|
+
}
|
|
87
116
|
//# sourceMappingURL=rehydrate.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rehydrate.js","sourceRoot":"","sources":["../../src/storage/rehydrate.ts"],"names":[],"mappings":"AAYA,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;
|
|
1
|
+
{"version":3,"file":"rehydrate.js","sourceRoot":"","sources":["../../src/storage/rehydrate.ts"],"names":[],"mappings":"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"}
|
|
@@ -4,9 +4,12 @@ import { type WasCollectionConfig, type WasSyncConfig } from '../config.js';
|
|
|
4
4
|
import type { LocalStore } from './localStore.js';
|
|
5
5
|
import type { WasRemoteStore } from './wasRemoteStore.js';
|
|
6
6
|
/**
|
|
7
|
-
* Whether a replication error
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* Whether a replication error signals expired/revoked storage access. Every WAS
|
|
8
|
+
* request the replication makes funnels through the sync port, which maps a
|
|
9
|
+
* `401` / `403` to a typed {@link WasSyncAuthError} at the boundary. RxDB then
|
|
10
|
+
* wraps that thrown error inside an RxError (nested under `cause` / `errors` /
|
|
11
|
+
* `parameters.errors`), so this walks the error graph looking for a
|
|
12
|
+
* `WasSyncAuthError` instance rather than re-extracting raw status codes.
|
|
10
13
|
*
|
|
11
14
|
* @param err {unknown}
|
|
12
15
|
* @returns {boolean}
|
|
@@ -23,6 +26,7 @@ export declare class SyncController {
|
|
|
23
26
|
private _onlineHandler?;
|
|
24
27
|
private _pollTimer?;
|
|
25
28
|
private _started;
|
|
29
|
+
private _stopped;
|
|
26
30
|
constructor({ collections, sync }: {
|
|
27
31
|
collections: WasCollectionConfig[];
|
|
28
32
|
sync?: WasSyncConfig;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"syncController.d.ts","sourceRoot":"","sources":["../../src/storage/syncController.ts"],"names":[],"mappings":"AA+BA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AACtD,OAAO,
|
|
1
|
+
{"version":3,"file":"syncController.d.ts","sourceRoot":"","sources":["../../src/storage/syncController.ts"],"names":[],"mappings":"AA+BA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AACtD,OAAO,EAML,KAAK,SAAS,EACf,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAEL,KAAK,mBAAmB,EACxB,KAAK,aAAa,EACnB,MAAM,cAAc,CAAA;AACrB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AACjD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AAQzD;;;;;;;;;;GAUG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CA4BjD;AAOD;;;GAGG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,YAAY,CAAuB;IAC3C,OAAO,CAAC,KAAK,CAAe;IAC5B,OAAO,CAAC,aAAa,CAA8B;IACnD,OAAO,CAAC,cAAc,CAAC,CAAY;IACnC,OAAO,CAAC,UAAU,CAAC,CAAgC;IACnD,OAAO,CAAC,QAAQ,CAAQ;IACxB,OAAO,CAAC,QAAQ,CAAQ;gBAEZ,EACV,WAAW,EACX,IAAI,EACL,EAAE;QACD,WAAW,EAAE,mBAAmB,EAAE,CAAA;QAClC,IAAI,CAAC,EAAE,aAAa,CAAA;KACrB;IAKD;;;;;;;;;;;;;OAaG;IACG,KAAK,CAAC,EACV,WAAW,EACX,UAAU,EACV,cAAc,EACd,WAAW,EACZ,EAAE;QACD,WAAW,EAAE,cAAc,CAAA;QAC3B,UAAU,EAAE,UAAU,CAAA;QACtB,cAAc,CAAC,EAAE,CACf,aAAa,EAAE,MAAM,EACrB,KAAK,EAAE,aAAa,CAAC,SAAS,CAAC,KAC5B,IAAI,CAAA;QACT,WAAW,CAAC,EAAE,MAAM,IAAI,CAAA;KACzB,GAAG,OAAO,CAAC,IAAI,CAAC;IAoFjB;;;;;OAKG;IACH,MAAM,IAAI,IAAI;IAMd;;;;;OAKG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CA0B5B;AAED;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAAC,EACnC,WAAW,EACX,IAAI,EACL,EAAE;IACD,WAAW,EAAE,mBAAmB,EAAE,CAAA;IAClC,IAAI,CAAC,EAAE,aAAa,CAAA;CACrB,GAAG,cAAc,CAEjB"}
|
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
import { createWasReplication, createWasSyncPort, withFeedMasterRead } from '../sync/index.js';
|
|
1
|
+
import { createWasReplication, createWasSyncPort, withFeedMasterRead, WasSyncAuthError } from '../sync/index.js';
|
|
2
2
|
import { DEFAULT_SYNC_POLL_MS } from '../config.js';
|
|
3
3
|
import { useSyncStatusStore } from './syncStatusStore.js';
|
|
4
4
|
/**
|
|
5
|
-
* Whether a replication error
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* Whether a replication error signals expired/revoked storage access. Every WAS
|
|
6
|
+
* request the replication makes funnels through the sync port, which maps a
|
|
7
|
+
* `401` / `403` to a typed {@link WasSyncAuthError} at the boundary. RxDB then
|
|
8
|
+
* wraps that thrown error inside an RxError (nested under `cause` / `errors` /
|
|
9
|
+
* `parameters.errors`), so this walks the error graph looking for a
|
|
10
|
+
* `WasSyncAuthError` instance rather than re-extracting raw status codes.
|
|
8
11
|
*
|
|
9
12
|
* @param err {unknown}
|
|
10
13
|
* @returns {boolean}
|
|
@@ -18,15 +21,10 @@ export function isAuthError(err) {
|
|
|
18
21
|
continue;
|
|
19
22
|
}
|
|
20
23
|
seen.add(current);
|
|
21
|
-
|
|
22
|
-
const statuses = [
|
|
23
|
-
candidate.status,
|
|
24
|
-
candidate.statusCode,
|
|
25
|
-
candidate.response?.status
|
|
26
|
-
];
|
|
27
|
-
if (statuses.includes(401) || statuses.includes(403)) {
|
|
24
|
+
if (current instanceof WasSyncAuthError) {
|
|
28
25
|
return true;
|
|
29
26
|
}
|
|
27
|
+
const candidate = current;
|
|
30
28
|
if (candidate.cause) {
|
|
31
29
|
queue.push(candidate.cause);
|
|
32
30
|
}
|
|
@@ -50,6 +48,7 @@ export class SyncController {
|
|
|
50
48
|
_onlineHandler;
|
|
51
49
|
_pollTimer;
|
|
52
50
|
_started = false;
|
|
51
|
+
_stopped = false;
|
|
53
52
|
constructor({ collections, sync }) {
|
|
54
53
|
this._collections = collections;
|
|
55
54
|
this._sync = sync ?? {};
|
|
@@ -69,7 +68,10 @@ export class SyncController {
|
|
|
69
68
|
* @returns {Promise<void>}
|
|
70
69
|
*/
|
|
71
70
|
async start({ remoteStore, localStore, onRemoteChange, onAuthError }) {
|
|
72
|
-
|
|
71
|
+
// `stop()` is terminal: a controller stopped before `start()` ran (a logout
|
|
72
|
+
// that raced an in-flight session bootstrap) must never spin up
|
|
73
|
+
// replications against the now-closed database.
|
|
74
|
+
if (this._started || this._stopped) {
|
|
73
75
|
return;
|
|
74
76
|
}
|
|
75
77
|
this._started = true;
|
|
@@ -79,6 +81,16 @@ export class SyncController {
|
|
|
79
81
|
const pollMs = this._sync.pollMs ?? DEFAULT_SYNC_POLL_MS;
|
|
80
82
|
try {
|
|
81
83
|
for (const { key, id } of this._collections) {
|
|
84
|
+
const capability = remoteStore.collectionCapability(id);
|
|
85
|
+
// A collection the grant set does not cover would otherwise sync with no
|
|
86
|
+
// capability and draw a fail-closed 403, tripping the session-wide
|
|
87
|
+
// "storage access expired" banner. Skip it (and flag it) instead so an
|
|
88
|
+
// uncovered collection never masquerades as expired access.
|
|
89
|
+
if (!capability) {
|
|
90
|
+
console.warn(`Skipping sync for "${id}": no delegated capability covers it.`);
|
|
91
|
+
setStatus(id, 'error');
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
82
94
|
setStatus(id, 'idle');
|
|
83
95
|
// Wrap the verbatim port so the 412 conflict re-read resolves `version`
|
|
84
96
|
// from the changes-feed body (CORS hides the `GET` ETag cross-origin).
|
|
@@ -86,9 +98,7 @@ export class SyncController {
|
|
|
86
98
|
was: remoteStore.was,
|
|
87
99
|
spaceId: remoteStore.spaceId,
|
|
88
100
|
collectionId: id,
|
|
89
|
-
|
|
90
|
-
capability: remoteStore.collectionCapability(id)
|
|
91
|
-
})
|
|
101
|
+
capability
|
|
92
102
|
}));
|
|
93
103
|
const state = createWasReplication({
|
|
94
104
|
rxCollection: localStore.rxCollection(key),
|
|
@@ -149,6 +159,9 @@ export class SyncController {
|
|
|
149
159
|
* @returns {Promise<void>}
|
|
150
160
|
*/
|
|
151
161
|
async stop() {
|
|
162
|
+
// Latch first so a concurrent `start()` (a logout racing the session
|
|
163
|
+
// bootstrap) sees the controller as terminally stopped and bails.
|
|
164
|
+
this._stopped = true;
|
|
152
165
|
if (this._onlineHandler) {
|
|
153
166
|
window.removeEventListener('online', this._onlineHandler);
|
|
154
167
|
this._onlineHandler = undefined;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"syncController.js","sourceRoot":"","sources":["../../src/storage/syncController.ts"],"names":[],"mappings":"AAgCA,OAAO,EACL,oBAAoB,EACpB,iBAAiB,EACjB,kBAAkB,
|
|
1
|
+
{"version":3,"file":"syncController.js","sourceRoot":"","sources":["../../src/storage/syncController.ts"],"names":[],"mappings":"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"}
|
|
@@ -7,11 +7,36 @@
|
|
|
7
7
|
*/
|
|
8
8
|
export type SyncStatus = 'idle' | 'syncing' | 'synced' | 'error';
|
|
9
9
|
interface SyncStatusState {
|
|
10
|
-
/**
|
|
10
|
+
/**
|
|
11
|
+
* Keyed by WAS collection id (e.g. `action-items`).
|
|
12
|
+
*/
|
|
11
13
|
statuses: Record<string, SyncStatus>;
|
|
12
14
|
setStatus: (collectionId: string, status: SyncStatus) => void;
|
|
13
15
|
reset: () => void;
|
|
14
16
|
}
|
|
15
17
|
export declare const useSyncStatusStore: import("zustand").UseBoundStore<import("zustand").StoreApi<SyncStatusState>>;
|
|
18
|
+
/**
|
|
19
|
+
* The aggregate replication status derived from the per-collection statuses:
|
|
20
|
+
* `offline` when no replication is running (local-only), otherwise rolled up as
|
|
21
|
+
* error > syncing > synced.
|
|
22
|
+
*/
|
|
23
|
+
export type SyncRollup = 'offline' | 'error' | 'syncing' | 'synced';
|
|
24
|
+
/**
|
|
25
|
+
* Rolls the per-collection replication statuses up to a single aggregate plus
|
|
26
|
+
* its display copy. With no collections registered it reports `offline`
|
|
27
|
+
* (local-only, no sync running); otherwise it applies the
|
|
28
|
+
* error > syncing > synced precedence (`idle` counts as syncing -- a collection
|
|
29
|
+
* configured but not yet cycled). Kept beside the status vocabulary so the
|
|
30
|
+
* precedence lives with the store rather than the view; `useSyncStatus` is a
|
|
31
|
+
* thin subscription over it.
|
|
32
|
+
*
|
|
33
|
+
* @param statuses {SyncStatus[]} the per-collection statuses (store values)
|
|
34
|
+
* @returns {{ state: SyncRollup, label: string, title: string }}
|
|
35
|
+
*/
|
|
36
|
+
export declare function deriveSyncRollup(statuses: SyncStatus[]): {
|
|
37
|
+
state: SyncRollup;
|
|
38
|
+
label: string;
|
|
39
|
+
title: string;
|
|
40
|
+
};
|
|
16
41
|
export {};
|
|
17
42
|
//# sourceMappingURL=syncStatusStore.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"syncStatusStore.d.ts","sourceRoot":"","sources":["../../src/storage/syncStatusStore.ts"],"names":[],"mappings":"AAYA;;;;;;GAMG;AACH,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,SAAS,GAAG,QAAQ,GAAG,OAAO,CAAA;AAEhE,UAAU,eAAe;IACvB
|
|
1
|
+
{"version":3,"file":"syncStatusStore.d.ts","sourceRoot":"","sources":["../../src/storage/syncStatusStore.ts"],"names":[],"mappings":"AAYA;;;;;;GAMG;AACH,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,SAAS,GAAG,QAAQ,GAAG,OAAO,CAAA;AAEhE,UAAU,eAAe;IACvB;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAA;IACpC,SAAS,EAAE,CAAC,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,KAAK,IAAI,CAAA;IAC7D,KAAK,EAAE,MAAM,IAAI,CAAA;CAClB;AAED,eAAO,MAAM,kBAAkB,8EAO5B,CAAA;AAEH;;;;GAIG;AACH,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,OAAO,GAAG,SAAS,GAAG,QAAQ,CAAA;AAEnE;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,UAAU,EAAE,GAAG;IACxD,KAAK,EAAE,UAAU,CAAA;IACjB,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;CACd,CA2BA"}
|