@tanstack/db-sqlite-persistence-core 0.2.16 → 0.2.18

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.
@@ -233,6 +233,8 @@ class PersistedCollectionRuntime {
233
233
  this.startupMetadataPromise = null;
234
234
  this.startPromise = null;
235
235
  this.internalApplyDepth = 0;
236
+ this.appliedReceiptSequence = 0;
237
+ this.pendingAppliedReceipts = /* @__PURE__ */ new Map();
236
238
  this.isHydrating = false;
237
239
  this.coordinatorUnsubscribe = null;
238
240
  this.indexAddedUnsubscribe = null;
@@ -247,7 +249,29 @@ class PersistedCollectionRuntime {
247
249
  this.localRowVersion = 0;
248
250
  }
249
251
  setSyncControls(syncControls) {
250
- this.syncControls = syncControls;
252
+ const commit = syncControls.commit;
253
+ this.syncControls = {
254
+ ...syncControls,
255
+ commit: commit ? (signal) => this.trackAppliedReceipt(commit(signal)) : null
256
+ };
257
+ }
258
+ trackAppliedReceipt(receipt) {
259
+ const sequence = ++this.appliedReceiptSequence;
260
+ if (receipt === true) {
261
+ return true;
262
+ }
263
+ this.pendingAppliedReceipts.set(sequence, receipt);
264
+ const removeReceipt = () => this.pendingAppliedReceipts.delete(sequence);
265
+ void receipt.then(removeReceipt, removeReceipt);
266
+ return receipt;
267
+ }
268
+ async waitForAppliedReceiptsAfter(cursor) {
269
+ await Promise.all(
270
+ Array.from(
271
+ this.pendingAppliedReceipts,
272
+ ([sequence, receipt]) => sequence > cursor ? receipt : void 0
273
+ )
274
+ );
251
275
  }
252
276
  clearSyncControls() {
253
277
  this.syncControls = {
@@ -302,9 +326,11 @@ class PersistedCollectionRuntime {
302
326
  await this.bootstrapPersistedIndexes(indexBootstrapSnapshot);
303
327
  if (this.syncMode !== `on-demand`) {
304
328
  this.activeSubsets.set(this.getSubsetKey({}), {});
329
+ const appliedCursor = this.appliedReceiptSequence;
305
330
  await this.applyMutex.run(
306
331
  () => this.hydrateSubsetUnsafe({}, { requestRemoteEnsure: false })
307
332
  );
333
+ await this.waitForAppliedReceiptsAfter(appliedCursor);
308
334
  }
309
335
  }
310
336
  async loadStartupMetadataInternal() {
@@ -353,16 +379,18 @@ class PersistedCollectionRuntime {
353
379
  }
354
380
  async loadSubset(options, upstreamLoadSubset) {
355
381
  this.activeSubsets.set(this.getSubsetKey(options), options);
382
+ const appliedCursor = this.appliedReceiptSequence;
356
383
  await this.applyMutex.run(
357
384
  () => this.hydrateSubsetUnsafe(options, {
358
385
  requestRemoteEnsure: this.mode === `sync-present`
359
386
  })
360
387
  );
388
+ await this.waitForAppliedReceiptsAfter(appliedCursor);
361
389
  if (upstreamLoadSubset) {
362
390
  try {
363
391
  const maybePromise = upstreamLoadSubset(options);
364
392
  if (maybePromise instanceof Promise) {
365
- maybePromise.catch((error) => {
393
+ await maybePromise.catch((error) => {
366
394
  console.warn(
367
395
  `Failed to load remote subset in persisted wrapper:`,
368
396
  error
@@ -485,6 +513,9 @@ class PersistedCollectionRuntime {
485
513
  }
486
514
  this.pendingRemoteSubsetEnsures.clear();
487
515
  this.activeSubsets.clear();
516
+ for (const transaction of this.queuedHydrationTransactions) {
517
+ transaction.rejectApplied?.(new db.SyncTransactionAbortedError());
518
+ }
488
519
  this.queuedHydrationTransactions.length = 0;
489
520
  this.queuedTxCommitted.length = 0;
490
521
  this.clearSyncControls();
@@ -492,7 +523,7 @@ class PersistedCollectionRuntime {
492
523
  withInternalApply(task) {
493
524
  this.internalApplyDepth++;
494
525
  try {
495
- task();
526
+ return task();
496
527
  } finally {
497
528
  this.internalApplyDepth--;
498
529
  }
@@ -588,26 +619,41 @@ class PersistedCollectionRuntime {
588
619
  if (!transaction) {
589
620
  continue;
590
621
  }
591
- await this.applyBufferedSyncTransactionUnsafe(transaction);
622
+ try {
623
+ await this.applyBufferedSyncTransactionUnsafe(transaction);
624
+ } catch (error) {
625
+ transaction.rejectApplied?.(error);
626
+ for (const abandoned of this.queuedHydrationTransactions) {
627
+ abandoned.rejectApplied?.(error);
628
+ }
629
+ this.queuedHydrationTransactions.length = 0;
630
+ throw error;
631
+ }
592
632
  }
593
633
  }
594
634
  async applyBufferedSyncTransactionUnsafe(transaction) {
595
- if (!this.syncControls.begin || !this.syncControls.write || !this.syncControls.commit) {
635
+ if (transaction.signal?.aborted) {
636
+ transaction.rejectApplied?.(new db.SyncTransactionAbortedError());
637
+ return;
638
+ }
639
+ const { begin, write, commit, truncate, metadata } = this.syncControls;
640
+ if (!begin || !write || !commit) {
641
+ transaction.rejectApplied?.(new db.SyncTransactionAbortedError());
596
642
  return;
597
643
  }
598
644
  const applyToCollection = () => {
599
- this.syncControls.begin?.();
645
+ begin();
600
646
  if (transaction.truncate) {
601
- this.syncControls.truncate?.();
647
+ truncate?.();
602
648
  }
603
649
  for (const operation of transaction.operations) {
604
650
  if (operation.type === `delete`) {
605
- this.syncControls.write?.({
651
+ write({
606
652
  type: `delete`,
607
653
  key: operation.key
608
654
  });
609
655
  } else {
610
- this.syncControls.write?.({
656
+ write({
611
657
  type: `update`,
612
658
  value: operation.value,
613
659
  metadata: operation.metadata
@@ -616,26 +662,33 @@ class PersistedCollectionRuntime {
616
662
  }
617
663
  for (const [key, metadataWrite] of transaction.rowMetadataWrites) {
618
664
  if (metadataWrite.type === `delete`) {
619
- this.syncControls.metadata?.row.delete(key);
665
+ metadata?.row.delete(key);
620
666
  } else {
621
- this.syncControls.metadata?.row.set(key, metadataWrite.value);
667
+ metadata?.row.set(key, metadataWrite.value);
622
668
  }
623
669
  }
624
670
  for (const [key, metadataWrite] of transaction.collectionMetadataWrites) {
625
671
  if (metadataWrite.type === `delete`) {
626
- this.syncControls.metadata?.collection.delete(key);
672
+ metadata?.collection.delete(key);
627
673
  } else {
628
- this.syncControls.metadata?.collection.set(key, metadataWrite.value);
674
+ metadata?.collection.set(key, metadataWrite.value);
629
675
  }
630
676
  }
631
- this.syncControls.commit?.();
677
+ return commit(transaction.signal);
632
678
  };
633
- if (transaction.internal) {
634
- this.withInternalApply(applyToCollection);
635
- return;
679
+ try {
680
+ const applied = transaction.internal ? this.withInternalApply(applyToCollection) : applyToCollection();
681
+ if (applied !== true) {
682
+ await applied;
683
+ }
684
+ if (!transaction.internal) {
685
+ await this.persistAndBroadcastExternalSyncTransactionUnsafe(transaction);
686
+ }
687
+ transaction.resolveApplied?.();
688
+ } catch (error) {
689
+ transaction.rejectApplied?.(error);
690
+ throw error;
636
691
  }
637
- applyToCollection();
638
- await this.persistAndBroadcastExternalSyncTransactionUnsafe(transaction);
639
692
  }
640
693
  async persistAndBroadcastExternalSyncTransactionUnsafe(transaction) {
641
694
  if (transaction.internal) {
@@ -1452,37 +1505,53 @@ function createWrappedSyncConfig(sourceSyncConfig, runtime) {
1452
1505
  params.truncate();
1453
1506
  }
1454
1507
  },
1455
- commit: () => {
1508
+ commit: (signal) => {
1456
1509
  const openTransaction = transactionStack.pop();
1457
1510
  if (!openTransaction) {
1458
- params.commit();
1459
- return;
1511
+ return params.commit(signal);
1460
1512
  }
1461
1513
  if (openTransaction.queuedBecauseHydrating) {
1514
+ if (signal?.aborted) {
1515
+ const aborted = Promise.reject(new db.SyncTransactionAbortedError());
1516
+ void aborted.catch(() => void 0);
1517
+ return aborted;
1518
+ }
1519
+ let resolveApplied;
1520
+ let rejectApplied;
1521
+ const applied2 = new Promise((resolve, reject) => {
1522
+ resolveApplied = resolve;
1523
+ rejectApplied = reject;
1524
+ });
1525
+ void applied2.catch(() => void 0);
1462
1526
  runtime.queueHydrationBufferedTransaction({
1463
1527
  operations: openTransaction.operations,
1464
1528
  rowMetadataWrites: openTransaction.rowMetadataWrites,
1465
1529
  collectionMetadataWrites: openTransaction.collectionMetadataWrites,
1466
1530
  truncate: openTransaction.truncate,
1467
- internal: openTransaction.internal
1531
+ internal: openTransaction.internal,
1532
+ signal,
1533
+ resolveApplied,
1534
+ rejectApplied
1468
1535
  });
1469
- return;
1536
+ return applied2;
1470
1537
  }
1471
- params.commit();
1538
+ const applied = params.commit(signal);
1472
1539
  if (!openTransaction.internal) {
1473
- void runtime.persistAndBroadcastExternalSyncTransaction({
1474
- operations: openTransaction.operations,
1475
- rowMetadataWrites: openTransaction.rowMetadataWrites,
1476
- collectionMetadataWrites: openTransaction.collectionMetadataWrites,
1477
- truncate: openTransaction.truncate,
1478
- internal: false
1479
- }).catch((error) => {
1480
- console.warn(
1481
- `Failed to persist wrapped sync transaction:`,
1482
- error
1483
- );
1484
- });
1540
+ const persistAfterApplication = async () => {
1541
+ if (applied !== true) await applied;
1542
+ await runtime.persistAndBroadcastExternalSyncTransaction({
1543
+ operations: openTransaction.operations,
1544
+ rowMetadataWrites: openTransaction.rowMetadataWrites,
1545
+ collectionMetadataWrites: openTransaction.collectionMetadataWrites,
1546
+ truncate: openTransaction.truncate,
1547
+ internal: false
1548
+ });
1549
+ };
1550
+ const persisted = persistAfterApplication();
1551
+ void persisted.catch(() => void 0);
1552
+ return persisted;
1485
1553
  }
1554
+ return applied;
1486
1555
  }
1487
1556
  };
1488
1557
  let sourceResult = {};