@tanstack/db-sqlite-persistence-core 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/README.md +14 -6
- package/dist/cjs/errors.cjs.map +1 -1
- package/dist/cjs/index.cjs +5 -0
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/persisted.cjs +59 -62
- package/dist/cjs/persisted.cjs.map +1 -1
- package/dist/cjs/persisted.d.cts +7 -1
- package/dist/cjs/remote-subset-wire.cjs +100 -73
- package/dist/cjs/remote-subset-wire.cjs.map +1 -1
- package/dist/cjs/remote-subset-wire.d.cts +2 -0
- package/dist/cjs/sqlite-core-adapter.cjs +433 -184
- package/dist/cjs/sqlite-core-adapter.cjs.map +1 -1
- package/dist/cjs/sqlite-value.cjs +51 -0
- package/dist/cjs/sqlite-value.cjs.map +1 -1
- package/dist/cjs/sqlite-value.d.cts +12 -0
- package/dist/esm/errors.js.map +1 -1
- package/dist/esm/index.js +8 -3
- package/dist/esm/persisted.d.ts +7 -1
- package/dist/esm/persisted.js +63 -66
- package/dist/esm/persisted.js.map +1 -1
- package/dist/esm/remote-subset-wire.d.ts +2 -0
- package/dist/esm/remote-subset-wire.js +101 -74
- package/dist/esm/remote-subset-wire.js.map +1 -1
- package/dist/esm/sqlite-core-adapter.js +435 -186
- package/dist/esm/sqlite-core-adapter.js.map +1 -1
- package/dist/esm/sqlite-value.d.ts +12 -0
- package/dist/esm/sqlite-value.js +53 -2
- package/dist/esm/sqlite-value.js.map +1 -1
- package/package.json +4 -3
- package/src/errors.ts +1 -2
- package/src/persisted.ts +111 -91
- package/src/remote-subset-wire.ts +150 -85
- package/src/sqlite-core-adapter.ts +686 -235
- package/src/sqlite-value.ts +98 -1
package/src/persisted.ts
CHANGED
|
@@ -9,6 +9,8 @@ import {
|
|
|
9
9
|
getLoadSubsetDemandKey,
|
|
10
10
|
safeRandomUUID,
|
|
11
11
|
toBooleanPredicate,
|
|
12
|
+
whenSyncAccepted,
|
|
13
|
+
withAcceptedReceipt,
|
|
12
14
|
withCollectionConfigFactory,
|
|
13
15
|
} from '@tanstack/db'
|
|
14
16
|
import {
|
|
@@ -22,10 +24,11 @@ import {
|
|
|
22
24
|
PersistedCollectionDurabilityError,
|
|
23
25
|
toPersistedCollectionDurabilityError,
|
|
24
26
|
} from './errors'
|
|
25
|
-
import { serializeSQLiteBigInt } from './sqlite-value'
|
|
27
|
+
import { serializeSQLiteBigInt, serializeSQLiteTemporal } from './sqlite-value'
|
|
26
28
|
import {
|
|
27
29
|
toProcessLocalLoadSubsetOptions,
|
|
28
30
|
toTransportedLoadSubsetOptions,
|
|
31
|
+
validateRemoteSubsetOptions,
|
|
29
32
|
} from './remote-subset-wire'
|
|
30
33
|
import {
|
|
31
34
|
reportRemoteSubsetOwnerError,
|
|
@@ -289,8 +292,7 @@ export type PersistedRowMetadataMutation<
|
|
|
289
292
|
> = { type: `set`; key: TKey; value: unknown } | { type: `delete`; key: TKey }
|
|
290
293
|
|
|
291
294
|
export type PersistedCollectionMetadataMutation =
|
|
292
|
-
|
|
293
|
-
| { type: `delete`; key: string }
|
|
295
|
+
{ type: `set`; key: string; value: unknown } | { type: `delete`; key: string }
|
|
294
296
|
|
|
295
297
|
export type ReplayableTxDelta<
|
|
296
298
|
T extends Record<string, unknown> = Record<string, unknown>,
|
|
@@ -542,6 +544,12 @@ export interface PersistedCollectionCoordinator {
|
|
|
542
544
|
collectionId: string,
|
|
543
545
|
mutations: Array<PersistedMutationEnvelope>,
|
|
544
546
|
) => Promise<ApplyLocalMutationsResponse>
|
|
547
|
+
/**
|
|
548
|
+
* Applies a committed transaction through the supplied adapter when present.
|
|
549
|
+
* Wrappers must forward this leader-local loan: scheduling it again can wait
|
|
550
|
+
* on the hydration that is awaiting this call. Never serialize or retain the
|
|
551
|
+
* loan after the call; ordinary work uses the registered collection adapter.
|
|
552
|
+
*/
|
|
545
553
|
requestApplyCommittedTx: (
|
|
546
554
|
collectionId: string,
|
|
547
555
|
tx: PersistedTx,
|
|
@@ -738,7 +746,7 @@ const DEFAULT_DB_NAME = `tanstack-db`
|
|
|
738
746
|
const REMOTE_ENSURE_RETRY_DELAY_MS = 50
|
|
739
747
|
|
|
740
748
|
type SyncControlFns<T extends object, TKey extends string | number> = {
|
|
741
|
-
begin: ((
|
|
749
|
+
begin: (() => void) | null
|
|
742
750
|
write:
|
|
743
751
|
| ((
|
|
744
752
|
message:
|
|
@@ -886,8 +894,9 @@ export class SingleProcessCoordinator implements PersistedCollectionCoordinator
|
|
|
886
894
|
public async requestApplyCommittedTx(
|
|
887
895
|
collectionId: string,
|
|
888
896
|
tx: PersistedTx,
|
|
897
|
+
scopedAdapter?: HydrationPersistenceAdapter,
|
|
889
898
|
): Promise<ApplyCommittedTxResponse> {
|
|
890
|
-
const adapter = this.collectionAdapters.get(collectionId)
|
|
899
|
+
const adapter = scopedAdapter ?? this.collectionAdapters.get(collectionId)
|
|
891
900
|
if (!adapter) {
|
|
892
901
|
throw new InvalidPersistedCollectionConfigError(
|
|
893
902
|
`SingleProcessCoordinator has no persistence adapter configured for collection "${collectionId}"`,
|
|
@@ -1046,7 +1055,6 @@ type BufferedSyncTransaction<T extends object, TKey extends string | number> = {
|
|
|
1046
1055
|
truncate: boolean
|
|
1047
1056
|
internal: boolean
|
|
1048
1057
|
lifecycleGeneration: number
|
|
1049
|
-
beginOptions?: { immediate?: boolean }
|
|
1050
1058
|
expectedResumeGenerationOwner?: symbol
|
|
1051
1059
|
signal?: AbortSignal
|
|
1052
1060
|
prependHydrationRows: (
|
|
@@ -1066,6 +1074,8 @@ type OpenSyncTransaction<T extends object, TKey extends string | number> = Omit<
|
|
|
1066
1074
|
applicationReceipt?: SyncAppliedReceipt
|
|
1067
1075
|
operationKeys: Set<TKey>
|
|
1068
1076
|
queuedBecauseHydrating: boolean
|
|
1077
|
+
// Set by `reserveCommitTurn`: subset hydration waits for this commit.
|
|
1078
|
+
reservesCommitTurn?: boolean
|
|
1069
1079
|
hasDependentSuccessor: boolean
|
|
1070
1080
|
publicationAdmissionWaiters?: Set<{
|
|
1071
1081
|
resolve: () => void
|
|
@@ -1150,6 +1160,9 @@ function toStableSerializable(value: unknown): unknown {
|
|
|
1150
1160
|
return undefined
|
|
1151
1161
|
}
|
|
1152
1162
|
|
|
1163
|
+
const temporal = serializeSQLiteTemporal(value)
|
|
1164
|
+
if (temporal) return temporal
|
|
1165
|
+
|
|
1153
1166
|
if (value instanceof Date) {
|
|
1154
1167
|
return value.toISOString()
|
|
1155
1168
|
}
|
|
@@ -1326,15 +1339,13 @@ class PersistedCollectionRuntime<
|
|
|
1326
1339
|
private resumeGenerationOwner = Symbol(`persisted resume generation owner`)
|
|
1327
1340
|
private lifecycleGeneration = 0
|
|
1328
1341
|
private internalApplyDepth = 0
|
|
1329
|
-
private sourcePublicationWaitDepth = 0
|
|
1330
1342
|
private appliedReceiptSequence = 0
|
|
1331
1343
|
private syncErrorReported = false
|
|
1332
1344
|
private reportedSyncError: unknown
|
|
1333
1345
|
private readonly pendingAppliedReceipts = new Map<number, Promise<void>>()
|
|
1334
1346
|
private hydratingGeneration: number | null = null
|
|
1335
1347
|
private terminalFailure:
|
|
1336
|
-
|
|
1337
|
-
| undefined
|
|
1348
|
+
{ lifecycleGeneration: number; error: unknown } | undefined
|
|
1338
1349
|
private coordinatorUnsubscribe: (() => void) | null = null
|
|
1339
1350
|
private remoteSubsetOwnerUnsubscribe: (() => void) | null = null
|
|
1340
1351
|
private indexAddedUnsubscribe: (() => void) | null = null
|
|
@@ -1409,10 +1420,20 @@ class PersistedCollectionRuntime<
|
|
|
1409
1420
|
return receipt
|
|
1410
1421
|
}
|
|
1411
1422
|
|
|
1412
|
-
|
|
1423
|
+
// Startup waits for acceptance: a local mutation's handler can wait for
|
|
1424
|
+
// startup, and the rows publish when that mutation settles. Readiness waits
|
|
1425
|
+
// for them in core. A subset load waits for visibility.
|
|
1426
|
+
private async waitForAppliedReceiptsAfter(
|
|
1427
|
+
cursor: number,
|
|
1428
|
+
until: `accepted` | `visible` = `visible`,
|
|
1429
|
+
): Promise<void> {
|
|
1413
1430
|
await Promise.all(
|
|
1414
1431
|
Array.from(this.pendingAppliedReceipts, ([sequence, receipt]) =>
|
|
1415
|
-
sequence
|
|
1432
|
+
sequence <= cursor
|
|
1433
|
+
? undefined
|
|
1434
|
+
: until === `accepted`
|
|
1435
|
+
? whenSyncAccepted(receipt)
|
|
1436
|
+
: receipt,
|
|
1416
1437
|
),
|
|
1417
1438
|
)
|
|
1418
1439
|
}
|
|
@@ -1623,7 +1644,10 @@ class PersistedCollectionRuntime<
|
|
|
1623
1644
|
startup.appliedCursor !== undefined &&
|
|
1624
1645
|
lifecycleGeneration === this.lifecycleGeneration
|
|
1625
1646
|
) {
|
|
1626
|
-
await this.waitForAppliedReceiptsAfter(
|
|
1647
|
+
await this.waitForAppliedReceiptsAfter(
|
|
1648
|
+
startup.appliedCursor,
|
|
1649
|
+
`accepted`,
|
|
1650
|
+
)
|
|
1627
1651
|
}
|
|
1628
1652
|
}
|
|
1629
1653
|
if (lifecycleGeneration === this.lifecycleGeneration) {
|
|
@@ -1664,7 +1688,7 @@ class PersistedCollectionRuntime<
|
|
|
1664
1688
|
appliedCursor !== undefined &&
|
|
1665
1689
|
lifecycleGeneration === this.lifecycleGeneration
|
|
1666
1690
|
) {
|
|
1667
|
-
await this.waitForAppliedReceiptsAfter(appliedCursor)
|
|
1691
|
+
await this.waitForAppliedReceiptsAfter(appliedCursor, `accepted`)
|
|
1668
1692
|
}
|
|
1669
1693
|
})().catch((error) => {
|
|
1670
1694
|
throw this.markTerminalFailure(error, lifecycleGeneration)
|
|
@@ -1717,7 +1741,6 @@ class PersistedCollectionRuntime<
|
|
|
1717
1741
|
await this.hydrateSubsetUnsafe(
|
|
1718
1742
|
baseline,
|
|
1719
1743
|
{
|
|
1720
|
-
requestRemoteEnsure: false,
|
|
1721
1744
|
lifecycleGeneration,
|
|
1722
1745
|
bindKeySetEvidence: true,
|
|
1723
1746
|
},
|
|
@@ -1795,7 +1818,7 @@ class PersistedCollectionRuntime<
|
|
|
1795
1818
|
const applied = this.replaceCollectionMetadataSnapshot(
|
|
1796
1819
|
snapshot.collectionMetadata,
|
|
1797
1820
|
)
|
|
1798
|
-
|
|
1821
|
+
await whenSyncAccepted(applied)
|
|
1799
1822
|
}
|
|
1800
1823
|
|
|
1801
1824
|
private async loadCollectionMetadataSnapshot(
|
|
@@ -1827,7 +1850,7 @@ class PersistedCollectionRuntime<
|
|
|
1827
1850
|
.map(({ key }) => key)
|
|
1828
1851
|
|
|
1829
1852
|
return this.withInternalApply(() => {
|
|
1830
|
-
this.syncControls.begin?.(
|
|
1853
|
+
this.syncControls.begin?.()
|
|
1831
1854
|
|
|
1832
1855
|
currentKeys.forEach((key) => {
|
|
1833
1856
|
if (!nextMetadata.has(key)) {
|
|
@@ -1853,6 +1876,8 @@ class PersistedCollectionRuntime<
|
|
|
1853
1876
|
const truncateGeneration = this.sourceTruncateGeneration
|
|
1854
1877
|
const routeRemoteDemandDuringHydration =
|
|
1855
1878
|
this.canRouteRemoteDemandThroughCoordinator()
|
|
1879
|
+
// Wire admission failures are permanent input errors, not transport retries.
|
|
1880
|
+
if (routeRemoteDemandDuringHydration) validateRemoteSubsetOptions(options)
|
|
1856
1881
|
this.activeSubsets.set(subsetKey, options)
|
|
1857
1882
|
const appliedCursor = this.appliedReceiptSequence
|
|
1858
1883
|
try {
|
|
@@ -1862,9 +1887,6 @@ class PersistedCollectionRuntime<
|
|
|
1862
1887
|
this.hydrateSubsetUnsafe(
|
|
1863
1888
|
options,
|
|
1864
1889
|
{
|
|
1865
|
-
requestRemoteEnsure:
|
|
1866
|
-
this.mode === `sync-present` &&
|
|
1867
|
-
!routeRemoteDemandDuringHydration,
|
|
1868
1890
|
lifecycleGeneration,
|
|
1869
1891
|
requestLocalLoadFailure: true,
|
|
1870
1892
|
rejectBufferedReplayFailure: true,
|
|
@@ -1883,6 +1905,14 @@ class PersistedCollectionRuntime<
|
|
|
1883
1905
|
})
|
|
1884
1906
|
if (lifecycleGeneration !== this.lifecycleGeneration) return
|
|
1885
1907
|
await this.waitForAppliedReceiptsAfter(appliedCursor)
|
|
1908
|
+
// Leadership can change while hydration waits. Validate newly remote
|
|
1909
|
+
// demand inside the admission cleanup boundary, before retry can own it.
|
|
1910
|
+
if (
|
|
1911
|
+
!routeRemoteDemandDuringHydration &&
|
|
1912
|
+
this.canRouteRemoteDemandThroughCoordinator()
|
|
1913
|
+
) {
|
|
1914
|
+
validateRemoteSubsetOptions(options)
|
|
1915
|
+
}
|
|
1886
1916
|
} catch (error) {
|
|
1887
1917
|
if (this.activeSubsets.get(subsetKey) === options) {
|
|
1888
1918
|
this.activeSubsets.delete(subsetKey)
|
|
@@ -2043,7 +2073,6 @@ class PersistedCollectionRuntime<
|
|
|
2043
2073
|
this.hydrateSubsetUnsafe(
|
|
2044
2074
|
options,
|
|
2045
2075
|
{
|
|
2046
|
-
requestRemoteEnsure: false,
|
|
2047
2076
|
lifecycleGeneration,
|
|
2048
2077
|
rejectBufferedReplayFailure: true,
|
|
2049
2078
|
},
|
|
@@ -2072,16 +2101,6 @@ class PersistedCollectionRuntime<
|
|
|
2072
2101
|
): Promise<void> {
|
|
2073
2102
|
const failure = this.getCurrentTerminalFailure()
|
|
2074
2103
|
if (failure) return Promise.reject(failure.error)
|
|
2075
|
-
if (
|
|
2076
|
-
transaction.beginOptions?.immediate &&
|
|
2077
|
-
this.sourcePublicationWaitDepth > 0
|
|
2078
|
-
) {
|
|
2079
|
-
return Promise.reject(
|
|
2080
|
-
new InvalidPersistedCollectionConfigError(
|
|
2081
|
-
`immediate persisted source replay cannot enter while an earlier source publication is waiting`,
|
|
2082
|
-
),
|
|
2083
|
-
)
|
|
2084
|
-
}
|
|
2085
2104
|
return this.applyMutex.run(async () => {
|
|
2086
2105
|
await this.applyBufferedSyncTransactionUnsafe(transaction)
|
|
2087
2106
|
})
|
|
@@ -2326,7 +2345,6 @@ class PersistedCollectionRuntime<
|
|
|
2326
2345
|
private async hydrateSubsetUnsafe(
|
|
2327
2346
|
options: LoadSubsetOptions,
|
|
2328
2347
|
config: {
|
|
2329
|
-
requestRemoteEnsure: boolean
|
|
2330
2348
|
lifecycleGeneration: number
|
|
2331
2349
|
bindKeySetEvidence?: boolean
|
|
2332
2350
|
requestLocalLoadFailure?: boolean
|
|
@@ -2373,7 +2391,7 @@ class PersistedCollectionRuntime<
|
|
|
2373
2391
|
hydrationContext.suppliedRowKeys.add(row.key)
|
|
2374
2392
|
}
|
|
2375
2393
|
const applied = this.applyRowsToCollection(rows)
|
|
2376
|
-
|
|
2394
|
+
await whenSyncAccepted(applied)
|
|
2377
2395
|
}
|
|
2378
2396
|
} finally {
|
|
2379
2397
|
if (
|
|
@@ -2387,10 +2405,6 @@ class PersistedCollectionRuntime<
|
|
|
2387
2405
|
if (config.lifecycleGeneration !== this.lifecycleGeneration) return
|
|
2388
2406
|
replayFailure = await this.flushQueuedHydrationTransactionsUnsafe(adapter)
|
|
2389
2407
|
if (config.lifecycleGeneration !== this.lifecycleGeneration) return
|
|
2390
|
-
|
|
2391
|
-
if (config.requestRemoteEnsure && !replayFailure) {
|
|
2392
|
-
this.queueRemoteSubsetEnsure(options)
|
|
2393
|
-
}
|
|
2394
2408
|
} catch (error) {
|
|
2395
2409
|
if (config.requestLocalLoadFailure && !rowsLoaded) {
|
|
2396
2410
|
// Keep admitting source work to the hydration queue while recovery
|
|
@@ -2433,8 +2447,7 @@ class PersistedCollectionRuntime<
|
|
|
2433
2447
|
adapter: HydrationPersistenceAdapter,
|
|
2434
2448
|
): Promise<void> {
|
|
2435
2449
|
let snapshotRows:
|
|
2436
|
-
|
|
2437
|
-
| undefined
|
|
2450
|
+
Map<TKey, { key: TKey; value: T; metadata?: unknown }> | undefined
|
|
2438
2451
|
type RecoveryPresence = `present` | `absent` | `unknown`
|
|
2439
2452
|
const recoveredPresence = new Map<TKey, RecoveryPresence>()
|
|
2440
2453
|
let snapshotInvalidatedByTruncate = false
|
|
@@ -2570,7 +2583,7 @@ class PersistedCollectionRuntime<
|
|
|
2570
2583
|
}
|
|
2571
2584
|
|
|
2572
2585
|
return this.withInternalApply(() => {
|
|
2573
|
-
this.syncControls.begin?.(
|
|
2586
|
+
this.syncControls.begin?.()
|
|
2574
2587
|
|
|
2575
2588
|
for (const row of rows) {
|
|
2576
2589
|
if (this.collection?._hasHydratedKey(row.key)) {
|
|
@@ -2668,7 +2681,7 @@ class PersistedCollectionRuntime<
|
|
|
2668
2681
|
.map(({ key }) => key)
|
|
2669
2682
|
|
|
2670
2683
|
return this.withInternalApply(() => {
|
|
2671
|
-
this.syncControls.begin?.(
|
|
2684
|
+
this.syncControls.begin?.()
|
|
2672
2685
|
this.syncControls.truncate?.()
|
|
2673
2686
|
|
|
2674
2687
|
for (const row of rows) {
|
|
@@ -2739,14 +2752,17 @@ class PersistedCollectionRuntime<
|
|
|
2739
2752
|
: transaction.applyToCollection()
|
|
2740
2753
|
applicationReturned = true
|
|
2741
2754
|
abortedDuringApplication = transaction.signal?.aborted === true
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2755
|
+
// Inside the apply mutex, internal work waits only for acceptance: a
|
|
2756
|
+
// local mutation's handler may need this mutex, and the rows publish
|
|
2757
|
+
// when that mutation settles. A source transaction is stored once it is
|
|
2758
|
+
// visible, except one held by a persisting optimistic transaction,
|
|
2759
|
+
// which is stored now so a handler can await its acceptance.
|
|
2760
|
+
if (transaction.internal) await whenSyncAccepted(applied)
|
|
2761
|
+
else if (
|
|
2762
|
+
applied !== true &&
|
|
2763
|
+
!this.collection?._state.hasPersistingTransaction()
|
|
2764
|
+
)
|
|
2765
|
+
await applied
|
|
2750
2766
|
this.throwIfLifecycleReplaced(transaction.lifecycleGeneration)
|
|
2751
2767
|
|
|
2752
2768
|
if (!transaction.internal) {
|
|
@@ -2972,7 +2988,7 @@ class PersistedCollectionRuntime<
|
|
|
2972
2988
|
}
|
|
2973
2989
|
|
|
2974
2990
|
const applied = this.withInternalApply(() => {
|
|
2975
|
-
this.syncControls.begin?.(
|
|
2991
|
+
this.syncControls.begin?.()
|
|
2976
2992
|
|
|
2977
2993
|
for (const mutation of mutations) {
|
|
2978
2994
|
if (mutation.type === `delete`) {
|
|
@@ -2991,9 +3007,10 @@ class PersistedCollectionRuntime<
|
|
|
2991
3007
|
return this.syncControls.commit?.() ?? true
|
|
2992
3008
|
})
|
|
2993
3009
|
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
3010
|
+
// The mutation handler awaits this confirmation, so wait only for
|
|
3011
|
+
// acceptance: the rows become visible when the mutation settles.
|
|
3012
|
+
const accepted = whenSyncAccepted(applied)
|
|
3013
|
+
if (accepted !== true) await accepted
|
|
2997
3014
|
}
|
|
2998
3015
|
|
|
2999
3016
|
private filterMutationsForCollection(
|
|
@@ -3256,6 +3273,9 @@ class PersistedCollectionRuntime<
|
|
|
3256
3273
|
const subsetKey = this.getSubsetKey(options)
|
|
3257
3274
|
if (this.activeSubsets.get(subsetKey) !== options) return
|
|
3258
3275
|
|
|
3276
|
+
// A previously local acquisition can become remote after an ownership
|
|
3277
|
+
// change. Recovery must reject permanent wire errors before retry owns it.
|
|
3278
|
+
validateRemoteSubsetOptions(options)
|
|
3259
3279
|
this.pendingRemoteSubsetEnsures.set(subsetKey, options)
|
|
3260
3280
|
void this.flushPendingRemoteSubsetEnsures()
|
|
3261
3281
|
}
|
|
@@ -3545,11 +3565,11 @@ class PersistedCollectionRuntime<
|
|
|
3545
3565
|
this.resetSequence++
|
|
3546
3566
|
if (this.syncControls.begin && this.syncControls.commit) {
|
|
3547
3567
|
const applied = this.withInternalApply(() => {
|
|
3548
|
-
this.syncControls.begin?.(
|
|
3568
|
+
this.syncControls.begin?.()
|
|
3549
3569
|
this.syncControls.truncate?.()
|
|
3550
3570
|
return this.syncControls.commit?.() ?? true
|
|
3551
3571
|
})
|
|
3552
|
-
|
|
3572
|
+
await whenSyncAccepted(applied)
|
|
3553
3573
|
}
|
|
3554
3574
|
|
|
3555
3575
|
if (lifecycleGeneration !== this.lifecycleGeneration) return
|
|
@@ -3600,7 +3620,7 @@ class PersistedCollectionRuntime<
|
|
|
3600
3620
|
)
|
|
3601
3621
|
|
|
3602
3622
|
const applied = this.withInternalApply(() => {
|
|
3603
|
-
this.syncControls.begin?.(
|
|
3623
|
+
this.syncControls.begin?.()
|
|
3604
3624
|
|
|
3605
3625
|
for (const {
|
|
3606
3626
|
key: changedKey,
|
|
@@ -3646,7 +3666,7 @@ class PersistedCollectionRuntime<
|
|
|
3646
3666
|
|
|
3647
3667
|
return this.syncControls.commit?.() ?? true
|
|
3648
3668
|
})
|
|
3649
|
-
|
|
3669
|
+
await whenSyncAccepted(applied)
|
|
3650
3670
|
}
|
|
3651
3671
|
|
|
3652
3672
|
private async reloadActiveSubsetsUnsafe(
|
|
@@ -3692,7 +3712,7 @@ class PersistedCollectionRuntime<
|
|
|
3692
3712
|
})),
|
|
3693
3713
|
collectionMetadata,
|
|
3694
3714
|
)
|
|
3695
|
-
|
|
3715
|
+
await whenSyncAccepted(applied)
|
|
3696
3716
|
} finally {
|
|
3697
3717
|
if (
|
|
3698
3718
|
this.activeHydrationContext === hydrationContext &&
|
|
@@ -3802,9 +3822,8 @@ class PersistedCollectionRuntime<
|
|
|
3802
3822
|
indexMetadata: CollectionIndexMetadata,
|
|
3803
3823
|
adapter: HydrationPersistenceAdapter,
|
|
3804
3824
|
): Promise<boolean> {
|
|
3805
|
-
const spec = this.buildPersistedIndexSpec(indexMetadata)
|
|
3806
|
-
|
|
3807
3825
|
try {
|
|
3826
|
+
const spec = this.buildPersistedIndexSpec(indexMetadata)
|
|
3808
3827
|
await adapter.ensureIndex(
|
|
3809
3828
|
this.collectionId,
|
|
3810
3829
|
indexMetadata.signature,
|
|
@@ -3821,9 +3840,8 @@ class PersistedCollectionRuntime<
|
|
|
3821
3840
|
indexMetadata: CollectionIndexMetadata,
|
|
3822
3841
|
completedLocally: boolean,
|
|
3823
3842
|
): Promise<void> {
|
|
3824
|
-
const spec = this.buildPersistedIndexSpec(indexMetadata)
|
|
3825
|
-
|
|
3826
3843
|
try {
|
|
3844
|
+
const spec = this.buildPersistedIndexSpec(indexMetadata)
|
|
3827
3845
|
await this.persistence.coordinator.requestEnsurePersistedIndex(
|
|
3828
3846
|
this.collectionId,
|
|
3829
3847
|
indexMetadata.signature,
|
|
@@ -3934,6 +3952,19 @@ function createWrappedSyncConfig<
|
|
|
3934
3952
|
) => {
|
|
3935
3953
|
removePendingPublicationTransaction(transaction)
|
|
3936
3954
|
}
|
|
3955
|
+
// A source receipt is accepted once its rows are durable, and resolves
|
|
3956
|
+
// when core makes them visible.
|
|
3957
|
+
const visibleAfterDurable = (
|
|
3958
|
+
transaction: OpenSyncTransaction<T, TKey>,
|
|
3959
|
+
durable: Promise<void>,
|
|
3960
|
+
): Promise<void> => {
|
|
3961
|
+
const visible = durable.then(async () => {
|
|
3962
|
+
const receipt = transaction.applicationReceipt
|
|
3963
|
+
if (receipt !== undefined && receipt !== true) await receipt
|
|
3964
|
+
})
|
|
3965
|
+
void visible.catch(() => undefined)
|
|
3966
|
+
return withAcceptedReceipt(visible, durable)
|
|
3967
|
+
}
|
|
3937
3968
|
const settleRuntimeTransaction = (
|
|
3938
3969
|
transaction: OpenSyncTransaction<T, TKey>,
|
|
3939
3970
|
applied: Promise<void>,
|
|
@@ -4072,14 +4103,7 @@ function createWrappedSyncConfig<
|
|
|
4072
4103
|
}
|
|
4073
4104
|
transaction.deferredHydrationMetadataDeleteKeys.clear()
|
|
4074
4105
|
|
|
4075
|
-
|
|
4076
|
-
// It must not wait for a mutation whose persistence is queued
|
|
4077
|
-
// behind that hydrate's apply mutex.
|
|
4078
|
-
params.begin(
|
|
4079
|
-
transaction.queuedBecauseHydrating
|
|
4080
|
-
? { immediate: true }
|
|
4081
|
-
: transaction.beginOptions,
|
|
4082
|
-
)
|
|
4106
|
+
params.begin()
|
|
4083
4107
|
if (transaction.truncate) params.truncate()
|
|
4084
4108
|
for (const operation of transaction.operations) {
|
|
4085
4109
|
if (operation.type === `delete`) {
|
|
@@ -4106,14 +4130,10 @@ function createWrappedSyncConfig<
|
|
|
4106
4130
|
|
|
4107
4131
|
const applied = params.commit(signal)
|
|
4108
4132
|
transaction.applicationReceipt = applied
|
|
4109
|
-
|
|
4110
|
-
|
|
4111
|
-
|
|
4112
|
-
|
|
4113
|
-
() => removePendingPublicationTransaction(transaction),
|
|
4114
|
-
() => removePendingPublicationTransaction(transaction),
|
|
4115
|
-
)
|
|
4116
|
-
}
|
|
4133
|
+
// Core accepted the transaction, so it is no longer staged here;
|
|
4134
|
+
// the receipt resolves when it is visible.
|
|
4135
|
+
removePendingPublicationTransaction(transaction)
|
|
4136
|
+
if (applied !== true) void applied.catch(() => undefined)
|
|
4117
4137
|
return applied
|
|
4118
4138
|
} catch (error) {
|
|
4119
4139
|
removePendingPublicationTransaction(transaction)
|
|
@@ -4135,6 +4155,11 @@ function createWrappedSyncConfig<
|
|
|
4135
4155
|
const persistenceCapability: SyncPersistenceCapabilityV1<TKey> = {
|
|
4136
4156
|
protocol: SYNC_PERSISTENCE_PROTOCOL,
|
|
4137
4157
|
version: SYNC_PERSISTENCE_VERSION,
|
|
4158
|
+
reserveCommitTurn: () => {
|
|
4159
|
+
const openTransaction = getOpenTransaction()
|
|
4160
|
+
if (openTransaction && !openTransaction.internal)
|
|
4161
|
+
openTransaction.reservesCommitTurn = true
|
|
4162
|
+
},
|
|
4138
4163
|
hydrateBaseline: async () => {
|
|
4139
4164
|
if (startupState.cleanedUp) return
|
|
4140
4165
|
try {
|
|
@@ -4192,7 +4217,7 @@ function createWrappedSyncConfig<
|
|
|
4192
4217
|
})
|
|
4193
4218
|
.catch(() => undefined)
|
|
4194
4219
|
},
|
|
4195
|
-
begin: (
|
|
4220
|
+
begin: () => {
|
|
4196
4221
|
if (startupState.cleanedUp) return undefined
|
|
4197
4222
|
const terminalFailure = getTerminalFailure()
|
|
4198
4223
|
const internal = runtime.isApplyingInternally()
|
|
@@ -4205,7 +4230,6 @@ function createWrappedSyncConfig<
|
|
|
4205
4230
|
truncate: false,
|
|
4206
4231
|
internal,
|
|
4207
4232
|
lifecycleGeneration: runtime.getLifecycleGeneration(),
|
|
4208
|
-
beginOptions: options,
|
|
4209
4233
|
operationKeys: new Set(),
|
|
4210
4234
|
hasDependentSuccessor: false,
|
|
4211
4235
|
queuedBecauseHydrating:
|
|
@@ -4522,7 +4546,6 @@ function createWrappedSyncConfig<
|
|
|
4522
4546
|
truncate: openTransaction.truncate,
|
|
4523
4547
|
internal: false,
|
|
4524
4548
|
lifecycleGeneration: openTransaction.lifecycleGeneration,
|
|
4525
|
-
beginOptions: openTransaction.beginOptions,
|
|
4526
4549
|
expectedResumeGenerationOwner:
|
|
4527
4550
|
openTransaction.expectedResumeGenerationOwner,
|
|
4528
4551
|
signal,
|
|
@@ -4576,9 +4599,12 @@ function createWrappedSyncConfig<
|
|
|
4576
4599
|
})
|
|
4577
4600
|
settlePublicationAdmissionWaiters(openTransaction)
|
|
4578
4601
|
settleRuntimeTransaction(openTransaction, applied)
|
|
4579
|
-
return applied
|
|
4602
|
+
return visibleAfterDurable(openTransaction, applied)
|
|
4580
4603
|
}
|
|
4581
4604
|
|
|
4605
|
+
// Admission is complete. Only queued hydration replay remains tied
|
|
4606
|
+
// to its sequence; retain hydrationContext for row metadata ownership.
|
|
4607
|
+
openTransaction.hydrationSequence = undefined
|
|
4582
4608
|
let applied: Promise<void>
|
|
4583
4609
|
try {
|
|
4584
4610
|
applied = runtime.applyHydrationBufferedTransaction(transaction)
|
|
@@ -4588,7 +4614,7 @@ function createWrappedSyncConfig<
|
|
|
4588
4614
|
}
|
|
4589
4615
|
settlePublicationAdmissionWaiters(openTransaction)
|
|
4590
4616
|
settleRuntimeTransaction(openTransaction, applied)
|
|
4591
|
-
return applied
|
|
4617
|
+
return visibleAfterDurable(openTransaction, applied)
|
|
4592
4618
|
},
|
|
4593
4619
|
}
|
|
4594
4620
|
|
|
@@ -4728,9 +4754,7 @@ function createWrappedSyncConfig<
|
|
|
4728
4754
|
|
|
4729
4755
|
const openTransaction = getOpenTransaction()
|
|
4730
4756
|
const needsPublicationAdmission =
|
|
4731
|
-
openTransaction
|
|
4732
|
-
!openTransaction.internal &&
|
|
4733
|
-
openTransaction.beginOptions?.immediate
|
|
4757
|
+
openTransaction?.reservesCommitTurn === true
|
|
4734
4758
|
const hydrated =
|
|
4735
4759
|
sourceResultSettled && !needsPublicationAdmission
|
|
4736
4760
|
? runtime.loadHydratedSubset(options, (loadOptions) =>
|
|
@@ -4751,13 +4775,9 @@ function createWrappedSyncConfig<
|
|
|
4751
4775
|
return
|
|
4752
4776
|
}
|
|
4753
4777
|
const currentTransaction = getOpenTransaction()
|
|
4754
|
-
//
|
|
4778
|
+
// A source transaction that reserved its FIFO turn commits before
|
|
4755
4779
|
// subset hydration advances the generation it was built against.
|
|
4756
|
-
if (
|
|
4757
|
-
currentTransaction &&
|
|
4758
|
-
!currentTransaction.internal &&
|
|
4759
|
-
currentTransaction.beginOptions?.immediate
|
|
4760
|
-
) {
|
|
4780
|
+
if (currentTransaction?.reservesCommitTurn) {
|
|
4761
4781
|
await waitForPublicationAdmission(
|
|
4762
4782
|
currentTransaction,
|
|
4763
4783
|
options,
|