@tanstack/db-sqlite-persistence-core 0.2.21 → 0.2.23

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.
@@ -165,15 +165,6 @@ function toStableSerializable(value) {
165
165
  function stableSerialize(value) {
166
166
  return JSON.stringify(toStableSerializable(value) ?? null);
167
167
  }
168
- function normalizeSubsetOptionsForKey(options) {
169
- return {
170
- where: toStableSerializable(options.where),
171
- orderBy: toStableSerializable(options.orderBy),
172
- limit: options.limit,
173
- cursor: toStableSerializable(options.cursor),
174
- offset: options.offset
175
- };
176
- }
177
168
  function normalizeSyncFnResult(result) {
178
169
  if (typeof result === `function`) {
179
170
  return { cleanup: result };
@@ -220,7 +211,7 @@ class PersistedCollectionRuntime {
220
211
  this.pendingRemoteSubsetEnsures = /* @__PURE__ */ new Map();
221
212
  this.queuedHydrationTransactions = [];
222
213
  this.queuedTxCommitted = [];
223
- this.subscriptionIds = /* @__PURE__ */ new WeakMap();
214
+ this.requestIds = /* @__PURE__ */ new WeakMap();
224
215
  this.collection = null;
225
216
  this.syncControls = {
226
217
  begin: null,
@@ -232,15 +223,17 @@ class PersistedCollectionRuntime {
232
223
  this.started = false;
233
224
  this.startupMetadataPromise = null;
234
225
  this.startPromise = null;
226
+ this.resumeBaselinePromise = null;
227
+ this.lifecycleGeneration = 0;
235
228
  this.internalApplyDepth = 0;
236
229
  this.appliedReceiptSequence = 0;
237
230
  this.pendingAppliedReceipts = /* @__PURE__ */ new Map();
238
- this.isHydrating = false;
231
+ this.hydratingGeneration = null;
239
232
  this.coordinatorUnsubscribe = null;
240
233
  this.indexAddedUnsubscribe = null;
241
234
  this.indexRemovedUnsubscribe = null;
242
235
  this.remoteEnsureRetryTimer = null;
243
- this.nextSubscriptionId = 0;
236
+ this.nextRequestId = 0;
244
237
  this.latestTerm = 0;
245
238
  this.latestSeq = 0;
246
239
  this.latestRowVersion = 0;
@@ -249,6 +242,7 @@ class PersistedCollectionRuntime {
249
242
  this.localRowVersion = 0;
250
243
  }
251
244
  setSyncControls(syncControls) {
245
+ this.advanceLifecycle();
252
246
  const commit = syncControls.commit;
253
247
  this.syncControls = {
254
248
  ...syncControls,
@@ -283,7 +277,7 @@ class PersistedCollectionRuntime {
283
277
  };
284
278
  }
285
279
  isHydratingNow() {
286
- return this.isHydrating;
280
+ return this.hydratingGeneration === this.lifecycleGeneration;
287
281
  }
288
282
  isApplyingInternally() {
289
283
  return this.internalApplyDepth > 0;
@@ -305,49 +299,75 @@ class PersistedCollectionRuntime {
305
299
  if (this.startPromise) {
306
300
  return this.startPromise;
307
301
  }
308
- this.startPromise = this.startInternal();
302
+ const lifecycleGeneration = this.lifecycleGeneration;
303
+ this.startPromise = this.startInternal(lifecycleGeneration);
309
304
  return this.startPromise;
310
305
  }
306
+ ensureResumeBaselineHydrated() {
307
+ if (this.resumeBaselinePromise) {
308
+ return this.resumeBaselinePromise;
309
+ }
310
+ const lifecycleGeneration = this.lifecycleGeneration;
311
+ this.resumeBaselinePromise = (async () => {
312
+ await this.ensureStarted();
313
+ if (lifecycleGeneration !== this.lifecycleGeneration) return;
314
+ if (this.syncMode !== `on-demand`) return;
315
+ await this.hydrateBaseline(lifecycleGeneration);
316
+ })();
317
+ return this.resumeBaselinePromise;
318
+ }
319
+ async hydrateBaseline(lifecycleGeneration) {
320
+ if (lifecycleGeneration !== this.lifecycleGeneration) return;
321
+ const baseline = {};
322
+ this.activeSubsets.set(this.getSubsetKey(baseline), baseline);
323
+ const appliedCursor = this.appliedReceiptSequence;
324
+ await this.applyMutex.run(async () => {
325
+ if (lifecycleGeneration !== this.lifecycleGeneration) return;
326
+ await this.hydrateSubsetUnsafe(baseline, {
327
+ requestRemoteEnsure: false,
328
+ lifecycleGeneration
329
+ });
330
+ });
331
+ if (lifecycleGeneration !== this.lifecycleGeneration) return;
332
+ await this.waitForAppliedReceiptsAfter(appliedCursor);
333
+ }
311
334
  async ensureStartupMetadataLoaded() {
312
335
  if (this.startupMetadataPromise) {
313
336
  return this.startupMetadataPromise;
314
337
  }
315
- this.startupMetadataPromise = this.loadStartupMetadataInternal();
338
+ const lifecycleGeneration = this.lifecycleGeneration;
339
+ this.startupMetadataPromise = this.loadStartupMetadataInternal(lifecycleGeneration);
316
340
  return this.startupMetadataPromise;
317
341
  }
318
- async startInternal() {
342
+ async startInternal(lifecycleGeneration) {
319
343
  if (this.started) {
320
344
  return;
321
345
  }
322
346
  this.started = true;
323
347
  await this.ensureStartupMetadataLoaded();
348
+ if (lifecycleGeneration !== this.lifecycleGeneration) return;
324
349
  const indexBootstrapSnapshot = this.collection?.getIndexMetadata() ?? [];
325
350
  this.attachIndexLifecycleListeners();
326
351
  await this.bootstrapPersistedIndexes(indexBootstrapSnapshot);
352
+ if (lifecycleGeneration !== this.lifecycleGeneration) return;
327
353
  if (this.syncMode !== `on-demand`) {
328
- this.activeSubsets.set(this.getSubsetKey({}), {});
329
- const appliedCursor = this.appliedReceiptSequence;
330
- await this.applyMutex.run(
331
- () => this.hydrateSubsetUnsafe({}, { requestRemoteEnsure: false })
332
- );
333
- await this.waitForAppliedReceiptsAfter(appliedCursor);
354
+ await this.hydrateBaseline(lifecycleGeneration);
334
355
  }
335
356
  }
336
- async loadStartupMetadataInternal() {
357
+ async loadStartupMetadataInternal(lifecycleGeneration) {
337
358
  if (this.persistence.adapter.getStreamPosition) {
338
359
  const position = await this.persistence.adapter.getStreamPosition(
339
360
  this.collectionId
340
361
  );
362
+ if (lifecycleGeneration !== this.lifecycleGeneration) return;
341
363
  this.observeStreamPosition(
342
364
  position.latestTerm,
343
365
  position.latestSeq,
344
366
  position.latestRowVersion
345
367
  );
346
368
  }
347
- await this.loadCollectionMetadataIntoCollection();
348
- }
349
- async loadCollectionMetadataIntoCollection() {
350
369
  const collectionMetadata = await this.loadCollectionMetadataSnapshot();
370
+ if (lifecycleGeneration !== this.lifecycleGeneration) return;
351
371
  this.replaceCollectionMetadataSnapshot(collectionMetadata);
352
372
  }
353
373
  async loadCollectionMetadataSnapshot() {
@@ -378,13 +398,16 @@ class PersistedCollectionRuntime {
378
398
  });
379
399
  }
380
400
  async loadSubset(options, upstreamLoadSubset) {
401
+ const lifecycleGeneration = this.lifecycleGeneration;
381
402
  this.activeSubsets.set(this.getSubsetKey(options), options);
382
403
  const appliedCursor = this.appliedReceiptSequence;
383
404
  await this.applyMutex.run(
384
405
  () => this.hydrateSubsetUnsafe(options, {
385
- requestRemoteEnsure: this.mode === `sync-present`
406
+ requestRemoteEnsure: this.mode === `sync-present`,
407
+ lifecycleGeneration
386
408
  })
387
409
  );
410
+ if (lifecycleGeneration !== this.lifecycleGeneration) return;
388
411
  await this.waitForAppliedReceiptsAfter(appliedCursor);
389
412
  if (upstreamLoadSubset) {
390
413
  try {
@@ -396,6 +419,7 @@ class PersistedCollectionRuntime {
396
419
  }
397
420
  console.warn(`Failed to trigger remote subset load:`, error);
398
421
  this.queueRemoteSubsetEnsure(options);
422
+ throw error;
399
423
  }
400
424
  }
401
425
  }
@@ -405,9 +429,12 @@ class PersistedCollectionRuntime {
405
429
  upstreamUnloadSubset?.(options);
406
430
  }
407
431
  async forceReloadSubset(options) {
408
- this.activeSubsets.set(this.getSubsetKey(options), options);
432
+ const lifecycleGeneration = this.lifecycleGeneration;
409
433
  await this.applyMutex.run(
410
- () => this.hydrateSubsetUnsafe(options, { requestRemoteEnsure: false })
434
+ () => this.hydrateSubsetUnsafe(options, {
435
+ requestRemoteEnsure: false,
436
+ lifecycleGeneration
437
+ })
411
438
  );
412
439
  }
413
440
  queueHydrationBufferedTransaction(transaction) {
@@ -497,6 +524,7 @@ class PersistedCollectionRuntime {
497
524
  await this.persistAndConfirmCollectionMutations(collectionMutations);
498
525
  }
499
526
  cleanup() {
527
+ this.advanceLifecycle();
500
528
  this.coordinatorUnsubscribe?.();
501
529
  this.coordinatorUnsubscribe = null;
502
530
  this.indexAddedUnsubscribe?.();
@@ -515,6 +543,14 @@ class PersistedCollectionRuntime {
515
543
  this.queuedHydrationTransactions.length = 0;
516
544
  this.queuedTxCommitted.length = 0;
517
545
  this.clearSyncControls();
546
+ this.collection = null;
547
+ }
548
+ advanceLifecycle() {
549
+ this.lifecycleGeneration++;
550
+ this.started = false;
551
+ this.startupMetadataPromise = null;
552
+ this.startPromise = null;
553
+ this.resumeBaselinePromise = null;
518
554
  }
519
555
  withInternalApply(task) {
520
556
  this.internalApplyDepth++;
@@ -548,12 +584,15 @@ class PersistedCollectionRuntime {
548
584
  return this.applyMutex.run(() => this.scanPersistedRowsUnsafe(options));
549
585
  }
550
586
  async hydrateSubsetUnsafe(options, config) {
551
- this.isHydrating = true;
587
+ this.hydratingGeneration = config.lifecycleGeneration;
552
588
  try {
553
589
  const rows = await this.loadSubsetRowsUnsafe(options);
590
+ if (config.lifecycleGeneration !== this.lifecycleGeneration) return;
554
591
  this.applyRowsToCollection(rows);
555
592
  } finally {
556
- this.isHydrating = false;
593
+ if (this.hydratingGeneration === config.lifecycleGeneration) {
594
+ this.hydratingGeneration = null;
595
+ }
557
596
  }
558
597
  await this.flushQueuedHydrationTransactionsUnsafe();
559
598
  await this.flushQueuedTxCommittedUnsafe();
@@ -947,18 +986,12 @@ class PersistedCollectionRuntime {
947
986
  };
948
987
  }
949
988
  getSubsetKey(options) {
950
- const subscription = options.subscription;
951
- if (subscription && typeof subscription === `object`) {
952
- const existingId = this.subscriptionIds.get(subscription);
953
- if (existingId) {
954
- return existingId;
955
- }
956
- this.nextSubscriptionId++;
957
- const id = `sub:${this.nextSubscriptionId}`;
958
- this.subscriptionIds.set(subscription, id);
959
- return id;
989
+ let id = this.requestIds.get(options);
990
+ if (id === void 0) {
991
+ id = `request:${++this.nextRequestId}`;
992
+ this.requestIds.set(options, id);
960
993
  }
961
- return `opts:${stableSerialize(normalizeSubsetOptionsForKey(options))}`;
994
+ return id;
962
995
  }
963
996
  queueRemoteSubsetEnsure(options) {
964
997
  if (this.mode !== `sync-present` || !this.persistence.coordinator.requestEnsureRemoteSubset || this.activeSubsets.get(this.getSubsetKey(options)) !== options) {
@@ -1018,7 +1051,7 @@ class PersistedCollectionRuntime {
1018
1051
  const { payload } = message;
1019
1052
  const isSelf = message.senderId === this.persistence.coordinator.getNodeId();
1020
1053
  if (isTxCommittedPayload(payload)) {
1021
- if (this.isHydrating) {
1054
+ if (this.isHydratingNow()) {
1022
1055
  this.queuedTxCommitted.push(payload);
1023
1056
  return;
1024
1057
  }
@@ -1188,13 +1221,16 @@ class PersistedCollectionRuntime {
1188
1221
  });
1189
1222
  }
1190
1223
  async reloadActiveSubsetsUnsafe() {
1224
+ const lifecycleGeneration = this.lifecycleGeneration;
1191
1225
  const activeSubsetOptions = this.activeSubsets.size > 0 ? Array.from(this.activeSubsets.values()) : [{}];
1192
- this.isHydrating = true;
1226
+ this.hydratingGeneration = lifecycleGeneration;
1193
1227
  try {
1194
1228
  const mergedRows = /* @__PURE__ */ new Map();
1195
1229
  const collectionMetadata = await this.loadCollectionMetadataSnapshot();
1230
+ if (lifecycleGeneration !== this.lifecycleGeneration) return;
1196
1231
  for (const options of activeSubsetOptions) {
1197
1232
  const subsetRows = await this.loadSubsetRowsUnsafe(options);
1233
+ if (lifecycleGeneration !== this.lifecycleGeneration) return;
1198
1234
  for (const row of subsetRows) {
1199
1235
  mergedRows.set(row.key, {
1200
1236
  value: row.value,
@@ -1211,7 +1247,9 @@ class PersistedCollectionRuntime {
1211
1247
  collectionMetadata
1212
1248
  );
1213
1249
  } finally {
1214
- this.isHydrating = false;
1250
+ if (this.hydratingGeneration === lifecycleGeneration) {
1251
+ this.hydratingGeneration = null;
1252
+ }
1215
1253
  }
1216
1254
  await this.flushQueuedHydrationTransactionsUnsafe();
1217
1255
  await this.flushQueuedTxCommittedUnsafe();
@@ -1295,6 +1333,7 @@ function createWrappedSyncConfig(sourceSyncConfig, runtime) {
1295
1333
  const transactionStack = [];
1296
1334
  const getOpenTransaction = () => transactionStack[transactionStack.length - 1];
1297
1335
  let fullStartPromise = null;
1336
+ const startupState = { cleanedUp: false };
1298
1337
  const acquisitions = /* @__PURE__ */ new Map();
1299
1338
  runtime.setSyncControls({
1300
1339
  begin: params.begin,
@@ -1309,9 +1348,12 @@ function createWrappedSyncConfig(sourceSyncConfig, runtime) {
1309
1348
  const wrappedParams = {
1310
1349
  ...params,
1311
1350
  markReady: () => {
1351
+ if (startupState.cleanedUp) return;
1312
1352
  void (fullStartPromise ?? runtime.ensureStarted()).then(() => {
1353
+ if (startupState.cleanedUp) return;
1313
1354
  params.markReady();
1314
1355
  }).catch((error) => {
1356
+ if (startupState.cleanedUp) return;
1315
1357
  console.warn(
1316
1358
  `Failed persisted sync startup before markReady:`,
1317
1359
  error
@@ -1320,6 +1362,7 @@ function createWrappedSyncConfig(sourceSyncConfig, runtime) {
1320
1362
  });
1321
1363
  },
1322
1364
  begin: (options) => {
1365
+ if (startupState.cleanedUp) return;
1323
1366
  const transaction = {
1324
1367
  operations: [],
1325
1368
  rowMetadataWrites: /* @__PURE__ */ new Map(),
@@ -1334,6 +1377,7 @@ function createWrappedSyncConfig(sourceSyncConfig, runtime) {
1334
1377
  }
1335
1378
  },
1336
1379
  write: (message) => {
1380
+ if (startupState.cleanedUp) return;
1337
1381
  const normalization = runtime.normalizeSyncWriteMessage(message);
1338
1382
  const openTransaction = getOpenTransaction();
1339
1383
  if (!openTransaction) {
@@ -1368,7 +1412,9 @@ function createWrappedSyncConfig(sourceSyncConfig, runtime) {
1368
1412
  },
1369
1413
  metadata: params.metadata ? {
1370
1414
  row: {
1415
+ whenHydrated: () => startupState.cleanedUp ? Promise.resolve() : runtime.ensureResumeBaselineHydrated(),
1371
1416
  get: (key) => {
1417
+ if (startupState.cleanedUp) return void 0;
1372
1418
  const openTransaction = getOpenTransaction();
1373
1419
  const pendingWrite = openTransaction?.rowMetadataWrites.get(key);
1374
1420
  if (pendingWrite) {
@@ -1379,8 +1425,9 @@ function createWrappedSyncConfig(sourceSyncConfig, runtime) {
1379
1425
  }
1380
1426
  return params.metadata.row.get(key);
1381
1427
  },
1382
- scanPersisted: (options) => runtime.scanPersistedRows(options),
1428
+ scanPersisted: (options) => startupState.cleanedUp ? Promise.resolve([]) : runtime.scanPersistedRows(options),
1383
1429
  set: (key, value) => {
1430
+ if (startupState.cleanedUp) return;
1384
1431
  const openTransaction = getOpenTransaction();
1385
1432
  if (!openTransaction) {
1386
1433
  throw new errors.InvalidPersistedCollectionConfigError(
@@ -1396,6 +1443,7 @@ function createWrappedSyncConfig(sourceSyncConfig, runtime) {
1396
1443
  }
1397
1444
  },
1398
1445
  delete: (key) => {
1446
+ if (startupState.cleanedUp) return;
1399
1447
  const openTransaction = getOpenTransaction();
1400
1448
  if (!openTransaction) {
1401
1449
  throw new errors.InvalidPersistedCollectionConfigError(
@@ -1412,6 +1460,7 @@ function createWrappedSyncConfig(sourceSyncConfig, runtime) {
1412
1460
  },
1413
1461
  collection: {
1414
1462
  get: (key) => {
1463
+ if (startupState.cleanedUp) return void 0;
1415
1464
  const openTransaction = getOpenTransaction();
1416
1465
  const pendingWrite = openTransaction?.collectionMetadataWrites.get(key);
1417
1466
  if (pendingWrite) {
@@ -1420,6 +1469,7 @@ function createWrappedSyncConfig(sourceSyncConfig, runtime) {
1420
1469
  return params.metadata.collection.get(key);
1421
1470
  },
1422
1471
  set: (key, value) => {
1472
+ if (startupState.cleanedUp) return;
1423
1473
  const openTransaction = getOpenTransaction();
1424
1474
  if (!openTransaction) {
1425
1475
  throw new errors.InvalidPersistedCollectionConfigError(
@@ -1435,6 +1485,7 @@ function createWrappedSyncConfig(sourceSyncConfig, runtime) {
1435
1485
  }
1436
1486
  },
1437
1487
  delete: (key) => {
1488
+ if (startupState.cleanedUp) return;
1438
1489
  const openTransaction = getOpenTransaction();
1439
1490
  if (!openTransaction) {
1440
1491
  throw new errors.InvalidPersistedCollectionConfigError(
@@ -1449,6 +1500,7 @@ function createWrappedSyncConfig(sourceSyncConfig, runtime) {
1449
1500
  }
1450
1501
  },
1451
1502
  list: (prefix) => {
1503
+ if (startupState.cleanedUp) return [];
1452
1504
  const merged = new Map(
1453
1505
  params.metadata.collection.list().map(({ key, value }) => [key, value])
1454
1506
  );
@@ -1473,6 +1525,7 @@ function createWrappedSyncConfig(sourceSyncConfig, runtime) {
1473
1525
  }
1474
1526
  } : void 0,
1475
1527
  truncate: () => {
1528
+ if (startupState.cleanedUp) return;
1476
1529
  const openTransaction = getOpenTransaction();
1477
1530
  if (!openTransaction) {
1478
1531
  params.truncate();
@@ -1486,6 +1539,7 @@ function createWrappedSyncConfig(sourceSyncConfig, runtime) {
1486
1539
  }
1487
1540
  },
1488
1541
  commit: (signal) => {
1542
+ if (startupState.cleanedUp) return true;
1489
1543
  const openTransaction = transactionStack.pop();
1490
1544
  if (!openTransaction) {
1491
1545
  return params.commit(signal);
@@ -1535,7 +1589,6 @@ function createWrappedSyncConfig(sourceSyncConfig, runtime) {
1535
1589
  }
1536
1590
  };
1537
1591
  let sourceResult = {};
1538
- const startupState = { cleanedUp: false };
1539
1592
  fullStartPromise = runtime.ensureStarted();
1540
1593
  const sourceResultPromise = (async () => {
1541
1594
  await runtime.ensureStartupMetadataLoaded();