@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.
@@ -248,6 +248,7 @@ export interface PersistedCollectionUtils extends UtilsRecord {
248
248
  mutations: Array<PendingMutation<Record<string, unknown>>>;
249
249
  }) => Promise<void> | void;
250
250
  getLeadershipState?: () => PersistedCollectionLeadershipState;
251
+ /** Hydrate once without acquiring a new ongoing subset lease. */
251
252
  forceReloadSubset?: (options: LoadSubsetOptions) => Promise<void> | void;
252
253
  }
253
254
  export type PersistedSyncWrappedOptions<T extends object, TKey extends string | number, TSchema extends StandardSchemaV1 = never, TUtils extends UtilsRecord = UtilsRecord> = CollectionConfig<T, TKey, TSchema, TUtils> & {
@@ -248,6 +248,7 @@ export interface PersistedCollectionUtils extends UtilsRecord {
248
248
  mutations: Array<PendingMutation<Record<string, unknown>>>;
249
249
  }) => Promise<void> | void;
250
250
  getLeadershipState?: () => PersistedCollectionLeadershipState;
251
+ /** Hydrate once without acquiring a new ongoing subset lease. */
251
252
  forceReloadSubset?: (options: LoadSubsetOptions) => Promise<void> | void;
252
253
  }
253
254
  export type PersistedSyncWrappedOptions<T extends object, TKey extends string | number, TSchema extends StandardSchemaV1 = never, TUtils extends UtilsRecord = UtilsRecord> = CollectionConfig<T, TKey, TSchema, TUtils> & {
@@ -163,15 +163,6 @@ function toStableSerializable(value) {
163
163
  function stableSerialize(value) {
164
164
  return JSON.stringify(toStableSerializable(value) ?? null);
165
165
  }
166
- function normalizeSubsetOptionsForKey(options) {
167
- return {
168
- where: toStableSerializable(options.where),
169
- orderBy: toStableSerializable(options.orderBy),
170
- limit: options.limit,
171
- cursor: toStableSerializable(options.cursor),
172
- offset: options.offset
173
- };
174
- }
175
166
  function normalizeSyncFnResult(result) {
176
167
  if (typeof result === `function`) {
177
168
  return { cleanup: result };
@@ -218,7 +209,7 @@ class PersistedCollectionRuntime {
218
209
  this.pendingRemoteSubsetEnsures = /* @__PURE__ */ new Map();
219
210
  this.queuedHydrationTransactions = [];
220
211
  this.queuedTxCommitted = [];
221
- this.subscriptionIds = /* @__PURE__ */ new WeakMap();
212
+ this.requestIds = /* @__PURE__ */ new WeakMap();
222
213
  this.collection = null;
223
214
  this.syncControls = {
224
215
  begin: null,
@@ -230,15 +221,17 @@ class PersistedCollectionRuntime {
230
221
  this.started = false;
231
222
  this.startupMetadataPromise = null;
232
223
  this.startPromise = null;
224
+ this.resumeBaselinePromise = null;
225
+ this.lifecycleGeneration = 0;
233
226
  this.internalApplyDepth = 0;
234
227
  this.appliedReceiptSequence = 0;
235
228
  this.pendingAppliedReceipts = /* @__PURE__ */ new Map();
236
- this.isHydrating = false;
229
+ this.hydratingGeneration = null;
237
230
  this.coordinatorUnsubscribe = null;
238
231
  this.indexAddedUnsubscribe = null;
239
232
  this.indexRemovedUnsubscribe = null;
240
233
  this.remoteEnsureRetryTimer = null;
241
- this.nextSubscriptionId = 0;
234
+ this.nextRequestId = 0;
242
235
  this.latestTerm = 0;
243
236
  this.latestSeq = 0;
244
237
  this.latestRowVersion = 0;
@@ -247,6 +240,7 @@ class PersistedCollectionRuntime {
247
240
  this.localRowVersion = 0;
248
241
  }
249
242
  setSyncControls(syncControls) {
243
+ this.advanceLifecycle();
250
244
  const commit = syncControls.commit;
251
245
  this.syncControls = {
252
246
  ...syncControls,
@@ -281,7 +275,7 @@ class PersistedCollectionRuntime {
281
275
  };
282
276
  }
283
277
  isHydratingNow() {
284
- return this.isHydrating;
278
+ return this.hydratingGeneration === this.lifecycleGeneration;
285
279
  }
286
280
  isApplyingInternally() {
287
281
  return this.internalApplyDepth > 0;
@@ -303,49 +297,75 @@ class PersistedCollectionRuntime {
303
297
  if (this.startPromise) {
304
298
  return this.startPromise;
305
299
  }
306
- this.startPromise = this.startInternal();
300
+ const lifecycleGeneration = this.lifecycleGeneration;
301
+ this.startPromise = this.startInternal(lifecycleGeneration);
307
302
  return this.startPromise;
308
303
  }
304
+ ensureResumeBaselineHydrated() {
305
+ if (this.resumeBaselinePromise) {
306
+ return this.resumeBaselinePromise;
307
+ }
308
+ const lifecycleGeneration = this.lifecycleGeneration;
309
+ this.resumeBaselinePromise = (async () => {
310
+ await this.ensureStarted();
311
+ if (lifecycleGeneration !== this.lifecycleGeneration) return;
312
+ if (this.syncMode !== `on-demand`) return;
313
+ await this.hydrateBaseline(lifecycleGeneration);
314
+ })();
315
+ return this.resumeBaselinePromise;
316
+ }
317
+ async hydrateBaseline(lifecycleGeneration) {
318
+ if (lifecycleGeneration !== this.lifecycleGeneration) return;
319
+ const baseline = {};
320
+ this.activeSubsets.set(this.getSubsetKey(baseline), baseline);
321
+ const appliedCursor = this.appliedReceiptSequence;
322
+ await this.applyMutex.run(async () => {
323
+ if (lifecycleGeneration !== this.lifecycleGeneration) return;
324
+ await this.hydrateSubsetUnsafe(baseline, {
325
+ requestRemoteEnsure: false,
326
+ lifecycleGeneration
327
+ });
328
+ });
329
+ if (lifecycleGeneration !== this.lifecycleGeneration) return;
330
+ await this.waitForAppliedReceiptsAfter(appliedCursor);
331
+ }
309
332
  async ensureStartupMetadataLoaded() {
310
333
  if (this.startupMetadataPromise) {
311
334
  return this.startupMetadataPromise;
312
335
  }
313
- this.startupMetadataPromise = this.loadStartupMetadataInternal();
336
+ const lifecycleGeneration = this.lifecycleGeneration;
337
+ this.startupMetadataPromise = this.loadStartupMetadataInternal(lifecycleGeneration);
314
338
  return this.startupMetadataPromise;
315
339
  }
316
- async startInternal() {
340
+ async startInternal(lifecycleGeneration) {
317
341
  if (this.started) {
318
342
  return;
319
343
  }
320
344
  this.started = true;
321
345
  await this.ensureStartupMetadataLoaded();
346
+ if (lifecycleGeneration !== this.lifecycleGeneration) return;
322
347
  const indexBootstrapSnapshot = this.collection?.getIndexMetadata() ?? [];
323
348
  this.attachIndexLifecycleListeners();
324
349
  await this.bootstrapPersistedIndexes(indexBootstrapSnapshot);
350
+ if (lifecycleGeneration !== this.lifecycleGeneration) return;
325
351
  if (this.syncMode !== `on-demand`) {
326
- this.activeSubsets.set(this.getSubsetKey({}), {});
327
- const appliedCursor = this.appliedReceiptSequence;
328
- await this.applyMutex.run(
329
- () => this.hydrateSubsetUnsafe({}, { requestRemoteEnsure: false })
330
- );
331
- await this.waitForAppliedReceiptsAfter(appliedCursor);
352
+ await this.hydrateBaseline(lifecycleGeneration);
332
353
  }
333
354
  }
334
- async loadStartupMetadataInternal() {
355
+ async loadStartupMetadataInternal(lifecycleGeneration) {
335
356
  if (this.persistence.adapter.getStreamPosition) {
336
357
  const position = await this.persistence.adapter.getStreamPosition(
337
358
  this.collectionId
338
359
  );
360
+ if (lifecycleGeneration !== this.lifecycleGeneration) return;
339
361
  this.observeStreamPosition(
340
362
  position.latestTerm,
341
363
  position.latestSeq,
342
364
  position.latestRowVersion
343
365
  );
344
366
  }
345
- await this.loadCollectionMetadataIntoCollection();
346
- }
347
- async loadCollectionMetadataIntoCollection() {
348
367
  const collectionMetadata = await this.loadCollectionMetadataSnapshot();
368
+ if (lifecycleGeneration !== this.lifecycleGeneration) return;
349
369
  this.replaceCollectionMetadataSnapshot(collectionMetadata);
350
370
  }
351
371
  async loadCollectionMetadataSnapshot() {
@@ -376,13 +396,16 @@ class PersistedCollectionRuntime {
376
396
  });
377
397
  }
378
398
  async loadSubset(options, upstreamLoadSubset) {
399
+ const lifecycleGeneration = this.lifecycleGeneration;
379
400
  this.activeSubsets.set(this.getSubsetKey(options), options);
380
401
  const appliedCursor = this.appliedReceiptSequence;
381
402
  await this.applyMutex.run(
382
403
  () => this.hydrateSubsetUnsafe(options, {
383
- requestRemoteEnsure: this.mode === `sync-present`
404
+ requestRemoteEnsure: this.mode === `sync-present`,
405
+ lifecycleGeneration
384
406
  })
385
407
  );
408
+ if (lifecycleGeneration !== this.lifecycleGeneration) return;
386
409
  await this.waitForAppliedReceiptsAfter(appliedCursor);
387
410
  if (upstreamLoadSubset) {
388
411
  try {
@@ -394,6 +417,7 @@ class PersistedCollectionRuntime {
394
417
  }
395
418
  console.warn(`Failed to trigger remote subset load:`, error);
396
419
  this.queueRemoteSubsetEnsure(options);
420
+ throw error;
397
421
  }
398
422
  }
399
423
  }
@@ -403,9 +427,12 @@ class PersistedCollectionRuntime {
403
427
  upstreamUnloadSubset?.(options);
404
428
  }
405
429
  async forceReloadSubset(options) {
406
- this.activeSubsets.set(this.getSubsetKey(options), options);
430
+ const lifecycleGeneration = this.lifecycleGeneration;
407
431
  await this.applyMutex.run(
408
- () => this.hydrateSubsetUnsafe(options, { requestRemoteEnsure: false })
432
+ () => this.hydrateSubsetUnsafe(options, {
433
+ requestRemoteEnsure: false,
434
+ lifecycleGeneration
435
+ })
409
436
  );
410
437
  }
411
438
  queueHydrationBufferedTransaction(transaction) {
@@ -495,6 +522,7 @@ class PersistedCollectionRuntime {
495
522
  await this.persistAndConfirmCollectionMutations(collectionMutations);
496
523
  }
497
524
  cleanup() {
525
+ this.advanceLifecycle();
498
526
  this.coordinatorUnsubscribe?.();
499
527
  this.coordinatorUnsubscribe = null;
500
528
  this.indexAddedUnsubscribe?.();
@@ -513,6 +541,14 @@ class PersistedCollectionRuntime {
513
541
  this.queuedHydrationTransactions.length = 0;
514
542
  this.queuedTxCommitted.length = 0;
515
543
  this.clearSyncControls();
544
+ this.collection = null;
545
+ }
546
+ advanceLifecycle() {
547
+ this.lifecycleGeneration++;
548
+ this.started = false;
549
+ this.startupMetadataPromise = null;
550
+ this.startPromise = null;
551
+ this.resumeBaselinePromise = null;
516
552
  }
517
553
  withInternalApply(task) {
518
554
  this.internalApplyDepth++;
@@ -546,12 +582,15 @@ class PersistedCollectionRuntime {
546
582
  return this.applyMutex.run(() => this.scanPersistedRowsUnsafe(options));
547
583
  }
548
584
  async hydrateSubsetUnsafe(options, config) {
549
- this.isHydrating = true;
585
+ this.hydratingGeneration = config.lifecycleGeneration;
550
586
  try {
551
587
  const rows = await this.loadSubsetRowsUnsafe(options);
588
+ if (config.lifecycleGeneration !== this.lifecycleGeneration) return;
552
589
  this.applyRowsToCollection(rows);
553
590
  } finally {
554
- this.isHydrating = false;
591
+ if (this.hydratingGeneration === config.lifecycleGeneration) {
592
+ this.hydratingGeneration = null;
593
+ }
555
594
  }
556
595
  await this.flushQueuedHydrationTransactionsUnsafe();
557
596
  await this.flushQueuedTxCommittedUnsafe();
@@ -945,18 +984,12 @@ class PersistedCollectionRuntime {
945
984
  };
946
985
  }
947
986
  getSubsetKey(options) {
948
- const subscription = options.subscription;
949
- if (subscription && typeof subscription === `object`) {
950
- const existingId = this.subscriptionIds.get(subscription);
951
- if (existingId) {
952
- return existingId;
953
- }
954
- this.nextSubscriptionId++;
955
- const id = `sub:${this.nextSubscriptionId}`;
956
- this.subscriptionIds.set(subscription, id);
957
- return id;
987
+ let id = this.requestIds.get(options);
988
+ if (id === void 0) {
989
+ id = `request:${++this.nextRequestId}`;
990
+ this.requestIds.set(options, id);
958
991
  }
959
- return `opts:${stableSerialize(normalizeSubsetOptionsForKey(options))}`;
992
+ return id;
960
993
  }
961
994
  queueRemoteSubsetEnsure(options) {
962
995
  if (this.mode !== `sync-present` || !this.persistence.coordinator.requestEnsureRemoteSubset || this.activeSubsets.get(this.getSubsetKey(options)) !== options) {
@@ -1016,7 +1049,7 @@ class PersistedCollectionRuntime {
1016
1049
  const { payload } = message;
1017
1050
  const isSelf = message.senderId === this.persistence.coordinator.getNodeId();
1018
1051
  if (isTxCommittedPayload(payload)) {
1019
- if (this.isHydrating) {
1052
+ if (this.isHydratingNow()) {
1020
1053
  this.queuedTxCommitted.push(payload);
1021
1054
  return;
1022
1055
  }
@@ -1186,13 +1219,16 @@ class PersistedCollectionRuntime {
1186
1219
  });
1187
1220
  }
1188
1221
  async reloadActiveSubsetsUnsafe() {
1222
+ const lifecycleGeneration = this.lifecycleGeneration;
1189
1223
  const activeSubsetOptions = this.activeSubsets.size > 0 ? Array.from(this.activeSubsets.values()) : [{}];
1190
- this.isHydrating = true;
1224
+ this.hydratingGeneration = lifecycleGeneration;
1191
1225
  try {
1192
1226
  const mergedRows = /* @__PURE__ */ new Map();
1193
1227
  const collectionMetadata = await this.loadCollectionMetadataSnapshot();
1228
+ if (lifecycleGeneration !== this.lifecycleGeneration) return;
1194
1229
  for (const options of activeSubsetOptions) {
1195
1230
  const subsetRows = await this.loadSubsetRowsUnsafe(options);
1231
+ if (lifecycleGeneration !== this.lifecycleGeneration) return;
1196
1232
  for (const row of subsetRows) {
1197
1233
  mergedRows.set(row.key, {
1198
1234
  value: row.value,
@@ -1209,7 +1245,9 @@ class PersistedCollectionRuntime {
1209
1245
  collectionMetadata
1210
1246
  );
1211
1247
  } finally {
1212
- this.isHydrating = false;
1248
+ if (this.hydratingGeneration === lifecycleGeneration) {
1249
+ this.hydratingGeneration = null;
1250
+ }
1213
1251
  }
1214
1252
  await this.flushQueuedHydrationTransactionsUnsafe();
1215
1253
  await this.flushQueuedTxCommittedUnsafe();
@@ -1293,6 +1331,7 @@ function createWrappedSyncConfig(sourceSyncConfig, runtime) {
1293
1331
  const transactionStack = [];
1294
1332
  const getOpenTransaction = () => transactionStack[transactionStack.length - 1];
1295
1333
  let fullStartPromise = null;
1334
+ const startupState = { cleanedUp: false };
1296
1335
  const acquisitions = /* @__PURE__ */ new Map();
1297
1336
  runtime.setSyncControls({
1298
1337
  begin: params.begin,
@@ -1307,9 +1346,12 @@ function createWrappedSyncConfig(sourceSyncConfig, runtime) {
1307
1346
  const wrappedParams = {
1308
1347
  ...params,
1309
1348
  markReady: () => {
1349
+ if (startupState.cleanedUp) return;
1310
1350
  void (fullStartPromise ?? runtime.ensureStarted()).then(() => {
1351
+ if (startupState.cleanedUp) return;
1311
1352
  params.markReady();
1312
1353
  }).catch((error) => {
1354
+ if (startupState.cleanedUp) return;
1313
1355
  console.warn(
1314
1356
  `Failed persisted sync startup before markReady:`,
1315
1357
  error
@@ -1318,6 +1360,7 @@ function createWrappedSyncConfig(sourceSyncConfig, runtime) {
1318
1360
  });
1319
1361
  },
1320
1362
  begin: (options) => {
1363
+ if (startupState.cleanedUp) return;
1321
1364
  const transaction = {
1322
1365
  operations: [],
1323
1366
  rowMetadataWrites: /* @__PURE__ */ new Map(),
@@ -1332,6 +1375,7 @@ function createWrappedSyncConfig(sourceSyncConfig, runtime) {
1332
1375
  }
1333
1376
  },
1334
1377
  write: (message) => {
1378
+ if (startupState.cleanedUp) return;
1335
1379
  const normalization = runtime.normalizeSyncWriteMessage(message);
1336
1380
  const openTransaction = getOpenTransaction();
1337
1381
  if (!openTransaction) {
@@ -1366,7 +1410,9 @@ function createWrappedSyncConfig(sourceSyncConfig, runtime) {
1366
1410
  },
1367
1411
  metadata: params.metadata ? {
1368
1412
  row: {
1413
+ whenHydrated: () => startupState.cleanedUp ? Promise.resolve() : runtime.ensureResumeBaselineHydrated(),
1369
1414
  get: (key) => {
1415
+ if (startupState.cleanedUp) return void 0;
1370
1416
  const openTransaction = getOpenTransaction();
1371
1417
  const pendingWrite = openTransaction?.rowMetadataWrites.get(key);
1372
1418
  if (pendingWrite) {
@@ -1377,8 +1423,9 @@ function createWrappedSyncConfig(sourceSyncConfig, runtime) {
1377
1423
  }
1378
1424
  return params.metadata.row.get(key);
1379
1425
  },
1380
- scanPersisted: (options) => runtime.scanPersistedRows(options),
1426
+ scanPersisted: (options) => startupState.cleanedUp ? Promise.resolve([]) : runtime.scanPersistedRows(options),
1381
1427
  set: (key, value) => {
1428
+ if (startupState.cleanedUp) return;
1382
1429
  const openTransaction = getOpenTransaction();
1383
1430
  if (!openTransaction) {
1384
1431
  throw new InvalidPersistedCollectionConfigError(
@@ -1394,6 +1441,7 @@ function createWrappedSyncConfig(sourceSyncConfig, runtime) {
1394
1441
  }
1395
1442
  },
1396
1443
  delete: (key) => {
1444
+ if (startupState.cleanedUp) return;
1397
1445
  const openTransaction = getOpenTransaction();
1398
1446
  if (!openTransaction) {
1399
1447
  throw new InvalidPersistedCollectionConfigError(
@@ -1410,6 +1458,7 @@ function createWrappedSyncConfig(sourceSyncConfig, runtime) {
1410
1458
  },
1411
1459
  collection: {
1412
1460
  get: (key) => {
1461
+ if (startupState.cleanedUp) return void 0;
1413
1462
  const openTransaction = getOpenTransaction();
1414
1463
  const pendingWrite = openTransaction?.collectionMetadataWrites.get(key);
1415
1464
  if (pendingWrite) {
@@ -1418,6 +1467,7 @@ function createWrappedSyncConfig(sourceSyncConfig, runtime) {
1418
1467
  return params.metadata.collection.get(key);
1419
1468
  },
1420
1469
  set: (key, value) => {
1470
+ if (startupState.cleanedUp) return;
1421
1471
  const openTransaction = getOpenTransaction();
1422
1472
  if (!openTransaction) {
1423
1473
  throw new InvalidPersistedCollectionConfigError(
@@ -1433,6 +1483,7 @@ function createWrappedSyncConfig(sourceSyncConfig, runtime) {
1433
1483
  }
1434
1484
  },
1435
1485
  delete: (key) => {
1486
+ if (startupState.cleanedUp) return;
1436
1487
  const openTransaction = getOpenTransaction();
1437
1488
  if (!openTransaction) {
1438
1489
  throw new InvalidPersistedCollectionConfigError(
@@ -1447,6 +1498,7 @@ function createWrappedSyncConfig(sourceSyncConfig, runtime) {
1447
1498
  }
1448
1499
  },
1449
1500
  list: (prefix) => {
1501
+ if (startupState.cleanedUp) return [];
1450
1502
  const merged = new Map(
1451
1503
  params.metadata.collection.list().map(({ key, value }) => [key, value])
1452
1504
  );
@@ -1471,6 +1523,7 @@ function createWrappedSyncConfig(sourceSyncConfig, runtime) {
1471
1523
  }
1472
1524
  } : void 0,
1473
1525
  truncate: () => {
1526
+ if (startupState.cleanedUp) return;
1474
1527
  const openTransaction = getOpenTransaction();
1475
1528
  if (!openTransaction) {
1476
1529
  params.truncate();
@@ -1484,6 +1537,7 @@ function createWrappedSyncConfig(sourceSyncConfig, runtime) {
1484
1537
  }
1485
1538
  },
1486
1539
  commit: (signal) => {
1540
+ if (startupState.cleanedUp) return true;
1487
1541
  const openTransaction = transactionStack.pop();
1488
1542
  if (!openTransaction) {
1489
1543
  return params.commit(signal);
@@ -1533,7 +1587,6 @@ function createWrappedSyncConfig(sourceSyncConfig, runtime) {
1533
1587
  }
1534
1588
  };
1535
1589
  let sourceResult = {};
1536
- const startupState = { cleanedUp: false };
1537
1590
  fullStartPromise = runtime.ensureStarted();
1538
1591
  const sourceResultPromise = (async () => {
1539
1592
  await runtime.ensureStartupMetadataLoaded();