@tanstack/electric-db-collection 0.4.3 → 0.4.5
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/electric.cjs +55 -9
- package/dist/cjs/electric.cjs.map +1 -1
- package/dist/esm/electric.js +55 -9
- package/dist/esm/electric.js.map +1 -1
- package/package.json +2 -2
- package/src/electric.ts +71 -11
package/src/electric.ts
CHANGED
|
@@ -47,6 +47,7 @@ import type {
|
|
|
47
47
|
DeleteMutationFnParams,
|
|
48
48
|
InsertMutationFnParams,
|
|
49
49
|
LoadSubsetOptions,
|
|
50
|
+
SyncAppliedReceipt,
|
|
50
51
|
SyncConfig,
|
|
51
52
|
SyncMode,
|
|
52
53
|
UpdateMutationFnParams,
|
|
@@ -526,6 +527,8 @@ function createLoadSubsetDedupe<T extends Row<unknown>>({
|
|
|
526
527
|
begin,
|
|
527
528
|
write,
|
|
528
529
|
commit,
|
|
530
|
+
getCommitCursor,
|
|
531
|
+
waitForCommitsAfter,
|
|
529
532
|
collectionId,
|
|
530
533
|
encodeColumnName,
|
|
531
534
|
signal,
|
|
@@ -539,7 +542,9 @@ function createLoadSubsetDedupe<T extends Row<unknown>>({
|
|
|
539
542
|
value: T
|
|
540
543
|
metadata: Record<string, unknown>
|
|
541
544
|
}) => void
|
|
542
|
-
commit: () =>
|
|
545
|
+
commit: (signal?: AbortSignal) => SyncAppliedReceipt
|
|
546
|
+
getCommitCursor: () => number
|
|
547
|
+
waitForCommitsAfter: (cursor: number) => Promise<void>
|
|
543
548
|
collectionId?: string
|
|
544
549
|
/**
|
|
545
550
|
* Optional function to encode column names (e.g., camelCase to snake_case).
|
|
@@ -573,12 +578,14 @@ function createLoadSubsetDedupe<T extends Row<unknown>>({
|
|
|
573
578
|
}
|
|
574
579
|
|
|
575
580
|
const loadSubset = async (opts: LoadSubsetOptions) => {
|
|
581
|
+
const commitCursor = getCommitCursor()
|
|
582
|
+
if (opts.signal?.aborted) return
|
|
583
|
+
|
|
576
584
|
if (isBufferingInitialSync()) {
|
|
577
585
|
const snapshotParams = compileSQL<T>(opts, compileOptions)
|
|
578
586
|
try {
|
|
579
587
|
const { data: rows } = await stream.fetchSnapshot(snapshotParams)
|
|
580
|
-
|
|
581
|
-
if (!isBufferingInitialSync()) {
|
|
588
|
+
if (opts.signal?.aborted || !isBufferingInitialSync()) {
|
|
582
589
|
debug(`${logPrefix}Ignoring snapshot - sync completed while fetching`)
|
|
583
590
|
return
|
|
584
591
|
}
|
|
@@ -592,10 +599,11 @@ function createLoadSubsetDedupe<T extends Row<unknown>>({
|
|
|
592
599
|
metadata: { ...row.headers },
|
|
593
600
|
})
|
|
594
601
|
}
|
|
595
|
-
commit()
|
|
602
|
+
await commit(opts.signal)
|
|
596
603
|
debug(`${logPrefix}Applied snapshot with ${rows.length} rows`)
|
|
597
604
|
}
|
|
598
605
|
} catch (error) {
|
|
606
|
+
if (opts.signal?.aborted) return
|
|
599
607
|
if (handleSnapshotError(error, `fetchSnapshot`)) {
|
|
600
608
|
return
|
|
601
609
|
}
|
|
@@ -643,6 +651,14 @@ function createLoadSubsetDedupe<T extends Row<unknown>>({
|
|
|
643
651
|
}
|
|
644
652
|
}
|
|
645
653
|
|
|
654
|
+
if (opts.signal?.aborted) return
|
|
655
|
+
|
|
656
|
+
// Upstream limitation: ShapeStream.requestSnapshot() publishes its rows
|
|
657
|
+
// through the stream callback before its Promise resolves. It accepts no
|
|
658
|
+
// request signal and exposes no request identity on those messages, so an
|
|
659
|
+
// aborted request can already have installed rows before the check below.
|
|
660
|
+
// Full request-scoped cancellation requires support in the Electric client;
|
|
661
|
+
// matching snapshots by parameters is unsafe for overlapping equal requests.
|
|
646
662
|
try {
|
|
647
663
|
if (cursor) {
|
|
648
664
|
const whereCurrentOpts: LoadSubsetOptions = {
|
|
@@ -675,11 +691,13 @@ function createLoadSubsetDedupe<T extends Row<unknown>>({
|
|
|
675
691
|
await stream.requestSnapshot(snapshotParams)
|
|
676
692
|
}
|
|
677
693
|
} catch (error) {
|
|
694
|
+
if (opts.signal?.aborted) return
|
|
678
695
|
if (handleSnapshotError(error, `requestSnapshot`)) {
|
|
679
696
|
return
|
|
680
697
|
}
|
|
681
698
|
throw error
|
|
682
699
|
}
|
|
700
|
+
await waitForCommitsAfter(commitCursor)
|
|
683
701
|
}
|
|
684
702
|
|
|
685
703
|
return new DeduplicatedLoadSubset({ loadSubset })
|
|
@@ -1476,13 +1494,33 @@ function createElectricSync<T extends Row<unknown>>(
|
|
|
1476
1494
|
const {
|
|
1477
1495
|
begin,
|
|
1478
1496
|
write,
|
|
1479
|
-
commit,
|
|
1497
|
+
commit: commitSyncTransaction,
|
|
1480
1498
|
markReady,
|
|
1481
1499
|
markError,
|
|
1482
1500
|
truncate,
|
|
1483
1501
|
collection,
|
|
1484
1502
|
metadata,
|
|
1485
1503
|
} = params
|
|
1504
|
+
let commitSequence = 0
|
|
1505
|
+
const pendingAppliedReceipts = new Map<number, Promise<void>>()
|
|
1506
|
+
const commit = (signal?: AbortSignal): SyncAppliedReceipt => {
|
|
1507
|
+
const sequence = ++commitSequence
|
|
1508
|
+
const applied = commitSyncTransaction(signal)
|
|
1509
|
+
if (applied === true) {
|
|
1510
|
+
return true
|
|
1511
|
+
}
|
|
1512
|
+
pendingAppliedReceipts.set(sequence, applied)
|
|
1513
|
+
const removeReceipt = () => pendingAppliedReceipts.delete(sequence)
|
|
1514
|
+
void applied.then(removeReceipt, removeReceipt)
|
|
1515
|
+
return applied
|
|
1516
|
+
}
|
|
1517
|
+
const waitForCommitsAfter = async (cursor: number): Promise<void> => {
|
|
1518
|
+
await Promise.all(
|
|
1519
|
+
Array.from(pendingAppliedReceipts, ([sequence, applied]) =>
|
|
1520
|
+
sequence > cursor ? applied : undefined,
|
|
1521
|
+
),
|
|
1522
|
+
)
|
|
1523
|
+
}
|
|
1486
1524
|
const readPersistedResumeState = (): ElectricResumeState | undefined => {
|
|
1487
1525
|
const persistedResumeState = metadata?.collection.get(`electric:resume`)
|
|
1488
1526
|
return parseElectricResumeState(persistedResumeState)
|
|
@@ -1507,7 +1545,13 @@ function createElectricSync<T extends Row<unknown>>(
|
|
|
1507
1545
|
|
|
1508
1546
|
// Wrap markReady to wait for test hook in progressive mode
|
|
1509
1547
|
let progressiveReadyGate: Promise<void> | null = null
|
|
1510
|
-
|
|
1548
|
+
let streamErrorVersion = 0
|
|
1549
|
+
const wrappedMarkReady = (
|
|
1550
|
+
isBuffering: boolean,
|
|
1551
|
+
expectedErrorVersion = streamErrorVersion,
|
|
1552
|
+
) => {
|
|
1553
|
+
if (streamErrorVersion !== expectedErrorVersion) return
|
|
1554
|
+
|
|
1511
1555
|
// Only create gate if we're in buffering phase (first up-to-date)
|
|
1512
1556
|
if (
|
|
1513
1557
|
isBuffering &&
|
|
@@ -1517,7 +1561,9 @@ function createElectricSync<T extends Row<unknown>>(
|
|
|
1517
1561
|
// Create a new gate promise for this sync cycle
|
|
1518
1562
|
progressiveReadyGate = testHooks.beforeMarkingReady()
|
|
1519
1563
|
progressiveReadyGate.then(() => {
|
|
1520
|
-
|
|
1564
|
+
if (streamErrorVersion === expectedErrorVersion) {
|
|
1565
|
+
markReady()
|
|
1566
|
+
}
|
|
1521
1567
|
})
|
|
1522
1568
|
} else {
|
|
1523
1569
|
// No hook, not buffering, or already past first up-to-date
|
|
@@ -1572,6 +1618,7 @@ function createElectricSync<T extends Row<unknown>>(
|
|
|
1572
1618
|
(canUsePersistedResume ? persistedResumeState.handle : undefined),
|
|
1573
1619
|
signal: abortController.signal,
|
|
1574
1620
|
onError: (errorParams) => {
|
|
1621
|
+
streamErrorVersion++
|
|
1575
1622
|
// Note that Electric sends a 409 error on a `must-refetch` message, but the
|
|
1576
1623
|
// ShapeStream handled this and it will not reach this handler, therefor
|
|
1577
1624
|
// this handler will not run for a `must-refetch`.
|
|
@@ -1719,6 +1766,8 @@ function createElectricSync<T extends Row<unknown>>(
|
|
|
1719
1766
|
begin,
|
|
1720
1767
|
write,
|
|
1721
1768
|
commit,
|
|
1769
|
+
getCommitCursor: () => commitSequence,
|
|
1770
|
+
waitForCommitsAfter,
|
|
1722
1771
|
collectionId,
|
|
1723
1772
|
// Pass the columnMapper's encode function to transform column names
|
|
1724
1773
|
// (e.g., camelCase to snake_case) when compiling SQL for subset queries
|
|
@@ -1881,6 +1930,8 @@ function createElectricSync<T extends Row<unknown>>(
|
|
|
1881
1930
|
}
|
|
1882
1931
|
|
|
1883
1932
|
if (commitPoint !== null) {
|
|
1933
|
+
let applied: SyncAppliedReceipt = true
|
|
1934
|
+
const wasBufferingInitialSync = isBufferingInitialSync()
|
|
1884
1935
|
// PROGRESSIVE MODE: Atomic swap on first up-to-date (not subset-end)
|
|
1885
1936
|
// EXCEPTION: Skip atomic swap if a transaction is already started (e.g., from must-refetch).
|
|
1886
1937
|
// In that case, do a normal commit to properly close the existing transaction.
|
|
@@ -1935,7 +1986,7 @@ function createElectricSync<T extends Row<unknown>>(
|
|
|
1935
1986
|
|
|
1936
1987
|
// Commit the atomic swap
|
|
1937
1988
|
stageResumeMetadata()
|
|
1938
|
-
commit()
|
|
1989
|
+
applied = commit()
|
|
1939
1990
|
|
|
1940
1991
|
// Exit buffering phase by marking that we've received up-to-date
|
|
1941
1992
|
// isBufferingInitialSync() will now return false
|
|
@@ -1949,15 +2000,24 @@ function createElectricSync<T extends Row<unknown>>(
|
|
|
1949
2000
|
// Both up-to-date and subset-end trigger a commit
|
|
1950
2001
|
if (transactionStarted) {
|
|
1951
2002
|
stageResumeMetadata()
|
|
1952
|
-
commit()
|
|
2003
|
+
applied = commit()
|
|
1953
2004
|
transactionStarted = false
|
|
1954
2005
|
} else if (commitPoint === `up-to-date` && metadata) {
|
|
1955
2006
|
begin()
|
|
1956
2007
|
stageResumeMetadata()
|
|
1957
|
-
commit()
|
|
2008
|
+
applied = commit()
|
|
1958
2009
|
}
|
|
1959
2010
|
}
|
|
1960
|
-
|
|
2011
|
+
const readyErrorVersion = streamErrorVersion
|
|
2012
|
+
if (applied === true) {
|
|
2013
|
+
wrappedMarkReady(wasBufferingInitialSync, readyErrorVersion)
|
|
2014
|
+
} else {
|
|
2015
|
+
void applied.then(
|
|
2016
|
+
() =>
|
|
2017
|
+
wrappedMarkReady(wasBufferingInitialSync, readyErrorVersion),
|
|
2018
|
+
() => undefined,
|
|
2019
|
+
)
|
|
2020
|
+
}
|
|
1961
2021
|
|
|
1962
2022
|
// Track that we've received the first up-to-date for progressive mode
|
|
1963
2023
|
if (commitPoint === `up-to-date`) {
|