@tanstack/powersync-db-collection 0.1.65 → 0.1.67
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/dist/cjs/PowerSyncTransactor.cjs +16 -1
- package/dist/cjs/PowerSyncTransactor.cjs.map +1 -1
- package/dist/cjs/powersync.cjs +219 -90
- package/dist/cjs/powersync.cjs.map +1 -1
- package/dist/esm/PowerSyncTransactor.js +16 -1
- package/dist/esm/PowerSyncTransactor.js.map +1 -1
- package/dist/esm/powersync.js +219 -90
- package/dist/esm/powersync.js.map +1 -1
- package/package.json +2 -2
- package/src/PowerSyncTransactor.ts +24 -1
- package/src/powersync.ts +286 -126
package/src/powersync.ts
CHANGED
|
@@ -323,6 +323,7 @@ function createPowerSyncCollectionConfig<
|
|
|
323
323
|
let disposeTracking:
|
|
324
324
|
| ((options?: { context?: LockContext }) => Promise<void>)
|
|
325
325
|
| null = null
|
|
326
|
+
let trackingSetup: Promise<void> | null = null
|
|
326
327
|
|
|
327
328
|
if (syncMode === `eager`) {
|
|
328
329
|
return runEagerSync()
|
|
@@ -337,6 +338,13 @@ function createPowerSyncCollectionConfig<
|
|
|
337
338
|
async function safelyDisposeTracking(
|
|
338
339
|
context?: LockContext,
|
|
339
340
|
): Promise<void> {
|
|
341
|
+
// Cleanup can race trigger creation. Wait until the disposer has been
|
|
342
|
+
// published so an abort cannot strand a freshly-created trigger.
|
|
343
|
+
const setup = trackingSetup
|
|
344
|
+
if (setup) {
|
|
345
|
+
await setup.catch(() => undefined)
|
|
346
|
+
}
|
|
347
|
+
|
|
340
348
|
const dispose = disposeTracking
|
|
341
349
|
if (!dispose) {
|
|
342
350
|
return
|
|
@@ -346,6 +354,25 @@ function createPowerSyncCollectionConfig<
|
|
|
346
354
|
await dispose(context ? { context } : undefined)
|
|
347
355
|
}
|
|
348
356
|
|
|
357
|
+
async function establishTracking(
|
|
358
|
+
options: Parameters<typeof createDiffTrigger>[0],
|
|
359
|
+
appliedReceipts: Array<SyncAppliedReceipt>,
|
|
360
|
+
): Promise<void> {
|
|
361
|
+
const setup = (async () => {
|
|
362
|
+
const dispose = await createDiffTrigger(options, appliedReceipts)
|
|
363
|
+
disposeTracking = dispose
|
|
364
|
+
})()
|
|
365
|
+
trackingSetup = setup
|
|
366
|
+
|
|
367
|
+
try {
|
|
368
|
+
await setup
|
|
369
|
+
} finally {
|
|
370
|
+
if (trackingSetup === setup) {
|
|
371
|
+
trackingSetup = null
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
349
376
|
async function createDiffTrigger(
|
|
350
377
|
options: {
|
|
351
378
|
setupContext?: LockContext
|
|
@@ -398,6 +425,17 @@ function createPowerSyncCollectionConfig<
|
|
|
398
425
|
}
|
|
399
426
|
|
|
400
427
|
async function flushDiffRecords(): Promise<void> {
|
|
428
|
+
// PowerSync can notify after creating the tracking table but before its
|
|
429
|
+
// create call returns. Preserve that notification until the disposer,
|
|
430
|
+
// which proves the trigger is usable, has been published.
|
|
431
|
+
const setup = trackingSetup
|
|
432
|
+
if (setup) {
|
|
433
|
+
await setup.catch(() => undefined)
|
|
434
|
+
}
|
|
435
|
+
if (!disposeTracking) {
|
|
436
|
+
return
|
|
437
|
+
}
|
|
438
|
+
|
|
401
439
|
const ignoredReceipts: Array<SyncAppliedReceipt> = []
|
|
402
440
|
await database
|
|
403
441
|
.writeTransaction(async (context) => {
|
|
@@ -512,10 +550,15 @@ function createPowerSyncCollectionConfig<
|
|
|
512
550
|
let onUnload: CleanupFn | void | null = null
|
|
513
551
|
|
|
514
552
|
start(async () => {
|
|
515
|
-
|
|
553
|
+
const cleanup = await restConfig.onLoad?.()
|
|
554
|
+
if (abortController.signal.aborted) {
|
|
555
|
+
cleanup?.()
|
|
556
|
+
return
|
|
557
|
+
}
|
|
558
|
+
onUnload = cleanup
|
|
516
559
|
|
|
517
560
|
const appliedReceipts: Array<SyncAppliedReceipt> = []
|
|
518
|
-
|
|
561
|
+
await establishTracking(
|
|
519
562
|
{
|
|
520
563
|
// Initial eager hydration must make the source usable before
|
|
521
564
|
// PowerSync can persist a mutation queued during startup.
|
|
@@ -562,110 +605,162 @@ function createPowerSyncCollectionConfig<
|
|
|
562
605
|
// On-demand mode.
|
|
563
606
|
// Registers a diff trigger for the active WHERE expressions.
|
|
564
607
|
function runOnDemandSync() {
|
|
565
|
-
|
|
608
|
+
type DemandRecord = {
|
|
609
|
+
options: LoadSubsetOptions
|
|
610
|
+
active: boolean
|
|
611
|
+
cleanup?: CleanupFn
|
|
612
|
+
}
|
|
613
|
+
type PendingRelease = {
|
|
614
|
+
options: LoadSubsetOptions
|
|
615
|
+
failures: number
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
const demands = new Map<LoadSubsetOptions, DemandRecord>()
|
|
566
619
|
const releasedSubsets = new WeakSet<LoadSubsetOptions>()
|
|
620
|
+
const pendingReleases: Array<PendingRelease> = []
|
|
567
621
|
let stopped = false
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
622
|
+
let trackingRevision = 0
|
|
623
|
+
let reconciledTrackingRevision = 0
|
|
624
|
+
let rebuildPromise: Promise<void> | null = null
|
|
625
|
+
let drainingReleases = false
|
|
626
|
+
let releaseRetryTimer: ReturnType<typeof setTimeout> | undefined
|
|
627
|
+
const startup = start()
|
|
628
|
+
void startup.catch((error) =>
|
|
571
629
|
database.logger.error(
|
|
572
630
|
`Could not start syncing process for ${viewName} into ${trackedTableName}`,
|
|
573
631
|
error,
|
|
574
632
|
),
|
|
575
633
|
)
|
|
576
634
|
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
635
|
+
const activeWhereExpressions = () =>
|
|
636
|
+
Array.from(demands.values())
|
|
637
|
+
.filter((demand) => demand.active)
|
|
638
|
+
.map((demand) => demand.options.where)
|
|
580
639
|
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
): Promise<void> => {
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
activeWhereExpressions.push(options.where)
|
|
589
|
-
const cleanup = await restConfig.onLoadSubset?.(options)
|
|
590
|
-
if (hasStopped()) {
|
|
591
|
-
cleanup?.()
|
|
592
|
-
return
|
|
593
|
-
}
|
|
594
|
-
if (cleanup) {
|
|
595
|
-
if (releasedSubsets.has(options) || options.signal?.aborted) {
|
|
596
|
-
cleanup()
|
|
597
|
-
} else {
|
|
598
|
-
unloadSubsetCallbacks.set(options, cleanup)
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
}
|
|
640
|
+
// One reconciliation owns every queued revision so callers cannot
|
|
641
|
+
// settle against a stale trigger configuration.
|
|
642
|
+
const reconcileTracking = async (): Promise<void> => {
|
|
643
|
+
while (!stopped && reconciledTrackingRevision !== trackingRevision) {
|
|
644
|
+
const revision = trackingRevision
|
|
645
|
+
const isCurrent = () => !stopped && trackingRevision === revision
|
|
646
|
+
const appliedReceipts: Array<SyncAppliedReceipt> = []
|
|
602
647
|
|
|
603
|
-
// No predicates remain, so stop tracking entirely. Both calls are no-ops
|
|
604
|
-
// when no tracking table is currently active.
|
|
605
|
-
if (activeWhereExpressions.length === 0) {
|
|
606
648
|
await database.writeLock(async (ctx) => {
|
|
649
|
+
if (!isCurrent()) return
|
|
607
650
|
await flushDiffRecordsWithContext(ctx, appliedReceipts)
|
|
651
|
+
if (!isCurrent()) return
|
|
608
652
|
await safelyDisposeTracking(ctx)
|
|
653
|
+
if (!isCurrent()) return
|
|
654
|
+
|
|
655
|
+
const active = activeWhereExpressions()
|
|
656
|
+
if (active.length === 0) return
|
|
657
|
+
const combinedWhere =
|
|
658
|
+
active.length === 1
|
|
659
|
+
? active[0]
|
|
660
|
+
: or(active[0], active[1], ...active.slice(2))
|
|
661
|
+
const compiledNewData = compileSQLite(
|
|
662
|
+
{ where: combinedWhere },
|
|
663
|
+
{ jsonColumn: 'NEW.data' },
|
|
664
|
+
)
|
|
665
|
+
const compiledOldData = compileSQLite(
|
|
666
|
+
{ where: combinedWhere },
|
|
667
|
+
{ jsonColumn: 'OLD.data' },
|
|
668
|
+
)
|
|
669
|
+
const compiledView = compileSQLite({ where: combinedWhere })
|
|
670
|
+
const newDataWhenClause = toInlinedWhereClause(compiledNewData)
|
|
671
|
+
const oldDataWhenClause = toInlinedWhereClause(compiledOldData)
|
|
672
|
+
const viewWhereClause = toInlinedWhereClause(compiledView)
|
|
673
|
+
|
|
674
|
+
await establishTracking(
|
|
675
|
+
{
|
|
676
|
+
setupContext: ctx,
|
|
677
|
+
when: {
|
|
678
|
+
[DiffTriggerOperation.INSERT]: newDataWhenClause,
|
|
679
|
+
[DiffTriggerOperation.UPDATE]: `(${newDataWhenClause}) OR (${oldDataWhenClause})`,
|
|
680
|
+
[DiffTriggerOperation.DELETE]: oldDataWhenClause,
|
|
681
|
+
},
|
|
682
|
+
writeType: (rowId: string) =>
|
|
683
|
+
collection.has(rowId) ? `update` : `insert`,
|
|
684
|
+
batchQuery: (
|
|
685
|
+
lockContext: LockContext,
|
|
686
|
+
batchSize: number,
|
|
687
|
+
cursor: number,
|
|
688
|
+
) =>
|
|
689
|
+
lockContext.getAll<TableType>(
|
|
690
|
+
`SELECT * FROM ${viewName} WHERE ${viewWhereClause} LIMIT ? OFFSET ?`,
|
|
691
|
+
[batchSize, cursor],
|
|
692
|
+
),
|
|
693
|
+
},
|
|
694
|
+
appliedReceipts,
|
|
695
|
+
)
|
|
696
|
+
if (!isCurrent()) await safelyDisposeTracking(ctx)
|
|
609
697
|
})
|
|
610
698
|
await Promise.all(appliedReceipts)
|
|
611
|
-
|
|
699
|
+
if (isCurrent()) {
|
|
700
|
+
reconciledTrackingRevision = revision
|
|
701
|
+
}
|
|
612
702
|
}
|
|
703
|
+
}
|
|
613
704
|
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
705
|
+
const rebuildTracking = async (): Promise<void> => {
|
|
706
|
+
// New demand can join after reconciliation exits but before its
|
|
707
|
+
// shared promise clears. Each waiter must check its revision again.
|
|
708
|
+
while (!stopped && reconciledTrackingRevision !== trackingRevision) {
|
|
709
|
+
await (rebuildPromise ??= reconcileTracking()
|
|
710
|
+
.catch((error) => {
|
|
711
|
+
// A rebuild may already have removed every active diff trigger.
|
|
712
|
+
// Do not leave healthy consumers ready against a stale source.
|
|
713
|
+
if (!stopped) markError(error)
|
|
714
|
+
throw error
|
|
715
|
+
})
|
|
716
|
+
.finally(() => {
|
|
717
|
+
rebuildPromise = null
|
|
718
|
+
}))
|
|
719
|
+
}
|
|
720
|
+
}
|
|
622
721
|
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
)
|
|
722
|
+
const loadSubset = async (
|
|
723
|
+
options: LoadSubsetOptions,
|
|
724
|
+
): Promise<void> => {
|
|
725
|
+
if (stopped) return
|
|
726
|
+
// Never create a trigger that has no observer to drain its diff table.
|
|
727
|
+
await startup
|
|
728
|
+
if (
|
|
729
|
+
// Cleanup can run while startup is pending.
|
|
730
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
731
|
+
stopped ||
|
|
732
|
+
releasedSubsets.has(options) ||
|
|
733
|
+
options.signal?.aborted
|
|
734
|
+
) {
|
|
735
|
+
return
|
|
736
|
+
}
|
|
627
737
|
|
|
628
|
-
const
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
738
|
+
const demand: DemandRecord = { options, active: false }
|
|
739
|
+
demands.set(options, demand)
|
|
740
|
+
try {
|
|
741
|
+
const cleanup = await restConfig.onLoadSubset?.(options)
|
|
742
|
+
if (cleanup) demand.cleanup = cleanup
|
|
743
|
+
} catch (error) {
|
|
744
|
+
demands.delete(options)
|
|
745
|
+
throw error
|
|
746
|
+
}
|
|
632
747
|
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
[DiffTriggerOperation.INSERT]: newDataWhenClause,
|
|
650
|
-
[DiffTriggerOperation.UPDATE]: `(${newDataWhenClause}) OR (${oldDataWhenClause})`,
|
|
651
|
-
[DiffTriggerOperation.DELETE]: oldDataWhenClause,
|
|
652
|
-
},
|
|
653
|
-
writeType: (rowId: string) =>
|
|
654
|
-
collection.has(rowId) ? `update` : `insert`,
|
|
655
|
-
batchQuery: (
|
|
656
|
-
lockContext: LockContext,
|
|
657
|
-
batchSize: number,
|
|
658
|
-
cursor: number,
|
|
659
|
-
) =>
|
|
660
|
-
lockContext.getAll<TableType>(
|
|
661
|
-
`SELECT * FROM ${viewName} WHERE ${viewWhereClause} LIMIT ? OFFSET ?`,
|
|
662
|
-
[batchSize, cursor],
|
|
663
|
-
),
|
|
664
|
-
},
|
|
665
|
-
appliedReceipts,
|
|
666
|
-
)
|
|
667
|
-
})
|
|
668
|
-
await Promise.all(appliedReceipts)
|
|
748
|
+
if (
|
|
749
|
+
// The user hook can reenter cleanup.
|
|
750
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
751
|
+
stopped ||
|
|
752
|
+
releasedSubsets.has(options) ||
|
|
753
|
+
options.signal?.aborted ||
|
|
754
|
+
demands.get(options) !== demand
|
|
755
|
+
) {
|
|
756
|
+
demands.delete(options)
|
|
757
|
+
demand.cleanup?.()
|
|
758
|
+
return
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
demand.active = true
|
|
762
|
+
trackingRevision++
|
|
763
|
+
await rebuildTracking()
|
|
669
764
|
}
|
|
670
765
|
|
|
671
766
|
const toInlinedWhereClause = (compiled: {
|
|
@@ -680,56 +775,118 @@ function createPowerSyncCollectionConfig<
|
|
|
680
775
|
)
|
|
681
776
|
}
|
|
682
777
|
|
|
683
|
-
const
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
778
|
+
const cleanupDemand = (demand: DemandRecord): void => {
|
|
779
|
+
demands.delete(demand.options)
|
|
780
|
+
try {
|
|
781
|
+
demand.cleanup?.()
|
|
782
|
+
} catch (error) {
|
|
783
|
+
database.logger.error(
|
|
784
|
+
`Could not clean up subset hook for ${viewName}`,
|
|
785
|
+
error,
|
|
786
|
+
)
|
|
691
787
|
}
|
|
788
|
+
}
|
|
692
789
|
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
790
|
+
const performPhysicalRelease = async (
|
|
791
|
+
options: LoadSubsetOptions,
|
|
792
|
+
): Promise<void> => {
|
|
696
793
|
const compiledDeparting = compileSQLite({ where: options.where })
|
|
697
794
|
const departingWhereSQL = toInlinedWhereClause(compiledDeparting)
|
|
795
|
+
let rowsToEvict: Array<{ id: string }>
|
|
796
|
+
for (;;) {
|
|
797
|
+
if (stopped) return
|
|
798
|
+
const revision = trackingRevision
|
|
799
|
+
const active = activeWhereExpressions()
|
|
800
|
+
let evictionSQL: string
|
|
801
|
+
if (active.length === 0) {
|
|
802
|
+
evictionSQL = `SELECT id FROM ${viewName} WHERE ${departingWhereSQL}`
|
|
803
|
+
} else {
|
|
804
|
+
const combinedRemaining =
|
|
805
|
+
active.length === 1
|
|
806
|
+
? active[0]!
|
|
807
|
+
: or(active[0], active[1], ...active.slice(2))
|
|
808
|
+
const compiledRemaining = compileSQLite({
|
|
809
|
+
where: combinedRemaining,
|
|
810
|
+
})
|
|
811
|
+
const remainingWhereSQL = toInlinedWhereClause(compiledRemaining)
|
|
812
|
+
evictionSQL = `SELECT id FROM ${viewName} WHERE (${departingWhereSQL}) AND NOT (${remainingWhereSQL})`
|
|
813
|
+
}
|
|
698
814
|
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
const combinedRemaining =
|
|
704
|
-
activeWhereExpressions.length === 1
|
|
705
|
-
? activeWhereExpressions[0]!
|
|
706
|
-
: or(
|
|
707
|
-
activeWhereExpressions[0],
|
|
708
|
-
activeWhereExpressions[1],
|
|
709
|
-
...activeWhereExpressions.slice(2),
|
|
710
|
-
)
|
|
711
|
-
const compiledRemaining = compileSQLite({
|
|
712
|
-
where: combinedRemaining,
|
|
713
|
-
})
|
|
714
|
-
const remainingWhereSQL = toInlinedWhereClause(compiledRemaining)
|
|
715
|
-
evictionSQL = `SELECT id FROM ${viewName} WHERE (${departingWhereSQL}) AND NOT (${remainingWhereSQL})`
|
|
815
|
+
rowsToEvict = await database.getAll<{ id: string }>(evictionSQL)
|
|
816
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- cleanup can run during the query
|
|
817
|
+
if (stopped) return
|
|
818
|
+
if (trackingRevision === revision) break
|
|
716
819
|
}
|
|
717
|
-
|
|
718
|
-
const rowsToEvict = await database.getAll<{ id: string }>(evictionSQL)
|
|
719
820
|
if (rowsToEvict.length > 0) {
|
|
720
821
|
begin()
|
|
721
822
|
for (const { id } of rowsToEvict) {
|
|
722
823
|
write({ type: `delete`, key: id })
|
|
723
824
|
}
|
|
724
|
-
// Eviction does not establish new subset coverage. Keep trigger
|
|
725
|
-
// replacement in the same unload turn even when this delete waits
|
|
726
|
-
// behind a persisting mutation; the later load tracks its own
|
|
727
|
-
// establishing receipts.
|
|
728
825
|
void commit()
|
|
729
826
|
}
|
|
827
|
+
await rebuildTracking()
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
function scheduleReleaseDrain(delay = 0): void {
|
|
831
|
+
if (stopped || drainingReleases || releaseRetryTimer) return
|
|
832
|
+
if (delay > 0) {
|
|
833
|
+
releaseRetryTimer = setTimeout(() => {
|
|
834
|
+
releaseRetryTimer = undefined
|
|
835
|
+
void drainReleases()
|
|
836
|
+
}, delay)
|
|
837
|
+
return
|
|
838
|
+
}
|
|
839
|
+
void drainReleases()
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
async function drainReleases(): Promise<void> {
|
|
843
|
+
if (stopped || drainingReleases) return
|
|
844
|
+
drainingReleases = true
|
|
845
|
+
let retryDelay = 0
|
|
846
|
+
try {
|
|
847
|
+
const attempts = pendingReleases.length
|
|
848
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- each release can reenter cleanup
|
|
849
|
+
for (let index = 0; !stopped && index < attempts; index++) {
|
|
850
|
+
const pending = pendingReleases.shift()!
|
|
851
|
+
try {
|
|
852
|
+
await performPhysicalRelease(pending.options)
|
|
853
|
+
} catch (error) {
|
|
854
|
+
pending.failures++
|
|
855
|
+
pendingReleases.push(pending)
|
|
856
|
+
const delay = Math.min(
|
|
857
|
+
1000 * 2 ** (pending.failures - 1),
|
|
858
|
+
30000,
|
|
859
|
+
)
|
|
860
|
+
retryDelay =
|
|
861
|
+
retryDelay === 0 ? delay : Math.min(retryDelay, delay)
|
|
862
|
+
database.logger.error(
|
|
863
|
+
`Could not release subset tracking for ${viewName}; retrying`,
|
|
864
|
+
error,
|
|
865
|
+
)
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
} finally {
|
|
869
|
+
drainingReleases = false
|
|
870
|
+
}
|
|
871
|
+
if (pendingReleases.length > 0) scheduleReleaseDrain(retryDelay)
|
|
872
|
+
}
|
|
730
873
|
|
|
731
|
-
|
|
732
|
-
|
|
874
|
+
const unloadSubset = (options: LoadSubsetOptions): void => {
|
|
875
|
+
releasedSubsets.add(options)
|
|
876
|
+
const demand = demands.get(options)
|
|
877
|
+
if (!demand) return
|
|
878
|
+
|
|
879
|
+
const wasActive = demand.active
|
|
880
|
+
if (wasActive) trackingRevision++
|
|
881
|
+
cleanupDemand(demand)
|
|
882
|
+
|
|
883
|
+
if (wasActive) {
|
|
884
|
+
pendingReleases.push({ options, failures: 0 })
|
|
885
|
+
// New work must not wait for another release's backoff.
|
|
886
|
+
clearTimeout(releaseRetryTimer)
|
|
887
|
+
releaseRetryTimer = undefined
|
|
888
|
+
scheduleReleaseDrain()
|
|
889
|
+
}
|
|
733
890
|
}
|
|
734
891
|
|
|
735
892
|
markReady()
|
|
@@ -737,16 +894,19 @@ function createPowerSyncCollectionConfig<
|
|
|
737
894
|
return {
|
|
738
895
|
cleanup: () => {
|
|
739
896
|
stopped = true
|
|
897
|
+
clearTimeout(releaseRetryTimer)
|
|
898
|
+
releaseRetryTimer = undefined
|
|
740
899
|
database.logger.info(
|
|
741
900
|
`Sync has been stopped for ${viewName} into ${trackedTableName}`,
|
|
742
901
|
)
|
|
743
902
|
abortController.abort()
|
|
744
|
-
for (const
|
|
745
|
-
|
|
746
|
-
|
|
903
|
+
for (const demand of demands.values()) {
|
|
904
|
+
cleanupDemand(demand)
|
|
905
|
+
}
|
|
906
|
+
pendingReleases.length = 0
|
|
747
907
|
},
|
|
748
908
|
loadSubset: (options: LoadSubsetOptions) => loadSubset(options),
|
|
749
|
-
unloadSubset
|
|
909
|
+
unloadSubset,
|
|
750
910
|
}
|
|
751
911
|
}
|
|
752
912
|
},
|