@tanstack/db-sqlite-persistence-core 0.2.17 → 0.2.19
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/persisted.cjs +106 -37
- package/dist/cjs/persisted.cjs.map +1 -1
- package/dist/esm/persisted.js +107 -38
- package/dist/esm/persisted.js.map +1 -1
- package/package.json +2 -2
- package/src/persisted.ts +114 -40
package/src/persisted.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
SyncTransactionAbortedError,
|
|
2
3
|
compileSingleRowExpression,
|
|
3
4
|
safeRandomUUID,
|
|
4
5
|
toBooleanPredicate,
|
|
@@ -22,6 +23,7 @@ import type {
|
|
|
22
23
|
InsertMutationFnParams,
|
|
23
24
|
LoadSubsetOptions,
|
|
24
25
|
PendingMutation,
|
|
26
|
+
SyncAppliedReceipt,
|
|
25
27
|
SyncConfig,
|
|
26
28
|
SyncConfigRes,
|
|
27
29
|
SyncMetadataApi,
|
|
@@ -433,7 +435,7 @@ type SyncControlFns<T extends object, TKey extends string | number> = {
|
|
|
433
435
|
| { type: `delete`; key: TKey },
|
|
434
436
|
) => void)
|
|
435
437
|
| null
|
|
436
|
-
commit: (() =>
|
|
438
|
+
commit: ((signal?: AbortSignal) => SyncAppliedReceipt) | null
|
|
437
439
|
truncate: (() => void) | null
|
|
438
440
|
metadata: SyncMetadataApi<TKey> | null
|
|
439
441
|
}
|
|
@@ -586,6 +588,9 @@ type BufferedSyncTransaction<T extends object, TKey extends string | number> = {
|
|
|
586
588
|
>
|
|
587
589
|
truncate: boolean
|
|
588
590
|
internal: boolean
|
|
591
|
+
signal?: AbortSignal
|
|
592
|
+
resolveApplied?: () => void
|
|
593
|
+
rejectApplied?: (error: unknown) => void
|
|
589
594
|
}
|
|
590
595
|
|
|
591
596
|
type OpenSyncTransaction<
|
|
@@ -811,6 +816,8 @@ class PersistedCollectionRuntime<
|
|
|
811
816
|
private startupMetadataPromise: Promise<void> | null = null
|
|
812
817
|
private startPromise: Promise<void> | null = null
|
|
813
818
|
private internalApplyDepth = 0
|
|
819
|
+
private appliedReceiptSequence = 0
|
|
820
|
+
private readonly pendingAppliedReceipts = new Map<number, Promise<void>>()
|
|
814
821
|
private isHydrating = false
|
|
815
822
|
private coordinatorUnsubscribe: (() => void) | null = null
|
|
816
823
|
private indexAddedUnsubscribe: (() => void) | null = null
|
|
@@ -834,7 +841,32 @@ class PersistedCollectionRuntime<
|
|
|
834
841
|
) {}
|
|
835
842
|
|
|
836
843
|
setSyncControls(syncControls: SyncControlFns<T, TKey>): void {
|
|
837
|
-
|
|
844
|
+
const commit = syncControls.commit
|
|
845
|
+
this.syncControls = {
|
|
846
|
+
...syncControls,
|
|
847
|
+
commit: commit
|
|
848
|
+
? (signal) => this.trackAppliedReceipt(commit(signal))
|
|
849
|
+
: null,
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
private trackAppliedReceipt(receipt: SyncAppliedReceipt): SyncAppliedReceipt {
|
|
854
|
+
const sequence = ++this.appliedReceiptSequence
|
|
855
|
+
if (receipt === true) {
|
|
856
|
+
return true
|
|
857
|
+
}
|
|
858
|
+
this.pendingAppliedReceipts.set(sequence, receipt)
|
|
859
|
+
const removeReceipt = () => this.pendingAppliedReceipts.delete(sequence)
|
|
860
|
+
void receipt.then(removeReceipt, removeReceipt)
|
|
861
|
+
return receipt
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
private async waitForAppliedReceiptsAfter(cursor: number): Promise<void> {
|
|
865
|
+
await Promise.all(
|
|
866
|
+
Array.from(this.pendingAppliedReceipts, ([sequence, receipt]) =>
|
|
867
|
+
sequence > cursor ? receipt : undefined,
|
|
868
|
+
),
|
|
869
|
+
)
|
|
838
870
|
}
|
|
839
871
|
|
|
840
872
|
clearSyncControls(): void {
|
|
@@ -906,9 +938,11 @@ class PersistedCollectionRuntime<
|
|
|
906
938
|
|
|
907
939
|
if (this.syncMode !== `on-demand`) {
|
|
908
940
|
this.activeSubsets.set(this.getSubsetKey({}), {})
|
|
941
|
+
const appliedCursor = this.appliedReceiptSequence
|
|
909
942
|
await this.applyMutex.run(() =>
|
|
910
943
|
this.hydrateSubsetUnsafe({}, { requestRemoteEnsure: false }),
|
|
911
944
|
)
|
|
945
|
+
await this.waitForAppliedReceiptsAfter(appliedCursor)
|
|
912
946
|
}
|
|
913
947
|
}
|
|
914
948
|
|
|
@@ -985,17 +1019,19 @@ class PersistedCollectionRuntime<
|
|
|
985
1019
|
): Promise<void> {
|
|
986
1020
|
this.activeSubsets.set(this.getSubsetKey(options), options)
|
|
987
1021
|
|
|
1022
|
+
const appliedCursor = this.appliedReceiptSequence
|
|
988
1023
|
await this.applyMutex.run(() =>
|
|
989
1024
|
this.hydrateSubsetUnsafe(options, {
|
|
990
1025
|
requestRemoteEnsure: this.mode === `sync-present`,
|
|
991
1026
|
}),
|
|
992
1027
|
)
|
|
1028
|
+
await this.waitForAppliedReceiptsAfter(appliedCursor)
|
|
993
1029
|
|
|
994
1030
|
if (upstreamLoadSubset) {
|
|
995
1031
|
try {
|
|
996
1032
|
const maybePromise = upstreamLoadSubset(options)
|
|
997
1033
|
if (maybePromise instanceof Promise) {
|
|
998
|
-
maybePromise.catch((error) => {
|
|
1034
|
+
await maybePromise.catch((error) => {
|
|
999
1035
|
console.warn(
|
|
1000
1036
|
`Failed to load remote subset in persisted wrapper:`,
|
|
1001
1037
|
error,
|
|
@@ -1156,15 +1192,18 @@ class PersistedCollectionRuntime<
|
|
|
1156
1192
|
|
|
1157
1193
|
this.pendingRemoteSubsetEnsures.clear()
|
|
1158
1194
|
this.activeSubsets.clear()
|
|
1195
|
+
for (const transaction of this.queuedHydrationTransactions) {
|
|
1196
|
+
transaction.rejectApplied?.(new SyncTransactionAbortedError())
|
|
1197
|
+
}
|
|
1159
1198
|
this.queuedHydrationTransactions.length = 0
|
|
1160
1199
|
this.queuedTxCommitted.length = 0
|
|
1161
1200
|
this.clearSyncControls()
|
|
1162
1201
|
}
|
|
1163
1202
|
|
|
1164
|
-
private withInternalApply(task: () =>
|
|
1203
|
+
private withInternalApply<TResult>(task: () => TResult): TResult {
|
|
1165
1204
|
this.internalApplyDepth++
|
|
1166
1205
|
try {
|
|
1167
|
-
task()
|
|
1206
|
+
return task()
|
|
1168
1207
|
} finally {
|
|
1169
1208
|
this.internalApplyDepth--
|
|
1170
1209
|
}
|
|
@@ -1311,36 +1350,48 @@ class PersistedCollectionRuntime<
|
|
|
1311
1350
|
if (!transaction) {
|
|
1312
1351
|
continue
|
|
1313
1352
|
}
|
|
1314
|
-
|
|
1353
|
+
try {
|
|
1354
|
+
await this.applyBufferedSyncTransactionUnsafe(transaction)
|
|
1355
|
+
} catch (error) {
|
|
1356
|
+
transaction.rejectApplied?.(error)
|
|
1357
|
+
for (const abandoned of this.queuedHydrationTransactions) {
|
|
1358
|
+
abandoned.rejectApplied?.(error)
|
|
1359
|
+
}
|
|
1360
|
+
this.queuedHydrationTransactions.length = 0
|
|
1361
|
+
throw error
|
|
1362
|
+
}
|
|
1315
1363
|
}
|
|
1316
1364
|
}
|
|
1317
1365
|
|
|
1318
1366
|
private async applyBufferedSyncTransactionUnsafe(
|
|
1319
1367
|
transaction: BufferedSyncTransaction<T, TKey>,
|
|
1320
1368
|
): Promise<void> {
|
|
1321
|
-
if (
|
|
1322
|
-
|
|
1323
|
-
!this.syncControls.write ||
|
|
1324
|
-
!this.syncControls.commit
|
|
1325
|
-
) {
|
|
1369
|
+
if (transaction.signal?.aborted) {
|
|
1370
|
+
transaction.rejectApplied?.(new SyncTransactionAbortedError())
|
|
1326
1371
|
return
|
|
1327
1372
|
}
|
|
1328
1373
|
|
|
1329
|
-
const
|
|
1330
|
-
|
|
1374
|
+
const { begin, write, commit, truncate, metadata } = this.syncControls
|
|
1375
|
+
if (!begin || !write || !commit) {
|
|
1376
|
+
transaction.rejectApplied?.(new SyncTransactionAbortedError())
|
|
1377
|
+
return
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
const applyToCollection = (): SyncAppliedReceipt => {
|
|
1381
|
+
begin()
|
|
1331
1382
|
|
|
1332
1383
|
if (transaction.truncate) {
|
|
1333
|
-
|
|
1384
|
+
truncate?.()
|
|
1334
1385
|
}
|
|
1335
1386
|
|
|
1336
1387
|
for (const operation of transaction.operations) {
|
|
1337
1388
|
if (operation.type === `delete`) {
|
|
1338
|
-
|
|
1389
|
+
write({
|
|
1339
1390
|
type: `delete`,
|
|
1340
1391
|
key: operation.key,
|
|
1341
1392
|
})
|
|
1342
1393
|
} else {
|
|
1343
|
-
|
|
1394
|
+
write({
|
|
1344
1395
|
type: `update`,
|
|
1345
1396
|
value: operation.value,
|
|
1346
1397
|
metadata: operation.metadata,
|
|
@@ -1350,30 +1401,39 @@ class PersistedCollectionRuntime<
|
|
|
1350
1401
|
|
|
1351
1402
|
for (const [key, metadataWrite] of transaction.rowMetadataWrites) {
|
|
1352
1403
|
if (metadataWrite.type === `delete`) {
|
|
1353
|
-
|
|
1404
|
+
metadata?.row.delete(key)
|
|
1354
1405
|
} else {
|
|
1355
|
-
|
|
1406
|
+
metadata?.row.set(key, metadataWrite.value)
|
|
1356
1407
|
}
|
|
1357
1408
|
}
|
|
1358
1409
|
|
|
1359
1410
|
for (const [key, metadataWrite] of transaction.collectionMetadataWrites) {
|
|
1360
1411
|
if (metadataWrite.type === `delete`) {
|
|
1361
|
-
|
|
1412
|
+
metadata?.collection.delete(key)
|
|
1362
1413
|
} else {
|
|
1363
|
-
|
|
1414
|
+
metadata?.collection.set(key, metadataWrite.value)
|
|
1364
1415
|
}
|
|
1365
1416
|
}
|
|
1366
1417
|
|
|
1367
|
-
|
|
1418
|
+
return commit(transaction.signal)
|
|
1368
1419
|
}
|
|
1369
1420
|
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1421
|
+
try {
|
|
1422
|
+
const applied = transaction.internal
|
|
1423
|
+
? this.withInternalApply(applyToCollection)
|
|
1424
|
+
: applyToCollection()
|
|
1425
|
+
if (applied !== true) {
|
|
1426
|
+
await applied
|
|
1427
|
+
}
|
|
1374
1428
|
|
|
1375
|
-
|
|
1376
|
-
|
|
1429
|
+
if (!transaction.internal) {
|
|
1430
|
+
await this.persistAndBroadcastExternalSyncTransactionUnsafe(transaction)
|
|
1431
|
+
}
|
|
1432
|
+
transaction.resolveApplied?.()
|
|
1433
|
+
} catch (error) {
|
|
1434
|
+
transaction.rejectApplied?.(error)
|
|
1435
|
+
throw error
|
|
1436
|
+
}
|
|
1377
1437
|
}
|
|
1378
1438
|
|
|
1379
1439
|
private async persistAndBroadcastExternalSyncTransactionUnsafe(
|
|
@@ -2457,14 +2517,25 @@ function createWrappedSyncConfig<
|
|
|
2457
2517
|
params.truncate()
|
|
2458
2518
|
}
|
|
2459
2519
|
},
|
|
2460
|
-
commit: () => {
|
|
2520
|
+
commit: (signal?: AbortSignal) => {
|
|
2461
2521
|
const openTransaction = transactionStack.pop()
|
|
2462
2522
|
if (!openTransaction) {
|
|
2463
|
-
params.commit()
|
|
2464
|
-
return
|
|
2523
|
+
return params.commit(signal)
|
|
2465
2524
|
}
|
|
2466
2525
|
|
|
2467
2526
|
if (openTransaction.queuedBecauseHydrating) {
|
|
2527
|
+
if (signal?.aborted) {
|
|
2528
|
+
const aborted = Promise.reject(new SyncTransactionAbortedError())
|
|
2529
|
+
void aborted.catch(() => undefined)
|
|
2530
|
+
return aborted
|
|
2531
|
+
}
|
|
2532
|
+
let resolveApplied!: () => void
|
|
2533
|
+
let rejectApplied!: (error: unknown) => void
|
|
2534
|
+
const applied = new Promise<void>((resolve, reject) => {
|
|
2535
|
+
resolveApplied = resolve
|
|
2536
|
+
rejectApplied = reject
|
|
2537
|
+
})
|
|
2538
|
+
void applied.catch(() => undefined)
|
|
2468
2539
|
runtime.queueHydrationBufferedTransaction({
|
|
2469
2540
|
operations: openTransaction.operations,
|
|
2470
2541
|
rowMetadataWrites: openTransaction.rowMetadataWrites,
|
|
@@ -2472,14 +2543,18 @@ function createWrappedSyncConfig<
|
|
|
2472
2543
|
openTransaction.collectionMetadataWrites,
|
|
2473
2544
|
truncate: openTransaction.truncate,
|
|
2474
2545
|
internal: openTransaction.internal,
|
|
2546
|
+
signal,
|
|
2547
|
+
resolveApplied,
|
|
2548
|
+
rejectApplied,
|
|
2475
2549
|
})
|
|
2476
|
-
return
|
|
2550
|
+
return applied
|
|
2477
2551
|
}
|
|
2478
2552
|
|
|
2479
|
-
params.commit()
|
|
2553
|
+
const applied = params.commit(signal)
|
|
2480
2554
|
if (!openTransaction.internal) {
|
|
2481
|
-
|
|
2482
|
-
|
|
2555
|
+
const persistAfterApplication = async () => {
|
|
2556
|
+
if (applied !== true) await applied
|
|
2557
|
+
await runtime.persistAndBroadcastExternalSyncTransaction({
|
|
2483
2558
|
operations: openTransaction.operations,
|
|
2484
2559
|
rowMetadataWrites: openTransaction.rowMetadataWrites,
|
|
2485
2560
|
collectionMetadataWrites:
|
|
@@ -2487,13 +2562,12 @@ function createWrappedSyncConfig<
|
|
|
2487
2562
|
truncate: openTransaction.truncate,
|
|
2488
2563
|
internal: false,
|
|
2489
2564
|
})
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
)
|
|
2495
|
-
})
|
|
2565
|
+
}
|
|
2566
|
+
const persisted = persistAfterApplication()
|
|
2567
|
+
void persisted.catch(() => undefined)
|
|
2568
|
+
return persisted
|
|
2496
2569
|
}
|
|
2570
|
+
return applied
|
|
2497
2571
|
},
|
|
2498
2572
|
}
|
|
2499
2573
|
|