@tanstack/db-sqlite-persistence-core 0.4.8 → 0.4.9
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 +10 -1
- package/dist/cjs/broadcast-coordinator.cjs +117 -29
- package/dist/cjs/broadcast-coordinator.cjs.map +1 -1
- package/dist/cjs/broadcast-coordinator.d.cts +16 -8
- package/dist/cjs/index.cjs +4 -0
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/persisted.cjs +1408 -278
- package/dist/cjs/persisted.cjs.map +1 -1
- package/dist/cjs/persisted.d.cts +62 -15
- package/dist/cjs/remote-subset-wire.cjs +11 -0
- package/dist/cjs/remote-subset-wire.cjs.map +1 -1
- package/dist/cjs/remote-subset-wire.d.cts +1 -0
- package/dist/cjs/sqlite-core-adapter.cjs +623 -73
- package/dist/cjs/sqlite-core-adapter.cjs.map +1 -1
- package/dist/cjs/sqlite-core-adapter.d.cts +46 -12
- package/dist/esm/broadcast-coordinator.d.ts +16 -8
- package/dist/esm/broadcast-coordinator.js +118 -30
- package/dist/esm/broadcast-coordinator.js.map +1 -1
- package/dist/esm/index.js +6 -2
- package/dist/esm/persisted.d.ts +62 -15
- package/dist/esm/persisted.js +1409 -279
- package/dist/esm/persisted.js.map +1 -1
- package/dist/esm/remote-subset-wire.d.ts +1 -0
- package/dist/esm/remote-subset-wire.js +11 -0
- package/dist/esm/remote-subset-wire.js.map +1 -1
- package/dist/esm/sqlite-core-adapter.d.ts +46 -12
- package/dist/esm/sqlite-core-adapter.js +626 -76
- package/dist/esm/sqlite-core-adapter.js.map +1 -1
- package/package.json +2 -2
- package/src/broadcast-coordinator.ts +153 -23
- package/src/persisted.ts +1973 -322
- package/src/remote-subset-wire.ts +12 -0
- package/src/sqlite-core-adapter.ts +760 -77
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/db-sqlite-persistence-core",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.9",
|
|
4
4
|
"description": "SQLite persisted collection core for TanStack DB",
|
|
5
5
|
"author": "TanStack Team",
|
|
6
6
|
"license": "MIT",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
],
|
|
51
51
|
"dependencies": {
|
|
52
52
|
"@standard-schema/spec": "^1.1.0",
|
|
53
|
-
"@tanstack/db": "0.
|
|
53
|
+
"@tanstack/db": "0.14.0"
|
|
54
54
|
},
|
|
55
55
|
"peerDependencies": {
|
|
56
56
|
"typescript": ">=4.7"
|
|
@@ -3,6 +3,7 @@ import { isValidCommittedTxAnchor } from './committed-tx-anchor'
|
|
|
3
3
|
import {
|
|
4
4
|
DuplicateRemoteSubsetOwnerError,
|
|
5
5
|
IndeterminateCommitError,
|
|
6
|
+
InvalidPersistedCollectionConfigError,
|
|
6
7
|
InvalidPersistenceAdapterError,
|
|
7
8
|
PersistedCollectionDurabilityError,
|
|
8
9
|
RetryableRemoteSubsetAcquisitionError,
|
|
@@ -13,6 +14,7 @@ import {
|
|
|
13
14
|
toTransportedLoadSubsetOptions,
|
|
14
15
|
} from './remote-subset-wire'
|
|
15
16
|
import { unloadRemoteSubsetOwner } from './remote-subset-owner'
|
|
17
|
+
import { eagerStorageTarget, managedStorageTarget } from './persisted'
|
|
16
18
|
import type { LoadSubsetOptions } from '@tanstack/db'
|
|
17
19
|
import type { IndeterminateCommitRequestType } from './errors'
|
|
18
20
|
import type { TransportedLoadSubsetOptions } from './remote-subset-wire'
|
|
@@ -28,6 +30,7 @@ import type {
|
|
|
28
30
|
PersistedIndexSpec,
|
|
29
31
|
PersistedMutationEnvelope,
|
|
30
32
|
PersistedRowMetadataMutation,
|
|
33
|
+
PersistedStorageTarget,
|
|
31
34
|
PersistedTx,
|
|
32
35
|
PersistenceAdapter,
|
|
33
36
|
ProtocolEnvelope,
|
|
@@ -64,12 +67,14 @@ type RPCRequest =
|
|
|
64
67
|
rpcId: string
|
|
65
68
|
signature: string
|
|
66
69
|
spec: PersistedIndexSpec
|
|
70
|
+
cacheGenerationClaimId?: string
|
|
67
71
|
}
|
|
68
72
|
| {
|
|
69
73
|
type: `rpc:applyLocalMutations:req`
|
|
70
74
|
rpcId: string
|
|
71
75
|
envelopeId: string
|
|
72
76
|
mutations: Array<PersistedMutationEnvelope>
|
|
77
|
+
cacheGenerationClaimId?: string
|
|
73
78
|
}
|
|
74
79
|
| {
|
|
75
80
|
type: `rpc:applyCommittedTx:req`
|
|
@@ -87,6 +92,7 @@ type RPCRequest =
|
|
|
87
92
|
type: `rpc:pullSince:req`
|
|
88
93
|
rpcId: string
|
|
89
94
|
fromRowVersion: number
|
|
95
|
+
cacheGenerationClaimId?: string
|
|
90
96
|
}
|
|
91
97
|
|
|
92
98
|
type RPCResponse =
|
|
@@ -133,8 +139,9 @@ type CoordinatorAdapter = Pick<
|
|
|
133
139
|
| `runInRegularScope`
|
|
134
140
|
> & {
|
|
135
141
|
pullSince?: (
|
|
136
|
-
|
|
142
|
+
target: PersistedStorageTarget,
|
|
137
143
|
fromRowVersion: number,
|
|
144
|
+
ctx?: { cacheGenerationClaimId?: string },
|
|
138
145
|
) => Promise<
|
|
139
146
|
| {
|
|
140
147
|
latestRowVersion: number
|
|
@@ -147,14 +154,18 @@ type CoordinatorAdapter = Pick<
|
|
|
147
154
|
deletedKeys: Array<string | number>
|
|
148
155
|
}
|
|
149
156
|
>
|
|
150
|
-
getStreamPosition?: (
|
|
157
|
+
getStreamPosition?: (
|
|
158
|
+
target: PersistedStorageTarget,
|
|
159
|
+
ctx?: { cacheGenerationClaimId?: string },
|
|
160
|
+
) => Promise<{
|
|
151
161
|
latestTerm: number
|
|
152
162
|
latestSeq: number
|
|
153
163
|
latestRowVersion: number
|
|
154
164
|
}>
|
|
155
165
|
reserveLeadershipTerm?: (
|
|
156
|
-
|
|
166
|
+
target: PersistedStorageTarget,
|
|
157
167
|
observedTerm: number,
|
|
168
|
+
ctx?: { cacheGenerationClaimId?: string },
|
|
158
169
|
) => Promise<{
|
|
159
170
|
latestTerm: number
|
|
160
171
|
latestSeq: number
|
|
@@ -175,6 +186,7 @@ type ActiveRemoteSubsetAcquisition = {
|
|
|
175
186
|
owner: RemoteSubsetOwner
|
|
176
187
|
options: TransportedLoadSubsetOptions
|
|
177
188
|
load: Promise<void>
|
|
189
|
+
admitted: Promise<void>
|
|
178
190
|
transferred: boolean
|
|
179
191
|
released: boolean
|
|
180
192
|
terminalRelease: boolean
|
|
@@ -259,6 +271,7 @@ export class BroadcastCollectionCoordinator implements PersistedCollectionCoordi
|
|
|
259
271
|
private readonly coordinatorName: string
|
|
260
272
|
private defaultAdapter: CoordinatorAdapter | null
|
|
261
273
|
private readonly collectionAdapters = new Map<string, CoordinatorAdapter>()
|
|
274
|
+
private readonly collectionClaimIds = new Map<string, string>()
|
|
262
275
|
private readonly remoteSubsetOwners = new Map<string, RemoteSubsetOwner>()
|
|
263
276
|
private readonly remoteSubsetIds = new Map<
|
|
264
277
|
string,
|
|
@@ -338,9 +351,15 @@ export class BroadcastCollectionCoordinator implements PersistedCollectionCoordi
|
|
|
338
351
|
setAdapterForCollection(
|
|
339
352
|
collectionId: string,
|
|
340
353
|
adapter: CoordinatorAdapter,
|
|
354
|
+
cacheGenerationClaimId?: string,
|
|
341
355
|
): void {
|
|
342
356
|
requireDurableElection(adapter)
|
|
343
357
|
this.collectionAdapters.set(collectionId, adapter)
|
|
358
|
+
if (cacheGenerationClaimId) {
|
|
359
|
+
this.collectionClaimIds.set(collectionId, cacheGenerationClaimId)
|
|
360
|
+
} else {
|
|
361
|
+
this.collectionClaimIds.delete(collectionId)
|
|
362
|
+
}
|
|
344
363
|
}
|
|
345
364
|
|
|
346
365
|
registerRemoteSubsetOwner(
|
|
@@ -523,11 +542,38 @@ export class BroadcastCollectionCoordinator implements PersistedCollectionCoordi
|
|
|
523
542
|
}
|
|
524
543
|
try {
|
|
525
544
|
await acquisition.release
|
|
545
|
+
} catch (error) {
|
|
546
|
+
this.scheduleRemoteSubsetReleaseRetry(acquisition, options)
|
|
547
|
+
throw error
|
|
526
548
|
} finally {
|
|
527
549
|
acquisition.release = null
|
|
528
550
|
}
|
|
529
551
|
}
|
|
530
552
|
|
|
553
|
+
private scheduleRemoteSubsetReleaseRetry(
|
|
554
|
+
acquisition: OutboundRemoteSubsetAcquisition,
|
|
555
|
+
options: LoadSubsetOptions,
|
|
556
|
+
): void {
|
|
557
|
+
const key = remoteSubsetAcquisitionKey(
|
|
558
|
+
acquisition.collectionId,
|
|
559
|
+
acquisition.acquisitionId,
|
|
560
|
+
)
|
|
561
|
+
if (
|
|
562
|
+
this.isDisposed() ||
|
|
563
|
+
this.outboundRemoteSubsetAcquisitions.get(key) !== acquisition ||
|
|
564
|
+
acquisition.retryTimer !== null
|
|
565
|
+
) {
|
|
566
|
+
return
|
|
567
|
+
}
|
|
568
|
+
acquisition.retryTimer = setTimeout(() => {
|
|
569
|
+
acquisition.retryTimer = null
|
|
570
|
+
void this.requestReleaseRemoteSubset(
|
|
571
|
+
acquisition.collectionId,
|
|
572
|
+
options,
|
|
573
|
+
).catch(() => undefined)
|
|
574
|
+
}, RPC_RETRY_DELAY_MS)
|
|
575
|
+
}
|
|
576
|
+
|
|
531
577
|
private async acquireRemoteSubset(
|
|
532
578
|
acquisition: OutboundRemoteSubsetAcquisition,
|
|
533
579
|
): Promise<void> {
|
|
@@ -603,7 +649,11 @@ export class BroadcastCollectionCoordinator implements PersistedCollectionCoordi
|
|
|
603
649
|
try {
|
|
604
650
|
await work
|
|
605
651
|
acquired = true
|
|
606
|
-
this.
|
|
652
|
+
// Release may have failed while this acquisition was in flight. In that
|
|
653
|
+
// case the shared timer carries release retry debt, not replay work.
|
|
654
|
+
if (!acquisition.releaseRequested) {
|
|
655
|
+
this.cancelRemoteSubsetReplayRetry(acquisition)
|
|
656
|
+
}
|
|
607
657
|
} finally {
|
|
608
658
|
if (acquisition.inFlight === work) acquisition.inFlight = null
|
|
609
659
|
const current = this.collections.get(acquisition.collectionId)
|
|
@@ -636,14 +686,27 @@ export class BroadcastCollectionCoordinator implements PersistedCollectionCoordi
|
|
|
636
686
|
spec: PersistedIndexSpec,
|
|
637
687
|
scopedAdapter?: HydrationPersistenceAdapter,
|
|
638
688
|
localEnsureCompleted = false,
|
|
689
|
+
cacheGenerationClaimId?: string,
|
|
639
690
|
): Promise<void> {
|
|
640
691
|
if (this.isLeader(collectionId)) {
|
|
641
692
|
if (localEnsureCompleted) return
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
693
|
+
const adapter = scopedAdapter ?? this.requireAdapter(collectionId)
|
|
694
|
+
if (cacheGenerationClaimId) {
|
|
695
|
+
await adapter.ensureIndex(
|
|
696
|
+
managedStorageTarget(collectionId, cacheGenerationClaimId),
|
|
697
|
+
signature,
|
|
698
|
+
spec,
|
|
699
|
+
{
|
|
700
|
+
cacheGenerationClaimId,
|
|
701
|
+
},
|
|
702
|
+
)
|
|
703
|
+
} else {
|
|
704
|
+
await adapter.ensureIndex(
|
|
705
|
+
eagerStorageTarget(collectionId),
|
|
706
|
+
signature,
|
|
707
|
+
spec,
|
|
708
|
+
)
|
|
709
|
+
}
|
|
647
710
|
return
|
|
648
711
|
}
|
|
649
712
|
|
|
@@ -657,6 +720,7 @@ export class BroadcastCollectionCoordinator implements PersistedCollectionCoordi
|
|
|
657
720
|
rpcId: safeRandomUUID(),
|
|
658
721
|
signature,
|
|
659
722
|
spec,
|
|
723
|
+
cacheGenerationClaimId,
|
|
660
724
|
})
|
|
661
725
|
|
|
662
726
|
if (!response.ok) {
|
|
@@ -669,6 +733,7 @@ export class BroadcastCollectionCoordinator implements PersistedCollectionCoordi
|
|
|
669
733
|
async requestApplyLocalMutations(
|
|
670
734
|
collectionId: string,
|
|
671
735
|
mutations: Array<PersistedMutationEnvelope>,
|
|
736
|
+
cacheGenerationClaimId?: string,
|
|
672
737
|
): Promise<ApplyLocalMutationsResponse> {
|
|
673
738
|
const initialRoute = this.waitForInitialRoute(collectionId)
|
|
674
739
|
if (initialRoute) await initialRoute
|
|
@@ -678,6 +743,7 @@ export class BroadcastCollectionCoordinator implements PersistedCollectionCoordi
|
|
|
678
743
|
rpcId: safeRandomUUID(),
|
|
679
744
|
envelopeId: safeRandomUUID(),
|
|
680
745
|
mutations,
|
|
746
|
+
cacheGenerationClaimId,
|
|
681
747
|
})
|
|
682
748
|
}
|
|
683
749
|
|
|
@@ -686,6 +752,7 @@ export class BroadcastCollectionCoordinator implements PersistedCollectionCoordi
|
|
|
686
752
|
rpcId: safeRandomUUID(),
|
|
687
753
|
envelopeId: safeRandomUUID(),
|
|
688
754
|
mutations,
|
|
755
|
+
cacheGenerationClaimId,
|
|
689
756
|
})
|
|
690
757
|
}
|
|
691
758
|
|
|
@@ -750,6 +817,7 @@ export class BroadcastCollectionCoordinator implements PersistedCollectionCoordi
|
|
|
750
817
|
collectionId: string,
|
|
751
818
|
fromRowVersion: number,
|
|
752
819
|
scopedAdapter?: HydrationPersistenceAdapter,
|
|
820
|
+
cacheGenerationClaimId?: string,
|
|
753
821
|
): Promise<PullSinceResponse> {
|
|
754
822
|
if (this.isLeader(collectionId)) {
|
|
755
823
|
return this.handlePullSince(
|
|
@@ -758,6 +826,7 @@ export class BroadcastCollectionCoordinator implements PersistedCollectionCoordi
|
|
|
758
826
|
type: `rpc:pullSince:req`,
|
|
759
827
|
rpcId: safeRandomUUID(),
|
|
760
828
|
fromRowVersion,
|
|
829
|
+
cacheGenerationClaimId,
|
|
761
830
|
},
|
|
762
831
|
scopedAdapter,
|
|
763
832
|
)
|
|
@@ -767,6 +836,7 @@ export class BroadcastCollectionCoordinator implements PersistedCollectionCoordi
|
|
|
767
836
|
type: `rpc:pullSince:req`,
|
|
768
837
|
rpcId: safeRandomUUID(),
|
|
769
838
|
fromRowVersion,
|
|
839
|
+
cacheGenerationClaimId,
|
|
770
840
|
})
|
|
771
841
|
}
|
|
772
842
|
|
|
@@ -805,6 +875,7 @@ export class BroadcastCollectionCoordinator implements PersistedCollectionCoordi
|
|
|
805
875
|
this.channel.close()
|
|
806
876
|
this.collections.clear()
|
|
807
877
|
this.collectionAdapters.clear()
|
|
878
|
+
this.collectionClaimIds.clear()
|
|
808
879
|
for (const collectionId of this.remoteSubsetOwners.keys()) {
|
|
809
880
|
this.releaseInboundRemoteSubsetAcquisitions(collectionId)
|
|
810
881
|
}
|
|
@@ -883,9 +954,17 @@ export class BroadcastCollectionCoordinator implements PersistedCollectionCoordi
|
|
|
883
954
|
const reserveTerm = adapter.reserveLeadershipTerm
|
|
884
955
|
if (!reserveTerm)
|
|
885
956
|
throw new InvalidPersistenceAdapterError(`reserveLeadershipTerm`)
|
|
886
|
-
const pos = await this.withWriterLock(() =>
|
|
887
|
-
|
|
888
|
-
|
|
957
|
+
const pos = await this.withWriterLock(() => {
|
|
958
|
+
const claimId = this.collectionClaimIds.get(collectionId)
|
|
959
|
+
return reserveTerm.call(
|
|
960
|
+
adapter,
|
|
961
|
+
claimId
|
|
962
|
+
? managedStorageTarget(collectionId, claimId)
|
|
963
|
+
: eagerStorageTarget(collectionId),
|
|
964
|
+
state.latestTerm,
|
|
965
|
+
{ cacheGenerationClaimId: claimId },
|
|
966
|
+
)
|
|
967
|
+
})
|
|
889
968
|
state.latestTerm = pos.latestTerm
|
|
890
969
|
state.latestSeq = pos.latestSeq
|
|
891
970
|
state.latestRowVersion = pos.latestRowVersion
|
|
@@ -1079,6 +1158,7 @@ export class BroadcastCollectionCoordinator implements PersistedCollectionCoordi
|
|
|
1079
1158
|
this.releaseLeadership(collectionId, state)
|
|
1080
1159
|
this.collections.delete(collectionId)
|
|
1081
1160
|
this.collectionAdapters.delete(collectionId)
|
|
1161
|
+
this.collectionClaimIds.delete(collectionId)
|
|
1082
1162
|
const prefix = `${JSON.stringify([collectionId]).slice(0, -1)},`
|
|
1083
1163
|
for (const key of this.appliedEnvelopes.keys()) {
|
|
1084
1164
|
if (key.startsWith(prefix)) this.appliedEnvelopes.delete(key)
|
|
@@ -1625,12 +1705,11 @@ export class BroadcastCollectionCoordinator implements PersistedCollectionCoordi
|
|
|
1625
1705
|
acquisition.released = true
|
|
1626
1706
|
acquisition.release = (async () => {
|
|
1627
1707
|
try {
|
|
1628
|
-
await acquisition.
|
|
1629
|
-
} catch {
|
|
1630
|
-
// A returned promise transfers the lease even when initial loading fails.
|
|
1631
|
-
}
|
|
1632
|
-
try {
|
|
1708
|
+
await acquisition.admitted
|
|
1633
1709
|
if (acquisition.transferred) {
|
|
1710
|
+
// The owner may require unload to settle its load. Invocation of the
|
|
1711
|
+
// owner already transferred this lease; source settlement is not a
|
|
1712
|
+
// prerequisite for giving it the cancellation opportunity.
|
|
1634
1713
|
await unloadRemoteSubsetOwner(acquisition.owner, acquisition.options)
|
|
1635
1714
|
}
|
|
1636
1715
|
} finally {
|
|
@@ -1790,6 +1869,7 @@ export class BroadcastCollectionCoordinator implements PersistedCollectionCoordi
|
|
|
1790
1869
|
owner: options.owner,
|
|
1791
1870
|
options: options.options,
|
|
1792
1871
|
load: Promise.resolve(),
|
|
1872
|
+
admitted: Promise.resolve(),
|
|
1793
1873
|
transferred: false,
|
|
1794
1874
|
released: false,
|
|
1795
1875
|
terminalRelease: false,
|
|
@@ -1797,6 +1877,10 @@ export class BroadcastCollectionCoordinator implements PersistedCollectionCoordi
|
|
|
1797
1877
|
}
|
|
1798
1878
|
this.releasedRemoteSubsetAcquisitionTimes.delete(key)
|
|
1799
1879
|
this.inboundRemoteSubsetAcquisitions.set(key, acquisition)
|
|
1880
|
+
let resolveAdmission!: () => void
|
|
1881
|
+
acquisition.admitted = new Promise<void>((resolve) => {
|
|
1882
|
+
resolveAdmission = resolve
|
|
1883
|
+
})
|
|
1800
1884
|
let resolveLoad!: () => void
|
|
1801
1885
|
let rejectLoad!: (error: unknown) => void
|
|
1802
1886
|
acquisition.load = new Promise<void>((resolve, reject) => {
|
|
@@ -1809,6 +1893,8 @@ export class BroadcastCollectionCoordinator implements PersistedCollectionCoordi
|
|
|
1809
1893
|
void Promise.resolve(load).then(resolveLoad, rejectLoad)
|
|
1810
1894
|
} catch (error) {
|
|
1811
1895
|
rejectLoad(error)
|
|
1896
|
+
} finally {
|
|
1897
|
+
resolveAdmission()
|
|
1812
1898
|
}
|
|
1813
1899
|
|
|
1814
1900
|
try {
|
|
@@ -1844,11 +1930,26 @@ export class BroadcastCollectionCoordinator implements PersistedCollectionCoordi
|
|
|
1844
1930
|
rpcId: string
|
|
1845
1931
|
signature: string
|
|
1846
1932
|
spec: PersistedIndexSpec
|
|
1933
|
+
cacheGenerationClaimId?: string
|
|
1847
1934
|
},
|
|
1848
1935
|
): Promise<RPCResponse> {
|
|
1849
|
-
await this.withScheduledWriterLock(collectionId, (adapter) =>
|
|
1850
|
-
|
|
1851
|
-
|
|
1936
|
+
await this.withScheduledWriterLock(collectionId, (adapter) => {
|
|
1937
|
+
if (request.cacheGenerationClaimId) {
|
|
1938
|
+
return adapter.ensureIndex(
|
|
1939
|
+
managedStorageTarget(collectionId, request.cacheGenerationClaimId),
|
|
1940
|
+
request.signature,
|
|
1941
|
+
request.spec,
|
|
1942
|
+
{
|
|
1943
|
+
cacheGenerationClaimId: request.cacheGenerationClaimId,
|
|
1944
|
+
},
|
|
1945
|
+
)
|
|
1946
|
+
}
|
|
1947
|
+
return adapter.ensureIndex(
|
|
1948
|
+
eagerStorageTarget(collectionId),
|
|
1949
|
+
request.signature,
|
|
1950
|
+
request.spec,
|
|
1951
|
+
)
|
|
1952
|
+
})
|
|
1852
1953
|
return {
|
|
1853
1954
|
type: `rpc:ensurePersistedIndex:res`,
|
|
1854
1955
|
rpcId: request.rpcId,
|
|
@@ -1863,6 +1964,7 @@ export class BroadcastCollectionCoordinator implements PersistedCollectionCoordi
|
|
|
1863
1964
|
rpcId: string
|
|
1864
1965
|
envelopeId: string
|
|
1865
1966
|
mutations: Array<PersistedMutationEnvelope>
|
|
1967
|
+
cacheGenerationClaimId?: string
|
|
1866
1968
|
},
|
|
1867
1969
|
): Promise<ApplyLocalMutationsResponse> {
|
|
1868
1970
|
const envelopeKey = appliedEnvelopeKey(collectionId, request.envelopeId)
|
|
@@ -1896,6 +1998,14 @@ export class BroadcastCollectionCoordinator implements PersistedCollectionCoordi
|
|
|
1896
1998
|
collectionId: string,
|
|
1897
1999
|
request: Extract<RPCRequest, { type: `rpc:applyLocalMutations:req` }>,
|
|
1898
2000
|
): Promise<ApplyLocalMutationsResponse> {
|
|
2001
|
+
if (
|
|
2002
|
+
this.collectionClaimIds.has(collectionId) &&
|
|
2003
|
+
!request.cacheGenerationClaimId
|
|
2004
|
+
) {
|
|
2005
|
+
throw new InvalidPersistedCollectionConfigError(
|
|
2006
|
+
`A managed local mutation requires a persisted cache claim`,
|
|
2007
|
+
)
|
|
2008
|
+
}
|
|
1899
2009
|
const state = this.collections.get(collectionId)
|
|
1900
2010
|
if (!state || !state.isLeader) {
|
|
1901
2011
|
return {
|
|
@@ -1925,6 +2035,7 @@ export class BroadcastCollectionCoordinator implements PersistedCollectionCoordi
|
|
|
1925
2035
|
}
|
|
1926
2036
|
const pendingTx = {
|
|
1927
2037
|
txId: safeRandomUUID(),
|
|
2038
|
+
cacheGenerationClaimId: request.cacheGenerationClaimId,
|
|
1928
2039
|
mutations: request.mutations.map((m) => ({
|
|
1929
2040
|
type: m.type,
|
|
1930
2041
|
key: m.key,
|
|
@@ -2230,7 +2341,12 @@ export class BroadcastCollectionCoordinator implements PersistedCollectionCoordi
|
|
|
2230
2341
|
if (!adapter.reconcileCommittedTx) return { kind: `unknown` as const }
|
|
2231
2342
|
try {
|
|
2232
2343
|
const reconciled = await adapter.reconcileCommittedTx(
|
|
2233
|
-
|
|
2344
|
+
candidate.cacheGenerationClaimId
|
|
2345
|
+
? managedStorageTarget(
|
|
2346
|
+
collectionId,
|
|
2347
|
+
candidate.cacheGenerationClaimId,
|
|
2348
|
+
)
|
|
2349
|
+
: eagerStorageTarget(collectionId),
|
|
2234
2350
|
candidate,
|
|
2235
2351
|
request.anchor,
|
|
2236
2352
|
)
|
|
@@ -2327,7 +2443,12 @@ export class BroadcastCollectionCoordinator implements PersistedCollectionCoordi
|
|
|
2327
2443
|
)
|
|
2328
2444
|
if (tx === null) return null
|
|
2329
2445
|
try {
|
|
2330
|
-
await adapter.applyCommittedTx(
|
|
2446
|
+
await adapter.applyCommittedTx(
|
|
2447
|
+
tx.cacheGenerationClaimId
|
|
2448
|
+
? managedStorageTarget(collectionId, tx.cacheGenerationClaimId)
|
|
2449
|
+
: eagerStorageTarget(collectionId),
|
|
2450
|
+
tx,
|
|
2451
|
+
)
|
|
2331
2452
|
} catch (error) {
|
|
2332
2453
|
throw toPersistedCollectionDurabilityError(collectionId, error)
|
|
2333
2454
|
}
|
|
@@ -2345,6 +2466,7 @@ export class BroadcastCollectionCoordinator implements PersistedCollectionCoordi
|
|
|
2345
2466
|
type: `rpc:pullSince:req`
|
|
2346
2467
|
rpcId: string
|
|
2347
2468
|
fromRowVersion: number
|
|
2469
|
+
cacheGenerationClaimId?: string
|
|
2348
2470
|
},
|
|
2349
2471
|
scopedAdapter?: HydrationPersistenceAdapter,
|
|
2350
2472
|
): Promise<PullSinceResponse> {
|
|
@@ -2363,7 +2485,15 @@ export class BroadcastCollectionCoordinator implements PersistedCollectionCoordi
|
|
|
2363
2485
|
}
|
|
2364
2486
|
}
|
|
2365
2487
|
|
|
2366
|
-
const result = await adapter.pullSince(
|
|
2488
|
+
const result = await adapter.pullSince(
|
|
2489
|
+
request.cacheGenerationClaimId
|
|
2490
|
+
? managedStorageTarget(collectionId, request.cacheGenerationClaimId)
|
|
2491
|
+
: eagerStorageTarget(collectionId),
|
|
2492
|
+
request.fromRowVersion,
|
|
2493
|
+
{
|
|
2494
|
+
cacheGenerationClaimId: request.cacheGenerationClaimId,
|
|
2495
|
+
},
|
|
2496
|
+
)
|
|
2367
2497
|
|
|
2368
2498
|
if (result.requiresFullReload) {
|
|
2369
2499
|
return {
|