@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.
Files changed (60) hide show
  1. package/__tests__/src/Reactor.test.js +97 -21
  2. package/__tests__/src/utils/PersistedObject.test.ts +383 -0
  3. package/dist/commonjs/InMemoryStorage.d.ts +11 -0
  4. package/dist/commonjs/InMemoryStorage.d.ts.map +1 -0
  5. package/dist/commonjs/InMemoryStorage.js +48 -0
  6. package/dist/commonjs/InMemoryStorage.js.map +1 -0
  7. package/dist/commonjs/IndexedDBStorage.d.ts +10 -3
  8. package/dist/commonjs/IndexedDBStorage.d.ts.map +1 -1
  9. package/dist/commonjs/IndexedDBStorage.js +254 -8
  10. package/dist/commonjs/IndexedDBStorage.js.map +1 -1
  11. package/dist/commonjs/Reactor.d.ts +7 -16
  12. package/dist/commonjs/Reactor.d.ts.map +1 -1
  13. package/dist/commonjs/Reactor.js +199 -149
  14. package/dist/commonjs/Reactor.js.map +1 -1
  15. package/dist/commonjs/SyncTable.d.ts +3 -1
  16. package/dist/commonjs/SyncTable.d.ts.map +1 -1
  17. package/dist/commonjs/SyncTable.js +194 -168
  18. package/dist/commonjs/SyncTable.js.map +1 -1
  19. package/dist/commonjs/index.d.ts +2 -1
  20. package/dist/commonjs/index.d.ts.map +1 -1
  21. package/dist/commonjs/index.js +3 -1
  22. package/dist/commonjs/index.js.map +1 -1
  23. package/dist/commonjs/utils/PersistedObject.d.ts +73 -29
  24. package/dist/commonjs/utils/PersistedObject.d.ts.map +1 -1
  25. package/dist/commonjs/utils/PersistedObject.js +366 -77
  26. package/dist/commonjs/utils/PersistedObject.js.map +1 -1
  27. package/dist/esm/InMemoryStorage.d.ts +11 -0
  28. package/dist/esm/InMemoryStorage.d.ts.map +1 -0
  29. package/dist/esm/InMemoryStorage.js +45 -0
  30. package/dist/esm/InMemoryStorage.js.map +1 -0
  31. package/dist/esm/IndexedDBStorage.d.ts +10 -3
  32. package/dist/esm/IndexedDBStorage.d.ts.map +1 -1
  33. package/dist/esm/IndexedDBStorage.js +254 -8
  34. package/dist/esm/IndexedDBStorage.js.map +1 -1
  35. package/dist/esm/Reactor.d.ts +7 -16
  36. package/dist/esm/Reactor.d.ts.map +1 -1
  37. package/dist/esm/Reactor.js +199 -149
  38. package/dist/esm/Reactor.js.map +1 -1
  39. package/dist/esm/SyncTable.d.ts +3 -1
  40. package/dist/esm/SyncTable.d.ts.map +1 -1
  41. package/dist/esm/SyncTable.js +194 -168
  42. package/dist/esm/SyncTable.js.map +1 -1
  43. package/dist/esm/index.d.ts +2 -1
  44. package/dist/esm/index.d.ts.map +1 -1
  45. package/dist/esm/index.js +4 -1
  46. package/dist/esm/index.js.map +1 -1
  47. package/dist/esm/utils/PersistedObject.d.ts +73 -29
  48. package/dist/esm/utils/PersistedObject.d.ts.map +1 -1
  49. package/dist/esm/utils/PersistedObject.js +364 -76
  50. package/dist/esm/utils/PersistedObject.js.map +1 -1
  51. package/dist/standalone/index.js +1978 -1567
  52. package/dist/standalone/index.umd.cjs +3 -3
  53. package/package.json +2 -2
  54. package/src/InMemoryStorage.ts +34 -0
  55. package/src/IndexedDBStorage.ts +281 -9
  56. package/src/Reactor.js +212 -184
  57. package/src/SyncTable.ts +220 -191
  58. package/src/index.ts +8 -0
  59. package/src/utils/PersistedObject.ts +480 -109
  60. package/src/InMemoryStorage.js +0 -14
@@ -139,30 +139,46 @@ const ignoreLogging = {
139
139
  'refresh-presence': true,
140
140
  'patch-presence': true,
141
141
  };
142
- function querySubsFromStorage(x, useDateObjects) {
142
+ function querySubFromStorage(x, useDateObjects) {
143
143
  var _a;
144
- const parsed = typeof x === 'string' ? JSON.parse(x) : x;
145
- for (const key in parsed) {
146
- const v = parsed[key];
147
- if ((_a = v === null || v === void 0 ? void 0 : v.result) === null || _a === void 0 ? void 0 : _a.store) {
148
- const storeJSON = v.result.store;
149
- v.result.store = s.fromJSON(Object.assign(Object.assign({}, storeJSON), { useDateObjects: useDateObjects }));
150
- }
144
+ const v = typeof x === 'string' ? JSON.parse(x) : x;
145
+ if ((_a = v === null || v === void 0 ? void 0 : v.result) === null || _a === void 0 ? void 0 : _a.store) {
146
+ const storeJSON = v.result.store;
147
+ v.result.store = s.fromJSON(Object.assign(Object.assign({}, storeJSON), { useDateObjects: useDateObjects }));
151
148
  }
152
- return parsed;
149
+ return v;
153
150
  }
154
- function querySubsToStorage(querySubs) {
151
+ function querySubToStorage(_key, sub) {
155
152
  var _a;
156
- const jsonSubs = {};
157
- for (const key in querySubs) {
158
- const sub = querySubs[key];
159
- const jsonSub = Object.assign({}, sub);
160
- if ((_a = sub.result) === null || _a === void 0 ? void 0 : _a.store) {
161
- jsonSub.result = Object.assign(Object.assign({}, sub.result), { store: s.toJSON(sub.result.store) });
162
- }
163
- jsonSubs[key] = jsonSub;
153
+ const jsonSub = Object.assign({}, sub);
154
+ if ((_a = sub.result) === null || _a === void 0 ? void 0 : _a.store) {
155
+ jsonSub.result = Object.assign(Object.assign({}, sub.result), { store: s.toJSON(sub.result.store) });
156
+ }
157
+ return jsonSub;
158
+ }
159
+ function kvFromStorage(key, x) {
160
+ switch (key) {
161
+ case 'pendingMutations':
162
+ return new Map(typeof x === 'string' ? JSON.parse(x) : x);
163
+ default:
164
+ return x;
164
165
  }
165
- return jsonSubs;
166
+ }
167
+ function kvToStorage(key, x) {
168
+ switch (key) {
169
+ case 'pendingMutations':
170
+ return [...x.entries()];
171
+ default:
172
+ return x;
173
+ }
174
+ }
175
+ function onMergeQuerySub(_k, storageSub, inMemorySub) {
176
+ const storageResult = storageSub === null || storageSub === void 0 ? void 0 : storageSub.result;
177
+ const memoryResult = inMemorySub === null || inMemorySub === void 0 ? void 0 : inMemorySub.result;
178
+ if (storageResult && !memoryResult && inMemorySub) {
179
+ inMemorySub.result = storageResult;
180
+ }
181
+ return inMemorySub || storageSub;
166
182
  }
167
183
  function sortedMutationEntries(entries) {
168
184
  return [...entries].sort((a, b) => {
@@ -217,65 +233,27 @@ class Reactor {
217
233
  this._beforeUnloadCbs = [];
218
234
  this._dataForQueryCache = {};
219
235
  this._inFlightMutationEventIds = new Set();
220
- /**
221
- * merge querySubs from storage and in memory. Has the following side
222
- * effects:
223
- * - We notify all queryCbs because results may been added during merge
224
- */
225
- this._onMergeQuerySubs = (_storageSubs, inMemorySubs) => {
226
- const storageSubs = _storageSubs || {};
227
- const ret = Object.assign({}, inMemorySubs);
228
- // Consider an inMemorySub with no result;
229
- // If we have a result from storageSubs, let's add it
230
- Object.entries(inMemorySubs).forEach(([hash, querySub]) => {
231
- var _a;
232
- const storageResult = (_a = storageSubs === null || storageSubs === void 0 ? void 0 : storageSubs[hash]) === null || _a === void 0 ? void 0 : _a.result;
233
- const memoryResult = querySub.result;
234
- if (storageResult && !memoryResult) {
235
- ret[hash].result = storageResult;
236
- }
237
- });
238
- // Consider a storageSub with no corresponding inMemorySub
239
- // This means that at least at this point,
240
- // the user has not asked to subscribe to the query.
241
- // We may _still_ want to add it, because in just a
242
- // few milliseconds, the user will ask to subscribe to the
243
- // query.
244
- // For now, we can't really tell if the user will ask to subscribe
245
- // or not. So for now let's just add the first 10 queries from storage.
246
- // Eventually, we could be smarter about this. For example,
247
- // we can keep usage information about which queries are popular.
248
- const storageKsToAdd = Object.keys(storageSubs)
249
- .filter((k) => !inMemorySubs[k])
250
- .sort((a, b) => {
251
- var _a, _b;
252
- // Sort by lastAccessed, newest first
253
- const aTime = ((_a = storageSubs[a]) === null || _a === void 0 ? void 0 : _a.lastAccessed) || 0;
254
- const bTime = ((_b = storageSubs[b]) === null || _b === void 0 ? void 0 : _b.lastAccessed) || 0;
255
- return bTime - aTime;
256
- })
257
- .slice(0, this.queryCacheLimit);
258
- storageKsToAdd.forEach((k) => {
259
- ret[k] = storageSubs[k];
260
- });
261
- // Okay, now we have merged our querySubs
262
- this.querySubs.set((_) => ret);
263
- this.loadedNotifyAll();
264
- };
265
- /**
266
- * merge pendingMutations from storage and in memory. Has a side effect of
267
- * sending mutations that were stored but not acked
268
- */
269
- this._onMergePendingMutations = (storageMuts, inMemoryMuts) => {
270
- const ret = new Map([...storageMuts.entries(), ...inMemoryMuts.entries()]);
271
- this.pendingMutations.set((_) => ret);
272
- this.loadedNotifyAll();
273
- const rewrittenStorageMuts = this._rewriteMutationsSorted(this.attrs, storageMuts);
274
- rewrittenStorageMuts.forEach(([k, mut]) => {
275
- if (!inMemoryMuts.has(k) && !mut['tx-id']) {
276
- this._sendMutation(k, mut);
236
+ this._onMergeKv = (key, storageV, inMemoryV) => {
237
+ var _a, _b;
238
+ switch (key) {
239
+ case 'pendingMutations': {
240
+ const storageEntries = (_a = storageV === null || storageV === void 0 ? void 0 : storageV.entries()) !== null && _a !== void 0 ? _a : [];
241
+ const inMemoryEntries = (_b = inMemoryV === null || inMemoryV === void 0 ? void 0 : inMemoryV.entries()) !== null && _b !== void 0 ? _b : [];
242
+ const muts = new Map([...storageEntries, ...inMemoryEntries]);
243
+ const rewrittenStorageMuts = storageV
244
+ ? this._rewriteMutationsSorted(this.attrs, storageV)
245
+ : [];
246
+ rewrittenStorageMuts.forEach(([k, mut]) => {
247
+ var _a;
248
+ if (!((_a = inMemoryV === null || inMemoryV === void 0 ? void 0 : inMemoryV.pendingMutations) === null || _a === void 0 ? void 0 : _a.has(k)) && !mut['tx-id']) {
249
+ this._sendMutation(k, mut);
250
+ }
251
+ });
252
+ return muts;
277
253
  }
278
- });
254
+ default:
255
+ return inMemoryV || storageV;
256
+ }
279
257
  };
280
258
  // ---------------------------
281
259
  // Queries
@@ -334,7 +312,7 @@ class Reactor {
334
312
  */
335
313
  this.pushOps = (txSteps, error) => {
336
314
  const eventId = (0, uuid_ts_1.default)();
337
- const mutations = [...this.pendingMutations.currentValue.values()];
315
+ const mutations = [...this._pendingMutations().values()];
338
316
  const order = Math.max(0, ...mutations.map((mut) => mut.order || 0)) + 1;
339
317
  const mutation = {
340
318
  op: 'transact',
@@ -343,9 +321,8 @@ class Reactor {
343
321
  error,
344
322
  order,
345
323
  };
346
- this.pendingMutations.set((prev) => {
324
+ this._updatePendingMutations((prev) => {
347
325
  prev.set(eventId, mutation);
348
- return prev;
349
326
  });
350
327
  const dfd = new Deferred_js_1.Deferred();
351
328
  this.mutationDeferredStore.set(eventId, dfd);
@@ -474,7 +451,7 @@ class Reactor {
474
451
  }));
475
452
  }
476
453
  this._initStorage(Storage);
477
- this._syncTable = new SyncTable_ts_1.SyncTable(this._trySendAuthed.bind(this), this._persister, {
454
+ this._syncTable = new SyncTable_ts_1.SyncTable(this._trySendAuthed.bind(this), new Storage(this.config.appId, 'syncSubs'), {
478
455
  useDateObjects: this.config.useDateObjects,
479
456
  }, this._log, (triples) => s.createStore(this.attrs, triples, this.config.enableCardinalityInference, this._linkIndex, this.config.useDateObjects));
480
457
  this._oauthCallbackResponse = this._oauthLoginInit();
@@ -513,21 +490,55 @@ class Reactor {
513
490
  _reactorStats() {
514
491
  return {
515
492
  inFlightMutationCount: this._inFlightMutationEventIds.size,
516
- pendingMutationCount: this.pendingMutations.currentValue.size,
493
+ pendingMutationCount: this._pendingMutations().size,
517
494
  transportType: this._transportType,
518
495
  };
519
496
  }
497
+ _onQuerySubLoaded(hash) {
498
+ this.kv
499
+ .waitForKeyToLoad('pendingMutations')
500
+ .then(() => this.notifyOne(hash));
501
+ }
520
502
  _initStorage(Storage) {
521
- this._persister = new Storage(`instant_${this.config.appId}_5`);
522
- this.querySubs = new PersistedObject_ts_1.PersistedObject(this._persister, 'querySubs', {}, this._onMergeQuerySubs, querySubsToStorage, (x) => querySubsFromStorage(x, this.config.useDateObjects));
523
- this.pendingMutations = new PersistedObject_ts_1.PersistedObject(this._persister, 'pendingMutations', new Map(), this._onMergePendingMutations, (x) => {
524
- return [...x.entries()];
525
- }, (x) => {
526
- return new Map(typeof x === 'string' ? JSON.parse(x) : x);
527
- }, 100, // throttle
528
- 100);
503
+ this.querySubs = new PersistedObject_ts_1.PersistedObject({
504
+ persister: new Storage(this.config.appId, 'querySubs'),
505
+ merge: onMergeQuerySub,
506
+ serialize: querySubToStorage,
507
+ parse: (_key, x) => querySubFromStorage(x, this.config.useDateObjects),
508
+ // objectSize
509
+ 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; },
510
+ logger: this._log,
511
+ preloadEntryCount: 10,
512
+ gc: {
513
+ maxAgeMs: 1000 * 60 * 60 * 24 * 7 * 52, // 1 year
514
+ maxEntries: 1000,
515
+ // Size of each query is the number of triples
516
+ maxSize: 1000000, // 1 million triples
517
+ },
518
+ });
519
+ this.querySubs.onKeyLoaded = (k) => this._onQuerySubLoaded(k);
520
+ this.kv = new PersistedObject_ts_1.PersistedObject({
521
+ persister: new Storage(this.config.appId, 'kv'),
522
+ merge: this._onMergeKv,
523
+ serialize: kvToStorage,
524
+ parse: kvFromStorage,
525
+ objectSize: () => 0,
526
+ logger: this._log,
527
+ saveThrottleMs: 100,
528
+ idleCallbackMaxWaitMs: 100,
529
+ // Don't GC the kv store
530
+ gc: null,
531
+ });
532
+ this.kv.onKeyLoaded = (k) => {
533
+ if (k === 'pendingMutations') {
534
+ this.notifyAll();
535
+ }
536
+ };
537
+ // Trigger immediate load for pendingMutations and currentUser
538
+ this.kv.waitForKeyToLoad('pendingMutations');
539
+ this.kv.waitForKeyToLoad(currentUserKey);
529
540
  this._beforeUnloadCbs.push(() => {
530
- this.pendingMutations.flush();
541
+ this.kv.flush();
531
542
  this.querySubs.flush();
532
543
  });
533
544
  }
@@ -623,18 +634,24 @@ class Reactor {
623
634
  case 'add-query-ok': {
624
635
  const { q, result } = msg;
625
636
  const hash = (0, weakHash_ts_1.default)(q);
637
+ if (!this._hasQueryListeners() && !this.querySubs.currentValue[hash]) {
638
+ break;
639
+ }
626
640
  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'];
627
641
  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'];
628
642
  const triples = (0, instaqlResult_js_1.extractTriples)(result);
629
643
  const store = s.createStore(this.attrs, triples, enableCardinalityInference, this._linkIndex, this.config.useDateObjects);
630
- this.querySubs.set((prev) => {
644
+ this.querySubs.updateInPlace((prev) => {
645
+ if (!prev[hash]) {
646
+ this._log.info('Missing value in querySubs', { hash, q });
647
+ return;
648
+ }
631
649
  prev[hash].result = {
632
650
  store,
633
651
  pageInfo,
634
652
  aggregate,
635
653
  processedTxId: msg['processed-tx-id'],
636
654
  };
637
- return prev;
638
655
  });
639
656
  this._cleanupPendingMutationsQueries();
640
657
  this.notifyOne(hash);
@@ -665,12 +682,14 @@ class Reactor {
665
682
  this._setAttrs(attrs);
666
683
  }
667
684
  this._cleanupPendingMutationsTimeout();
668
- const rewrittenMutations = this._rewriteMutations(this.attrs, this.pendingMutations.currentValue, processedTxId);
669
- if (rewrittenMutations !== this.pendingMutations.currentValue) {
685
+ const rewrittenMutations = this._rewriteMutations(this.attrs, this._pendingMutations(), processedTxId);
686
+ if (rewrittenMutations !== this._pendingMutations()) {
670
687
  // We know we've changed the mutations to fix the attr ids and removed
671
688
  // processed attrs, so we'll persist those changes to prevent optimisticAttrs
672
689
  // from using old attr definitions
673
- this.pendingMutations.set(() => rewrittenMutations);
690
+ this.kv.updateInPlace((prev) => {
691
+ prev.pendingMutations = rewrittenMutations;
692
+ });
674
693
  }
675
694
  const mutations = sortedMutationEntries(rewrittenMutations.entries());
676
695
  const updates = computations.map((x) => {
@@ -683,12 +702,15 @@ class Reactor {
683
702
  const newStore = this._applyOptimisticUpdates(store, mutations, processedTxId);
684
703
  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'];
685
704
  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'];
686
- return { hash, store: newStore, pageInfo, aggregate };
705
+ return { q, hash, store: newStore, pageInfo, aggregate };
687
706
  });
688
- updates.forEach(({ hash, store, pageInfo, aggregate }) => {
689
- this.querySubs.set((prev) => {
707
+ updates.forEach(({ hash, q, store, pageInfo, aggregate }) => {
708
+ this.querySubs.updateInPlace((prev) => {
709
+ if (!prev[hash]) {
710
+ this._log.error('Missing value in querySubs', { hash, q });
711
+ return;
712
+ }
690
713
  prev[hash].result = { store, pageInfo, aggregate, processedTxId };
691
- return prev;
692
714
  });
693
715
  });
694
716
  this._cleanupPendingMutationsQueries();
@@ -700,15 +722,14 @@ class Reactor {
700
722
  case 'transact-ok': {
701
723
  const { 'client-event-id': eventId, 'tx-id': txId } = msg;
702
724
  this._inFlightMutationEventIds.delete(eventId);
703
- const muts = this._rewriteMutations(this.attrs, this.pendingMutations.currentValue);
725
+ const muts = this._rewriteMutations(this.attrs, this._pendingMutations());
704
726
  const prevMutation = muts.get(eventId);
705
727
  if (!prevMutation) {
706
728
  break;
707
729
  }
708
730
  // update pendingMutation with server-side tx-id
709
- this.pendingMutations.set((prev) => {
731
+ this._updatePendingMutations((prev) => {
710
732
  prev.set(eventId, Object.assign(Object.assign({}, prev.get(eventId)), { 'tx-id': txId, confirmed: Date.now() }));
711
- return prev;
712
733
  });
713
734
  const newAttrs = prevMutation['tx-steps']
714
735
  .filter(([action, ..._args]) => action === 'add-attr')
@@ -775,15 +796,27 @@ class Reactor {
775
796
  break;
776
797
  }
777
798
  }
799
+ _pendingMutations() {
800
+ var _a;
801
+ return (_a = this.kv.currentValue.pendingMutations) !== null && _a !== void 0 ? _a : new Map();
802
+ }
803
+ _updatePendingMutations(f) {
804
+ this.kv.updateInPlace((prev) => {
805
+ var _a;
806
+ const muts = (_a = prev.pendingMutations) !== null && _a !== void 0 ? _a : new Map();
807
+ prev.pendingMutations = muts;
808
+ f(muts);
809
+ });
810
+ }
778
811
  /**
779
812
  * @param {'timeout' | 'error'} status
780
813
  * @param {string} eventId
781
814
  * @param {{message: string, type?: string, status?: number, hint?: unknown}} errorMsg
782
815
  */
783
816
  _handleMutationError(status, eventId, errorMsg) {
784
- const mut = this.pendingMutations.currentValue.get(eventId);
817
+ const mut = this._pendingMutations().get(eventId);
785
818
  if (mut && (status !== 'timeout' || !mut['tx-id'])) {
786
- this.pendingMutations.set((prev) => {
819
+ this._updatePendingMutations((prev) => {
787
820
  prev.delete(eventId);
788
821
  return prev;
789
822
  });
@@ -804,7 +837,7 @@ class Reactor {
804
837
  const eventId = msg['client-event-id'];
805
838
  // This might not be a mutation, but it can't hurt to delete it
806
839
  this._inFlightMutationEventIds.delete(eventId);
807
- const prevMutation = this.pendingMutations.currentValue.get(eventId);
840
+ const prevMutation = this._pendingMutations().get(eventId);
808
841
  const errorMessage = {
809
842
  message: msg.message || 'Uh-oh, something went wrong. Ping Joe & Stopa.',
810
843
  };
@@ -871,10 +904,9 @@ class Reactor {
871
904
  }
872
905
  _startQuerySub(q, hash) {
873
906
  const eventId = (0, uuid_ts_1.default)();
874
- this.querySubs.set((prev) => {
907
+ this.querySubs.updateInPlace((prev) => {
875
908
  prev[hash] = prev[hash] || { q, result: null, eventId };
876
909
  prev[hash].lastAccessed = Date.now();
877
- return prev;
878
910
  });
879
911
  this._trySendAuthed(eventId, { op: 'add-query', q });
880
912
  return eventId;
@@ -948,13 +980,18 @@ class Reactor {
948
980
  this.queryCbs[hash] = this.queryCbs[hash].filter((r) => r.cb !== cb);
949
981
  this._cleanupQuery(q, hash);
950
982
  }
951
- _cleanupQuery(q, hash) {
983
+ _hasQueryListeners(hash) {
952
984
  var _a, _b;
953
- const hasListeners = ((_a = this.queryCbs[hash]) === null || _a === void 0 ? void 0 : _a.length) || ((_b = this.queryOnceDfds[hash]) === null || _b === void 0 ? void 0 : _b.length);
985
+ 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));
986
+ }
987
+ _cleanupQuery(q, hash) {
988
+ const hasListeners = this._hasQueryListeners(hash);
954
989
  if (hasListeners)
955
990
  return;
956
991
  delete this.queryCbs[hash];
957
992
  delete this.queryOnceDfds[hash];
993
+ delete this._dataForQueryCache[hash];
994
+ this.querySubs.unloadKey(hash);
958
995
  this._trySendAuthed((0, uuid_ts_1.default)(), { op: 'remove-query', q });
959
996
  }
960
997
  // When we `pushTx`, it's possible that we don't yet have `this.attrs`
@@ -971,6 +1008,8 @@ class Reactor {
971
1008
  _rewriteMutations(attrs, muts, processedTxId) {
972
1009
  if (!attrs)
973
1010
  return muts;
1011
+ if (!muts)
1012
+ return new Map();
974
1013
  const findExistingAttr = (attr) => {
975
1014
  const [_, etype, label] = attr['forward-identity'];
976
1015
  const existing = instaml.getAttrByFwdIdentName(attrs, etype, label);
@@ -1043,9 +1082,7 @@ class Reactor {
1043
1082
  // Transact
1044
1083
  optimisticAttrs() {
1045
1084
  var _a;
1046
- const pendingMutationSteps = [
1047
- ...this.pendingMutations.currentValue.values(),
1048
- ] // hack due to Map()
1085
+ const pendingMutationSteps = [...this._pendingMutations().values()] // hack due to Map()
1049
1086
  .flatMap((x) => x['tx-steps']);
1050
1087
  const deletedAttrIds = new Set(pendingMutationSteps
1051
1088
  .filter(([action, _attr]) => action === 'delete-attr')
@@ -1077,12 +1114,12 @@ class Reactor {
1077
1114
  }
1078
1115
  if (!this.querySubs)
1079
1116
  return;
1080
- if (!this.pendingMutations)
1117
+ if (!this.kv.currentValue.pendingMutations)
1081
1118
  return;
1082
1119
  const querySubVersion = this.querySubs.version();
1083
1120
  const querySubs = this.querySubs.currentValue;
1084
- const pendingMutationsVersion = this.pendingMutations.version();
1085
- const pendingMutations = this.pendingMutations.currentValue;
1121
+ const pendingMutationsVersion = this.kv.version();
1122
+ const pendingMutations = this._pendingMutations();
1086
1123
  const { q, result } = querySubs[hash] || {};
1087
1124
  if (!result)
1088
1125
  return;
@@ -1114,13 +1151,17 @@ class Reactor {
1114
1151
  /** Re-compute all subscriptions */
1115
1152
  notifyAll() {
1116
1153
  Object.keys(this.queryCbs).forEach((hash) => {
1117
- this.notifyOne(hash);
1154
+ this.querySubs
1155
+ .waitForKeyToLoad(hash)
1156
+ .then(() => this.notifyOne(hash))
1157
+ .catch(() => this.notifyOne(hash));
1118
1158
  });
1119
1159
  }
1120
1160
  loadedNotifyAll() {
1121
- if (this.pendingMutations.isLoading() || this.querySubs.isLoading())
1122
- return;
1123
- this.notifyAll();
1161
+ this.kv
1162
+ .waitForKeyToLoad('pendingMutations')
1163
+ .then(() => this.notifyAll())
1164
+ .catch(() => this.notifyAll());
1124
1165
  }
1125
1166
  shutdown() {
1126
1167
  var _a;
@@ -1148,7 +1189,7 @@ class Reactor {
1148
1189
  }
1149
1190
  const timeoutMs = Math.max(6000, Math.min(this._inFlightMutationEventIds.size + 1,
1150
1191
  // Defensive code in case we don't clean up in flight mutation event ids
1151
- this.pendingMutations.currentValue.size + 1) * 6000);
1192
+ this._pendingMutations().size + 1) * 6000);
1152
1193
  if (!this._isOnline) {
1153
1194
  this._finishTransaction('enqueued', eventId);
1154
1195
  }
@@ -1185,7 +1226,7 @@ class Reactor {
1185
1226
  .forEach(({ eventId, q }) => {
1186
1227
  this._trySendAuthed(eventId, { op: 'add-query', q });
1187
1228
  });
1188
- const muts = this._rewriteMutationsSorted(this.attrs, this.pendingMutations.currentValue);
1229
+ const muts = this._rewriteMutationsSorted(this.attrs, this._pendingMutations());
1189
1230
  muts.forEach(([eventId, mut]) => {
1190
1231
  if (!mut['tx-id']) {
1191
1232
  this._sendMutation(eventId, mut);
@@ -1203,13 +1244,12 @@ class Reactor {
1203
1244
  minProcessedTxId = Math.min(minProcessedTxId, result === null || result === void 0 ? void 0 : result.processedTxId);
1204
1245
  }
1205
1246
  }
1206
- this.pendingMutations.set((prev) => {
1247
+ this._updatePendingMutations((prev) => {
1207
1248
  for (const [eventId, mut] of Array.from(prev.entries())) {
1208
1249
  if (mut['tx-id'] && mut['tx-id'] <= minProcessedTxId) {
1209
1250
  prev.delete(eventId);
1210
1251
  }
1211
1252
  }
1212
- return prev;
1213
1253
  });
1214
1254
  }
1215
1255
  /**
@@ -1218,19 +1258,17 @@ class Reactor {
1218
1258
  * unaffected by this mutation and it’s safe to delete it from local queue
1219
1259
  */
1220
1260
  _cleanupPendingMutationsTimeout() {
1221
- if (this.pendingMutations.currentValue.size <
1222
- this._pendingMutationCleanupThreshold) {
1261
+ if (this._pendingMutations().size < this._pendingMutationCleanupThreshold) {
1223
1262
  return;
1224
1263
  }
1225
1264
  const now = Date.now();
1226
- this.pendingMutations.set((prev) => {
1265
+ this._updatePendingMutations((prev) => {
1227
1266
  for (const [eventId, mut] of Array.from(prev.entries())) {
1228
1267
  if (mut.confirmed &&
1229
1268
  mut.confirmed + this._pendingTxCleanupTimeout < now) {
1230
1269
  prev.delete(eventId);
1231
1270
  }
1232
1271
  }
1233
- return prev;
1234
1272
  });
1235
1273
  }
1236
1274
  _trySendAuthed(...args) {
@@ -1306,23 +1344,24 @@ class Reactor {
1306
1344
  *
1307
1345
  * Note: If the user deletes their local storage, this id will change.
1308
1346
  *
1309
- * We use this._localIdPromises to ensure that we only generate a local
1310
- * id once, even if multiple callers call this function concurrently.
1311
1347
  */
1312
1348
  getLocalId(name) {
1313
1349
  return __awaiter(this, void 0, void 0, function* () {
1314
1350
  const k = `localToken_${name}`;
1315
- const id = yield this._persister.getItem(k);
1316
- if (id)
1317
- return id;
1318
- if (this._localIdPromises[k]) {
1319
- return this._localIdPromises[k];
1351
+ if (this.kv.currentValue[k]) {
1352
+ return this.kv.currentValue[k];
1353
+ }
1354
+ const current = yield this.kv.waitForKeyToLoad(k);
1355
+ if (current) {
1356
+ return current;
1320
1357
  }
1321
1358
  const newId = (0, uuid_ts_1.default)();
1322
- this._localIdPromises[k] = this._persister
1323
- .setItem(k, newId)
1324
- .then(() => newId);
1325
- return this._localIdPromises[k];
1359
+ this.kv.updateInPlace((prev) => {
1360
+ if (prev[k])
1361
+ return;
1362
+ prev[k] = newId;
1363
+ });
1364
+ return yield this.kv.waitForKeyToLoad(k);
1326
1365
  });
1327
1366
  }
1328
1367
  // ----
@@ -1497,7 +1536,10 @@ class Reactor {
1497
1536
  }
1498
1537
  setCurrentUser(user) {
1499
1538
  return __awaiter(this, void 0, void 0, function* () {
1500
- yield this._persister.setItem(currentUserKey, user);
1539
+ this.kv.updateInPlace((prev) => {
1540
+ prev[currentUserKey] = user;
1541
+ });
1542
+ yield this.kv.waitForKeyToLoad(currentUserKey);
1501
1543
  });
1502
1544
  }
1503
1545
  getCurrentUserCached() {
@@ -1505,7 +1547,7 @@ class Reactor {
1505
1547
  }
1506
1548
  _getCurrentUser() {
1507
1549
  return __awaiter(this, void 0, void 0, function* () {
1508
- const user = yield this._persister.getItem(currentUserKey);
1550
+ const user = yield this.kv.waitForKeyToLoad(currentUserKey);
1509
1551
  return typeof user === 'string' ? JSON.parse(user) : user;
1510
1552
  });
1511
1553
  }
@@ -1517,16 +1559,25 @@ class Reactor {
1517
1559
  this._currentUserCached = Object.assign({ isLoading: false }, errorV);
1518
1560
  return errorV;
1519
1561
  }
1520
- const user = yield this._getCurrentUser();
1521
- const userV = { user: user, error: undefined };
1522
- this._currentUserCached = Object.assign({ isLoading: false }, userV);
1523
- return userV;
1562
+ try {
1563
+ const user = yield this._getCurrentUser();
1564
+ const userV = { user: user, error: undefined };
1565
+ this._currentUserCached = Object.assign({ isLoading: false }, userV);
1566
+ return userV;
1567
+ }
1568
+ catch (e) {
1569
+ return {
1570
+ user: undefined,
1571
+ isLoading: false,
1572
+ error: { message: (e === null || e === void 0 ? void 0 : e.message) || 'Error loading user' },
1573
+ };
1574
+ }
1524
1575
  });
1525
1576
  }
1526
1577
  _hasCurrentUser() {
1527
1578
  return __awaiter(this, void 0, void 0, function* () {
1528
- const user = yield this._persister.getItem(currentUserKey);
1529
- return JSON.parse(user) != null;
1579
+ const user = yield this.kv.waitForKeyToLoad(currentUserKey);
1580
+ return typeof user === 'string' ? JSON.parse(user) != null : user != null;
1530
1581
  });
1531
1582
  }
1532
1583
  changeCurrentUser(newUser) {
@@ -1554,11 +1605,10 @@ class Reactor {
1554
1605
  const newV = { error: undefined, user: newUser };
1555
1606
  this._currentUserCached = Object.assign({ isLoading: false }, newV);
1556
1607
  this._dataForQueryCache = {};
1557
- this.querySubs.set((prev) => {
1608
+ this.querySubs.updateInPlace((prev) => {
1558
1609
  Object.keys(prev).forEach((k) => {
1559
1610
  delete prev[k].result;
1560
1611
  });
1561
- return prev;
1562
1612
  });
1563
1613
  this._reconnectTimeoutMs = 0;
1564
1614
  this._transport.close();