@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/dist/esm/Reactor.js
CHANGED
|
@@ -101,30 +101,46 @@ const ignoreLogging = {
|
|
|
101
101
|
'refresh-presence': true,
|
|
102
102
|
'patch-presence': true,
|
|
103
103
|
};
|
|
104
|
-
function
|
|
104
|
+
function querySubFromStorage(x, useDateObjects) {
|
|
105
105
|
var _a;
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
const storeJSON = v.result.store;
|
|
111
|
-
v.result.store = s.fromJSON(Object.assign(Object.assign({}, storeJSON), { useDateObjects: useDateObjects }));
|
|
112
|
-
}
|
|
106
|
+
const v = typeof x === 'string' ? JSON.parse(x) : x;
|
|
107
|
+
if ((_a = v === null || v === void 0 ? void 0 : v.result) === null || _a === void 0 ? void 0 : _a.store) {
|
|
108
|
+
const storeJSON = v.result.store;
|
|
109
|
+
v.result.store = s.fromJSON(Object.assign(Object.assign({}, storeJSON), { useDateObjects: useDateObjects }));
|
|
113
110
|
}
|
|
114
|
-
return
|
|
111
|
+
return v;
|
|
115
112
|
}
|
|
116
|
-
function
|
|
113
|
+
function querySubToStorage(_key, sub) {
|
|
117
114
|
var _a;
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
115
|
+
const jsonSub = Object.assign({}, sub);
|
|
116
|
+
if ((_a = sub.result) === null || _a === void 0 ? void 0 : _a.store) {
|
|
117
|
+
jsonSub.result = Object.assign(Object.assign({}, sub.result), { store: s.toJSON(sub.result.store) });
|
|
118
|
+
}
|
|
119
|
+
return jsonSub;
|
|
120
|
+
}
|
|
121
|
+
function kvFromStorage(key, x) {
|
|
122
|
+
switch (key) {
|
|
123
|
+
case 'pendingMutations':
|
|
124
|
+
return new Map(typeof x === 'string' ? JSON.parse(x) : x);
|
|
125
|
+
default:
|
|
126
|
+
return x;
|
|
126
127
|
}
|
|
127
|
-
|
|
128
|
+
}
|
|
129
|
+
function kvToStorage(key, x) {
|
|
130
|
+
switch (key) {
|
|
131
|
+
case 'pendingMutations':
|
|
132
|
+
return [...x.entries()];
|
|
133
|
+
default:
|
|
134
|
+
return x;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
function onMergeQuerySub(_k, storageSub, inMemorySub) {
|
|
138
|
+
const storageResult = storageSub === null || storageSub === void 0 ? void 0 : storageSub.result;
|
|
139
|
+
const memoryResult = inMemorySub === null || inMemorySub === void 0 ? void 0 : inMemorySub.result;
|
|
140
|
+
if (storageResult && !memoryResult && inMemorySub) {
|
|
141
|
+
inMemorySub.result = storageResult;
|
|
142
|
+
}
|
|
143
|
+
return inMemorySub || storageSub;
|
|
128
144
|
}
|
|
129
145
|
function sortedMutationEntries(entries) {
|
|
130
146
|
return [...entries].sort((a, b) => {
|
|
@@ -179,65 +195,27 @@ export default class Reactor {
|
|
|
179
195
|
this._beforeUnloadCbs = [];
|
|
180
196
|
this._dataForQueryCache = {};
|
|
181
197
|
this._inFlightMutationEventIds = new Set();
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
});
|
|
200
|
-
// Consider a storageSub with no corresponding inMemorySub
|
|
201
|
-
// This means that at least at this point,
|
|
202
|
-
// the user has not asked to subscribe to the query.
|
|
203
|
-
// We may _still_ want to add it, because in just a
|
|
204
|
-
// few milliseconds, the user will ask to subscribe to the
|
|
205
|
-
// query.
|
|
206
|
-
// For now, we can't really tell if the user will ask to subscribe
|
|
207
|
-
// or not. So for now let's just add the first 10 queries from storage.
|
|
208
|
-
// Eventually, we could be smarter about this. For example,
|
|
209
|
-
// we can keep usage information about which queries are popular.
|
|
210
|
-
const storageKsToAdd = Object.keys(storageSubs)
|
|
211
|
-
.filter((k) => !inMemorySubs[k])
|
|
212
|
-
.sort((a, b) => {
|
|
213
|
-
var _a, _b;
|
|
214
|
-
// Sort by lastAccessed, newest first
|
|
215
|
-
const aTime = ((_a = storageSubs[a]) === null || _a === void 0 ? void 0 : _a.lastAccessed) || 0;
|
|
216
|
-
const bTime = ((_b = storageSubs[b]) === null || _b === void 0 ? void 0 : _b.lastAccessed) || 0;
|
|
217
|
-
return bTime - aTime;
|
|
218
|
-
})
|
|
219
|
-
.slice(0, this.queryCacheLimit);
|
|
220
|
-
storageKsToAdd.forEach((k) => {
|
|
221
|
-
ret[k] = storageSubs[k];
|
|
222
|
-
});
|
|
223
|
-
// Okay, now we have merged our querySubs
|
|
224
|
-
this.querySubs.set((_) => ret);
|
|
225
|
-
this.loadedNotifyAll();
|
|
226
|
-
};
|
|
227
|
-
/**
|
|
228
|
-
* merge pendingMutations from storage and in memory. Has a side effect of
|
|
229
|
-
* sending mutations that were stored but not acked
|
|
230
|
-
*/
|
|
231
|
-
this._onMergePendingMutations = (storageMuts, inMemoryMuts) => {
|
|
232
|
-
const ret = new Map([...storageMuts.entries(), ...inMemoryMuts.entries()]);
|
|
233
|
-
this.pendingMutations.set((_) => ret);
|
|
234
|
-
this.loadedNotifyAll();
|
|
235
|
-
const rewrittenStorageMuts = this._rewriteMutationsSorted(this.attrs, storageMuts);
|
|
236
|
-
rewrittenStorageMuts.forEach(([k, mut]) => {
|
|
237
|
-
if (!inMemoryMuts.has(k) && !mut['tx-id']) {
|
|
238
|
-
this._sendMutation(k, mut);
|
|
198
|
+
this._onMergeKv = (key, storageV, inMemoryV) => {
|
|
199
|
+
var _a, _b;
|
|
200
|
+
switch (key) {
|
|
201
|
+
case 'pendingMutations': {
|
|
202
|
+
const storageEntries = (_a = storageV === null || storageV === void 0 ? void 0 : storageV.entries()) !== null && _a !== void 0 ? _a : [];
|
|
203
|
+
const inMemoryEntries = (_b = inMemoryV === null || inMemoryV === void 0 ? void 0 : inMemoryV.entries()) !== null && _b !== void 0 ? _b : [];
|
|
204
|
+
const muts = new Map([...storageEntries, ...inMemoryEntries]);
|
|
205
|
+
const rewrittenStorageMuts = storageV
|
|
206
|
+
? this._rewriteMutationsSorted(this.attrs, storageV)
|
|
207
|
+
: [];
|
|
208
|
+
rewrittenStorageMuts.forEach(([k, mut]) => {
|
|
209
|
+
var _a;
|
|
210
|
+
if (!((_a = inMemoryV === null || inMemoryV === void 0 ? void 0 : inMemoryV.pendingMutations) === null || _a === void 0 ? void 0 : _a.has(k)) && !mut['tx-id']) {
|
|
211
|
+
this._sendMutation(k, mut);
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
return muts;
|
|
239
215
|
}
|
|
240
|
-
|
|
216
|
+
default:
|
|
217
|
+
return inMemoryV || storageV;
|
|
218
|
+
}
|
|
241
219
|
};
|
|
242
220
|
// ---------------------------
|
|
243
221
|
// Queries
|
|
@@ -296,7 +274,7 @@ export default class Reactor {
|
|
|
296
274
|
*/
|
|
297
275
|
this.pushOps = (txSteps, error) => {
|
|
298
276
|
const eventId = uuid();
|
|
299
|
-
const mutations = [...this.
|
|
277
|
+
const mutations = [...this._pendingMutations().values()];
|
|
300
278
|
const order = Math.max(0, ...mutations.map((mut) => mut.order || 0)) + 1;
|
|
301
279
|
const mutation = {
|
|
302
280
|
op: 'transact',
|
|
@@ -305,9 +283,8 @@ export default class Reactor {
|
|
|
305
283
|
error,
|
|
306
284
|
order,
|
|
307
285
|
};
|
|
308
|
-
this.
|
|
286
|
+
this._updatePendingMutations((prev) => {
|
|
309
287
|
prev.set(eventId, mutation);
|
|
310
|
-
return prev;
|
|
311
288
|
});
|
|
312
289
|
const dfd = new Deferred();
|
|
313
290
|
this.mutationDeferredStore.set(eventId, dfd);
|
|
@@ -436,7 +413,7 @@ export default class Reactor {
|
|
|
436
413
|
}));
|
|
437
414
|
}
|
|
438
415
|
this._initStorage(Storage);
|
|
439
|
-
this._syncTable = new SyncTable(this._trySendAuthed.bind(this), this.
|
|
416
|
+
this._syncTable = new SyncTable(this._trySendAuthed.bind(this), new Storage(this.config.appId, 'syncSubs'), {
|
|
440
417
|
useDateObjects: this.config.useDateObjects,
|
|
441
418
|
}, this._log, (triples) => s.createStore(this.attrs, triples, this.config.enableCardinalityInference, this._linkIndex, this.config.useDateObjects));
|
|
442
419
|
this._oauthCallbackResponse = this._oauthLoginInit();
|
|
@@ -475,21 +452,55 @@ export default class Reactor {
|
|
|
475
452
|
_reactorStats() {
|
|
476
453
|
return {
|
|
477
454
|
inFlightMutationCount: this._inFlightMutationEventIds.size,
|
|
478
|
-
pendingMutationCount: this.
|
|
455
|
+
pendingMutationCount: this._pendingMutations().size,
|
|
479
456
|
transportType: this._transportType,
|
|
480
457
|
};
|
|
481
458
|
}
|
|
459
|
+
_onQuerySubLoaded(hash) {
|
|
460
|
+
this.kv
|
|
461
|
+
.waitForKeyToLoad('pendingMutations')
|
|
462
|
+
.then(() => this.notifyOne(hash));
|
|
463
|
+
}
|
|
482
464
|
_initStorage(Storage) {
|
|
483
|
-
this.
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
465
|
+
this.querySubs = new PersistedObject({
|
|
466
|
+
persister: new Storage(this.config.appId, 'querySubs'),
|
|
467
|
+
merge: onMergeQuerySub,
|
|
468
|
+
serialize: querySubToStorage,
|
|
469
|
+
parse: (_key, x) => querySubFromStorage(x, this.config.useDateObjects),
|
|
470
|
+
// objectSize
|
|
471
|
+
objectSize: (x) => { var _a, _b, _c, _d; return (_d = (_c = (_b = (_a = x.result) === null || _a === void 0 ? void 0 : _a.store) === null || _b === void 0 ? void 0 : _b.triples) === null || _c === void 0 ? void 0 : _c.length) !== null && _d !== void 0 ? _d : 0; },
|
|
472
|
+
logger: this._log,
|
|
473
|
+
preloadEntryCount: 10,
|
|
474
|
+
gc: {
|
|
475
|
+
maxAgeMs: 1000 * 60 * 60 * 24 * 7 * 52, // 1 year
|
|
476
|
+
maxEntries: 1000,
|
|
477
|
+
// Size of each query is the number of triples
|
|
478
|
+
maxSize: 1000000, // 1 million triples
|
|
479
|
+
},
|
|
480
|
+
});
|
|
481
|
+
this.querySubs.onKeyLoaded = (k) => this._onQuerySubLoaded(k);
|
|
482
|
+
this.kv = new PersistedObject({
|
|
483
|
+
persister: new Storage(this.config.appId, 'kv'),
|
|
484
|
+
merge: this._onMergeKv,
|
|
485
|
+
serialize: kvToStorage,
|
|
486
|
+
parse: kvFromStorage,
|
|
487
|
+
objectSize: () => 0,
|
|
488
|
+
logger: this._log,
|
|
489
|
+
saveThrottleMs: 100,
|
|
490
|
+
idleCallbackMaxWaitMs: 100,
|
|
491
|
+
// Don't GC the kv store
|
|
492
|
+
gc: null,
|
|
493
|
+
});
|
|
494
|
+
this.kv.onKeyLoaded = (k) => {
|
|
495
|
+
if (k === 'pendingMutations') {
|
|
496
|
+
this.notifyAll();
|
|
497
|
+
}
|
|
498
|
+
};
|
|
499
|
+
// Trigger immediate load for pendingMutations and currentUser
|
|
500
|
+
this.kv.waitForKeyToLoad('pendingMutations');
|
|
501
|
+
this.kv.waitForKeyToLoad(currentUserKey);
|
|
491
502
|
this._beforeUnloadCbs.push(() => {
|
|
492
|
-
this.
|
|
503
|
+
this.kv.flush();
|
|
493
504
|
this.querySubs.flush();
|
|
494
505
|
});
|
|
495
506
|
}
|
|
@@ -585,18 +596,24 @@ export default class Reactor {
|
|
|
585
596
|
case 'add-query-ok': {
|
|
586
597
|
const { q, result } = msg;
|
|
587
598
|
const hash = weakHash(q);
|
|
599
|
+
if (!this._hasQueryListeners() && !this.querySubs.currentValue[hash]) {
|
|
600
|
+
break;
|
|
601
|
+
}
|
|
588
602
|
const pageInfo = (_d = (_c = result === null || result === void 0 ? void 0 : result[0]) === null || _c === void 0 ? void 0 : _c.data) === null || _d === void 0 ? void 0 : _d['page-info'];
|
|
589
603
|
const aggregate = (_f = (_e = result === null || result === void 0 ? void 0 : result[0]) === null || _e === void 0 ? void 0 : _e.data) === null || _f === void 0 ? void 0 : _f['aggregate'];
|
|
590
604
|
const triples = extractTriples(result);
|
|
591
605
|
const store = s.createStore(this.attrs, triples, enableCardinalityInference, this._linkIndex, this.config.useDateObjects);
|
|
592
|
-
this.querySubs.
|
|
606
|
+
this.querySubs.updateInPlace((prev) => {
|
|
607
|
+
if (!prev[hash]) {
|
|
608
|
+
this._log.info('Missing value in querySubs', { hash, q });
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
593
611
|
prev[hash].result = {
|
|
594
612
|
store,
|
|
595
613
|
pageInfo,
|
|
596
614
|
aggregate,
|
|
597
615
|
processedTxId: msg['processed-tx-id'],
|
|
598
616
|
};
|
|
599
|
-
return prev;
|
|
600
617
|
});
|
|
601
618
|
this._cleanupPendingMutationsQueries();
|
|
602
619
|
this.notifyOne(hash);
|
|
@@ -627,12 +644,14 @@ export default class Reactor {
|
|
|
627
644
|
this._setAttrs(attrs);
|
|
628
645
|
}
|
|
629
646
|
this._cleanupPendingMutationsTimeout();
|
|
630
|
-
const rewrittenMutations = this._rewriteMutations(this.attrs, this.
|
|
631
|
-
if (rewrittenMutations !== this.
|
|
647
|
+
const rewrittenMutations = this._rewriteMutations(this.attrs, this._pendingMutations(), processedTxId);
|
|
648
|
+
if (rewrittenMutations !== this._pendingMutations()) {
|
|
632
649
|
// We know we've changed the mutations to fix the attr ids and removed
|
|
633
650
|
// processed attrs, so we'll persist those changes to prevent optimisticAttrs
|
|
634
651
|
// from using old attr definitions
|
|
635
|
-
this.
|
|
652
|
+
this.kv.updateInPlace((prev) => {
|
|
653
|
+
prev.pendingMutations = rewrittenMutations;
|
|
654
|
+
});
|
|
636
655
|
}
|
|
637
656
|
const mutations = sortedMutationEntries(rewrittenMutations.entries());
|
|
638
657
|
const updates = computations.map((x) => {
|
|
@@ -645,12 +664,15 @@ export default class Reactor {
|
|
|
645
664
|
const newStore = this._applyOptimisticUpdates(store, mutations, processedTxId);
|
|
646
665
|
const pageInfo = (_b = (_a = result === null || result === void 0 ? void 0 : result[0]) === null || _a === void 0 ? void 0 : _a.data) === null || _b === void 0 ? void 0 : _b['page-info'];
|
|
647
666
|
const aggregate = (_d = (_c = result === null || result === void 0 ? void 0 : result[0]) === null || _c === void 0 ? void 0 : _c.data) === null || _d === void 0 ? void 0 : _d['aggregate'];
|
|
648
|
-
return { hash, store: newStore, pageInfo, aggregate };
|
|
667
|
+
return { q, hash, store: newStore, pageInfo, aggregate };
|
|
649
668
|
});
|
|
650
|
-
updates.forEach(({ hash, store, pageInfo, aggregate }) => {
|
|
651
|
-
this.querySubs.
|
|
669
|
+
updates.forEach(({ hash, q, store, pageInfo, aggregate }) => {
|
|
670
|
+
this.querySubs.updateInPlace((prev) => {
|
|
671
|
+
if (!prev[hash]) {
|
|
672
|
+
this._log.error('Missing value in querySubs', { hash, q });
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
652
675
|
prev[hash].result = { store, pageInfo, aggregate, processedTxId };
|
|
653
|
-
return prev;
|
|
654
676
|
});
|
|
655
677
|
});
|
|
656
678
|
this._cleanupPendingMutationsQueries();
|
|
@@ -662,15 +684,14 @@ export default class Reactor {
|
|
|
662
684
|
case 'transact-ok': {
|
|
663
685
|
const { 'client-event-id': eventId, 'tx-id': txId } = msg;
|
|
664
686
|
this._inFlightMutationEventIds.delete(eventId);
|
|
665
|
-
const muts = this._rewriteMutations(this.attrs, this.
|
|
687
|
+
const muts = this._rewriteMutations(this.attrs, this._pendingMutations());
|
|
666
688
|
const prevMutation = muts.get(eventId);
|
|
667
689
|
if (!prevMutation) {
|
|
668
690
|
break;
|
|
669
691
|
}
|
|
670
692
|
// update pendingMutation with server-side tx-id
|
|
671
|
-
this.
|
|
693
|
+
this._updatePendingMutations((prev) => {
|
|
672
694
|
prev.set(eventId, Object.assign(Object.assign({}, prev.get(eventId)), { 'tx-id': txId, confirmed: Date.now() }));
|
|
673
|
-
return prev;
|
|
674
695
|
});
|
|
675
696
|
const newAttrs = prevMutation['tx-steps']
|
|
676
697
|
.filter(([action, ..._args]) => action === 'add-attr')
|
|
@@ -737,15 +758,27 @@ export default class Reactor {
|
|
|
737
758
|
break;
|
|
738
759
|
}
|
|
739
760
|
}
|
|
761
|
+
_pendingMutations() {
|
|
762
|
+
var _a;
|
|
763
|
+
return (_a = this.kv.currentValue.pendingMutations) !== null && _a !== void 0 ? _a : new Map();
|
|
764
|
+
}
|
|
765
|
+
_updatePendingMutations(f) {
|
|
766
|
+
this.kv.updateInPlace((prev) => {
|
|
767
|
+
var _a;
|
|
768
|
+
const muts = (_a = prev.pendingMutations) !== null && _a !== void 0 ? _a : new Map();
|
|
769
|
+
prev.pendingMutations = muts;
|
|
770
|
+
f(muts);
|
|
771
|
+
});
|
|
772
|
+
}
|
|
740
773
|
/**
|
|
741
774
|
* @param {'timeout' | 'error'} status
|
|
742
775
|
* @param {string} eventId
|
|
743
776
|
* @param {{message: string, type?: string, status?: number, hint?: unknown}} errorMsg
|
|
744
777
|
*/
|
|
745
778
|
_handleMutationError(status, eventId, errorMsg) {
|
|
746
|
-
const mut = this.
|
|
779
|
+
const mut = this._pendingMutations().get(eventId);
|
|
747
780
|
if (mut && (status !== 'timeout' || !mut['tx-id'])) {
|
|
748
|
-
this.
|
|
781
|
+
this._updatePendingMutations((prev) => {
|
|
749
782
|
prev.delete(eventId);
|
|
750
783
|
return prev;
|
|
751
784
|
});
|
|
@@ -766,7 +799,7 @@ export default class Reactor {
|
|
|
766
799
|
const eventId = msg['client-event-id'];
|
|
767
800
|
// This might not be a mutation, but it can't hurt to delete it
|
|
768
801
|
this._inFlightMutationEventIds.delete(eventId);
|
|
769
|
-
const prevMutation = this.
|
|
802
|
+
const prevMutation = this._pendingMutations().get(eventId);
|
|
770
803
|
const errorMessage = {
|
|
771
804
|
message: msg.message || 'Uh-oh, something went wrong. Ping Joe & Stopa.',
|
|
772
805
|
};
|
|
@@ -833,10 +866,9 @@ export default class Reactor {
|
|
|
833
866
|
}
|
|
834
867
|
_startQuerySub(q, hash) {
|
|
835
868
|
const eventId = uuid();
|
|
836
|
-
this.querySubs.
|
|
869
|
+
this.querySubs.updateInPlace((prev) => {
|
|
837
870
|
prev[hash] = prev[hash] || { q, result: null, eventId };
|
|
838
871
|
prev[hash].lastAccessed = Date.now();
|
|
839
|
-
return prev;
|
|
840
872
|
});
|
|
841
873
|
this._trySendAuthed(eventId, { op: 'add-query', q });
|
|
842
874
|
return eventId;
|
|
@@ -910,13 +942,18 @@ export default class Reactor {
|
|
|
910
942
|
this.queryCbs[hash] = this.queryCbs[hash].filter((r) => r.cb !== cb);
|
|
911
943
|
this._cleanupQuery(q, hash);
|
|
912
944
|
}
|
|
913
|
-
|
|
945
|
+
_hasQueryListeners(hash) {
|
|
914
946
|
var _a, _b;
|
|
915
|
-
|
|
947
|
+
return !!(((_a = this.queryCbs[hash]) === null || _a === void 0 ? void 0 : _a.length) || ((_b = this.queryOnceDfds[hash]) === null || _b === void 0 ? void 0 : _b.length));
|
|
948
|
+
}
|
|
949
|
+
_cleanupQuery(q, hash) {
|
|
950
|
+
const hasListeners = this._hasQueryListeners(hash);
|
|
916
951
|
if (hasListeners)
|
|
917
952
|
return;
|
|
918
953
|
delete this.queryCbs[hash];
|
|
919
954
|
delete this.queryOnceDfds[hash];
|
|
955
|
+
delete this._dataForQueryCache[hash];
|
|
956
|
+
this.querySubs.unloadKey(hash);
|
|
920
957
|
this._trySendAuthed(uuid(), { op: 'remove-query', q });
|
|
921
958
|
}
|
|
922
959
|
// When we `pushTx`, it's possible that we don't yet have `this.attrs`
|
|
@@ -933,6 +970,8 @@ export default class Reactor {
|
|
|
933
970
|
_rewriteMutations(attrs, muts, processedTxId) {
|
|
934
971
|
if (!attrs)
|
|
935
972
|
return muts;
|
|
973
|
+
if (!muts)
|
|
974
|
+
return new Map();
|
|
936
975
|
const findExistingAttr = (attr) => {
|
|
937
976
|
const [_, etype, label] = attr['forward-identity'];
|
|
938
977
|
const existing = instaml.getAttrByFwdIdentName(attrs, etype, label);
|
|
@@ -1005,9 +1044,7 @@ export default class Reactor {
|
|
|
1005
1044
|
// Transact
|
|
1006
1045
|
optimisticAttrs() {
|
|
1007
1046
|
var _a;
|
|
1008
|
-
const pendingMutationSteps = [
|
|
1009
|
-
...this.pendingMutations.currentValue.values(),
|
|
1010
|
-
] // hack due to Map()
|
|
1047
|
+
const pendingMutationSteps = [...this._pendingMutations().values()] // hack due to Map()
|
|
1011
1048
|
.flatMap((x) => x['tx-steps']);
|
|
1012
1049
|
const deletedAttrIds = new Set(pendingMutationSteps
|
|
1013
1050
|
.filter(([action, _attr]) => action === 'delete-attr')
|
|
@@ -1039,12 +1076,12 @@ export default class Reactor {
|
|
|
1039
1076
|
}
|
|
1040
1077
|
if (!this.querySubs)
|
|
1041
1078
|
return;
|
|
1042
|
-
if (!this.pendingMutations)
|
|
1079
|
+
if (!this.kv.currentValue.pendingMutations)
|
|
1043
1080
|
return;
|
|
1044
1081
|
const querySubVersion = this.querySubs.version();
|
|
1045
1082
|
const querySubs = this.querySubs.currentValue;
|
|
1046
|
-
const pendingMutationsVersion = this.
|
|
1047
|
-
const pendingMutations = this.
|
|
1083
|
+
const pendingMutationsVersion = this.kv.version();
|
|
1084
|
+
const pendingMutations = this._pendingMutations();
|
|
1048
1085
|
const { q, result } = querySubs[hash] || {};
|
|
1049
1086
|
if (!result)
|
|
1050
1087
|
return;
|
|
@@ -1076,13 +1113,17 @@ export default class Reactor {
|
|
|
1076
1113
|
/** Re-compute all subscriptions */
|
|
1077
1114
|
notifyAll() {
|
|
1078
1115
|
Object.keys(this.queryCbs).forEach((hash) => {
|
|
1079
|
-
this.
|
|
1116
|
+
this.querySubs
|
|
1117
|
+
.waitForKeyToLoad(hash)
|
|
1118
|
+
.then(() => this.notifyOne(hash))
|
|
1119
|
+
.catch(() => this.notifyOne(hash));
|
|
1080
1120
|
});
|
|
1081
1121
|
}
|
|
1082
1122
|
loadedNotifyAll() {
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1123
|
+
this.kv
|
|
1124
|
+
.waitForKeyToLoad('pendingMutations')
|
|
1125
|
+
.then(() => this.notifyAll())
|
|
1126
|
+
.catch(() => this.notifyAll());
|
|
1086
1127
|
}
|
|
1087
1128
|
shutdown() {
|
|
1088
1129
|
var _a;
|
|
@@ -1110,7 +1151,7 @@ export default class Reactor {
|
|
|
1110
1151
|
}
|
|
1111
1152
|
const timeoutMs = Math.max(6000, Math.min(this._inFlightMutationEventIds.size + 1,
|
|
1112
1153
|
// Defensive code in case we don't clean up in flight mutation event ids
|
|
1113
|
-
this.
|
|
1154
|
+
this._pendingMutations().size + 1) * 6000);
|
|
1114
1155
|
if (!this._isOnline) {
|
|
1115
1156
|
this._finishTransaction('enqueued', eventId);
|
|
1116
1157
|
}
|
|
@@ -1147,7 +1188,7 @@ export default class Reactor {
|
|
|
1147
1188
|
.forEach(({ eventId, q }) => {
|
|
1148
1189
|
this._trySendAuthed(eventId, { op: 'add-query', q });
|
|
1149
1190
|
});
|
|
1150
|
-
const muts = this._rewriteMutationsSorted(this.attrs, this.
|
|
1191
|
+
const muts = this._rewriteMutationsSorted(this.attrs, this._pendingMutations());
|
|
1151
1192
|
muts.forEach(([eventId, mut]) => {
|
|
1152
1193
|
if (!mut['tx-id']) {
|
|
1153
1194
|
this._sendMutation(eventId, mut);
|
|
@@ -1165,13 +1206,12 @@ export default class Reactor {
|
|
|
1165
1206
|
minProcessedTxId = Math.min(minProcessedTxId, result === null || result === void 0 ? void 0 : result.processedTxId);
|
|
1166
1207
|
}
|
|
1167
1208
|
}
|
|
1168
|
-
this.
|
|
1209
|
+
this._updatePendingMutations((prev) => {
|
|
1169
1210
|
for (const [eventId, mut] of Array.from(prev.entries())) {
|
|
1170
1211
|
if (mut['tx-id'] && mut['tx-id'] <= minProcessedTxId) {
|
|
1171
1212
|
prev.delete(eventId);
|
|
1172
1213
|
}
|
|
1173
1214
|
}
|
|
1174
|
-
return prev;
|
|
1175
1215
|
});
|
|
1176
1216
|
}
|
|
1177
1217
|
/**
|
|
@@ -1180,19 +1220,17 @@ export default class Reactor {
|
|
|
1180
1220
|
* unaffected by this mutation and it’s safe to delete it from local queue
|
|
1181
1221
|
*/
|
|
1182
1222
|
_cleanupPendingMutationsTimeout() {
|
|
1183
|
-
if (this.
|
|
1184
|
-
this._pendingMutationCleanupThreshold) {
|
|
1223
|
+
if (this._pendingMutations().size < this._pendingMutationCleanupThreshold) {
|
|
1185
1224
|
return;
|
|
1186
1225
|
}
|
|
1187
1226
|
const now = Date.now();
|
|
1188
|
-
this.
|
|
1227
|
+
this._updatePendingMutations((prev) => {
|
|
1189
1228
|
for (const [eventId, mut] of Array.from(prev.entries())) {
|
|
1190
1229
|
if (mut.confirmed &&
|
|
1191
1230
|
mut.confirmed + this._pendingTxCleanupTimeout < now) {
|
|
1192
1231
|
prev.delete(eventId);
|
|
1193
1232
|
}
|
|
1194
1233
|
}
|
|
1195
|
-
return prev;
|
|
1196
1234
|
});
|
|
1197
1235
|
}
|
|
1198
1236
|
_trySendAuthed(...args) {
|
|
@@ -1268,23 +1306,24 @@ export default class Reactor {
|
|
|
1268
1306
|
*
|
|
1269
1307
|
* Note: If the user deletes their local storage, this id will change.
|
|
1270
1308
|
*
|
|
1271
|
-
* We use this._localIdPromises to ensure that we only generate a local
|
|
1272
|
-
* id once, even if multiple callers call this function concurrently.
|
|
1273
1309
|
*/
|
|
1274
1310
|
getLocalId(name) {
|
|
1275
1311
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1276
1312
|
const k = `localToken_${name}`;
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1313
|
+
if (this.kv.currentValue[k]) {
|
|
1314
|
+
return this.kv.currentValue[k];
|
|
1315
|
+
}
|
|
1316
|
+
const current = yield this.kv.waitForKeyToLoad(k);
|
|
1317
|
+
if (current) {
|
|
1318
|
+
return current;
|
|
1282
1319
|
}
|
|
1283
1320
|
const newId = uuid();
|
|
1284
|
-
this.
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1321
|
+
this.kv.updateInPlace((prev) => {
|
|
1322
|
+
if (prev[k])
|
|
1323
|
+
return;
|
|
1324
|
+
prev[k] = newId;
|
|
1325
|
+
});
|
|
1326
|
+
return yield this.kv.waitForKeyToLoad(k);
|
|
1288
1327
|
});
|
|
1289
1328
|
}
|
|
1290
1329
|
// ----
|
|
@@ -1459,7 +1498,10 @@ export default class Reactor {
|
|
|
1459
1498
|
}
|
|
1460
1499
|
setCurrentUser(user) {
|
|
1461
1500
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1462
|
-
|
|
1501
|
+
this.kv.updateInPlace((prev) => {
|
|
1502
|
+
prev[currentUserKey] = user;
|
|
1503
|
+
});
|
|
1504
|
+
yield this.kv.waitForKeyToLoad(currentUserKey);
|
|
1463
1505
|
});
|
|
1464
1506
|
}
|
|
1465
1507
|
getCurrentUserCached() {
|
|
@@ -1467,7 +1509,7 @@ export default class Reactor {
|
|
|
1467
1509
|
}
|
|
1468
1510
|
_getCurrentUser() {
|
|
1469
1511
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1470
|
-
const user = yield this.
|
|
1512
|
+
const user = yield this.kv.waitForKeyToLoad(currentUserKey);
|
|
1471
1513
|
return typeof user === 'string' ? JSON.parse(user) : user;
|
|
1472
1514
|
});
|
|
1473
1515
|
}
|
|
@@ -1479,16 +1521,25 @@ export default class Reactor {
|
|
|
1479
1521
|
this._currentUserCached = Object.assign({ isLoading: false }, errorV);
|
|
1480
1522
|
return errorV;
|
|
1481
1523
|
}
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1524
|
+
try {
|
|
1525
|
+
const user = yield this._getCurrentUser();
|
|
1526
|
+
const userV = { user: user, error: undefined };
|
|
1527
|
+
this._currentUserCached = Object.assign({ isLoading: false }, userV);
|
|
1528
|
+
return userV;
|
|
1529
|
+
}
|
|
1530
|
+
catch (e) {
|
|
1531
|
+
return {
|
|
1532
|
+
user: undefined,
|
|
1533
|
+
isLoading: false,
|
|
1534
|
+
error: { message: (e === null || e === void 0 ? void 0 : e.message) || 'Error loading user' },
|
|
1535
|
+
};
|
|
1536
|
+
}
|
|
1486
1537
|
});
|
|
1487
1538
|
}
|
|
1488
1539
|
_hasCurrentUser() {
|
|
1489
1540
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1490
|
-
const user = yield this.
|
|
1491
|
-
return JSON.parse(user) != null;
|
|
1541
|
+
const user = yield this.kv.waitForKeyToLoad(currentUserKey);
|
|
1542
|
+
return typeof user === 'string' ? JSON.parse(user) != null : user != null;
|
|
1492
1543
|
});
|
|
1493
1544
|
}
|
|
1494
1545
|
changeCurrentUser(newUser) {
|
|
@@ -1516,11 +1567,10 @@ export default class Reactor {
|
|
|
1516
1567
|
const newV = { error: undefined, user: newUser };
|
|
1517
1568
|
this._currentUserCached = Object.assign({ isLoading: false }, newV);
|
|
1518
1569
|
this._dataForQueryCache = {};
|
|
1519
|
-
this.querySubs.
|
|
1570
|
+
this.querySubs.updateInPlace((prev) => {
|
|
1520
1571
|
Object.keys(prev).forEach((k) => {
|
|
1521
1572
|
delete prev[k].result;
|
|
1522
1573
|
});
|
|
1523
|
-
return prev;
|
|
1524
1574
|
});
|
|
1525
1575
|
this._reconnectTimeoutMs = 0;
|
|
1526
1576
|
this._transport.close();
|