@tanstack/powersync-db-collection 0.1.69 → 0.2.2
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 +31 -18
- package/dist/cjs/PowerSyncTransactor.cjs.map +1 -1
- package/dist/cjs/PowerSyncTransactor.d.cts +5 -5
- package/dist/cjs/definitions.cjs.map +1 -1
- package/dist/cjs/definitions.d.cts +4 -4
- package/dist/cjs/helpers.cjs.map +1 -1
- package/dist/cjs/helpers.d.cts +6 -9
- package/dist/cjs/index.d.cts +1 -0
- package/dist/cjs/powersync.cjs +214 -53
- package/dist/cjs/powersync.cjs.map +1 -1
- package/dist/cjs/serialization.cjs.map +1 -1
- package/dist/cjs/serialization.d.cts +2 -6
- package/dist/esm/PowerSyncTransactor.d.ts +5 -5
- package/dist/esm/PowerSyncTransactor.js +32 -19
- package/dist/esm/PowerSyncTransactor.js.map +1 -1
- package/dist/esm/definitions.d.ts +4 -4
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/helpers.d.ts +6 -9
- package/dist/esm/helpers.js.map +1 -1
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/powersync.js +215 -54
- package/dist/esm/powersync.js.map +1 -1
- package/dist/esm/serialization.d.ts +2 -6
- package/dist/esm/serialization.js.map +1 -1
- package/package.json +7 -6
- package/src/PowerSyncTransactor.ts +47 -26
- package/src/definitions.ts +11 -5
- package/src/helpers.ts +10 -18
- package/src/index.ts +1 -0
- package/src/powersync.ts +258 -62
- package/src/serialization.ts +8 -17
package/src/powersync.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DiffTriggerOperation, sanitizeSQL } from '@powersync/common'
|
|
1
|
+
import { DiffTriggerOperation, LogLevels, sanitizeSQL } from '@powersync/common'
|
|
2
2
|
import { or, withCollectionConfigFactory } from '@tanstack/db'
|
|
3
3
|
import { compileSQLite } from './sqlite-compiler'
|
|
4
4
|
import { PendingOperationStore } from './PendingOperationStore'
|
|
@@ -17,8 +17,6 @@ import type {
|
|
|
17
17
|
import type {
|
|
18
18
|
AnyTableColumnType,
|
|
19
19
|
ExtractedTable,
|
|
20
|
-
ExtractedTableColumns,
|
|
21
|
-
MapBaseColumnType,
|
|
22
20
|
OptionalExtractedTable,
|
|
23
21
|
} from './helpers'
|
|
24
22
|
import type {
|
|
@@ -26,7 +24,6 @@ import type {
|
|
|
26
24
|
ConfigWithArbitraryCollectionTypes,
|
|
27
25
|
ConfigWithSQLiteInputType,
|
|
28
26
|
ConfigWithSQLiteTypes,
|
|
29
|
-
CustomSQLiteSerializer,
|
|
30
27
|
EnhancedPowerSyncCollectionConfig,
|
|
31
28
|
InferPowerSyncOutputType,
|
|
32
29
|
PowerSyncCollectionConfig,
|
|
@@ -36,6 +33,14 @@ import type { PendingOperation } from './PendingOperationStore'
|
|
|
36
33
|
import type { StandardSchemaV1 } from '@standard-schema/spec'
|
|
37
34
|
import type { LockContext, Table, TriggerDiffRecord } from '@powersync/common'
|
|
38
35
|
|
|
36
|
+
function isPromiseLike(value: unknown): value is PromiseLike<unknown> {
|
|
37
|
+
return (
|
|
38
|
+
!!value &&
|
|
39
|
+
(typeof value === `object` || typeof value === `function`) &&
|
|
40
|
+
typeof (value as { then?: unknown }).then === `function`
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
|
|
39
44
|
/**
|
|
40
45
|
* Creates PowerSync collection options for use with a standard Collection.
|
|
41
46
|
*
|
|
@@ -275,12 +280,15 @@ function createPowerSyncCollectionConfig<
|
|
|
275
280
|
return validation.value
|
|
276
281
|
} else if (`issues` in validation) {
|
|
277
282
|
const issueMessage = `Failed to validate incoming data for ${viewName}. Issues: ${validation.issues.map((issue) => `${issue.path} - ${issue.message}`)}`
|
|
278
|
-
database.logger.error
|
|
283
|
+
database.logger.log({ level: LogLevels.error, message: issueMessage })
|
|
279
284
|
onDeserializationError!(validation)
|
|
280
285
|
throw new Error(issueMessage)
|
|
281
286
|
} else {
|
|
282
287
|
const unknownErrorMessage = `Unknown deserialization error for ${viewName}`
|
|
283
|
-
database.logger.
|
|
288
|
+
database.logger.log({
|
|
289
|
+
level: LogLevels.error,
|
|
290
|
+
message: unknownErrorMessage,
|
|
291
|
+
})
|
|
284
292
|
onDeserializationError!({ issues: [{ message: unknownErrorMessage }] })
|
|
285
293
|
throw new Error(unknownErrorMessage)
|
|
286
294
|
}
|
|
@@ -324,6 +332,8 @@ function createPowerSyncCollectionConfig<
|
|
|
324
332
|
| ((options?: { context?: LockContext }) => Promise<void>)
|
|
325
333
|
| null = null
|
|
326
334
|
let trackingSetup: Promise<void> | null = null
|
|
335
|
+
let activeTrackingDisposal: Promise<void> | null = null
|
|
336
|
+
let abortTrackingDisposal: Promise<void> | null = null
|
|
327
337
|
|
|
328
338
|
if (syncMode === `eager`) {
|
|
329
339
|
return runEagerSync()
|
|
@@ -347,11 +357,35 @@ function createPowerSyncCollectionConfig<
|
|
|
347
357
|
|
|
348
358
|
const dispose = disposeTracking
|
|
349
359
|
if (!dispose) {
|
|
360
|
+
// Reconciliation may have claimed the disposer after this caller
|
|
361
|
+
// observed setup. Join that invocation instead of settling early.
|
|
362
|
+
await activeTrackingDisposal
|
|
350
363
|
return
|
|
351
364
|
}
|
|
352
365
|
|
|
353
366
|
disposeTracking = null
|
|
354
|
-
|
|
367
|
+
let disposal: Promise<void>
|
|
368
|
+
try {
|
|
369
|
+
disposal = Promise.resolve(dispose(context ? { context } : undefined))
|
|
370
|
+
} catch (error) {
|
|
371
|
+
disposal = Promise.reject(error)
|
|
372
|
+
}
|
|
373
|
+
activeTrackingDisposal = disposal
|
|
374
|
+
try {
|
|
375
|
+
await disposal
|
|
376
|
+
} finally {
|
|
377
|
+
if (activeTrackingDisposal === disposal) {
|
|
378
|
+
activeTrackingDisposal = null
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
function disposeTrackingAfterAbort(): Promise<void> {
|
|
384
|
+
const disposal = (abortTrackingDisposal ??= safelyDisposeTracking())
|
|
385
|
+
// Abort event dispatch does not observe a listener's returned promise.
|
|
386
|
+
// The eager cleanup callback awaits this same promise below.
|
|
387
|
+
void disposal.catch(() => undefined)
|
|
388
|
+
return disposal
|
|
355
389
|
}
|
|
356
390
|
|
|
357
391
|
async function establishTracking(
|
|
@@ -392,6 +426,7 @@ function createPowerSyncCollectionConfig<
|
|
|
392
426
|
return await database.triggers.createDiffTrigger({
|
|
393
427
|
source: viewName,
|
|
394
428
|
destination: trackedTableName,
|
|
429
|
+
columns: table.columns.map((column) => column.name),
|
|
395
430
|
setupContext,
|
|
396
431
|
when,
|
|
397
432
|
hooks: {
|
|
@@ -416,9 +451,10 @@ function createPowerSyncCollectionConfig<
|
|
|
416
451
|
}
|
|
417
452
|
appliedReceipts.push(commit())
|
|
418
453
|
}
|
|
419
|
-
database.logger.
|
|
420
|
-
|
|
421
|
-
|
|
454
|
+
database.logger.log({
|
|
455
|
+
level: LogLevels.info,
|
|
456
|
+
message: `Sync is ready for ${viewName} into ${trackedTableName}`,
|
|
457
|
+
})
|
|
422
458
|
},
|
|
423
459
|
},
|
|
424
460
|
})
|
|
@@ -442,10 +478,11 @@ function createPowerSyncCollectionConfig<
|
|
|
442
478
|
await flushDiffRecordsWithContext(context, ignoredReceipts)
|
|
443
479
|
})
|
|
444
480
|
.catch((error) => {
|
|
445
|
-
database.logger.
|
|
446
|
-
|
|
481
|
+
database.logger.log({
|
|
482
|
+
level: LogLevels.error,
|
|
483
|
+
message: `An error has been detected in the sync handler`,
|
|
447
484
|
error,
|
|
448
|
-
)
|
|
485
|
+
})
|
|
449
486
|
})
|
|
450
487
|
}
|
|
451
488
|
|
|
@@ -503,18 +540,20 @@ function createPowerSyncCollectionConfig<
|
|
|
503
540
|
// transaction that currently parks it.
|
|
504
541
|
pendingOperationStore.resolvePendingFor(pendingOperations)
|
|
505
542
|
} catch (error) {
|
|
506
|
-
database.logger.
|
|
507
|
-
|
|
543
|
+
database.logger.log({
|
|
544
|
+
level: LogLevels.error,
|
|
545
|
+
message: `An error has been detected in the sync handler`,
|
|
508
546
|
error,
|
|
509
|
-
)
|
|
547
|
+
})
|
|
510
548
|
}
|
|
511
549
|
}
|
|
512
550
|
|
|
513
551
|
// The sync function needs to be synchronous.
|
|
514
552
|
async function start(afterOnChangeRegistered?: () => Promise<void>) {
|
|
515
|
-
database.logger.
|
|
516
|
-
|
|
517
|
-
|
|
553
|
+
database.logger.log({
|
|
554
|
+
level: LogLevels.info,
|
|
555
|
+
message: `Sync is starting for ${viewName} into ${trackedTableName}`,
|
|
556
|
+
})
|
|
518
557
|
database.onChangeWithCallback(
|
|
519
558
|
{
|
|
520
559
|
onChange: async () => {
|
|
@@ -532,12 +571,12 @@ function createPowerSyncCollectionConfig<
|
|
|
532
571
|
|
|
533
572
|
// If the abort controller was aborted while processing the request above
|
|
534
573
|
if (abortController.signal.aborted) {
|
|
535
|
-
await
|
|
574
|
+
await disposeTrackingAfterAbort()
|
|
536
575
|
} else {
|
|
537
576
|
abortController.signal.addEventListener(
|
|
538
577
|
`abort`,
|
|
539
|
-
|
|
540
|
-
|
|
578
|
+
() => {
|
|
579
|
+
void disposeTrackingAfterAbort()
|
|
541
580
|
},
|
|
542
581
|
{ once: true },
|
|
543
582
|
)
|
|
@@ -548,14 +587,14 @@ function createPowerSyncCollectionConfig<
|
|
|
548
587
|
// Registers a diff trigger for the entire table.
|
|
549
588
|
function runEagerSync() {
|
|
550
589
|
let onUnload: CleanupFn | void | null = null
|
|
590
|
+
let onUnloadStarted = false
|
|
551
591
|
|
|
552
|
-
start(async () => {
|
|
592
|
+
const startup = start(async () => {
|
|
553
593
|
const cleanup = await restConfig.onLoad?.()
|
|
594
|
+
onUnload = cleanup
|
|
554
595
|
if (abortController.signal.aborted) {
|
|
555
|
-
cleanup?.()
|
|
556
596
|
return
|
|
557
597
|
}
|
|
558
|
-
onUnload = cleanup
|
|
559
598
|
|
|
560
599
|
const appliedReceipts: Array<SyncAppliedReceipt> = []
|
|
561
600
|
await establishTracking(
|
|
@@ -584,21 +623,61 @@ function createPowerSyncCollectionConfig<
|
|
|
584
623
|
await Promise.all(appliedReceipts)
|
|
585
624
|
markReady()
|
|
586
625
|
}).catch((error) => {
|
|
587
|
-
database.logger.
|
|
588
|
-
|
|
626
|
+
database.logger.log({
|
|
627
|
+
level: LogLevels.error,
|
|
628
|
+
message: `Could not start syncing process for ${viewName} into ${trackedTableName}`,
|
|
589
629
|
error,
|
|
590
|
-
)
|
|
630
|
+
})
|
|
591
631
|
if (collection.status === `loading`) {
|
|
592
632
|
markError(error)
|
|
593
633
|
}
|
|
594
634
|
})
|
|
595
635
|
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
636
|
+
const invokeOnUnload = (): void | Promise<void> => {
|
|
637
|
+
if (onUnloadStarted || !onUnload) return
|
|
638
|
+
onUnloadStarted = true
|
|
639
|
+
const cleanup = onUnload
|
|
640
|
+
onUnload = null
|
|
641
|
+
return cleanup()
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
return async () => {
|
|
645
|
+
database.logger.log({
|
|
646
|
+
level: LogLevels.info,
|
|
647
|
+
message: `Sync has been stopped for ${viewName} into ${trackedTableName}`,
|
|
648
|
+
})
|
|
600
649
|
abortController.abort()
|
|
601
|
-
|
|
650
|
+
const trackingDisposal = disposeTrackingAfterAbort()
|
|
651
|
+
|
|
652
|
+
let onUnloadFailure: { error: unknown } | undefined
|
|
653
|
+
const settleOnUnload = async () => {
|
|
654
|
+
try {
|
|
655
|
+
await invokeOnUnload()
|
|
656
|
+
} catch (error) {
|
|
657
|
+
onUnloadFailure ??= { error }
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
// Invoke an already-installed callback in this stack, but also wait
|
|
662
|
+
// for startup to publish a callback acquired concurrently with abort.
|
|
663
|
+
const settlements = await Promise.allSettled([
|
|
664
|
+
startup,
|
|
665
|
+
trackingDisposal,
|
|
666
|
+
settleOnUnload(),
|
|
667
|
+
])
|
|
668
|
+
const disposalSettlement = settlements[1]
|
|
669
|
+
await settleOnUnload()
|
|
670
|
+
if (disposalSettlement.status === `rejected` && onUnloadFailure) {
|
|
671
|
+
throw new AggregateError(
|
|
672
|
+
[disposalSettlement.reason, onUnloadFailure.error],
|
|
673
|
+
`PowerSync tracking disposal and load-hook cleanup both failed`,
|
|
674
|
+
{ cause: disposalSettlement.reason },
|
|
675
|
+
)
|
|
676
|
+
}
|
|
677
|
+
if (disposalSettlement.status === `rejected`) {
|
|
678
|
+
throw disposalSettlement.reason
|
|
679
|
+
}
|
|
680
|
+
if (onUnloadFailure) throw onUnloadFailure.error
|
|
602
681
|
}
|
|
603
682
|
}
|
|
604
683
|
|
|
@@ -614,22 +693,40 @@ function createPowerSyncCollectionConfig<
|
|
|
614
693
|
options: LoadSubsetOptions
|
|
615
694
|
failures: number
|
|
616
695
|
}
|
|
696
|
+
type DemandCleanupTask = {
|
|
697
|
+
order: number
|
|
698
|
+
promise: Promise<unknown>
|
|
699
|
+
}
|
|
700
|
+
type DemandCleanupFailure = {
|
|
701
|
+
order: number
|
|
702
|
+
error: unknown
|
|
703
|
+
}
|
|
704
|
+
type DemandCleanupCollector = {
|
|
705
|
+
tasks: Array<DemandCleanupTask>
|
|
706
|
+
ownedTasks: Set<Promise<unknown>>
|
|
707
|
+
failures: Array<DemandCleanupFailure>
|
|
708
|
+
nextOrder: number
|
|
709
|
+
}
|
|
617
710
|
|
|
618
711
|
const demands = new Map<LoadSubsetOptions, DemandRecord>()
|
|
619
712
|
const releasedSubsets = new WeakSet<LoadSubsetOptions>()
|
|
620
713
|
const pendingReleases: Array<PendingRelease> = []
|
|
714
|
+
const pendingDemandLoads = new Set<Promise<void>>()
|
|
715
|
+
const pendingDemandCleanups = new Set<Promise<unknown>>()
|
|
621
716
|
let stopped = false
|
|
622
717
|
let trackingRevision = 0
|
|
623
718
|
let reconciledTrackingRevision = 0
|
|
624
719
|
let rebuildPromise: Promise<void> | null = null
|
|
625
720
|
let drainingReleases = false
|
|
626
721
|
let releaseRetryTimer: ReturnType<typeof setTimeout> | undefined
|
|
722
|
+
let demandCleanupCollector: DemandCleanupCollector | null = null
|
|
627
723
|
const startup = start()
|
|
628
724
|
void startup.catch((error) =>
|
|
629
|
-
database.logger.
|
|
630
|
-
|
|
725
|
+
database.logger.log({
|
|
726
|
+
level: LogLevels.error,
|
|
727
|
+
message: `Could not start syncing process for ${viewName} into ${trackedTableName}`,
|
|
631
728
|
error,
|
|
632
|
-
),
|
|
729
|
+
}),
|
|
633
730
|
)
|
|
634
731
|
|
|
635
732
|
const activeWhereExpressions = () =>
|
|
@@ -761,6 +858,13 @@ function createPowerSyncCollectionConfig<
|
|
|
761
858
|
if (cleanup) demand.cleanup = cleanup
|
|
762
859
|
} catch (error) {
|
|
763
860
|
demands.delete(options)
|
|
861
|
+
const collector = demandCleanupCollector
|
|
862
|
+
if (collector) {
|
|
863
|
+
collector.failures.push({
|
|
864
|
+
order: collector.nextOrder++,
|
|
865
|
+
error,
|
|
866
|
+
})
|
|
867
|
+
}
|
|
764
868
|
throw error
|
|
765
869
|
}
|
|
766
870
|
|
|
@@ -772,8 +876,7 @@ function createPowerSyncCollectionConfig<
|
|
|
772
876
|
options.signal?.aborted ||
|
|
773
877
|
demands.get(options) !== demand
|
|
774
878
|
) {
|
|
775
|
-
|
|
776
|
-
demand.cleanup?.()
|
|
879
|
+
cleanupDemand(demand)
|
|
777
880
|
return
|
|
778
881
|
}
|
|
779
882
|
|
|
@@ -782,6 +885,14 @@ function createPowerSyncCollectionConfig<
|
|
|
782
885
|
await rebuildTracking()
|
|
783
886
|
}
|
|
784
887
|
|
|
888
|
+
const trackLoadSubset = (options: LoadSubsetOptions): Promise<void> => {
|
|
889
|
+
const task = loadSubset(options)
|
|
890
|
+
pendingDemandLoads.add(task)
|
|
891
|
+
const finish = () => pendingDemandLoads.delete(task)
|
|
892
|
+
void task.then(finish, finish)
|
|
893
|
+
return task
|
|
894
|
+
}
|
|
895
|
+
|
|
785
896
|
const toInlinedWhereClause = (compiled: {
|
|
786
897
|
where?: string
|
|
787
898
|
params: Array<unknown>
|
|
@@ -794,15 +905,64 @@ function createPowerSyncCollectionConfig<
|
|
|
794
905
|
)
|
|
795
906
|
}
|
|
796
907
|
|
|
908
|
+
const reportDemandCleanupFailure = (error: unknown): void => {
|
|
909
|
+
database.logger.log({
|
|
910
|
+
level: LogLevels.error,
|
|
911
|
+
message: `Could not clean up subset hook for ${viewName}`,
|
|
912
|
+
error,
|
|
913
|
+
})
|
|
914
|
+
}
|
|
915
|
+
|
|
797
916
|
const cleanupDemand = (demand: DemandRecord): void => {
|
|
798
917
|
demands.delete(demand.options)
|
|
918
|
+
const collector = demandCleanupCollector
|
|
919
|
+
const order = collector ? collector.nextOrder++ : undefined
|
|
799
920
|
try {
|
|
800
|
-
demand.cleanup
|
|
921
|
+
const cleanup = demand.cleanup
|
|
922
|
+
demand.cleanup = undefined
|
|
923
|
+
const result: unknown = cleanup?.()
|
|
924
|
+
if (!isPromiseLike(result)) return
|
|
925
|
+
|
|
926
|
+
const task = Promise.resolve(result)
|
|
927
|
+
pendingDemandCleanups.add(task)
|
|
928
|
+
const finish = () => pendingDemandCleanups.delete(task)
|
|
929
|
+
void task.then(finish, finish)
|
|
930
|
+
if (collector) {
|
|
931
|
+
collector.tasks.push({ order: order!, promise: task })
|
|
932
|
+
collector.ownedTasks.add(task)
|
|
933
|
+
void task.catch(() => undefined)
|
|
934
|
+
} else {
|
|
935
|
+
void task.catch((error) => {
|
|
936
|
+
if (!demandCleanupCollector?.ownedTasks.has(task)) {
|
|
937
|
+
reportDemandCleanupFailure(error)
|
|
938
|
+
}
|
|
939
|
+
})
|
|
940
|
+
}
|
|
801
941
|
} catch (error) {
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
942
|
+
if (collector) {
|
|
943
|
+
collector.failures.push({ order: order!, error })
|
|
944
|
+
} else {
|
|
945
|
+
reportDemandCleanupFailure(error)
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
const throwDemandCleanupFailures = (
|
|
951
|
+
collector: DemandCleanupCollector,
|
|
952
|
+
primaryFailures: ReadonlyArray<unknown> = [],
|
|
953
|
+
): void => {
|
|
954
|
+
const errors = [
|
|
955
|
+
...primaryFailures,
|
|
956
|
+
...collector.failures
|
|
957
|
+
.slice()
|
|
958
|
+
.sort((left, right) => left.order - right.order)
|
|
959
|
+
.map(({ error }) => error),
|
|
960
|
+
]
|
|
961
|
+
if (errors.length === 1) throw errors[0]
|
|
962
|
+
if (errors.length > 1) {
|
|
963
|
+
throw new AggregateError(errors, `PowerSync cleanup failed`, {
|
|
964
|
+
cause: errors[0],
|
|
965
|
+
})
|
|
806
966
|
}
|
|
807
967
|
}
|
|
808
968
|
|
|
@@ -878,10 +1038,11 @@ function createPowerSyncCollectionConfig<
|
|
|
878
1038
|
)
|
|
879
1039
|
retryDelay =
|
|
880
1040
|
retryDelay === 0 ? delay : Math.min(retryDelay, delay)
|
|
881
|
-
database.logger.
|
|
882
|
-
|
|
1041
|
+
database.logger.log({
|
|
1042
|
+
level: LogLevels.error,
|
|
1043
|
+
message: `Could not release subset tracking for ${viewName}; retrying`,
|
|
883
1044
|
error,
|
|
884
|
-
)
|
|
1045
|
+
})
|
|
885
1046
|
}
|
|
886
1047
|
}
|
|
887
1048
|
} finally {
|
|
@@ -915,16 +1076,62 @@ function createPowerSyncCollectionConfig<
|
|
|
915
1076
|
stopped = true
|
|
916
1077
|
clearTimeout(releaseRetryTimer)
|
|
917
1078
|
releaseRetryTimer = undefined
|
|
918
|
-
database.logger.
|
|
919
|
-
|
|
920
|
-
|
|
1079
|
+
database.logger.log({
|
|
1080
|
+
level: LogLevels.info,
|
|
1081
|
+
message: `Sync has been stopped for ${viewName} into ${trackedTableName}`,
|
|
1082
|
+
})
|
|
921
1083
|
abortController.abort()
|
|
1084
|
+
const trackingDisposal = disposeTrackingAfterAbort()
|
|
1085
|
+
const pendingLoads = [...pendingDemandLoads]
|
|
1086
|
+
const pendingTasks = [...pendingDemandCleanups]
|
|
1087
|
+
const collector: DemandCleanupCollector = {
|
|
1088
|
+
tasks: pendingTasks.map((promise, order) => ({ order, promise })),
|
|
1089
|
+
ownedTasks: new Set(pendingTasks),
|
|
1090
|
+
failures: [],
|
|
1091
|
+
nextOrder: pendingTasks.length,
|
|
1092
|
+
}
|
|
1093
|
+
demandCleanupCollector = collector
|
|
922
1094
|
for (const demand of demands.values()) {
|
|
923
1095
|
cleanupDemand(demand)
|
|
924
1096
|
}
|
|
925
1097
|
pendingReleases.length = 0
|
|
1098
|
+
|
|
1099
|
+
return (async () => {
|
|
1100
|
+
const ownershipSettlements = await Promise.allSettled([
|
|
1101
|
+
trackingDisposal,
|
|
1102
|
+
...pendingLoads,
|
|
1103
|
+
])
|
|
1104
|
+
const disposalSettlement = ownershipSettlements[0]
|
|
1105
|
+
|
|
1106
|
+
let settledTasks = 0
|
|
1107
|
+
while (settledTasks < collector.tasks.length) {
|
|
1108
|
+
const tasks = collector.tasks.slice(settledTasks)
|
|
1109
|
+
settledTasks = collector.tasks.length
|
|
1110
|
+
const outcomes = await Promise.allSettled(
|
|
1111
|
+
tasks.map(({ promise }) => promise),
|
|
1112
|
+
)
|
|
1113
|
+
for (const [index, outcome] of outcomes.entries()) {
|
|
1114
|
+
if (outcome.status === `rejected`) {
|
|
1115
|
+
collector.failures.push({
|
|
1116
|
+
order: tasks[index]!.order,
|
|
1117
|
+
error: outcome.reason,
|
|
1118
|
+
})
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
throwDemandCleanupFailures(
|
|
1123
|
+
collector,
|
|
1124
|
+
disposalSettlement.status === `rejected`
|
|
1125
|
+
? [disposalSettlement.reason]
|
|
1126
|
+
: [],
|
|
1127
|
+
)
|
|
1128
|
+
})().finally(() => {
|
|
1129
|
+
if (demandCleanupCollector === collector) {
|
|
1130
|
+
demandCleanupCollector = null
|
|
1131
|
+
}
|
|
1132
|
+
})
|
|
926
1133
|
},
|
|
927
|
-
loadSubset:
|
|
1134
|
+
loadSubset: trackLoadSubset,
|
|
928
1135
|
unloadSubset,
|
|
929
1136
|
}
|
|
930
1137
|
}
|
|
@@ -965,18 +1172,7 @@ function createPowerSyncCollectionConfig<
|
|
|
965
1172
|
trackedTableName,
|
|
966
1173
|
metadataIsTracked,
|
|
967
1174
|
serializeValue: (value) =>
|
|
968
|
-
serializeForSQLite(
|
|
969
|
-
value,
|
|
970
|
-
// This is required by the input generic
|
|
971
|
-
table as Table<
|
|
972
|
-
MapBaseColumnType<InferPowerSyncOutputType<TTable, TSchema>>
|
|
973
|
-
>,
|
|
974
|
-
// Coerce serializer to the shape that corresponds to the Table constructed from OutputType
|
|
975
|
-
serializer as CustomSQLiteSerializer<
|
|
976
|
-
OutputType,
|
|
977
|
-
ExtractedTableColumns<Table<MapBaseColumnType<OutputType>>>
|
|
978
|
-
>,
|
|
979
|
-
),
|
|
1175
|
+
serializeForSQLite<TTable>(value, table, serializer),
|
|
980
1176
|
}),
|
|
981
1177
|
},
|
|
982
1178
|
}
|
package/src/serialization.ts
CHANGED
|
@@ -2,9 +2,9 @@ import { ColumnType } from '@powersync/common'
|
|
|
2
2
|
import type { Table } from '@powersync/common'
|
|
3
3
|
import type { CustomSQLiteSerializer } from './definitions'
|
|
4
4
|
import type {
|
|
5
|
+
AnyTableColumnType,
|
|
5
6
|
ExtractedTable,
|
|
6
7
|
ExtractedTableColumns,
|
|
7
|
-
MapBaseColumnType,
|
|
8
8
|
} from './helpers'
|
|
9
9
|
|
|
10
10
|
/**
|
|
@@ -13,10 +13,6 @@ import type {
|
|
|
13
13
|
* This function takes an object representing a row, a table schema, and an optional custom serializer map.
|
|
14
14
|
* It returns a new object with values transformed to be compatible with SQLite column types.
|
|
15
15
|
*
|
|
16
|
-
* ## Generics
|
|
17
|
-
* - `TOutput`: The shape of the input object, typically matching the row data.
|
|
18
|
-
* - `TTable`: The table schema, which must match the keys of `TOutput`.
|
|
19
|
-
*
|
|
20
16
|
* ## Parameters
|
|
21
17
|
* - `value`: The object to serialize (row data).
|
|
22
18
|
* - `tableSchema`: The schema describing the SQLite table columns and types.
|
|
@@ -37,17 +33,12 @@ import type {
|
|
|
37
33
|
* - Throws if a key in `value` does not exist in the schema.
|
|
38
34
|
* - Throws if a value cannot be converted to the required SQLite type.
|
|
39
35
|
*/
|
|
40
|
-
export function serializeForSQLite<
|
|
41
|
-
|
|
42
|
-
// The keys should match
|
|
43
|
-
TTable extends Table<MapBaseColumnType<TOutput>> = Table<
|
|
44
|
-
MapBaseColumnType<TOutput>
|
|
45
|
-
>,
|
|
46
|
-
>(
|
|
47
|
-
value: TOutput,
|
|
36
|
+
export function serializeForSQLite<TTable extends Table>(
|
|
37
|
+
value: AnyTableColumnType<TTable>,
|
|
48
38
|
tableSchema: TTable,
|
|
49
|
-
customSerializer:
|
|
50
|
-
|
|
39
|
+
customSerializer: CustomSQLiteSerializer<
|
|
40
|
+
AnyTableColumnType<TTable>,
|
|
41
|
+
ExtractedTableColumns<TTable>
|
|
51
42
|
> = {},
|
|
52
43
|
): ExtractedTable<TTable> {
|
|
53
44
|
return Object.fromEntries(
|
|
@@ -68,7 +59,7 @@ export function serializeForSQLite<
|
|
|
68
59
|
|
|
69
60
|
const customTransform = customSerializer[key]
|
|
70
61
|
if (customTransform) {
|
|
71
|
-
return [key, customTransform(value
|
|
62
|
+
return [key, customTransform(value)]
|
|
72
63
|
}
|
|
73
64
|
|
|
74
65
|
// Map to the output
|
|
@@ -98,5 +89,5 @@ export function serializeForSQLite<
|
|
|
98
89
|
}
|
|
99
90
|
}
|
|
100
91
|
}),
|
|
101
|
-
)
|
|
92
|
+
) as ExtractedTable<TTable>
|
|
102
93
|
}
|