@instantdb/core 0.22.57 → 0.22.58
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/__tests__/src/Reactor.test.js +97 -21
- package/__tests__/src/utils/PersistedObject.test.ts +383 -0
- package/dist/commonjs/InMemoryStorage.d.ts +11 -0
- package/dist/commonjs/InMemoryStorage.d.ts.map +1 -0
- package/dist/commonjs/InMemoryStorage.js +48 -0
- package/dist/commonjs/InMemoryStorage.js.map +1 -0
- package/dist/commonjs/IndexedDBStorage.d.ts +10 -3
- package/dist/commonjs/IndexedDBStorage.d.ts.map +1 -1
- package/dist/commonjs/IndexedDBStorage.js +254 -8
- package/dist/commonjs/IndexedDBStorage.js.map +1 -1
- package/dist/commonjs/Reactor.d.ts +7 -16
- package/dist/commonjs/Reactor.d.ts.map +1 -1
- package/dist/commonjs/Reactor.js +199 -149
- package/dist/commonjs/Reactor.js.map +1 -1
- package/dist/commonjs/SyncTable.d.ts +3 -1
- package/dist/commonjs/SyncTable.d.ts.map +1 -1
- package/dist/commonjs/SyncTable.js +194 -168
- package/dist/commonjs/SyncTable.js.map +1 -1
- package/dist/commonjs/index.d.ts +2 -1
- package/dist/commonjs/index.d.ts.map +1 -1
- package/dist/commonjs/index.js +3 -1
- package/dist/commonjs/index.js.map +1 -1
- package/dist/commonjs/utils/PersistedObject.d.ts +73 -29
- package/dist/commonjs/utils/PersistedObject.d.ts.map +1 -1
- package/dist/commonjs/utils/PersistedObject.js +366 -77
- package/dist/commonjs/utils/PersistedObject.js.map +1 -1
- package/dist/esm/InMemoryStorage.d.ts +11 -0
- package/dist/esm/InMemoryStorage.d.ts.map +1 -0
- package/dist/esm/InMemoryStorage.js +45 -0
- package/dist/esm/InMemoryStorage.js.map +1 -0
- package/dist/esm/IndexedDBStorage.d.ts +10 -3
- package/dist/esm/IndexedDBStorage.d.ts.map +1 -1
- package/dist/esm/IndexedDBStorage.js +254 -8
- package/dist/esm/IndexedDBStorage.js.map +1 -1
- package/dist/esm/Reactor.d.ts +7 -16
- package/dist/esm/Reactor.d.ts.map +1 -1
- package/dist/esm/Reactor.js +199 -149
- package/dist/esm/Reactor.js.map +1 -1
- package/dist/esm/SyncTable.d.ts +3 -1
- package/dist/esm/SyncTable.d.ts.map +1 -1
- package/dist/esm/SyncTable.js +194 -168
- package/dist/esm/SyncTable.js.map +1 -1
- package/dist/esm/index.d.ts +2 -1
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +4 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/utils/PersistedObject.d.ts +73 -29
- package/dist/esm/utils/PersistedObject.d.ts.map +1 -1
- package/dist/esm/utils/PersistedObject.js +364 -76
- package/dist/esm/utils/PersistedObject.js.map +1 -1
- package/dist/standalone/index.js +1978 -1567
- package/dist/standalone/index.umd.cjs +3 -3
- package/package.json +2 -2
- package/src/InMemoryStorage.ts +34 -0
- package/src/IndexedDBStorage.ts +281 -9
- package/src/Reactor.js +212 -184
- package/src/SyncTable.ts +220 -191
- package/src/index.ts +8 -0
- package/src/utils/PersistedObject.ts +480 -109
- package/src/InMemoryStorage.js +0 -14
package/src/Reactor.js
CHANGED
|
@@ -12,6 +12,7 @@ import * as flags from './utils/flags.ts';
|
|
|
12
12
|
import { buildPresenceSlice, hasPresenceResponseChanged } from './presence.ts';
|
|
13
13
|
import { Deferred } from './utils/Deferred.js';
|
|
14
14
|
import { PersistedObject } from './utils/PersistedObject.ts';
|
|
15
|
+
|
|
15
16
|
import { extractTriples } from './model/instaqlResult.js';
|
|
16
17
|
import {
|
|
17
18
|
areObjectsDeepEqual,
|
|
@@ -106,35 +107,56 @@ const ignoreLogging = {
|
|
|
106
107
|
'patch-presence': true,
|
|
107
108
|
};
|
|
108
109
|
|
|
109
|
-
function
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
});
|
|
119
|
-
}
|
|
110
|
+
function querySubFromStorage(x, useDateObjects) {
|
|
111
|
+
const v = typeof x === 'string' ? JSON.parse(x) : x;
|
|
112
|
+
|
|
113
|
+
if (v?.result?.store) {
|
|
114
|
+
const storeJSON = v.result.store;
|
|
115
|
+
v.result.store = s.fromJSON({
|
|
116
|
+
...storeJSON,
|
|
117
|
+
useDateObjects: useDateObjects,
|
|
118
|
+
});
|
|
120
119
|
}
|
|
121
|
-
|
|
120
|
+
|
|
121
|
+
return v;
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
-
function
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
124
|
+
function querySubToStorage(_key, sub) {
|
|
125
|
+
const jsonSub = { ...sub };
|
|
126
|
+
if (sub.result?.store) {
|
|
127
|
+
jsonSub.result = {
|
|
128
|
+
...sub.result,
|
|
129
|
+
store: s.toJSON(sub.result.store),
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
return jsonSub;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function kvFromStorage(key, x) {
|
|
136
|
+
switch (key) {
|
|
137
|
+
case 'pendingMutations':
|
|
138
|
+
return new Map(typeof x === 'string' ? JSON.parse(x) : x);
|
|
139
|
+
default:
|
|
140
|
+
return x;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function kvToStorage(key, x) {
|
|
145
|
+
switch (key) {
|
|
146
|
+
case 'pendingMutations':
|
|
147
|
+
return [...x.entries()];
|
|
148
|
+
default:
|
|
149
|
+
return x;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function onMergeQuerySub(_k, storageSub, inMemorySub) {
|
|
154
|
+
const storageResult = storageSub?.result;
|
|
155
|
+
const memoryResult = inMemorySub?.result;
|
|
156
|
+
if (storageResult && !memoryResult && inMemorySub) {
|
|
157
|
+
inMemorySub.result = storageResult;
|
|
136
158
|
}
|
|
137
|
-
return
|
|
159
|
+
return inMemorySub || storageSub;
|
|
138
160
|
}
|
|
139
161
|
|
|
140
162
|
function sortedMutationEntries(entries) {
|
|
@@ -161,8 +183,9 @@ export default class Reactor {
|
|
|
161
183
|
|
|
162
184
|
/** @type {PersistedObject} */
|
|
163
185
|
querySubs;
|
|
186
|
+
|
|
164
187
|
/** @type {PersistedObject} */
|
|
165
|
-
|
|
188
|
+
kv;
|
|
166
189
|
|
|
167
190
|
/** @type {Record<string, Array<{ q: any, cb: (data: any) => any }>>} */
|
|
168
191
|
queryCbs = {};
|
|
@@ -173,7 +196,6 @@ export default class Reactor {
|
|
|
173
196
|
mutationErrorCbs = [];
|
|
174
197
|
connectionStatusCbs = [];
|
|
175
198
|
config;
|
|
176
|
-
_persister;
|
|
177
199
|
mutationDeferredStore = new Map();
|
|
178
200
|
_reconnectTimeoutId = null;
|
|
179
201
|
_reconnectTimeoutMs = 0;
|
|
@@ -275,7 +297,7 @@ export default class Reactor {
|
|
|
275
297
|
|
|
276
298
|
this._syncTable = new SyncTable(
|
|
277
299
|
this._trySendAuthed.bind(this),
|
|
278
|
-
this.
|
|
300
|
+
new Storage(this.config.appId, 'syncSubs'),
|
|
279
301
|
{
|
|
280
302
|
useDateObjects: this.config.useDateObjects,
|
|
281
303
|
},
|
|
@@ -339,37 +361,57 @@ export default class Reactor {
|
|
|
339
361
|
_reactorStats() {
|
|
340
362
|
return {
|
|
341
363
|
inFlightMutationCount: this._inFlightMutationEventIds.size,
|
|
342
|
-
pendingMutationCount: this.
|
|
364
|
+
pendingMutationCount: this._pendingMutations().size,
|
|
343
365
|
transportType: this._transportType,
|
|
344
366
|
};
|
|
345
367
|
}
|
|
346
368
|
|
|
369
|
+
_onQuerySubLoaded(hash) {
|
|
370
|
+
this.kv
|
|
371
|
+
.waitForKeyToLoad('pendingMutations')
|
|
372
|
+
.then(() => this.notifyOne(hash));
|
|
373
|
+
}
|
|
374
|
+
|
|
347
375
|
_initStorage(Storage) {
|
|
348
|
-
this.
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
(x) => {
|
|
363
|
-
return [...x.entries()];
|
|
364
|
-
},
|
|
365
|
-
(x) => {
|
|
366
|
-
return new Map(typeof x === 'string' ? JSON.parse(x) : x);
|
|
376
|
+
this.querySubs = new PersistedObject({
|
|
377
|
+
persister: new Storage(this.config.appId, 'querySubs'),
|
|
378
|
+
merge: onMergeQuerySub,
|
|
379
|
+
serialize: querySubToStorage,
|
|
380
|
+
parse: (_key, x) => querySubFromStorage(x, this.config.useDateObjects),
|
|
381
|
+
// objectSize
|
|
382
|
+
objectSize: (x) => x.result?.store?.triples?.length ?? 0,
|
|
383
|
+
logger: this._log,
|
|
384
|
+
preloadEntryCount: 10,
|
|
385
|
+
gc: {
|
|
386
|
+
maxAgeMs: 1000 * 60 * 60 * 24 * 7 * 52, // 1 year
|
|
387
|
+
maxEntries: 1000,
|
|
388
|
+
// Size of each query is the number of triples
|
|
389
|
+
maxSize: 1_000_000, // 1 million triples
|
|
367
390
|
},
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
391
|
+
});
|
|
392
|
+
this.querySubs.onKeyLoaded = (k) => this._onQuerySubLoaded(k);
|
|
393
|
+
this.kv = new PersistedObject({
|
|
394
|
+
persister: new Storage(this.config.appId, 'kv'),
|
|
395
|
+
merge: this._onMergeKv,
|
|
396
|
+
serialize: kvToStorage,
|
|
397
|
+
parse: kvFromStorage,
|
|
398
|
+
objectSize: () => 0,
|
|
399
|
+
logger: this._log,
|
|
400
|
+
saveThrottleMs: 100,
|
|
401
|
+
idleCallbackMaxWaitMs: 100,
|
|
402
|
+
// Don't GC the kv store
|
|
403
|
+
gc: null,
|
|
404
|
+
});
|
|
405
|
+
this.kv.onKeyLoaded = (k) => {
|
|
406
|
+
if (k === 'pendingMutations') {
|
|
407
|
+
this.notifyAll();
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
// Trigger immediate load for pendingMutations and currentUser
|
|
411
|
+
this.kv.waitForKeyToLoad('pendingMutations');
|
|
412
|
+
this.kv.waitForKeyToLoad(currentUserKey);
|
|
371
413
|
this._beforeUnloadCbs.push(() => {
|
|
372
|
-
this.
|
|
414
|
+
this.kv.flush();
|
|
373
415
|
this.querySubs.flush();
|
|
374
416
|
});
|
|
375
417
|
}
|
|
@@ -423,72 +465,25 @@ export default class Reactor {
|
|
|
423
465
|
this.notifyConnectionStatusSubs(status);
|
|
424
466
|
}
|
|
425
467
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
ret[hash].result = storageResult;
|
|
442
|
-
}
|
|
443
|
-
});
|
|
444
|
-
|
|
445
|
-
// Consider a storageSub with no corresponding inMemorySub
|
|
446
|
-
// This means that at least at this point,
|
|
447
|
-
// the user has not asked to subscribe to the query.
|
|
448
|
-
// We may _still_ want to add it, because in just a
|
|
449
|
-
// few milliseconds, the user will ask to subscribe to the
|
|
450
|
-
// query.
|
|
451
|
-
// For now, we can't really tell if the user will ask to subscribe
|
|
452
|
-
// or not. So for now let's just add the first 10 queries from storage.
|
|
453
|
-
// Eventually, we could be smarter about this. For example,
|
|
454
|
-
// we can keep usage information about which queries are popular.
|
|
455
|
-
const storageKsToAdd = Object.keys(storageSubs)
|
|
456
|
-
.filter((k) => !inMemorySubs[k])
|
|
457
|
-
.sort((a, b) => {
|
|
458
|
-
// Sort by lastAccessed, newest first
|
|
459
|
-
const aTime = storageSubs[a]?.lastAccessed || 0;
|
|
460
|
-
const bTime = storageSubs[b]?.lastAccessed || 0;
|
|
461
|
-
return bTime - aTime;
|
|
462
|
-
})
|
|
463
|
-
.slice(0, this.queryCacheLimit);
|
|
464
|
-
|
|
465
|
-
storageKsToAdd.forEach((k) => {
|
|
466
|
-
ret[k] = storageSubs[k];
|
|
467
|
-
});
|
|
468
|
-
|
|
469
|
-
// Okay, now we have merged our querySubs
|
|
470
|
-
this.querySubs.set((_) => ret);
|
|
471
|
-
|
|
472
|
-
this.loadedNotifyAll();
|
|
473
|
-
};
|
|
474
|
-
|
|
475
|
-
/**
|
|
476
|
-
* merge pendingMutations from storage and in memory. Has a side effect of
|
|
477
|
-
* sending mutations that were stored but not acked
|
|
478
|
-
*/
|
|
479
|
-
_onMergePendingMutations = (storageMuts, inMemoryMuts) => {
|
|
480
|
-
const ret = new Map([...storageMuts.entries(), ...inMemoryMuts.entries()]);
|
|
481
|
-
this.pendingMutations.set((_) => ret);
|
|
482
|
-
this.loadedNotifyAll();
|
|
483
|
-
const rewrittenStorageMuts = this._rewriteMutationsSorted(
|
|
484
|
-
this.attrs,
|
|
485
|
-
storageMuts,
|
|
486
|
-
);
|
|
487
|
-
rewrittenStorageMuts.forEach(([k, mut]) => {
|
|
488
|
-
if (!inMemoryMuts.has(k) && !mut['tx-id']) {
|
|
489
|
-
this._sendMutation(k, mut);
|
|
468
|
+
_onMergeKv = (key, storageV, inMemoryV) => {
|
|
469
|
+
switch (key) {
|
|
470
|
+
case 'pendingMutations': {
|
|
471
|
+
const storageEntries = storageV?.entries() ?? [];
|
|
472
|
+
const inMemoryEntries = inMemoryV?.entries() ?? [];
|
|
473
|
+
const muts = new Map([...storageEntries, ...inMemoryEntries]);
|
|
474
|
+
const rewrittenStorageMuts = storageV
|
|
475
|
+
? this._rewriteMutationsSorted(this.attrs, storageV)
|
|
476
|
+
: [];
|
|
477
|
+
rewrittenStorageMuts.forEach(([k, mut]) => {
|
|
478
|
+
if (!inMemoryV?.pendingMutations?.has(k) && !mut['tx-id']) {
|
|
479
|
+
this._sendMutation(k, mut);
|
|
480
|
+
}
|
|
481
|
+
});
|
|
482
|
+
return muts;
|
|
490
483
|
}
|
|
491
|
-
|
|
484
|
+
default:
|
|
485
|
+
return inMemoryV || storageV;
|
|
486
|
+
}
|
|
492
487
|
};
|
|
493
488
|
|
|
494
489
|
_flushEnqueuedRoomData(roomId) {
|
|
@@ -542,6 +537,9 @@ export default class Reactor {
|
|
|
542
537
|
case 'add-query-ok': {
|
|
543
538
|
const { q, result } = msg;
|
|
544
539
|
const hash = weakHash(q);
|
|
540
|
+
if (!this._hasQueryListeners() && !this.querySubs.currentValue[hash]) {
|
|
541
|
+
break;
|
|
542
|
+
}
|
|
545
543
|
const pageInfo = result?.[0]?.data?.['page-info'];
|
|
546
544
|
const aggregate = result?.[0]?.data?.['aggregate'];
|
|
547
545
|
const triples = extractTriples(result);
|
|
@@ -552,14 +550,17 @@ export default class Reactor {
|
|
|
552
550
|
this._linkIndex,
|
|
553
551
|
this.config.useDateObjects,
|
|
554
552
|
);
|
|
555
|
-
this.querySubs.
|
|
553
|
+
this.querySubs.updateInPlace((prev) => {
|
|
554
|
+
if (!prev[hash]) {
|
|
555
|
+
this._log.info('Missing value in querySubs', { hash, q });
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
556
558
|
prev[hash].result = {
|
|
557
559
|
store,
|
|
558
560
|
pageInfo,
|
|
559
561
|
aggregate,
|
|
560
562
|
processedTxId: msg['processed-tx-id'],
|
|
561
563
|
};
|
|
562
|
-
return prev;
|
|
563
564
|
});
|
|
564
565
|
this._cleanupPendingMutationsQueries();
|
|
565
566
|
this.notifyOne(hash);
|
|
@@ -594,15 +595,17 @@ export default class Reactor {
|
|
|
594
595
|
|
|
595
596
|
const rewrittenMutations = this._rewriteMutations(
|
|
596
597
|
this.attrs,
|
|
597
|
-
this.
|
|
598
|
+
this._pendingMutations(),
|
|
598
599
|
processedTxId,
|
|
599
600
|
);
|
|
600
601
|
|
|
601
|
-
if (rewrittenMutations !== this.
|
|
602
|
+
if (rewrittenMutations !== this._pendingMutations()) {
|
|
602
603
|
// We know we've changed the mutations to fix the attr ids and removed
|
|
603
604
|
// processed attrs, so we'll persist those changes to prevent optimisticAttrs
|
|
604
605
|
// from using old attr definitions
|
|
605
|
-
this.
|
|
606
|
+
this.kv.updateInPlace((prev) => {
|
|
607
|
+
prev.pendingMutations = rewrittenMutations;
|
|
608
|
+
});
|
|
606
609
|
}
|
|
607
610
|
|
|
608
611
|
const mutations = sortedMutationEntries(rewrittenMutations.entries());
|
|
@@ -626,13 +629,16 @@ export default class Reactor {
|
|
|
626
629
|
);
|
|
627
630
|
const pageInfo = result?.[0]?.data?.['page-info'];
|
|
628
631
|
const aggregate = result?.[0]?.data?.['aggregate'];
|
|
629
|
-
return { hash, store: newStore, pageInfo, aggregate };
|
|
632
|
+
return { q, hash, store: newStore, pageInfo, aggregate };
|
|
630
633
|
});
|
|
631
634
|
|
|
632
|
-
updates.forEach(({ hash, store, pageInfo, aggregate }) => {
|
|
633
|
-
this.querySubs.
|
|
635
|
+
updates.forEach(({ hash, q, store, pageInfo, aggregate }) => {
|
|
636
|
+
this.querySubs.updateInPlace((prev) => {
|
|
637
|
+
if (!prev[hash]) {
|
|
638
|
+
this._log.error('Missing value in querySubs', { hash, q });
|
|
639
|
+
return;
|
|
640
|
+
}
|
|
634
641
|
prev[hash].result = { store, pageInfo, aggregate, processedTxId };
|
|
635
|
-
return prev;
|
|
636
642
|
});
|
|
637
643
|
});
|
|
638
644
|
|
|
@@ -650,7 +656,7 @@ export default class Reactor {
|
|
|
650
656
|
|
|
651
657
|
const muts = this._rewriteMutations(
|
|
652
658
|
this.attrs,
|
|
653
|
-
this.
|
|
659
|
+
this._pendingMutations(),
|
|
654
660
|
);
|
|
655
661
|
const prevMutation = muts.get(eventId);
|
|
656
662
|
if (!prevMutation) {
|
|
@@ -658,13 +664,12 @@ export default class Reactor {
|
|
|
658
664
|
}
|
|
659
665
|
|
|
660
666
|
// update pendingMutation with server-side tx-id
|
|
661
|
-
this.
|
|
667
|
+
this._updatePendingMutations((prev) => {
|
|
662
668
|
prev.set(eventId, {
|
|
663
669
|
...prev.get(eventId),
|
|
664
670
|
'tx-id': txId,
|
|
665
671
|
confirmed: Date.now(),
|
|
666
672
|
});
|
|
667
|
-
return prev;
|
|
668
673
|
});
|
|
669
674
|
|
|
670
675
|
const newAttrs = prevMutation['tx-steps']
|
|
@@ -740,16 +745,28 @@ export default class Reactor {
|
|
|
740
745
|
}
|
|
741
746
|
}
|
|
742
747
|
|
|
748
|
+
_pendingMutations() {
|
|
749
|
+
return this.kv.currentValue.pendingMutations ?? new Map();
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
_updatePendingMutations(f) {
|
|
753
|
+
this.kv.updateInPlace((prev) => {
|
|
754
|
+
const muts = prev.pendingMutations ?? new Map();
|
|
755
|
+
prev.pendingMutations = muts;
|
|
756
|
+
f(muts);
|
|
757
|
+
});
|
|
758
|
+
}
|
|
759
|
+
|
|
743
760
|
/**
|
|
744
761
|
* @param {'timeout' | 'error'} status
|
|
745
762
|
* @param {string} eventId
|
|
746
763
|
* @param {{message: string, type?: string, status?: number, hint?: unknown}} errorMsg
|
|
747
764
|
*/
|
|
748
765
|
_handleMutationError(status, eventId, errorMsg) {
|
|
749
|
-
const mut = this.
|
|
766
|
+
const mut = this._pendingMutations().get(eventId);
|
|
750
767
|
|
|
751
768
|
if (mut && (status !== 'timeout' || !mut['tx-id'])) {
|
|
752
|
-
this.
|
|
769
|
+
this._updatePendingMutations((prev) => {
|
|
753
770
|
prev.delete(eventId);
|
|
754
771
|
return prev;
|
|
755
772
|
});
|
|
@@ -770,7 +787,7 @@ export default class Reactor {
|
|
|
770
787
|
const eventId = msg['client-event-id'];
|
|
771
788
|
// This might not be a mutation, but it can't hurt to delete it
|
|
772
789
|
this._inFlightMutationEventIds.delete(eventId);
|
|
773
|
-
const prevMutation = this.
|
|
790
|
+
const prevMutation = this._pendingMutations().get(eventId);
|
|
774
791
|
const errorMessage = {
|
|
775
792
|
message: msg.message || 'Uh-oh, something went wrong. Ping Joe & Stopa.',
|
|
776
793
|
};
|
|
@@ -862,10 +879,9 @@ export default class Reactor {
|
|
|
862
879
|
|
|
863
880
|
_startQuerySub(q, hash) {
|
|
864
881
|
const eventId = uuid();
|
|
865
|
-
this.querySubs.
|
|
882
|
+
this.querySubs.updateInPlace((prev) => {
|
|
866
883
|
prev[hash] = prev[hash] || { q, result: null, eventId };
|
|
867
884
|
prev[hash].lastAccessed = Date.now();
|
|
868
|
-
return prev;
|
|
869
885
|
});
|
|
870
886
|
this._trySendAuthed(eventId, { op: 'add-query', q });
|
|
871
887
|
|
|
@@ -971,14 +987,17 @@ export default class Reactor {
|
|
|
971
987
|
this._cleanupQuery(q, hash);
|
|
972
988
|
}
|
|
973
989
|
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
990
|
+
_hasQueryListeners(hash) {
|
|
991
|
+
return !!(this.queryCbs[hash]?.length || this.queryOnceDfds[hash]?.length);
|
|
992
|
+
}
|
|
977
993
|
|
|
994
|
+
_cleanupQuery(q, hash) {
|
|
995
|
+
const hasListeners = this._hasQueryListeners(hash);
|
|
978
996
|
if (hasListeners) return;
|
|
979
|
-
|
|
980
997
|
delete this.queryCbs[hash];
|
|
981
998
|
delete this.queryOnceDfds[hash];
|
|
999
|
+
delete this._dataForQueryCache[hash];
|
|
1000
|
+
this.querySubs.unloadKey(hash);
|
|
982
1001
|
|
|
983
1002
|
this._trySendAuthed(uuid(), { op: 'remove-query', q });
|
|
984
1003
|
}
|
|
@@ -996,6 +1015,7 @@ export default class Reactor {
|
|
|
996
1015
|
// server attr-ids.
|
|
997
1016
|
_rewriteMutations(attrs, muts, processedTxId) {
|
|
998
1017
|
if (!attrs) return muts;
|
|
1018
|
+
if (!muts) return new Map();
|
|
999
1019
|
const findExistingAttr = (attr) => {
|
|
1000
1020
|
const [_, etype, label] = attr['forward-identity'];
|
|
1001
1021
|
const existing = instaml.getAttrByFwdIdentName(attrs, etype, label);
|
|
@@ -1081,9 +1101,7 @@ export default class Reactor {
|
|
|
1081
1101
|
// Transact
|
|
1082
1102
|
|
|
1083
1103
|
optimisticAttrs() {
|
|
1084
|
-
const pendingMutationSteps = [
|
|
1085
|
-
...this.pendingMutations.currentValue.values(),
|
|
1086
|
-
] // hack due to Map()
|
|
1104
|
+
const pendingMutationSteps = [...this._pendingMutations().values()] // hack due to Map()
|
|
1087
1105
|
.flatMap((x) => x['tx-steps']);
|
|
1088
1106
|
|
|
1089
1107
|
const deletedAttrIds = new Set(
|
|
@@ -1125,11 +1143,11 @@ export default class Reactor {
|
|
|
1125
1143
|
return { error: errorMessage };
|
|
1126
1144
|
}
|
|
1127
1145
|
if (!this.querySubs) return;
|
|
1128
|
-
if (!this.pendingMutations) return;
|
|
1146
|
+
if (!this.kv.currentValue.pendingMutations) return;
|
|
1129
1147
|
const querySubVersion = this.querySubs.version();
|
|
1130
1148
|
const querySubs = this.querySubs.currentValue;
|
|
1131
|
-
const pendingMutationsVersion = this.
|
|
1132
|
-
const pendingMutations = this.
|
|
1149
|
+
const pendingMutationsVersion = this.kv.version();
|
|
1150
|
+
const pendingMutations = this._pendingMutations();
|
|
1133
1151
|
|
|
1134
1152
|
const { q, result } = querySubs[hash] || {};
|
|
1135
1153
|
if (!result) return;
|
|
@@ -1203,13 +1221,18 @@ export default class Reactor {
|
|
|
1203
1221
|
/** Re-compute all subscriptions */
|
|
1204
1222
|
notifyAll() {
|
|
1205
1223
|
Object.keys(this.queryCbs).forEach((hash) => {
|
|
1206
|
-
this.
|
|
1224
|
+
this.querySubs
|
|
1225
|
+
.waitForKeyToLoad(hash)
|
|
1226
|
+
.then(() => this.notifyOne(hash))
|
|
1227
|
+
.catch(() => this.notifyOne(hash));
|
|
1207
1228
|
});
|
|
1208
1229
|
}
|
|
1209
1230
|
|
|
1210
1231
|
loadedNotifyAll() {
|
|
1211
|
-
|
|
1212
|
-
|
|
1232
|
+
this.kv
|
|
1233
|
+
.waitForKeyToLoad('pendingMutations')
|
|
1234
|
+
.then(() => this.notifyAll())
|
|
1235
|
+
.catch(() => this.notifyAll());
|
|
1213
1236
|
}
|
|
1214
1237
|
|
|
1215
1238
|
/** Applies transactions locally and sends transact message to server */
|
|
@@ -1243,7 +1266,7 @@ export default class Reactor {
|
|
|
1243
1266
|
*/
|
|
1244
1267
|
pushOps = (txSteps, error) => {
|
|
1245
1268
|
const eventId = uuid();
|
|
1246
|
-
const mutations = [...this.
|
|
1269
|
+
const mutations = [...this._pendingMutations().values()];
|
|
1247
1270
|
const order = Math.max(0, ...mutations.map((mut) => mut.order || 0)) + 1;
|
|
1248
1271
|
const mutation = {
|
|
1249
1272
|
op: 'transact',
|
|
@@ -1252,9 +1275,8 @@ export default class Reactor {
|
|
|
1252
1275
|
error,
|
|
1253
1276
|
order,
|
|
1254
1277
|
};
|
|
1255
|
-
this.
|
|
1278
|
+
this._updatePendingMutations((prev) => {
|
|
1256
1279
|
prev.set(eventId, mutation);
|
|
1257
|
-
return prev;
|
|
1258
1280
|
});
|
|
1259
1281
|
|
|
1260
1282
|
const dfd = new Deferred();
|
|
@@ -1295,7 +1317,7 @@ export default class Reactor {
|
|
|
1295
1317
|
Math.min(
|
|
1296
1318
|
this._inFlightMutationEventIds.size + 1,
|
|
1297
1319
|
// Defensive code in case we don't clean up in flight mutation event ids
|
|
1298
|
-
this.
|
|
1320
|
+
this._pendingMutations().size + 1,
|
|
1299
1321
|
) * 6000,
|
|
1300
1322
|
);
|
|
1301
1323
|
|
|
@@ -1341,7 +1363,7 @@ export default class Reactor {
|
|
|
1341
1363
|
|
|
1342
1364
|
const muts = this._rewriteMutationsSorted(
|
|
1343
1365
|
this.attrs,
|
|
1344
|
-
this.
|
|
1366
|
+
this._pendingMutations(),
|
|
1345
1367
|
);
|
|
1346
1368
|
muts.forEach(([eventId, mut]) => {
|
|
1347
1369
|
if (!mut['tx-id']) {
|
|
@@ -1363,13 +1385,12 @@ export default class Reactor {
|
|
|
1363
1385
|
}
|
|
1364
1386
|
}
|
|
1365
1387
|
|
|
1366
|
-
this.
|
|
1388
|
+
this._updatePendingMutations((prev) => {
|
|
1367
1389
|
for (const [eventId, mut] of Array.from(prev.entries())) {
|
|
1368
1390
|
if (mut['tx-id'] && mut['tx-id'] <= minProcessedTxId) {
|
|
1369
1391
|
prev.delete(eventId);
|
|
1370
1392
|
}
|
|
1371
1393
|
}
|
|
1372
|
-
return prev;
|
|
1373
1394
|
});
|
|
1374
1395
|
}
|
|
1375
1396
|
|
|
@@ -1379,16 +1400,13 @@ export default class Reactor {
|
|
|
1379
1400
|
* unaffected by this mutation and it’s safe to delete it from local queue
|
|
1380
1401
|
*/
|
|
1381
1402
|
_cleanupPendingMutationsTimeout() {
|
|
1382
|
-
if (
|
|
1383
|
-
this.pendingMutations.currentValue.size <
|
|
1384
|
-
this._pendingMutationCleanupThreshold
|
|
1385
|
-
) {
|
|
1403
|
+
if (this._pendingMutations().size < this._pendingMutationCleanupThreshold) {
|
|
1386
1404
|
return;
|
|
1387
1405
|
}
|
|
1388
1406
|
|
|
1389
1407
|
const now = Date.now();
|
|
1390
1408
|
|
|
1391
|
-
this.
|
|
1409
|
+
this._updatePendingMutations((prev) => {
|
|
1392
1410
|
for (const [eventId, mut] of Array.from(prev.entries())) {
|
|
1393
1411
|
if (
|
|
1394
1412
|
mut.confirmed &&
|
|
@@ -1397,8 +1415,6 @@ export default class Reactor {
|
|
|
1397
1415
|
prev.delete(eventId);
|
|
1398
1416
|
}
|
|
1399
1417
|
}
|
|
1400
|
-
|
|
1401
|
-
return prev;
|
|
1402
1418
|
});
|
|
1403
1419
|
}
|
|
1404
1420
|
|
|
@@ -1616,21 +1632,23 @@ export default class Reactor {
|
|
|
1616
1632
|
*
|
|
1617
1633
|
* Note: If the user deletes their local storage, this id will change.
|
|
1618
1634
|
*
|
|
1619
|
-
* We use this._localIdPromises to ensure that we only generate a local
|
|
1620
|
-
* id once, even if multiple callers call this function concurrently.
|
|
1621
1635
|
*/
|
|
1622
1636
|
async getLocalId(name) {
|
|
1623
1637
|
const k = `localToken_${name}`;
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1638
|
+
if (this.kv.currentValue[k]) {
|
|
1639
|
+
return this.kv.currentValue[k];
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1642
|
+
const current = await this.kv.waitForKeyToLoad(k);
|
|
1643
|
+
if (current) {
|
|
1644
|
+
return current;
|
|
1628
1645
|
}
|
|
1629
1646
|
const newId = uuid();
|
|
1630
|
-
this.
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1647
|
+
this.kv.updateInPlace((prev) => {
|
|
1648
|
+
if (prev[k]) return;
|
|
1649
|
+
prev[k] = newId;
|
|
1650
|
+
});
|
|
1651
|
+
return await this.kv.waitForKeyToLoad(k);
|
|
1634
1652
|
}
|
|
1635
1653
|
|
|
1636
1654
|
// ----
|
|
@@ -1821,7 +1839,10 @@ export default class Reactor {
|
|
|
1821
1839
|
}
|
|
1822
1840
|
|
|
1823
1841
|
async setCurrentUser(user) {
|
|
1824
|
-
|
|
1842
|
+
this.kv.updateInPlace((prev) => {
|
|
1843
|
+
prev[currentUserKey] = user;
|
|
1844
|
+
});
|
|
1845
|
+
await this.kv.waitForKeyToLoad(currentUserKey);
|
|
1825
1846
|
}
|
|
1826
1847
|
|
|
1827
1848
|
getCurrentUserCached() {
|
|
@@ -1829,7 +1850,7 @@ export default class Reactor {
|
|
|
1829
1850
|
}
|
|
1830
1851
|
|
|
1831
1852
|
async _getCurrentUser() {
|
|
1832
|
-
const user = await this.
|
|
1853
|
+
const user = await this.kv.waitForKeyToLoad(currentUserKey);
|
|
1833
1854
|
return typeof user === 'string' ? JSON.parse(user) : user;
|
|
1834
1855
|
}
|
|
1835
1856
|
|
|
@@ -1840,18 +1861,26 @@ export default class Reactor {
|
|
|
1840
1861
|
this._currentUserCached = { isLoading: false, ...errorV };
|
|
1841
1862
|
return errorV;
|
|
1842
1863
|
}
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1864
|
+
try {
|
|
1865
|
+
const user = await this._getCurrentUser();
|
|
1866
|
+
const userV = { user: user, error: undefined };
|
|
1867
|
+
this._currentUserCached = {
|
|
1868
|
+
isLoading: false,
|
|
1869
|
+
...userV,
|
|
1870
|
+
};
|
|
1871
|
+
return userV;
|
|
1872
|
+
} catch (e) {
|
|
1873
|
+
return {
|
|
1874
|
+
user: undefined,
|
|
1875
|
+
isLoading: false,
|
|
1876
|
+
error: { message: e?.message || 'Error loading user' },
|
|
1877
|
+
};
|
|
1878
|
+
}
|
|
1850
1879
|
}
|
|
1851
1880
|
|
|
1852
1881
|
async _hasCurrentUser() {
|
|
1853
|
-
const user = await this.
|
|
1854
|
-
return JSON.parse(user) != null;
|
|
1882
|
+
const user = await this.kv.waitForKeyToLoad(currentUserKey);
|
|
1883
|
+
return typeof user === 'string' ? JSON.parse(user) != null : user != null;
|
|
1855
1884
|
}
|
|
1856
1885
|
|
|
1857
1886
|
async changeCurrentUser(newUser) {
|
|
@@ -1877,11 +1906,10 @@ export default class Reactor {
|
|
|
1877
1906
|
const newV = { error: undefined, user: newUser };
|
|
1878
1907
|
this._currentUserCached = { isLoading: false, ...newV };
|
|
1879
1908
|
this._dataForQueryCache = {};
|
|
1880
|
-
this.querySubs.
|
|
1909
|
+
this.querySubs.updateInPlace((prev) => {
|
|
1881
1910
|
Object.keys(prev).forEach((k) => {
|
|
1882
1911
|
delete prev[k].result;
|
|
1883
1912
|
});
|
|
1884
|
-
return prev;
|
|
1885
1913
|
});
|
|
1886
1914
|
this._reconnectTimeoutMs = 0;
|
|
1887
1915
|
this._transport.close();
|