@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/dist/cjs/persisted.cjs
CHANGED
|
@@ -5,6 +5,58 @@ const errors = require("./errors.cjs");
|
|
|
5
5
|
const sqliteValue = require("./sqlite-value.cjs");
|
|
6
6
|
const remoteSubsetWire = require("./remote-subset-wire.cjs");
|
|
7
7
|
const remoteSubsetOwner = require("./remote-subset-owner.cjs");
|
|
8
|
+
function eagerStorageTarget(collectionId) {
|
|
9
|
+
return { kind: `eager`, collectionId };
|
|
10
|
+
}
|
|
11
|
+
function managedStorageTarget(storageCollectionId, claimId) {
|
|
12
|
+
return { kind: `managed`, storageCollectionId, claimId };
|
|
13
|
+
}
|
|
14
|
+
function resolvePersistedStorageTarget(target, legacyClaimId) {
|
|
15
|
+
const input = target;
|
|
16
|
+
if (typeof input !== `object` || input === null) {
|
|
17
|
+
throw new errors.InvalidPersistedCollectionConfigError(
|
|
18
|
+
`SQLite persistence requires an explicit eager or managed storage target`
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
const fields = input;
|
|
22
|
+
if (fields.kind === `eager`) {
|
|
23
|
+
if (typeof fields.collectionId !== `string` || fields.collectionId.trim().length === 0) {
|
|
24
|
+
throw new errors.InvalidPersistedCollectionConfigError(
|
|
25
|
+
`An eager storage target requires a nonempty collectionId`
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
if (legacyClaimId !== void 0 || `claimId` in fields || `storageCollectionId` in fields) {
|
|
29
|
+
throw new errors.InvalidPersistedCollectionConfigError(
|
|
30
|
+
`An eager storage target cannot carry a persisted cache claim`
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
return { collectionId: fields.collectionId };
|
|
34
|
+
}
|
|
35
|
+
if (fields.kind !== `managed`) {
|
|
36
|
+
throw new errors.InvalidPersistedCollectionConfigError(
|
|
37
|
+
`Unknown SQLite persistence storage target kind`
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
if (typeof fields.storageCollectionId !== `string` || fields.storageCollectionId.trim().length === 0 || typeof fields.claimId !== `string` || fields.claimId.trim().length === 0) {
|
|
41
|
+
throw new errors.InvalidPersistedCollectionConfigError(
|
|
42
|
+
`A managed storage target requires a storageCollectionId and persisted cache claim`
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
if (`collectionId` in fields) {
|
|
46
|
+
throw new errors.InvalidPersistedCollectionConfigError(
|
|
47
|
+
`A managed storage target cannot carry an eager collectionId`
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
if (legacyClaimId !== void 0 && legacyClaimId !== fields.claimId) {
|
|
51
|
+
throw new errors.InvalidPersistedCollectionConfigError(
|
|
52
|
+
`The persisted cache claim does not match its storage target`
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
collectionId: fields.storageCollectionId,
|
|
57
|
+
claimId: fields.claimId
|
|
58
|
+
};
|
|
59
|
+
}
|
|
8
60
|
const SQLITE_DRIVER_SHARED_LOGICAL_SCHEDULING_KEY = /* @__PURE__ */ Symbol.for(
|
|
9
61
|
`tanstack-db.sqlite-driver-supports-shared-logical-scheduling`
|
|
10
62
|
);
|
|
@@ -46,6 +98,7 @@ class PersistedReadinessTracker {
|
|
|
46
98
|
}
|
|
47
99
|
}
|
|
48
100
|
const DEFAULT_NETWORK_TIMEOUT_MS = 3e3;
|
|
101
|
+
const CACHE_CLAIM_RENEW_WINDOW_MS = 6e4;
|
|
49
102
|
const MAX_TIMER_TIMEOUT_MS = 2147483647;
|
|
50
103
|
function getNetworkTimeoutMs(initialRender) {
|
|
51
104
|
if (initialRender === void 0) return void 0;
|
|
@@ -146,10 +199,6 @@ class SingleProcessCoordinator {
|
|
|
146
199
|
if (acquisitions.size === 0) {
|
|
147
200
|
this.remoteSubsetAcquisitions.delete(collectionId);
|
|
148
201
|
}
|
|
149
|
-
try {
|
|
150
|
-
await acquisition.load;
|
|
151
|
-
} catch {
|
|
152
|
-
}
|
|
153
202
|
await remoteSubsetOwner.unloadRemoteSubsetOwner(acquisition.owner, acquisition.options);
|
|
154
203
|
}
|
|
155
204
|
registerRemoteSubsetOwner(collectionId, owner) {
|
|
@@ -163,13 +212,9 @@ class SingleProcessCoordinator {
|
|
|
163
212
|
this.remoteSubsetAcquisitions.delete(collectionId);
|
|
164
213
|
this.remoteSubsetOwners.delete(collectionId);
|
|
165
214
|
for (const acquisition of acquisitions?.values() ?? []) {
|
|
166
|
-
void (
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
} catch {
|
|
170
|
-
}
|
|
171
|
-
await remoteSubsetOwner.unloadRemoteSubsetOwner(owner, acquisition.options);
|
|
172
|
-
})().catch(() => void 0);
|
|
215
|
+
void remoteSubsetOwner.unloadRemoteSubsetOwner(owner, acquisition.options).catch(
|
|
216
|
+
() => void 0
|
|
217
|
+
);
|
|
173
218
|
}
|
|
174
219
|
};
|
|
175
220
|
}
|
|
@@ -186,7 +231,10 @@ class SingleProcessCoordinator {
|
|
|
186
231
|
);
|
|
187
232
|
}
|
|
188
233
|
try {
|
|
189
|
-
await adapter.applyCommittedTx(
|
|
234
|
+
await adapter.applyCommittedTx(
|
|
235
|
+
tx.cacheGenerationClaimId ? managedStorageTarget(collectionId, tx.cacheGenerationClaimId) : eagerStorageTarget(collectionId),
|
|
236
|
+
tx
|
|
237
|
+
);
|
|
190
238
|
} catch (error) {
|
|
191
239
|
throw errors.toPersistedCollectionDurabilityError(collectionId, error);
|
|
192
240
|
}
|
|
@@ -319,6 +367,13 @@ class ApplyMutex {
|
|
|
319
367
|
return this.reserve().run(task);
|
|
320
368
|
}
|
|
321
369
|
}
|
|
370
|
+
function matchesAnySubset(value, evaluators) {
|
|
371
|
+
return evaluators.some(
|
|
372
|
+
(evaluator) => !evaluator || db.toBooleanPredicate(
|
|
373
|
+
evaluator(value)
|
|
374
|
+
)
|
|
375
|
+
);
|
|
376
|
+
}
|
|
322
377
|
function toStableSerializable(value) {
|
|
323
378
|
if (value == null) {
|
|
324
379
|
return value;
|
|
@@ -411,23 +466,28 @@ function toPersistedMutationEnvelope(mutation) {
|
|
|
411
466
|
};
|
|
412
467
|
}
|
|
413
468
|
class PersistedCollectionRuntime {
|
|
414
|
-
constructor(mode, collectionId, persistence, syncMode, dbName, persistedReadiness) {
|
|
469
|
+
constructor(mode, collectionId, persistence, syncMode, dbName, persistedReadiness, optionsClaim) {
|
|
415
470
|
this.mode = mode;
|
|
416
471
|
this.collectionId = collectionId;
|
|
417
472
|
this.persistence = persistence;
|
|
418
473
|
this.syncMode = syncMode;
|
|
419
474
|
this.dbName = dbName;
|
|
420
475
|
this.persistedReadiness = persistedReadiness;
|
|
476
|
+
this.optionsClaim = optionsClaim;
|
|
421
477
|
this.applyMutex = new ApplyMutex();
|
|
422
478
|
this.activeSubsets = /* @__PURE__ */ new Map();
|
|
423
479
|
this.hydrationSequence = 0;
|
|
424
480
|
this.resetSequence = 0;
|
|
425
481
|
this.pendingRemoteSubsetEnsures = /* @__PURE__ */ new Map();
|
|
482
|
+
this.scopedRefreshes = /* @__PURE__ */ new Map();
|
|
483
|
+
this.rotatedAcquisitions = /* @__PURE__ */ new Map();
|
|
484
|
+
this.rotatedEnsures = /* @__PURE__ */ new WeakMap();
|
|
426
485
|
this.queuedHydrationTransactions = [];
|
|
427
486
|
this.queuedTxCommitted = [];
|
|
428
487
|
this.requestIds = /* @__PURE__ */ new WeakMap();
|
|
429
488
|
this.hydratedDemands = /* @__PURE__ */ new Set();
|
|
430
489
|
this.collection = null;
|
|
490
|
+
this.boundCollection = null;
|
|
431
491
|
this.syncControls = {
|
|
432
492
|
begin: null,
|
|
433
493
|
write: null,
|
|
@@ -453,9 +513,16 @@ class PersistedCollectionRuntime {
|
|
|
453
513
|
this.indexAddedUnsubscribe = null;
|
|
454
514
|
this.indexRemovedUnsubscribe = null;
|
|
455
515
|
this.remoteEnsureRetryTimer = null;
|
|
516
|
+
this.cacheClaimRenewTimer = null;
|
|
517
|
+
this.cacheClaimRecovery = null;
|
|
456
518
|
this.nextRequestId = 0;
|
|
457
519
|
this.startupSettled = false;
|
|
458
520
|
this.sourceTruncateGeneration = 0;
|
|
521
|
+
this.scopedRecovery = false;
|
|
522
|
+
this.scopedRecoveryPending = false;
|
|
523
|
+
this.scopedRecoverySequence = 0;
|
|
524
|
+
this.recoverQueuedAfterLocalLoadFailure = false;
|
|
525
|
+
this.startupCacheGenerationRotated = false;
|
|
459
526
|
this.latestTerm = 0;
|
|
460
527
|
this.latestSeq = 0;
|
|
461
528
|
this.latestRowVersion = 0;
|
|
@@ -464,6 +531,167 @@ class PersistedCollectionRuntime {
|
|
|
464
531
|
this.localSeq = 0;
|
|
465
532
|
this.localRowVersion = 0;
|
|
466
533
|
}
|
|
534
|
+
get storageCollectionId() {
|
|
535
|
+
return this.cacheGenerationClaim?.storageCollectionId ?? this.collectionId;
|
|
536
|
+
}
|
|
537
|
+
storageTargetFor(storageCollectionId = this.storageCollectionId, claimId = this.cacheGenerationClaim?.claimId) {
|
|
538
|
+
if (this.mode === `sync-present` && this.syncMode === `on-demand` && this.persistence.adapter.claimCacheGeneration) {
|
|
539
|
+
if (!claimId) {
|
|
540
|
+
throw new errors.InvalidPersistedCollectionConfigError(
|
|
541
|
+
`A managed on-demand cache operation requires a persisted cache claim`
|
|
542
|
+
);
|
|
543
|
+
}
|
|
544
|
+
return { kind: `managed`, storageCollectionId, claimId };
|
|
545
|
+
}
|
|
546
|
+
return { kind: `eager`, collectionId: storageCollectionId };
|
|
547
|
+
}
|
|
548
|
+
cacheGenerationNow() {
|
|
549
|
+
return this.persistence.adapter.getCacheGenerationNow?.() ?? Date.now();
|
|
550
|
+
}
|
|
551
|
+
hasExpiredCacheClaim() {
|
|
552
|
+
const expiresAtMs = this.cacheGenerationClaim?.expiresAtMs;
|
|
553
|
+
return expiresAtMs !== void 0 && this.cacheGenerationNow() >= expiresAtMs;
|
|
554
|
+
}
|
|
555
|
+
async claimCacheGeneration(lifecycleGeneration) {
|
|
556
|
+
if (this.mode !== `sync-present` || this.syncMode !== `on-demand`) return;
|
|
557
|
+
const adapter = this.persistence.adapter;
|
|
558
|
+
const capabilities = [
|
|
559
|
+
adapter.claimCacheGeneration,
|
|
560
|
+
adapter.rotateCacheGeneration,
|
|
561
|
+
adapter.renewCacheGenerationClaim,
|
|
562
|
+
adapter.releaseCacheGenerationClaim
|
|
563
|
+
];
|
|
564
|
+
if (capabilities.some(Boolean) && !capabilities.every(Boolean)) {
|
|
565
|
+
throw new errors.InvalidPersistedCollectionConfigError(
|
|
566
|
+
`An on-demand managed cache generation adapter must implement claimCacheGeneration, rotateCacheGeneration, renewCacheGenerationClaim, and releaseCacheGenerationClaim together`
|
|
567
|
+
);
|
|
568
|
+
}
|
|
569
|
+
if (!adapter.claimCacheGeneration) return;
|
|
570
|
+
const claim = await adapter.claimCacheGeneration(this.collectionId);
|
|
571
|
+
if (lifecycleGeneration !== this.lifecycleGeneration) {
|
|
572
|
+
await adapter.releaseCacheGenerationClaim?.(claim.claimId);
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
575
|
+
this.cacheGenerationClaim = claim;
|
|
576
|
+
this.scheduleCacheClaimRenewal(claim);
|
|
577
|
+
this.attachCoordinatorSubscription();
|
|
578
|
+
}
|
|
579
|
+
clearCacheClaimRenewal() {
|
|
580
|
+
if (this.cacheClaimRenewTimer !== null) {
|
|
581
|
+
clearTimeout(this.cacheClaimRenewTimer);
|
|
582
|
+
this.cacheClaimRenewTimer = null;
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
scheduleCacheClaimRenewal(claim) {
|
|
586
|
+
this.clearCacheClaimRenewal();
|
|
587
|
+
if (claim.expiresAtMs === void 0 || !this.persistence.adapter.renewCacheGenerationClaim) {
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
590
|
+
const remaining = claim.expiresAtMs - this.cacheGenerationNow();
|
|
591
|
+
const delay = remaining <= 0 ? 1e3 : Math.max(
|
|
592
|
+
1,
|
|
593
|
+
Math.min(CACHE_CLAIM_RENEW_WINDOW_MS, Math.floor(remaining / 2))
|
|
594
|
+
);
|
|
595
|
+
const lifecycleGeneration = this.lifecycleGeneration;
|
|
596
|
+
this.cacheClaimRenewTimer = setTimeout(() => {
|
|
597
|
+
this.cacheClaimRenewTimer = null;
|
|
598
|
+
void this.ensureCacheClaimActive().catch(() => {
|
|
599
|
+
if (lifecycleGeneration === this.lifecycleGeneration && this.cacheGenerationClaim === claim) {
|
|
600
|
+
this.scheduleCacheClaimRenewal(claim);
|
|
601
|
+
}
|
|
602
|
+
});
|
|
603
|
+
}, delay);
|
|
604
|
+
}
|
|
605
|
+
async ensureCacheClaimActive() {
|
|
606
|
+
if (this.cacheClaimRecovery) {
|
|
607
|
+
await this.cacheClaimRecovery;
|
|
608
|
+
return;
|
|
609
|
+
}
|
|
610
|
+
if (await this.renewCacheClaim(this.persistence.adapter)) return;
|
|
611
|
+
if (!this.cacheClaimRecovery) {
|
|
612
|
+
const recovery = this.restartSourceAfterScopedRecovery === void 0 ? this.reclaimStartupCacheGeneration() : this.startScopedRecovery();
|
|
613
|
+
this.cacheClaimRecovery = recovery;
|
|
614
|
+
void recovery.finally(() => {
|
|
615
|
+
if (this.cacheClaimRecovery === recovery) {
|
|
616
|
+
this.cacheClaimRecovery = null;
|
|
617
|
+
}
|
|
618
|
+
}).catch(() => void 0);
|
|
619
|
+
}
|
|
620
|
+
await this.cacheClaimRecovery;
|
|
621
|
+
}
|
|
622
|
+
scheduleCacheClaimRecovery(lifecycleGeneration) {
|
|
623
|
+
queueMicrotask(() => {
|
|
624
|
+
if (lifecycleGeneration !== this.lifecycleGeneration) return;
|
|
625
|
+
void this.ensureCacheClaimActive().then(
|
|
626
|
+
() => this.applyMutex.run(() => this.flushQueuedTxCommittedUnsafe())
|
|
627
|
+
).catch((error) => {
|
|
628
|
+
this.markTerminalFailure(error, lifecycleGeneration);
|
|
629
|
+
});
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
async reclaimStartupCacheGeneration() {
|
|
633
|
+
const lifecycleGeneration = this.lifecycleGeneration;
|
|
634
|
+
const previous = this.cacheGenerationClaim;
|
|
635
|
+
if (!previous) return;
|
|
636
|
+
if (!this.persistence.adapter.rotateCacheGeneration) {
|
|
637
|
+
throw new errors.InvalidPersistedCollectionConfigError(
|
|
638
|
+
`A managed on-demand cache adapter must rotate expired startup claims`
|
|
639
|
+
);
|
|
640
|
+
}
|
|
641
|
+
this.clearCacheClaimRenewal();
|
|
642
|
+
this.coordinatorUnsubscribe?.();
|
|
643
|
+
this.coordinatorUnsubscribe = null;
|
|
644
|
+
const rotated = await this.persistence.adapter.rotateCacheGeneration(
|
|
645
|
+
this.collectionId,
|
|
646
|
+
previous.claimId,
|
|
647
|
+
void 0,
|
|
648
|
+
previous.storageCollectionId
|
|
649
|
+
);
|
|
650
|
+
if (lifecycleGeneration !== this.lifecycleGeneration) {
|
|
651
|
+
await this.persistence.adapter.releaseCacheGenerationClaim?.(
|
|
652
|
+
rotated.claimId
|
|
653
|
+
);
|
|
654
|
+
return;
|
|
655
|
+
}
|
|
656
|
+
this.cacheGenerationClaim = rotated;
|
|
657
|
+
this.startupCacheGenerationRotated = true;
|
|
658
|
+
this.resetSequence++;
|
|
659
|
+
this.latestTerm = 0;
|
|
660
|
+
this.latestSeq = 0;
|
|
661
|
+
this.latestRowVersion = 0;
|
|
662
|
+
this.localTerm = 1;
|
|
663
|
+
this.localSeq = 0;
|
|
664
|
+
this.localRowVersion = 0;
|
|
665
|
+
this.persistedResumeGeneration = void 0;
|
|
666
|
+
this.persistedKeySetEvidence = { status: `incompatible` };
|
|
667
|
+
this.scheduleCacheClaimRenewal(rotated);
|
|
668
|
+
this.attachCoordinatorSubscription();
|
|
669
|
+
}
|
|
670
|
+
async renewCacheClaim(adapter) {
|
|
671
|
+
const claim = this.cacheGenerationClaim;
|
|
672
|
+
if (!claim || !adapter.renewCacheGenerationClaim || this.scopedRecoveryPending)
|
|
673
|
+
return true;
|
|
674
|
+
if (claim.expiresAtMs !== void 0 && claim.expiresAtMs - this.cacheGenerationNow() > CACHE_CLAIM_RENEW_WINDOW_MS) {
|
|
675
|
+
if (this.cacheClaimRenewTimer === null) {
|
|
676
|
+
this.scheduleCacheClaimRenewal(claim);
|
|
677
|
+
}
|
|
678
|
+
return true;
|
|
679
|
+
}
|
|
680
|
+
const lifecycleGeneration = this.lifecycleGeneration;
|
|
681
|
+
const expiresAtMs = await adapter.renewCacheGenerationClaim(
|
|
682
|
+
claim.storageCollectionId,
|
|
683
|
+
claim.claimId
|
|
684
|
+
);
|
|
685
|
+
if (lifecycleGeneration !== this.lifecycleGeneration || this.cacheGenerationClaim?.claimId !== claim.claimId || this.cacheGenerationClaim.storageCollectionId !== claim.storageCollectionId) {
|
|
686
|
+
return true;
|
|
687
|
+
}
|
|
688
|
+
if (expiresAtMs === void 0) {
|
|
689
|
+
return false;
|
|
690
|
+
}
|
|
691
|
+
this.cacheGenerationClaim = { ...claim, expiresAtMs };
|
|
692
|
+
this.scheduleCacheClaimRenewal(this.cacheGenerationClaim);
|
|
693
|
+
return true;
|
|
694
|
+
}
|
|
467
695
|
setSyncControls(syncControls) {
|
|
468
696
|
this.advanceLifecycle();
|
|
469
697
|
this.syncErrorReported = false;
|
|
@@ -474,6 +702,12 @@ class PersistedCollectionRuntime {
|
|
|
474
702
|
commit: commit ? (signal) => this.trackAppliedReceipt(commit(signal)) : null
|
|
475
703
|
};
|
|
476
704
|
}
|
|
705
|
+
setSourceRestartAfterScopedRecovery(restart) {
|
|
706
|
+
this.restartSourceAfterScopedRecovery = restart ?? null;
|
|
707
|
+
}
|
|
708
|
+
clearSourceRestartAfterScopedRecovery() {
|
|
709
|
+
this.restartSourceAfterScopedRecovery = void 0;
|
|
710
|
+
}
|
|
477
711
|
reportSyncError(error) {
|
|
478
712
|
const markError = this.syncControls.markError;
|
|
479
713
|
if (this.syncErrorReported) return this.reportedSyncError;
|
|
@@ -488,8 +722,9 @@ class PersistedCollectionRuntime {
|
|
|
488
722
|
}
|
|
489
723
|
registerRemoteSubsetOwner(owner) {
|
|
490
724
|
this.remoteSubsetOwnerUnsubscribe?.();
|
|
725
|
+
this.remoteSubsetOwner = owner;
|
|
491
726
|
this.remoteSubsetOwnerUnsubscribe = this.persistence.coordinator.registerRemoteSubsetOwner(
|
|
492
|
-
this.
|
|
727
|
+
this.storageCollectionId,
|
|
493
728
|
owner
|
|
494
729
|
);
|
|
495
730
|
}
|
|
@@ -514,6 +749,23 @@ class PersistedCollectionRuntime {
|
|
|
514
749
|
)
|
|
515
750
|
);
|
|
516
751
|
}
|
|
752
|
+
// An ensure reply settles a follower's load (law 7). A source's loadSubset
|
|
753
|
+
// must await every commit receipt that establishes its result, so the
|
|
754
|
+
// leader broadcasts those commits before it replies on the same channel.
|
|
755
|
+
// They are already queued on the apply mutex; settle after they show here.
|
|
756
|
+
async applyLeaderCommitsBeforeReply(options) {
|
|
757
|
+
try {
|
|
758
|
+
if (this.hasExpiredCacheClaim() || this.cacheClaimRecovery) {
|
|
759
|
+
await this.ensureCacheClaimActive();
|
|
760
|
+
}
|
|
761
|
+
await this.applyMutex.run(() => this.flushQueuedTxCommittedUnsafe());
|
|
762
|
+
this.throwIfTerminal();
|
|
763
|
+
} catch (error) {
|
|
764
|
+
if (this.getCurrentTerminalFailure()) this.unloadSubset(options);
|
|
765
|
+
else this.queueRemoteSubsetEnsure(options);
|
|
766
|
+
throw error;
|
|
767
|
+
}
|
|
768
|
+
}
|
|
517
769
|
clearSyncControls() {
|
|
518
770
|
this.syncControls = {
|
|
519
771
|
begin: null,
|
|
@@ -572,17 +824,46 @@ class PersistedCollectionRuntime {
|
|
|
572
824
|
isApplyingInternally() {
|
|
573
825
|
return this.internalApplyDepth > 0;
|
|
574
826
|
}
|
|
575
|
-
|
|
827
|
+
isScopedRecoveryPending() {
|
|
828
|
+
return this.scopedRecoveryPending;
|
|
829
|
+
}
|
|
830
|
+
hasBoundCollection() {
|
|
831
|
+
return this.boundCollection !== null;
|
|
832
|
+
}
|
|
833
|
+
setCollection(collection, syncControls) {
|
|
834
|
+
if (collection.config[db.collectionOptionsClaim] !== this.optionsClaim) {
|
|
835
|
+
throw new errors.InvalidPersistedCollectionConfigError(
|
|
836
|
+
`Persisted sync belongs to its options Collection`
|
|
837
|
+
);
|
|
838
|
+
}
|
|
839
|
+
if (this.boundCollection && this.boundCollection !== collection) {
|
|
840
|
+
throw new errors.InvalidPersistedCollectionConfigError(
|
|
841
|
+
`Persisted options cannot own more than one Collection`
|
|
842
|
+
);
|
|
843
|
+
}
|
|
844
|
+
const previousCollection = this.collection;
|
|
845
|
+
const previousControls = this.syncControls;
|
|
846
|
+
this.setSyncControls(syncControls);
|
|
576
847
|
if (this.collection === collection) {
|
|
848
|
+
this.boundCollection ??= collection;
|
|
577
849
|
return;
|
|
578
850
|
}
|
|
579
851
|
this.collection = collection;
|
|
580
|
-
|
|
852
|
+
try {
|
|
853
|
+
if (!this.persistence.adapter.claimCacheGeneration || this.mode !== `sync-present` || this.syncMode !== `on-demand`) {
|
|
854
|
+
this.attachCoordinatorSubscription();
|
|
855
|
+
}
|
|
856
|
+
} catch (error) {
|
|
857
|
+
this.collection = previousCollection;
|
|
858
|
+
this.syncControls = previousControls;
|
|
859
|
+
throw error;
|
|
860
|
+
}
|
|
861
|
+
this.boundCollection ??= collection;
|
|
581
862
|
}
|
|
582
863
|
getLeadershipState() {
|
|
583
864
|
return {
|
|
584
865
|
nodeId: this.persistence.coordinator.getNodeId(),
|
|
585
|
-
isLeader: this.persistence.coordinator.isLeader(this.
|
|
866
|
+
isLeader: this.persistence.coordinator.isLeader(this.storageCollectionId)
|
|
586
867
|
};
|
|
587
868
|
}
|
|
588
869
|
runInHydrationScope(task, adapter = this.persistence.adapter) {
|
|
@@ -597,6 +878,7 @@ class PersistedCollectionRuntime {
|
|
|
597
878
|
return this.startPromise;
|
|
598
879
|
}
|
|
599
880
|
const lifecycleGeneration = this.lifecycleGeneration;
|
|
881
|
+
this.startupCacheGenerationRotated = false;
|
|
600
882
|
const isRestart = this.hasAttemptedStartup;
|
|
601
883
|
this.hasAttemptedStartup = true;
|
|
602
884
|
let resolveStartupMetadata;
|
|
@@ -607,63 +889,98 @@ class PersistedCollectionRuntime {
|
|
|
607
889
|
});
|
|
608
890
|
void this.startupMetadataPromise.catch(() => void 0);
|
|
609
891
|
this.startPromise = (async () => {
|
|
892
|
+
await this.claimCacheGeneration(lifecycleGeneration);
|
|
893
|
+
if (lifecycleGeneration !== this.lifecycleGeneration) {
|
|
894
|
+
resolveStartupMetadata();
|
|
895
|
+
return;
|
|
896
|
+
}
|
|
897
|
+
let metadataExpired = false;
|
|
610
898
|
const loadStartupMetadata = async (adapter) => {
|
|
611
899
|
if (lifecycleGeneration !== this.lifecycleGeneration) {
|
|
612
900
|
resolveStartupMetadata();
|
|
613
901
|
return false;
|
|
614
902
|
}
|
|
615
903
|
try {
|
|
616
|
-
await this.loadStartupMetadataInternal(
|
|
904
|
+
if (!await this.loadStartupMetadataInternal(
|
|
905
|
+
lifecycleGeneration,
|
|
906
|
+
adapter
|
|
907
|
+
)) {
|
|
908
|
+
metadataExpired = true;
|
|
909
|
+
return false;
|
|
910
|
+
}
|
|
617
911
|
return lifecycleGeneration === this.lifecycleGeneration;
|
|
618
912
|
} catch (error) {
|
|
913
|
+
if (this.cacheGenerationClaim && this.hasExpiredCacheClaim()) {
|
|
914
|
+
metadataExpired = true;
|
|
915
|
+
return false;
|
|
916
|
+
}
|
|
619
917
|
rejectStartupMetadata(error);
|
|
620
918
|
throw error;
|
|
621
919
|
}
|
|
622
920
|
};
|
|
623
921
|
let startup;
|
|
624
922
|
const scheduleStartupAsOneHydrate = this.persistence.adapter.runInHydrationScope !== void 0 && (!isRestart || (this.persistence.adapter.isHydrationScopeScheduled?.() ?? true));
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
adapter
|
|
632
|
-
|
|
633
|
-
|
|
923
|
+
for (; ; ) {
|
|
924
|
+
metadataExpired = false;
|
|
925
|
+
const storageAtAttempt = this.storageCollectionId;
|
|
926
|
+
if (scheduleStartupAsOneHydrate) {
|
|
927
|
+
startup = await this.applyMutex.run(async () => {
|
|
928
|
+
const result = await this.runInHydrationScope(async (adapter) => {
|
|
929
|
+
if (!await loadStartupMetadata(adapter)) return void 0;
|
|
930
|
+
return this.startInternal(
|
|
931
|
+
lifecycleGeneration,
|
|
932
|
+
adapter,
|
|
933
|
+
resolveStartupMetadata
|
|
934
|
+
);
|
|
935
|
+
});
|
|
936
|
+
if (lifecycleGeneration === this.lifecycleGeneration) {
|
|
937
|
+
await this.flushQueuedTxCommittedUnsafe();
|
|
938
|
+
}
|
|
939
|
+
return result;
|
|
634
940
|
});
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
941
|
+
} else {
|
|
942
|
+
if (await loadStartupMetadata(this.persistence.adapter)) {
|
|
943
|
+
if (this.persistence.adapter.isHydrationScopeScheduled?.()) {
|
|
944
|
+
startup = await this.applyMutex.run(async () => {
|
|
945
|
+
const result = await this.runInHydrationScope(
|
|
946
|
+
(adapter) => this.startInternal(
|
|
947
|
+
lifecycleGeneration,
|
|
948
|
+
adapter,
|
|
949
|
+
resolveStartupMetadata
|
|
950
|
+
)
|
|
951
|
+
);
|
|
952
|
+
if (lifecycleGeneration === this.lifecycleGeneration) {
|
|
953
|
+
await this.flushQueuedTxCommittedUnsafe();
|
|
954
|
+
}
|
|
955
|
+
return result;
|
|
956
|
+
});
|
|
957
|
+
} else {
|
|
958
|
+
startup = await this.startInternal(
|
|
959
|
+
lifecycleGeneration,
|
|
960
|
+
this.persistence.adapter,
|
|
961
|
+
resolveStartupMetadata
|
|
650
962
|
);
|
|
651
963
|
if (lifecycleGeneration === this.lifecycleGeneration) {
|
|
652
964
|
await this.flushQueuedTxCommittedUnsafe();
|
|
653
965
|
}
|
|
654
|
-
return result;
|
|
655
|
-
});
|
|
656
|
-
} else {
|
|
657
|
-
startup = await this.startInternal(
|
|
658
|
-
lifecycleGeneration,
|
|
659
|
-
this.persistence.adapter,
|
|
660
|
-
resolveStartupMetadata
|
|
661
|
-
);
|
|
662
|
-
if (lifecycleGeneration === this.lifecycleGeneration) {
|
|
663
|
-
await this.flushQueuedTxCommittedUnsafe();
|
|
664
966
|
}
|
|
665
967
|
}
|
|
666
968
|
}
|
|
969
|
+
if (this.cacheGenerationClaim && this.syncMode === `on-demand` && // Hydration may have changed this through loadStartupMetadata().
|
|
970
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
971
|
+
!metadataExpired) {
|
|
972
|
+
await this.ensureCacheClaimActive();
|
|
973
|
+
metadataExpired = storageAtAttempt !== this.storageCollectionId;
|
|
974
|
+
}
|
|
975
|
+
if (!metadataExpired) break;
|
|
976
|
+
await this.ensureCacheClaimActive();
|
|
977
|
+
if (lifecycleGeneration !== this.lifecycleGeneration) {
|
|
978
|
+
resolveStartupMetadata();
|
|
979
|
+
return;
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
if (this.cacheGenerationClaim && this.syncMode === `on-demand` && lifecycleGeneration === this.lifecycleGeneration) {
|
|
983
|
+
resolveStartupMetadata();
|
|
667
984
|
}
|
|
668
985
|
if (startup !== void 0 && lifecycleGeneration === this.lifecycleGeneration) {
|
|
669
986
|
await this.requestCoordinatorPersistedIndexes(
|
|
@@ -699,6 +1016,7 @@ class PersistedCollectionRuntime {
|
|
|
699
1016
|
await this.ensureStarted();
|
|
700
1017
|
if (lifecycleGeneration !== this.lifecycleGeneration) return;
|
|
701
1018
|
if (this.syncMode !== `on-demand`) return;
|
|
1019
|
+
if (this.scopedRecovery) return;
|
|
702
1020
|
const appliedCursor = await this.applyMutex.run(async () => {
|
|
703
1021
|
const result = await this.runInHydrationScope(
|
|
704
1022
|
(adapter) => this.hydrateBaseline(lifecycleGeneration, adapter)
|
|
@@ -708,6 +1026,10 @@ class PersistedCollectionRuntime {
|
|
|
708
1026
|
}
|
|
709
1027
|
return result;
|
|
710
1028
|
});
|
|
1029
|
+
if (appliedCursor === false) {
|
|
1030
|
+
await this.ensureCacheClaimActive();
|
|
1031
|
+
return;
|
|
1032
|
+
}
|
|
711
1033
|
if (appliedCursor !== void 0 && lifecycleGeneration === this.lifecycleGeneration) {
|
|
712
1034
|
await this.waitForAppliedReceiptsAfter(appliedCursor, `accepted`);
|
|
713
1035
|
}
|
|
@@ -724,14 +1046,32 @@ class PersistedCollectionRuntime {
|
|
|
724
1046
|
this.resumeCertificationPromise = (async () => {
|
|
725
1047
|
await this.ensureStarted();
|
|
726
1048
|
if (lifecycleGeneration !== this.lifecycleGeneration) return;
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
1049
|
+
if (this.scopedRecovery) return;
|
|
1050
|
+
const storageCollectionId = this.storageCollectionId;
|
|
1051
|
+
const claimId = this.cacheGenerationClaim?.claimId;
|
|
1052
|
+
const certificationIsCurrent = () => lifecycleGeneration === this.lifecycleGeneration && !this.scopedRecovery && storageCollectionId === this.storageCollectionId && claimId === this.cacheGenerationClaim?.claimId;
|
|
1053
|
+
let snapshot;
|
|
1054
|
+
try {
|
|
1055
|
+
snapshot = await this.persistence.adapter.loadResumeSnapshot(
|
|
1056
|
+
this.storageTargetFor(storageCollectionId, claimId),
|
|
1057
|
+
{
|
|
1058
|
+
requiredIndexSignatures: this.getRequiredIndexSignatures(),
|
|
1059
|
+
includeRows: false,
|
|
1060
|
+
cacheGenerationClaimId: claimId
|
|
1061
|
+
}
|
|
1062
|
+
);
|
|
1063
|
+
} catch (error) {
|
|
1064
|
+
if (!certificationIsCurrent()) return;
|
|
1065
|
+
if (!claimId || !this.hasExpiredCacheClaim()) throw error;
|
|
1066
|
+
await this.ensureCacheClaimActive();
|
|
1067
|
+
return;
|
|
1068
|
+
}
|
|
1069
|
+
if (!certificationIsCurrent()) return;
|
|
1070
|
+
if (claimId && !await this.renewCacheClaim(this.persistence.adapter)) {
|
|
1071
|
+
await this.ensureCacheClaimActive();
|
|
1072
|
+
return;
|
|
1073
|
+
}
|
|
1074
|
+
if (!certificationIsCurrent()) return;
|
|
735
1075
|
this.bindResumeSnapshotEvidence(snapshot);
|
|
736
1076
|
})();
|
|
737
1077
|
return this.resumeCertificationPromise;
|
|
@@ -739,6 +1079,162 @@ class PersistedCollectionRuntime {
|
|
|
739
1079
|
getKeySetEvidence() {
|
|
740
1080
|
return this.persistedKeySetEvidence;
|
|
741
1081
|
}
|
|
1082
|
+
hasManagedCacheGeneration() {
|
|
1083
|
+
return this.cacheGenerationClaim !== void 0;
|
|
1084
|
+
}
|
|
1085
|
+
hasRotatedStartupCacheGeneration() {
|
|
1086
|
+
return this.startupCacheGenerationRotated;
|
|
1087
|
+
}
|
|
1088
|
+
async startScopedRecovery(resetMetadata) {
|
|
1089
|
+
this.throwIfTerminal();
|
|
1090
|
+
this.clearCacheClaimRenewal();
|
|
1091
|
+
const lifecycleGeneration = this.lifecycleGeneration;
|
|
1092
|
+
const restartSource = this.restartSourceAfterScopedRecovery;
|
|
1093
|
+
this.scopedRecovery = true;
|
|
1094
|
+
this.scopedRecoveryPending = true;
|
|
1095
|
+
const recoverySequence = ++this.scopedRecoverySequence;
|
|
1096
|
+
this.hydratedDemands.clear();
|
|
1097
|
+
this.resetSequence++;
|
|
1098
|
+
let resolveCacheRotated;
|
|
1099
|
+
let rejectCacheRotated;
|
|
1100
|
+
let sourceRestart;
|
|
1101
|
+
try {
|
|
1102
|
+
if (restartSource) {
|
|
1103
|
+
const cacheRotated = new Promise((resolve, reject) => {
|
|
1104
|
+
resolveCacheRotated = resolve;
|
|
1105
|
+
rejectCacheRotated = reject;
|
|
1106
|
+
});
|
|
1107
|
+
void cacheRotated.catch(() => void 0);
|
|
1108
|
+
sourceRestart = Promise.resolve(restartSource(cacheRotated));
|
|
1109
|
+
void sourceRestart.catch(() => void 0);
|
|
1110
|
+
}
|
|
1111
|
+
await this.applyMutex.run(async () => {
|
|
1112
|
+
this.throwIfLifecycleReplaced(lifecycleGeneration);
|
|
1113
|
+
const claim = this.cacheGenerationClaim;
|
|
1114
|
+
if (claim && this.persistence.adapter.rotateCacheGeneration) {
|
|
1115
|
+
const previousStorageId = claim.storageCollectionId;
|
|
1116
|
+
const rotated = await this.persistence.adapter.rotateCacheGeneration(
|
|
1117
|
+
this.collectionId,
|
|
1118
|
+
claim.claimId,
|
|
1119
|
+
resetMetadata,
|
|
1120
|
+
previousStorageId
|
|
1121
|
+
);
|
|
1122
|
+
if (lifecycleGeneration !== this.lifecycleGeneration) {
|
|
1123
|
+
await this.persistence.adapter.releaseCacheGenerationClaim?.(
|
|
1124
|
+
rotated.claimId
|
|
1125
|
+
);
|
|
1126
|
+
throw new db.SyncTransactionAbortedError();
|
|
1127
|
+
}
|
|
1128
|
+
for (const options of this.activeSubsets.values()) {
|
|
1129
|
+
void this.persistence.coordinator.requestReleaseRemoteSubset(
|
|
1130
|
+
previousStorageId,
|
|
1131
|
+
this.rotatedAcquisitions.get(options) ?? options
|
|
1132
|
+
).catch(() => void 0);
|
|
1133
|
+
if (options.signal?.aborted) {
|
|
1134
|
+
this.rotatedAcquisitions.delete(options);
|
|
1135
|
+
continue;
|
|
1136
|
+
}
|
|
1137
|
+
this.rotatedAcquisitions.set(options, {
|
|
1138
|
+
...options,
|
|
1139
|
+
refetch: true
|
|
1140
|
+
});
|
|
1141
|
+
}
|
|
1142
|
+
this.remoteSubsetOwnerUnsubscribe?.();
|
|
1143
|
+
this.remoteSubsetOwnerUnsubscribe = null;
|
|
1144
|
+
this.coordinatorUnsubscribe?.();
|
|
1145
|
+
this.coordinatorUnsubscribe = null;
|
|
1146
|
+
this.cacheGenerationClaim = rotated;
|
|
1147
|
+
this.latestTerm = 0;
|
|
1148
|
+
this.latestSeq = 0;
|
|
1149
|
+
this.latestRowVersion = 0;
|
|
1150
|
+
this.publicRowVersion = 0;
|
|
1151
|
+
this.resetTermFence = void 0;
|
|
1152
|
+
this.localTerm = 1;
|
|
1153
|
+
this.localSeq = 0;
|
|
1154
|
+
this.localRowVersion = 0;
|
|
1155
|
+
this.queuedTxCommitted.length = 0;
|
|
1156
|
+
this.persistedResumeGeneration = void 0;
|
|
1157
|
+
this.persistedKeySetEvidence = { status: `incompatible` };
|
|
1158
|
+
this.resumeCertificationPromise = null;
|
|
1159
|
+
this.resumeGenerationOwner = /* @__PURE__ */ Symbol(
|
|
1160
|
+
`persisted resume generation owner`
|
|
1161
|
+
);
|
|
1162
|
+
this.attachCoordinatorSubscription();
|
|
1163
|
+
if (this.remoteSubsetOwner) {
|
|
1164
|
+
this.registerRemoteSubsetOwner(this.remoteSubsetOwner);
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
if (this.syncControls.begin && this.syncControls.commit) {
|
|
1168
|
+
const applied = this.withInternalApply(() => {
|
|
1169
|
+
this.syncControls.begin?.();
|
|
1170
|
+
this.syncControls.truncate?.({ markReady: false });
|
|
1171
|
+
if (resetMetadata) {
|
|
1172
|
+
this.syncControls.metadata?.collection.set(
|
|
1173
|
+
resetMetadata.key,
|
|
1174
|
+
resetMetadata.value
|
|
1175
|
+
);
|
|
1176
|
+
}
|
|
1177
|
+
return this.syncControls.commit?.() ?? true;
|
|
1178
|
+
});
|
|
1179
|
+
await db.whenSyncAccepted(applied);
|
|
1180
|
+
if (!this.cacheGenerationClaim && recoverySequence === this.scopedRecoverySequence) {
|
|
1181
|
+
this.scopedRecoveryPending = false;
|
|
1182
|
+
}
|
|
1183
|
+
await this.runInHydrationScope(async (adapter) => {
|
|
1184
|
+
if (this.recoverQueuedAfterLocalLoadFailure) {
|
|
1185
|
+
this.recoverQueuedAfterLocalLoadFailure = false;
|
|
1186
|
+
await this.recoverBufferedTransactionsAfterLocalLoadFailureUnsafe(
|
|
1187
|
+
lifecycleGeneration,
|
|
1188
|
+
adapter
|
|
1189
|
+
);
|
|
1190
|
+
} else {
|
|
1191
|
+
await this.flushQueuedHydrationTransactionsUnsafe(adapter);
|
|
1192
|
+
}
|
|
1193
|
+
});
|
|
1194
|
+
await this.flushQueuedTxCommittedUnsafe();
|
|
1195
|
+
}
|
|
1196
|
+
if (recoverySequence === this.scopedRecoverySequence) {
|
|
1197
|
+
this.scopedRecoveryPending = false;
|
|
1198
|
+
if (this.cacheGenerationClaim) {
|
|
1199
|
+
const currentIndexes = this.collection?.getIndexMetadata() ?? [];
|
|
1200
|
+
const completedIndexes = await this.bootstrapPersistedIndexes(
|
|
1201
|
+
currentIndexes,
|
|
1202
|
+
this.persistence.adapter
|
|
1203
|
+
);
|
|
1204
|
+
await this.requestCoordinatorPersistedIndexes(
|
|
1205
|
+
currentIndexes,
|
|
1206
|
+
completedIndexes
|
|
1207
|
+
);
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
});
|
|
1211
|
+
this.throwIfLifecycleReplaced(lifecycleGeneration);
|
|
1212
|
+
resolveCacheRotated?.();
|
|
1213
|
+
await sourceRestart;
|
|
1214
|
+
if (recoverySequence === this.scopedRecoverySequence && this.cacheGenerationClaim) {
|
|
1215
|
+
this.scheduleCacheClaimRenewal(this.cacheGenerationClaim);
|
|
1216
|
+
}
|
|
1217
|
+
if (recoverySequence !== this.scopedRecoverySequence) return;
|
|
1218
|
+
for (const options of this.activeSubsets.values()) {
|
|
1219
|
+
if (options.signal?.aborted) continue;
|
|
1220
|
+
const rotatedOptions = this.rotatedAcquisitions.get(options);
|
|
1221
|
+
if (rotatedOptions) {
|
|
1222
|
+
const ensure = this.persistence.coordinator.requestEnsureRemoteSubset(
|
|
1223
|
+
this.storageCollectionId,
|
|
1224
|
+
rotatedOptions
|
|
1225
|
+
);
|
|
1226
|
+
this.rotatedEnsures.set(options, ensure);
|
|
1227
|
+
void ensure.catch((error) => this.reportSyncError(error));
|
|
1228
|
+
} else {
|
|
1229
|
+
this.refreshScopedSubsets();
|
|
1230
|
+
break;
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
} catch (error) {
|
|
1234
|
+
rejectCacheRotated?.(error);
|
|
1235
|
+
throw this.markTerminalFailure(error, lifecycleGeneration);
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
742
1238
|
getResumeGenerationOwner() {
|
|
743
1239
|
return this.resumeGenerationOwner;
|
|
744
1240
|
}
|
|
@@ -747,7 +1243,7 @@ class PersistedCollectionRuntime {
|
|
|
747
1243
|
const baseline = {};
|
|
748
1244
|
this.activeSubsets.set(this.getSubsetKey(baseline), baseline);
|
|
749
1245
|
const appliedCursor = this.appliedReceiptSequence;
|
|
750
|
-
await this.hydrateSubsetUnsafe(
|
|
1246
|
+
const hydrated = await this.hydrateSubsetUnsafe(
|
|
751
1247
|
baseline,
|
|
752
1248
|
{
|
|
753
1249
|
lifecycleGeneration,
|
|
@@ -755,6 +1251,7 @@ class PersistedCollectionRuntime {
|
|
|
755
1251
|
},
|
|
756
1252
|
adapter
|
|
757
1253
|
);
|
|
1254
|
+
if (!hydrated) return false;
|
|
758
1255
|
return lifecycleGeneration === this.lifecycleGeneration ? appliedCursor : void 0;
|
|
759
1256
|
}
|
|
760
1257
|
async ensureStartupMetadataLoaded() {
|
|
@@ -774,19 +1271,33 @@ class PersistedCollectionRuntime {
|
|
|
774
1271
|
adapter
|
|
775
1272
|
);
|
|
776
1273
|
if (lifecycleGeneration !== this.lifecycleGeneration) return void 0;
|
|
777
|
-
|
|
1274
|
+
if (!this.cacheGenerationClaim || this.syncMode !== `on-demand`) {
|
|
1275
|
+
onStartupMetadataLoaded();
|
|
1276
|
+
}
|
|
778
1277
|
const appliedCursor = this.syncMode !== `on-demand` ? await this.hydrateBaseline(lifecycleGeneration, adapter) : void 0;
|
|
779
1278
|
return lifecycleGeneration === this.lifecycleGeneration ? {
|
|
780
|
-
appliedCursor,
|
|
1279
|
+
appliedCursor: appliedCursor === false ? void 0 : appliedCursor,
|
|
781
1280
|
indexBootstrapSnapshot,
|
|
782
1281
|
completedLocalIndexSignatures
|
|
783
1282
|
} : void 0;
|
|
784
1283
|
}
|
|
785
1284
|
async loadStartupMetadataInternal(lifecycleGeneration, adapter) {
|
|
786
|
-
const
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
1285
|
+
const claimId = this.cacheGenerationClaim?.claimId;
|
|
1286
|
+
const storageCollectionId = this.storageCollectionId;
|
|
1287
|
+
const snapshot = await adapter.loadResumeSnapshot(
|
|
1288
|
+
this.storageTargetFor(storageCollectionId, claimId),
|
|
1289
|
+
{
|
|
1290
|
+
includeRows: false,
|
|
1291
|
+
cacheGenerationClaimId: claimId
|
|
1292
|
+
}
|
|
1293
|
+
);
|
|
1294
|
+
if (lifecycleGeneration !== this.lifecycleGeneration) return false;
|
|
1295
|
+
if (claimId !== this.cacheGenerationClaim?.claimId) return false;
|
|
1296
|
+
if (storageCollectionId !== this.storageCollectionId) return false;
|
|
1297
|
+
if (claimId && !await this.renewCacheClaim(adapter)) return false;
|
|
1298
|
+
if (lifecycleGeneration !== this.lifecycleGeneration) return false;
|
|
1299
|
+
if (claimId !== this.cacheGenerationClaim?.claimId) return false;
|
|
1300
|
+
if (storageCollectionId !== this.storageCollectionId) return false;
|
|
790
1301
|
this.persistedResumeGeneration = this.getResumeSnapshotGeneration(snapshot);
|
|
791
1302
|
this.persistedKeySetEvidence = snapshot.keySet;
|
|
792
1303
|
this.observeStreamPosition(
|
|
@@ -798,36 +1309,42 @@ class PersistedCollectionRuntime {
|
|
|
798
1309
|
snapshot.collectionMetadata
|
|
799
1310
|
);
|
|
800
1311
|
await db.whenSyncAccepted(applied);
|
|
1312
|
+
return true;
|
|
801
1313
|
}
|
|
802
1314
|
async loadCollectionMetadataSnapshot(adapter) {
|
|
803
1315
|
if (!adapter.loadCollectionMetadata) {
|
|
804
1316
|
return [];
|
|
805
1317
|
}
|
|
806
|
-
return adapter.loadCollectionMetadata(this.
|
|
1318
|
+
return adapter.loadCollectionMetadata(this.storageTargetFor(), {
|
|
1319
|
+
cacheGenerationClaimId: this.cacheGenerationClaim?.claimId
|
|
1320
|
+
});
|
|
807
1321
|
}
|
|
808
1322
|
replaceCollectionMetadataSnapshot(collectionMetadata) {
|
|
809
1323
|
if (!this.syncControls.begin || !this.syncControls.commit || !this.syncControls.metadata) {
|
|
810
1324
|
return true;
|
|
811
1325
|
}
|
|
812
|
-
const nextMetadata = new Map(
|
|
813
|
-
collectionMetadata.map(({ key, value }) => [key, value])
|
|
814
|
-
);
|
|
815
|
-
const currentKeys = this.syncControls.metadata.collection.list().map(({ key }) => key);
|
|
816
1326
|
return this.withInternalApply(() => {
|
|
817
1327
|
this.syncControls.begin?.();
|
|
818
|
-
|
|
819
|
-
if (!nextMetadata.has(key)) {
|
|
820
|
-
this.syncControls.metadata?.collection.delete(key);
|
|
821
|
-
}
|
|
822
|
-
});
|
|
823
|
-
nextMetadata.forEach((value, key) => {
|
|
824
|
-
this.syncControls.metadata?.collection.set(key, value);
|
|
825
|
-
});
|
|
1328
|
+
this.applyCollectionMetadataSnapshot(collectionMetadata);
|
|
826
1329
|
return this.syncControls.commit?.() ?? true;
|
|
827
1330
|
});
|
|
828
1331
|
}
|
|
829
|
-
|
|
1332
|
+
applyCollectionMetadataSnapshot(collectionMetadata) {
|
|
1333
|
+
const metadata = this.syncControls.metadata;
|
|
1334
|
+
if (!metadata) return;
|
|
1335
|
+
const next = new Map(
|
|
1336
|
+
collectionMetadata.map(({ key, value }) => [key, value])
|
|
1337
|
+
);
|
|
1338
|
+
for (const { key } of metadata.collection.list()) {
|
|
1339
|
+
if (!next.has(key)) metadata.collection.delete(key);
|
|
1340
|
+
}
|
|
1341
|
+
for (const [key, value] of next) metadata.collection.set(key, value);
|
|
1342
|
+
}
|
|
1343
|
+
async loadSubset(options, upstreamLoadSubset, onHydrationStarted, isDemandRetained) {
|
|
830
1344
|
this.throwIfTerminal();
|
|
1345
|
+
await this.ensureCacheClaimActive();
|
|
1346
|
+
if (options.signal?.aborted || isDemandRetained && !isDemandRetained())
|
|
1347
|
+
return;
|
|
831
1348
|
const lifecycleGeneration = this.lifecycleGeneration;
|
|
832
1349
|
const subsetKey = this.getSubsetKey(options);
|
|
833
1350
|
const truncateGeneration = this.sourceTruncateGeneration;
|
|
@@ -836,25 +1353,52 @@ class PersistedCollectionRuntime {
|
|
|
836
1353
|
this.activeSubsets.set(subsetKey, options);
|
|
837
1354
|
const appliedCursor = this.appliedReceiptSequence;
|
|
838
1355
|
try {
|
|
839
|
-
await this.applyMutex.run(
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
{
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
1356
|
+
const hydrated = await this.applyMutex.run(
|
|
1357
|
+
async () => {
|
|
1358
|
+
if (options.signal?.aborted) return `aborted`;
|
|
1359
|
+
try {
|
|
1360
|
+
return await this.runInHydrationScope(
|
|
1361
|
+
(adapter) => {
|
|
1362
|
+
if (options.signal?.aborted) {
|
|
1363
|
+
return Promise.resolve(`aborted`);
|
|
1364
|
+
}
|
|
1365
|
+
onHydrationStarted?.();
|
|
1366
|
+
return this.hydrateSubsetUnsafe(
|
|
1367
|
+
options,
|
|
1368
|
+
{
|
|
1369
|
+
lifecycleGeneration,
|
|
1370
|
+
requestLocalLoadFailure: true,
|
|
1371
|
+
rejectBufferedReplayFailure: true
|
|
1372
|
+
},
|
|
1373
|
+
adapter
|
|
1374
|
+
);
|
|
1375
|
+
}
|
|
1376
|
+
);
|
|
1377
|
+
} finally {
|
|
1378
|
+
if (lifecycleGeneration === this.lifecycleGeneration && !this.getCurrentTerminalFailure()) {
|
|
1379
|
+
await this.flushQueuedTxCommittedUnsafe();
|
|
1380
|
+
}
|
|
855
1381
|
}
|
|
856
1382
|
}
|
|
857
|
-
|
|
1383
|
+
);
|
|
1384
|
+
if (hydrated === `aborted`) {
|
|
1385
|
+
if (this.activeSubsets.get(subsetKey) === options) {
|
|
1386
|
+
this.activeSubsets.delete(subsetKey);
|
|
1387
|
+
}
|
|
1388
|
+
return;
|
|
1389
|
+
}
|
|
1390
|
+
if (!hydrated) {
|
|
1391
|
+
await this.ensureCacheClaimActive();
|
|
1392
|
+
const rotatedOptions = this.rotatedAcquisitions.get(options);
|
|
1393
|
+
if (rotatedOptions && this.activeSubsets.get(subsetKey) === options && !options.signal?.aborted) {
|
|
1394
|
+
await (this.rotatedEnsures.get(options) ?? this.persistence.coordinator.requestEnsureRemoteSubset(
|
|
1395
|
+
this.storageCollectionId,
|
|
1396
|
+
rotatedOptions
|
|
1397
|
+
));
|
|
1398
|
+
await this.applyLeaderCommitsBeforeReply(options);
|
|
1399
|
+
}
|
|
1400
|
+
return;
|
|
1401
|
+
}
|
|
858
1402
|
if (lifecycleGeneration !== this.lifecycleGeneration) return;
|
|
859
1403
|
await this.waitForAppliedReceiptsAfter(appliedCursor);
|
|
860
1404
|
if (!routeRemoteDemandDuringHydration && this.canRouteRemoteDemandThroughCoordinator()) {
|
|
@@ -867,13 +1411,16 @@ class PersistedCollectionRuntime {
|
|
|
867
1411
|
throw error;
|
|
868
1412
|
}
|
|
869
1413
|
if (options.signal?.aborted || this.activeSubsets.get(this.getSubsetKey(options)) !== options) {
|
|
1414
|
+
if (options.signal?.aborted && this.activeSubsets.get(subsetKey) === options) {
|
|
1415
|
+
this.activeSubsets.delete(subsetKey);
|
|
1416
|
+
}
|
|
870
1417
|
return;
|
|
871
1418
|
}
|
|
872
1419
|
if (this.canRouteRemoteDemandThroughCoordinator()) {
|
|
873
1420
|
try {
|
|
874
1421
|
await this.persistence.coordinator.requestEnsureRemoteSubset(
|
|
875
|
-
this.
|
|
876
|
-
options
|
|
1422
|
+
this.storageCollectionId,
|
|
1423
|
+
this.rotatedAcquisitions.get(options) ?? options
|
|
877
1424
|
);
|
|
878
1425
|
} catch (error) {
|
|
879
1426
|
if (options.signal?.aborted || typeof error === `object` && error !== null && `name` in error && error.name === `AbortError`) {
|
|
@@ -883,6 +1430,7 @@ class PersistedCollectionRuntime {
|
|
|
883
1430
|
this.queueRemoteSubsetEnsure(options);
|
|
884
1431
|
throw error;
|
|
885
1432
|
}
|
|
1433
|
+
await this.applyLeaderCommitsBeforeReply(options);
|
|
886
1434
|
return;
|
|
887
1435
|
}
|
|
888
1436
|
if (truncateGeneration === this.sourceTruncateGeneration && this.activeSubsets.get(subsetKey) === options) {
|
|
@@ -900,7 +1448,7 @@ class PersistedCollectionRuntime {
|
|
|
900
1448
|
loadHydratedSubset(options, upstreamLoadSubset) {
|
|
901
1449
|
const failure = this.getCurrentTerminalFailure();
|
|
902
1450
|
if (failure) return Promise.reject(failure.error);
|
|
903
|
-
if (!this.startupSettled || this.isHydratingNow() || this.canRouteRemoteDemandThroughCoordinator() || options.refetch === true || !this.hydratedDemands.has(db.getLoadSubsetDemandKey(options))) {
|
|
1451
|
+
if (this.hasExpiredCacheClaim() || !this.startupSettled || this.isHydratingNow() || this.canRouteRemoteDemandThroughCoordinator() || options.refetch === true || !this.hydratedDemands.has(db.getLoadSubsetDemandKey(options))) {
|
|
904
1452
|
return;
|
|
905
1453
|
}
|
|
906
1454
|
this.activeSubsets.set(this.getSubsetKey(options), options);
|
|
@@ -940,13 +1488,15 @@ class PersistedCollectionRuntime {
|
|
|
940
1488
|
const subsetKey = this.getSubsetKey(options);
|
|
941
1489
|
this.activeSubsets.delete(subsetKey);
|
|
942
1490
|
this.pendingRemoteSubsetEnsures.delete(subsetKey);
|
|
1491
|
+
const remoteOptions = this.rotatedAcquisitions.get(options) ?? options;
|
|
1492
|
+
this.rotatedAcquisitions.delete(options);
|
|
943
1493
|
const demandKey = db.getLoadSubsetDemandKey(options);
|
|
944
1494
|
const stillActive = Array.from(this.activeSubsets.values()).some(
|
|
945
1495
|
(active) => db.getLoadSubsetDemandKey(active) === demandKey
|
|
946
1496
|
);
|
|
947
1497
|
if (!stillActive) this.hydratedDemands.delete(demandKey);
|
|
948
1498
|
if (this.mode === `sync-present`) {
|
|
949
|
-
void this.persistence.coordinator.requestReleaseRemoteSubset(this.
|
|
1499
|
+
void this.persistence.coordinator.requestReleaseRemoteSubset(this.storageCollectionId, remoteOptions).catch((error) => {
|
|
950
1500
|
this.reportSyncError(error);
|
|
951
1501
|
});
|
|
952
1502
|
}
|
|
@@ -963,9 +1513,10 @@ class PersistedCollectionRuntime {
|
|
|
963
1513
|
}
|
|
964
1514
|
async forceReloadSubset(options) {
|
|
965
1515
|
this.throwIfTerminal();
|
|
1516
|
+
await this.ensureCacheClaimActive();
|
|
966
1517
|
const lifecycleGeneration = this.lifecycleGeneration;
|
|
967
|
-
await this.applyMutex.run(async () => {
|
|
968
|
-
await this.runInHydrationScope(
|
|
1518
|
+
const hydrated = await this.applyMutex.run(async () => {
|
|
1519
|
+
const result = await this.runInHydrationScope(
|
|
969
1520
|
(adapter) => this.hydrateSubsetUnsafe(
|
|
970
1521
|
options,
|
|
971
1522
|
{
|
|
@@ -978,7 +1529,9 @@ class PersistedCollectionRuntime {
|
|
|
978
1529
|
if (lifecycleGeneration === this.lifecycleGeneration) {
|
|
979
1530
|
await this.flushQueuedTxCommittedUnsafe();
|
|
980
1531
|
}
|
|
1532
|
+
return result;
|
|
981
1533
|
});
|
|
1534
|
+
if (!hydrated) await this.ensureCacheClaimActive();
|
|
982
1535
|
}
|
|
983
1536
|
queueHydrationBufferedTransaction(transaction) {
|
|
984
1537
|
const failure = this.getCurrentTerminalFailure();
|
|
@@ -986,11 +1539,13 @@ class PersistedCollectionRuntime {
|
|
|
986
1539
|
transaction.rejectApplied?.(failure.error);
|
|
987
1540
|
return;
|
|
988
1541
|
}
|
|
1542
|
+
transaction.admittedStorageCollectionId = this.storageCollectionId;
|
|
989
1543
|
this.queuedHydrationTransactions.push(transaction);
|
|
990
1544
|
}
|
|
991
1545
|
applyHydrationBufferedTransaction(transaction) {
|
|
992
1546
|
const failure = this.getCurrentTerminalFailure();
|
|
993
1547
|
if (failure) return Promise.reject(failure.error);
|
|
1548
|
+
transaction.admittedStorageCollectionId = this.storageCollectionId;
|
|
994
1549
|
return this.applyMutex.run(async () => {
|
|
995
1550
|
await this.applyBufferedSyncTransactionUnsafe(transaction);
|
|
996
1551
|
});
|
|
@@ -1095,7 +1650,10 @@ class PersistedCollectionRuntime {
|
|
|
1095
1650
|
for (const options of this.activeSubsets.values()) {
|
|
1096
1651
|
attempt(() => {
|
|
1097
1652
|
remoteReleases.push(
|
|
1098
|
-
this.persistence.coordinator.requestReleaseRemoteSubset(
|
|
1653
|
+
this.persistence.coordinator.requestReleaseRemoteSubset(
|
|
1654
|
+
this.storageCollectionId,
|
|
1655
|
+
this.rotatedAcquisitions.get(options) ?? options
|
|
1656
|
+
).catch((error) => {
|
|
1099
1657
|
this.reportSyncError(error);
|
|
1100
1658
|
})
|
|
1101
1659
|
);
|
|
@@ -1106,6 +1664,7 @@ class PersistedCollectionRuntime {
|
|
|
1106
1664
|
this.coordinatorUnsubscribe = null;
|
|
1107
1665
|
attempt(() => this.remoteSubsetOwnerUnsubscribe?.());
|
|
1108
1666
|
this.remoteSubsetOwnerUnsubscribe = null;
|
|
1667
|
+
this.remoteSubsetOwner = void 0;
|
|
1109
1668
|
attempt(() => this.indexAddedUnsubscribe?.());
|
|
1110
1669
|
this.indexAddedUnsubscribe = null;
|
|
1111
1670
|
attempt(() => this.indexRemovedUnsubscribe?.());
|
|
@@ -1115,6 +1674,16 @@ class PersistedCollectionRuntime {
|
|
|
1115
1674
|
this.remoteEnsureRetryTimer = null;
|
|
1116
1675
|
}
|
|
1117
1676
|
this.pendingRemoteSubsetEnsures.clear();
|
|
1677
|
+
this.rotatedAcquisitions.clear();
|
|
1678
|
+
for (const [options, storageId] of this.scopedRefreshes) {
|
|
1679
|
+
remoteReleases.push(
|
|
1680
|
+
this.persistence.coordinator.requestReleaseRemoteSubset(
|
|
1681
|
+
storageId,
|
|
1682
|
+
options
|
|
1683
|
+
)
|
|
1684
|
+
);
|
|
1685
|
+
}
|
|
1686
|
+
this.scopedRefreshes.clear();
|
|
1118
1687
|
this.activeSubsets.clear();
|
|
1119
1688
|
this.hydratedDemands.clear();
|
|
1120
1689
|
for (const transaction of this.queuedHydrationTransactions) {
|
|
@@ -1124,6 +1693,15 @@ class PersistedCollectionRuntime {
|
|
|
1124
1693
|
this.queuedTxCommitted.length = 0;
|
|
1125
1694
|
this.clearSyncControls();
|
|
1126
1695
|
this.collection = null;
|
|
1696
|
+
const claim = this.cacheGenerationClaim;
|
|
1697
|
+
this.cacheGenerationClaim = void 0;
|
|
1698
|
+
this.clearCacheClaimRenewal();
|
|
1699
|
+
this.cacheClaimRecovery = null;
|
|
1700
|
+
if (claim && this.persistence.adapter.releaseCacheGenerationClaim) {
|
|
1701
|
+
remoteReleases.push(
|
|
1702
|
+
this.persistence.adapter.releaseCacheGenerationClaim(claim.claimId)
|
|
1703
|
+
);
|
|
1704
|
+
}
|
|
1127
1705
|
return Promise.all(remoteReleases).then(() => {
|
|
1128
1706
|
if (failures.length === 1) throw failures[0];
|
|
1129
1707
|
if (failures.length > 1) {
|
|
@@ -1135,6 +1713,12 @@ class PersistedCollectionRuntime {
|
|
|
1135
1713
|
}
|
|
1136
1714
|
advanceLifecycle() {
|
|
1137
1715
|
this.lifecycleGeneration++;
|
|
1716
|
+
this.clearCacheClaimRenewal();
|
|
1717
|
+
this.cacheClaimRecovery = null;
|
|
1718
|
+
this.scopedRecovery = false;
|
|
1719
|
+
this.scopedRecoveryPending = false;
|
|
1720
|
+
this.scopedRecoverySequence++;
|
|
1721
|
+
this.recoverQueuedAfterLocalLoadFailure = false;
|
|
1138
1722
|
this.persistedReadiness?.set({ status: `loading` });
|
|
1139
1723
|
this.startupSettled = false;
|
|
1140
1724
|
this.publicRowVersion = 0;
|
|
@@ -1164,22 +1748,40 @@ class PersistedCollectionRuntime {
|
|
|
1164
1748
|
return this.collection.getIndexMetadata().map((metadata) => metadata.signature);
|
|
1165
1749
|
}
|
|
1166
1750
|
loadSubsetRowsUnsafe(options, adapter) {
|
|
1167
|
-
|
|
1168
|
-
|
|
1751
|
+
if (this.scopedRecovery) return Promise.resolve([]);
|
|
1752
|
+
return adapter.loadSubset(this.storageTargetFor(), options, {
|
|
1753
|
+
requiredIndexSignatures: this.getRequiredIndexSignatures(),
|
|
1754
|
+
cacheGenerationClaimId: this.cacheGenerationClaim?.claimId
|
|
1169
1755
|
});
|
|
1170
1756
|
}
|
|
1171
1757
|
async scanPersistedRowsUnsafe(options) {
|
|
1172
1758
|
if (!this.persistence.adapter.scanRows) {
|
|
1173
1759
|
return [];
|
|
1174
1760
|
}
|
|
1175
|
-
return this.persistence.adapter.scanRows(
|
|
1176
|
-
this.
|
|
1177
|
-
|
|
1178
|
-
);
|
|
1761
|
+
return this.persistence.adapter.scanRows(this.storageTargetFor(), options, {
|
|
1762
|
+
cacheGenerationClaimId: this.cacheGenerationClaim?.claimId
|
|
1763
|
+
});
|
|
1179
1764
|
}
|
|
1180
1765
|
async scanPersistedRows(options) {
|
|
1181
|
-
|
|
1182
|
-
|
|
1766
|
+
for (; ; ) {
|
|
1767
|
+
this.throwIfTerminal();
|
|
1768
|
+
await this.ensureCacheClaimActive();
|
|
1769
|
+
const claimId = this.cacheGenerationClaim?.claimId;
|
|
1770
|
+
const storageCollectionId = this.storageCollectionId;
|
|
1771
|
+
const rows = await this.applyMutex.run(
|
|
1772
|
+
() => this.scanPersistedRowsUnsafe(options)
|
|
1773
|
+
);
|
|
1774
|
+
if (this.scopedRecoveryPending) {
|
|
1775
|
+
throw new db.SyncTransactionAbortedError();
|
|
1776
|
+
}
|
|
1777
|
+
if (claimId !== this.cacheGenerationClaim?.claimId || storageCollectionId !== this.storageCollectionId) {
|
|
1778
|
+
continue;
|
|
1779
|
+
}
|
|
1780
|
+
if (claimId && !await this.renewCacheClaim(this.persistence.adapter)) {
|
|
1781
|
+
continue;
|
|
1782
|
+
}
|
|
1783
|
+
return rows;
|
|
1784
|
+
}
|
|
1183
1785
|
}
|
|
1184
1786
|
async hydrateSubsetUnsafe(options, config, adapter) {
|
|
1185
1787
|
let rowsLoaded = false;
|
|
@@ -1194,27 +1796,41 @@ class PersistedCollectionRuntime {
|
|
|
1194
1796
|
this.hydratingGeneration = config.lifecycleGeneration;
|
|
1195
1797
|
try {
|
|
1196
1798
|
let rows;
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1799
|
+
let bindResumeSnapshot;
|
|
1800
|
+
if (config.bindKeySetEvidence && !this.scopedRecovery) {
|
|
1801
|
+
const snapshot = await adapter.loadResumeSnapshot(
|
|
1802
|
+
this.storageTargetFor(),
|
|
1803
|
+
{
|
|
1804
|
+
requiredIndexSignatures: this.getRequiredIndexSignatures(),
|
|
1805
|
+
includeRows: true,
|
|
1806
|
+
cacheGenerationClaimId: this.cacheGenerationClaim?.claimId
|
|
1807
|
+
}
|
|
1808
|
+
);
|
|
1202
1809
|
rows = snapshot.rows;
|
|
1203
|
-
if (config.lifecycleGeneration !== this.lifecycleGeneration)
|
|
1204
|
-
|
|
1205
|
-
this.
|
|
1810
|
+
if (config.lifecycleGeneration !== this.lifecycleGeneration)
|
|
1811
|
+
return true;
|
|
1812
|
+
if (resetSequence !== this.resetSequence) return true;
|
|
1813
|
+
bindResumeSnapshot = () => this.bindResumeSnapshotEvidence(snapshot);
|
|
1206
1814
|
boundRowVersion = snapshot.latestRowVersion;
|
|
1207
1815
|
} else {
|
|
1208
1816
|
rows = await this.loadSubsetRowsUnsafe(options, adapter);
|
|
1209
1817
|
}
|
|
1818
|
+
const complete = this.syncMode === `on-demand` && !this.scopedRecovery && options.where === void 0 && options.limit === void 0 && options.offset === void 0 && options.cursor === void 0;
|
|
1819
|
+
const validationRows = complete ? rows : void 0;
|
|
1210
1820
|
rowsLoaded = true;
|
|
1211
|
-
if (config.lifecycleGeneration !== this.lifecycleGeneration) return;
|
|
1212
|
-
if (resetSequence !== this.resetSequence) return;
|
|
1821
|
+
if (config.lifecycleGeneration !== this.lifecycleGeneration) return true;
|
|
1822
|
+
if (resetSequence !== this.resetSequence) return true;
|
|
1823
|
+
if (this.cacheGenerationClaim && !await this.renewCacheClaim(adapter)) {
|
|
1824
|
+
return false;
|
|
1825
|
+
}
|
|
1826
|
+
if (config.lifecycleGeneration !== this.lifecycleGeneration) return true;
|
|
1827
|
+
if (resetSequence !== this.resetSequence) return true;
|
|
1828
|
+
bindResumeSnapshot?.();
|
|
1213
1829
|
if (!config.bindKeySetEvidence || this.persistedKeySetEvidence?.status !== `incompatible`) {
|
|
1214
1830
|
for (const row of rows) {
|
|
1215
1831
|
hydrationContext.suppliedRowKeys.add(row.key);
|
|
1216
1832
|
}
|
|
1217
|
-
const applied = this.applyRowsToCollection(rows);
|
|
1833
|
+
const applied = this.applyRowsToCollection(rows, validationRows);
|
|
1218
1834
|
await db.whenSyncAccepted(applied);
|
|
1219
1835
|
if (boundRowVersion !== void 0) {
|
|
1220
1836
|
this.publicRowVersion = Math.max(
|
|
@@ -1228,11 +1844,20 @@ class PersistedCollectionRuntime {
|
|
|
1228
1844
|
this.hydratingGeneration = null;
|
|
1229
1845
|
}
|
|
1230
1846
|
}
|
|
1231
|
-
if (config.lifecycleGeneration !== this.lifecycleGeneration) return;
|
|
1232
|
-
|
|
1233
|
-
|
|
1847
|
+
if (config.lifecycleGeneration !== this.lifecycleGeneration) return true;
|
|
1848
|
+
if (!this.scopedRecoveryPending) {
|
|
1849
|
+
replayFailure = await this.flushQueuedHydrationTransactionsUnsafe(adapter);
|
|
1850
|
+
}
|
|
1851
|
+
if (config.lifecycleGeneration !== this.lifecycleGeneration) return true;
|
|
1234
1852
|
} catch (error) {
|
|
1853
|
+
if (config.requestLocalLoadFailure && !rowsLoaded && this.cacheGenerationClaim && this.hasExpiredCacheClaim()) {
|
|
1854
|
+
return false;
|
|
1855
|
+
}
|
|
1235
1856
|
if (config.requestLocalLoadFailure && !rowsLoaded) {
|
|
1857
|
+
if (this.scopedRecoveryPending) {
|
|
1858
|
+
this.recoverQueuedAfterLocalLoadFailure = true;
|
|
1859
|
+
throw error;
|
|
1860
|
+
}
|
|
1236
1861
|
this.hydratingGeneration = config.lifecycleGeneration;
|
|
1237
1862
|
try {
|
|
1238
1863
|
await this.recoverBufferedTransactionsAfterLocalLoadFailureUnsafe(
|
|
@@ -1261,6 +1886,7 @@ class PersistedCollectionRuntime {
|
|
|
1261
1886
|
if (replayFailure && config.rejectBufferedReplayFailure) {
|
|
1262
1887
|
throw replayFailure.reason;
|
|
1263
1888
|
}
|
|
1889
|
+
return true;
|
|
1264
1890
|
}
|
|
1265
1891
|
async recoverBufferedTransactionsAfterLocalLoadFailureUnsafe(lifecycleGeneration, adapter) {
|
|
1266
1892
|
let snapshotRows;
|
|
@@ -1295,18 +1921,15 @@ class PersistedCollectionRuntime {
|
|
|
1295
1921
|
const key = operation.key;
|
|
1296
1922
|
if (operation.type === `update` && transaction.partialUpdateOperationIndexes.has(index) && getPresence(key) === `unknown`) {
|
|
1297
1923
|
if (snapshotRows === void 0) {
|
|
1298
|
-
const snapshot = await adapter.loadResumeSnapshot(
|
|
1299
|
-
this.
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
}
|
|
1304
|
-
);
|
|
1924
|
+
const snapshot = this.scopedRecovery ? void 0 : await adapter.loadResumeSnapshot(this.storageTargetFor(), {
|
|
1925
|
+
requiredIndexSignatures: this.getRequiredIndexSignatures(),
|
|
1926
|
+
includeRows: true,
|
|
1927
|
+
cacheGenerationClaimId: this.cacheGenerationClaim?.claimId
|
|
1928
|
+
});
|
|
1305
1929
|
this.throwIfLifecycleReplaced(lifecycleGeneration);
|
|
1306
1930
|
if (transaction.signal?.aborted) break;
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
);
|
|
1931
|
+
const rows = snapshot && !this.scopedRecovery ? snapshot.rows : [];
|
|
1932
|
+
snapshotRows = new Map(rows.map((row) => [row.key, row]));
|
|
1310
1933
|
}
|
|
1311
1934
|
const baseline = snapshotRows.get(key);
|
|
1312
1935
|
if (baseline === void 0) {
|
|
@@ -1356,22 +1979,65 @@ class PersistedCollectionRuntime {
|
|
|
1356
1979
|
}
|
|
1357
1980
|
this.queuedHydrationTransactions.length = 0;
|
|
1358
1981
|
}
|
|
1359
|
-
applyRowsToCollection(rows) {
|
|
1982
|
+
applyRowsToCollection(rows, validationRows, collectionMetadata) {
|
|
1360
1983
|
if (!this.syncControls.begin || !this.syncControls.write || !this.syncControls.commit) {
|
|
1361
1984
|
return true;
|
|
1362
1985
|
}
|
|
1363
1986
|
return this.withInternalApply(() => {
|
|
1364
1987
|
this.syncControls.begin?.();
|
|
1988
|
+
if (validationRows) {
|
|
1989
|
+
const durableRows = new Map(validationRows.map((row) => [row.key, row]));
|
|
1990
|
+
for (const key of this.collection?.base.keys() ?? []) {
|
|
1991
|
+
if (this.collection?._hasHydratedKey(key)) continue;
|
|
1992
|
+
const durable = durableRows.get(key);
|
|
1993
|
+
if (!durable) {
|
|
1994
|
+
this.syncControls.write?.({ type: `delete`, key });
|
|
1995
|
+
} else {
|
|
1996
|
+
const valueChanged = !db.equalPersistedSnapshotValues(
|
|
1997
|
+
this.collection?.base.get(key),
|
|
1998
|
+
durable.value
|
|
1999
|
+
);
|
|
2000
|
+
const metadataChanged = !db.equalPersistedSnapshotValues(
|
|
2001
|
+
this.syncControls.metadata?.row.get(key),
|
|
2002
|
+
durable.metadata
|
|
2003
|
+
);
|
|
2004
|
+
if (valueChanged || metadataChanged && durable.metadata !== void 0) {
|
|
2005
|
+
if (valueChanged) {
|
|
2006
|
+
this.syncControls.write?.({ type: `delete`, key });
|
|
2007
|
+
}
|
|
2008
|
+
this.syncControls.write?.({
|
|
2009
|
+
type: `update`,
|
|
2010
|
+
value: durable.value,
|
|
2011
|
+
metadata: durable.metadata
|
|
2012
|
+
});
|
|
2013
|
+
}
|
|
2014
|
+
if (metadataChanged && durable.metadata === void 0) {
|
|
2015
|
+
this.syncControls.metadata?.row.delete(key);
|
|
2016
|
+
}
|
|
2017
|
+
}
|
|
2018
|
+
}
|
|
2019
|
+
for (const key of this.collection?._syncedRowMetadataKeysOutsideBase() ?? []) {
|
|
2020
|
+
this.syncControls.metadata?.row.delete(key);
|
|
2021
|
+
}
|
|
2022
|
+
}
|
|
1365
2023
|
for (const row of rows) {
|
|
1366
|
-
if (this.collection?._hasHydratedKey(row.key)) {
|
|
2024
|
+
if (validationRows && this.collection?.base.has(row.key) || this.collection?._hasHydratedKey(row.key)) {
|
|
1367
2025
|
continue;
|
|
1368
2026
|
}
|
|
2027
|
+
if (this.collection?.base.has(row.key) && !db.equalPersistedSnapshotValues(
|
|
2028
|
+
this.collection.base.get(row.key),
|
|
2029
|
+
row.value
|
|
2030
|
+
)) {
|
|
2031
|
+
this.syncControls.write?.({ type: `delete`, key: row.key });
|
|
2032
|
+
}
|
|
1369
2033
|
this.syncControls.write?.({
|
|
1370
2034
|
type: `update`,
|
|
1371
2035
|
value: row.value,
|
|
1372
2036
|
metadata: row.metadata
|
|
1373
2037
|
});
|
|
1374
2038
|
}
|
|
2039
|
+
if (collectionMetadata)
|
|
2040
|
+
this.applyCollectionMetadataSnapshot(collectionMetadata);
|
|
1375
2041
|
return this.syncControls.commit?.() ?? true;
|
|
1376
2042
|
});
|
|
1377
2043
|
}
|
|
@@ -1400,32 +2066,45 @@ class PersistedCollectionRuntime {
|
|
|
1400
2066
|
);
|
|
1401
2067
|
this.persistedKeySetEvidence = this.isExpectedResumeGeneration(generation) || mayAcceptUnownedGeneration ? snapshot.keySet : { status: `incompatible` };
|
|
1402
2068
|
}
|
|
1403
|
-
replaceCollectionSnapshot(rows, collectionMetadata) {
|
|
2069
|
+
replaceCollectionSnapshot(rows, collectionMetadata, replayDemand) {
|
|
1404
2070
|
if (!this.syncControls.begin || !this.syncControls.write || !this.syncControls.commit || !this.syncControls.metadata) {
|
|
1405
2071
|
return true;
|
|
1406
2072
|
}
|
|
1407
|
-
const
|
|
1408
|
-
|
|
2073
|
+
const base = this.collection?.base;
|
|
2074
|
+
const hydratedKeys = new Set(
|
|
2075
|
+
Array.from(base?.keys() ?? []).filter(
|
|
2076
|
+
(key) => this.collection?._hasHydratedKey(key) === true
|
|
2077
|
+
)
|
|
1409
2078
|
);
|
|
1410
|
-
const
|
|
2079
|
+
const rowKeys = new Set(rows.map((row) => row.key));
|
|
2080
|
+
const followsLeader = this.canRouteRemoteDemandThroughCoordinator() && !this.persistence.coordinator.isLeader(this.storageCollectionId);
|
|
2081
|
+
const truncate = hydratedKeys.size === 0 && (replayDemand || !followsLeader);
|
|
1411
2082
|
return this.withInternalApply(() => {
|
|
1412
2083
|
this.syncControls.begin?.();
|
|
1413
|
-
|
|
2084
|
+
if (truncate) {
|
|
2085
|
+
this.syncControls.truncate?.();
|
|
2086
|
+
} else {
|
|
2087
|
+
for (const key of base?.keys() ?? []) {
|
|
2088
|
+
if (!hydratedKeys.has(key) && !rowKeys.has(key)) {
|
|
2089
|
+
this.syncControls.write?.({ type: `delete`, key });
|
|
2090
|
+
}
|
|
2091
|
+
}
|
|
2092
|
+
for (const key of this.collection?._syncedRowMetadataKeysOutsideBase() ?? []) {
|
|
2093
|
+
this.syncControls.metadata?.row.delete(key);
|
|
2094
|
+
}
|
|
2095
|
+
}
|
|
1414
2096
|
for (const row of rows) {
|
|
2097
|
+
if (hydratedKeys.has(row.key)) continue;
|
|
2098
|
+
if (!truncate && base?.has(row.key)) {
|
|
2099
|
+
this.syncControls.write?.({ type: `delete`, key: row.key });
|
|
2100
|
+
}
|
|
1415
2101
|
this.syncControls.write?.({
|
|
1416
2102
|
type: `update`,
|
|
1417
2103
|
value: row.value,
|
|
1418
2104
|
metadata: row.metadata
|
|
1419
2105
|
});
|
|
1420
2106
|
}
|
|
1421
|
-
|
|
1422
|
-
if (!nextMetadata.has(key)) {
|
|
1423
|
-
this.syncControls.metadata?.collection.delete(key);
|
|
1424
|
-
}
|
|
1425
|
-
});
|
|
1426
|
-
nextMetadata.forEach((value, key) => {
|
|
1427
|
-
this.syncControls.metadata?.collection.set(key, value);
|
|
1428
|
-
});
|
|
2107
|
+
this.applyCollectionMetadataSnapshot(collectionMetadata);
|
|
1429
2108
|
return this.syncControls.commit?.() ?? true;
|
|
1430
2109
|
});
|
|
1431
2110
|
}
|
|
@@ -1477,6 +2156,31 @@ class PersistedCollectionRuntime {
|
|
|
1477
2156
|
async applyBufferedSyncTransactionUnsafe(transaction, scopedAdapter) {
|
|
1478
2157
|
this.throwIfTerminal();
|
|
1479
2158
|
this.throwIfLifecycleReplaced(transaction.lifecycleGeneration);
|
|
2159
|
+
if (transaction.admittedStorageCollectionId !== void 0 && transaction.admittedStorageCollectionId !== this.storageCollectionId) {
|
|
2160
|
+
const error = new db.SyncTransactionAbortedError();
|
|
2161
|
+
if (!transaction.rejectApplied) throw error;
|
|
2162
|
+
transaction.rejectApplied(error);
|
|
2163
|
+
return { applied: false };
|
|
2164
|
+
}
|
|
2165
|
+
if (!transaction.internal && this.scopedRecoveryPending) {
|
|
2166
|
+
const error = new db.SyncTransactionAbortedError();
|
|
2167
|
+
if (!transaction.rejectApplied) throw error;
|
|
2168
|
+
transaction.rejectApplied(error);
|
|
2169
|
+
return { applied: false };
|
|
2170
|
+
}
|
|
2171
|
+
if (!transaction.internal && this.cacheGenerationClaim && (scopedAdapter ?? this.persistence.adapter).renewCacheGenerationClaim && !await this.renewCacheClaim(scopedAdapter ?? this.persistence.adapter)) {
|
|
2172
|
+
const error = new db.SyncTransactionAbortedError();
|
|
2173
|
+
queueMicrotask(() => {
|
|
2174
|
+
if (transaction.lifecycleGeneration !== this.lifecycleGeneration) return;
|
|
2175
|
+
if (this.scopedRecoveryPending) return;
|
|
2176
|
+
void this.startScopedRecovery().catch((failure) => {
|
|
2177
|
+
this.markTerminalFailure(failure, transaction.lifecycleGeneration);
|
|
2178
|
+
});
|
|
2179
|
+
});
|
|
2180
|
+
if (!transaction.rejectApplied) throw error;
|
|
2181
|
+
transaction.rejectApplied(error);
|
|
2182
|
+
return { applied: false };
|
|
2183
|
+
}
|
|
1480
2184
|
const abortedBeforeApplication = transaction.signal?.aborted === true;
|
|
1481
2185
|
let abortedDuringApplication = false;
|
|
1482
2186
|
let applicationReturned = false;
|
|
@@ -1540,10 +2244,11 @@ class PersistedCollectionRuntime {
|
|
|
1540
2244
|
};
|
|
1541
2245
|
const streamPosition = this.nextLocalStreamPosition();
|
|
1542
2246
|
const tx = this.createPersistedTxFromOperations(transaction, streamPosition);
|
|
2247
|
+
const storageCollectionId = this.storageCollectionId;
|
|
1543
2248
|
let response;
|
|
1544
2249
|
try {
|
|
1545
2250
|
response = await this.persistence.coordinator.requestApplyCommittedTx(
|
|
1546
|
-
|
|
2251
|
+
storageCollectionId,
|
|
1547
2252
|
tx,
|
|
1548
2253
|
// A hydration-scoped adapter is already inside the shared scheduler.
|
|
1549
2254
|
// Ordinary source commits enter it before taking the writer lock.
|
|
@@ -1556,7 +2261,7 @@ class PersistedCollectionRuntime {
|
|
|
1556
2261
|
let reconciled;
|
|
1557
2262
|
try {
|
|
1558
2263
|
reconciled = await this.persistence.coordinator.reconcileCommittedTx(
|
|
1559
|
-
|
|
2264
|
+
storageCollectionId,
|
|
1560
2265
|
tx,
|
|
1561
2266
|
anchor,
|
|
1562
2267
|
scopedAdapter
|
|
@@ -1624,7 +2329,10 @@ class PersistedCollectionRuntime {
|
|
|
1624
2329
|
this.throwIfTerminal();
|
|
1625
2330
|
this.throwIfLifecycleReplaced(lifecycleGeneration);
|
|
1626
2331
|
try {
|
|
1627
|
-
await this.persistence.adapter.applyCommittedTx(
|
|
2332
|
+
await this.persistence.adapter.applyCommittedTx(
|
|
2333
|
+
this.storageTargetFor(),
|
|
2334
|
+
tx
|
|
2335
|
+
);
|
|
1628
2336
|
} catch (error) {
|
|
1629
2337
|
throw this.markTerminalFailure(
|
|
1630
2338
|
errors.toPersistedCollectionDurabilityError(this.collectionId, error),
|
|
@@ -1639,6 +2347,7 @@ class PersistedCollectionRuntime {
|
|
|
1639
2347
|
term: streamPosition.term,
|
|
1640
2348
|
seq: streamPosition.seq,
|
|
1641
2349
|
rowVersion: streamPosition.rowVersion,
|
|
2350
|
+
cacheGenerationClaimId: this.cacheGenerationClaim?.claimId,
|
|
1642
2351
|
truncate: transaction.truncate,
|
|
1643
2352
|
mutations: transaction.operations.map(
|
|
1644
2353
|
(operation) => operation.type === `update` ? {
|
|
@@ -1673,6 +2382,7 @@ class PersistedCollectionRuntime {
|
|
|
1673
2382
|
term: streamPosition.term,
|
|
1674
2383
|
seq: streamPosition.seq,
|
|
1675
2384
|
rowVersion: streamPosition.rowVersion,
|
|
2385
|
+
cacheGenerationClaimId: this.cacheGenerationClaim?.claimId,
|
|
1676
2386
|
mutations: mutations.map((mutation) => {
|
|
1677
2387
|
if (mutation.type === `delete`) {
|
|
1678
2388
|
return {
|
|
@@ -1732,14 +2442,16 @@ class PersistedCollectionRuntime {
|
|
|
1732
2442
|
async persistCollectionMutationsUnsafe(mutations, lifecycleGeneration) {
|
|
1733
2443
|
this.throwIfLifecycleReplaced(lifecycleGeneration);
|
|
1734
2444
|
if (this.persistence.coordinator.requestApplyLocalMutations) {
|
|
2445
|
+
const target = this.storageTargetFor();
|
|
1735
2446
|
const envelopeMutations = mutations.map(
|
|
1736
2447
|
(mutation) => toPersistedMutationEnvelope(
|
|
1737
2448
|
mutation
|
|
1738
2449
|
)
|
|
1739
2450
|
);
|
|
1740
2451
|
const response = await this.persistence.coordinator.requestApplyLocalMutations(
|
|
1741
|
-
this.
|
|
1742
|
-
envelopeMutations
|
|
2452
|
+
this.storageCollectionId,
|
|
2453
|
+
envelopeMutations,
|
|
2454
|
+
target.kind === `managed` ? target.claimId : void 0
|
|
1743
2455
|
);
|
|
1744
2456
|
this.throwIfLifecycleReplaced(lifecycleGeneration);
|
|
1745
2457
|
if (!response.ok) {
|
|
@@ -1836,12 +2548,12 @@ class PersistedCollectionRuntime {
|
|
|
1836
2548
|
const envelope = {
|
|
1837
2549
|
v: 1,
|
|
1838
2550
|
dbName: this.dbName,
|
|
1839
|
-
collectionId: this.
|
|
2551
|
+
collectionId: this.storageCollectionId,
|
|
1840
2552
|
senderId: this.persistence.coordinator.getNodeId(),
|
|
1841
2553
|
ts: Date.now(),
|
|
1842
2554
|
payload: txCommitted
|
|
1843
2555
|
};
|
|
1844
|
-
this.persistence.coordinator.publish(this.
|
|
2556
|
+
this.persistence.coordinator.publish(this.storageCollectionId, envelope);
|
|
1845
2557
|
}
|
|
1846
2558
|
observeStreamPosition(term, seq, rowVersion) {
|
|
1847
2559
|
if (term > this.latestTerm || term === this.latestTerm && seq > this.latestSeq) {
|
|
@@ -1883,7 +2595,7 @@ class PersistedCollectionRuntime {
|
|
|
1883
2595
|
if (this.getCurrentTerminalFailure() || this.mode !== `sync-present` || this.persistence.coordinator instanceof SingleProcessCoordinator) {
|
|
1884
2596
|
return false;
|
|
1885
2597
|
}
|
|
1886
|
-
return !this.persistence.coordinator.isLeader(this.
|
|
2598
|
+
return !this.persistence.coordinator.isLeader(this.storageCollectionId) || this.remoteSubsetOwnerUnsubscribe !== null;
|
|
1887
2599
|
}
|
|
1888
2600
|
queueRemoteSubsetEnsure(options) {
|
|
1889
2601
|
if (options.signal?.aborted || !this.canRouteRemoteDemandThroughCoordinator() || this.activeSubsets.get(this.getSubsetKey(options)) !== options) {
|
|
@@ -1922,8 +2634,8 @@ class PersistedCollectionRuntime {
|
|
|
1922
2634
|
}
|
|
1923
2635
|
try {
|
|
1924
2636
|
await this.persistence.coordinator.requestEnsureRemoteSubset(
|
|
1925
|
-
this.
|
|
1926
|
-
options
|
|
2637
|
+
this.storageCollectionId,
|
|
2638
|
+
this.rotatedAcquisitions.get(options) ?? options
|
|
1927
2639
|
);
|
|
1928
2640
|
this.pendingRemoteSubsetEnsures.delete(subsetKey);
|
|
1929
2641
|
} catch (error) {
|
|
@@ -1941,18 +2653,19 @@ class PersistedCollectionRuntime {
|
|
|
1941
2653
|
return;
|
|
1942
2654
|
}
|
|
1943
2655
|
this.persistence.coordinator.setAdapterForCollection?.(
|
|
1944
|
-
this.
|
|
1945
|
-
this.persistence.adapter
|
|
2656
|
+
this.storageCollectionId,
|
|
2657
|
+
this.persistence.adapter,
|
|
2658
|
+
this.cacheGenerationClaim?.claimId
|
|
1946
2659
|
);
|
|
1947
2660
|
this.coordinatorUnsubscribe = this.persistence.coordinator.subscribe(
|
|
1948
|
-
this.
|
|
2661
|
+
this.storageCollectionId,
|
|
1949
2662
|
(message) => {
|
|
1950
2663
|
this.onCoordinatorMessage(message);
|
|
1951
2664
|
}
|
|
1952
2665
|
);
|
|
1953
2666
|
}
|
|
1954
2667
|
onCoordinatorMessage(message) {
|
|
1955
|
-
if (message.collectionId !== this.
|
|
2668
|
+
if (message.collectionId !== this.storageCollectionId) {
|
|
1956
2669
|
return;
|
|
1957
2670
|
}
|
|
1958
2671
|
if (this.getCurrentTerminalFailure()) {
|
|
@@ -1961,17 +2674,24 @@ class PersistedCollectionRuntime {
|
|
|
1961
2674
|
const { payload } = message;
|
|
1962
2675
|
const lifecycleGeneration = this.lifecycleGeneration;
|
|
1963
2676
|
const isSelf = message.senderId === this.persistence.coordinator.getNodeId();
|
|
2677
|
+
const claimExpired = this.hasExpiredCacheClaim();
|
|
2678
|
+
if (claimExpired && !isTxCommittedPayload(payload)) {
|
|
2679
|
+
this.scheduleCacheClaimRecovery(lifecycleGeneration);
|
|
2680
|
+
return;
|
|
2681
|
+
}
|
|
1964
2682
|
if (isTxCommittedPayload(payload)) {
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
2683
|
+
this.queuedTxCommitted.push({
|
|
2684
|
+
txCommitted: payload,
|
|
2685
|
+
lifecycleGeneration,
|
|
2686
|
+
storageCollectionId: message.collectionId
|
|
2687
|
+
});
|
|
2688
|
+
if (claimExpired) {
|
|
2689
|
+
this.scheduleCacheClaimRecovery(lifecycleGeneration);
|
|
2690
|
+
} else if (!this.isHydratingNow()) {
|
|
2691
|
+
void this.applyMutex.run(() => this.flushQueuedTxCommittedUnsafe()).catch((error) => {
|
|
2692
|
+
this.markTerminalFailure(error, lifecycleGeneration);
|
|
1969
2693
|
});
|
|
1970
|
-
return;
|
|
1971
2694
|
}
|
|
1972
|
-
void this.applyMutex.run(() => this.processCommittedTxUnsafe(payload, lifecycleGeneration)).catch((error) => {
|
|
1973
|
-
this.markTerminalFailure(error, lifecycleGeneration);
|
|
1974
|
-
});
|
|
1975
2695
|
return;
|
|
1976
2696
|
}
|
|
1977
2697
|
if (isSelf) {
|
|
@@ -1979,6 +2699,7 @@ class PersistedCollectionRuntime {
|
|
|
1979
2699
|
}
|
|
1980
2700
|
if (isCollectionResetPayload(payload)) {
|
|
1981
2701
|
void this.applyMutex.run(async () => {
|
|
2702
|
+
if (message.collectionId !== this.storageCollectionId) return;
|
|
1982
2703
|
await this.runInHydrationScope(async (adapter) => {
|
|
1983
2704
|
await this.truncateAndReloadUnsafe(adapter, lifecycleGeneration);
|
|
1984
2705
|
await this.rebaseDurableStreamFromSnapshotUnsafe(
|
|
@@ -2000,14 +2721,23 @@ class PersistedCollectionRuntime {
|
|
|
2000
2721
|
}
|
|
2001
2722
|
async flushQueuedTxCommittedUnsafe() {
|
|
2002
2723
|
while (this.queuedTxCommitted.length > 0) {
|
|
2724
|
+
if (this.hasExpiredCacheClaim()) {
|
|
2725
|
+
this.scheduleCacheClaimRecovery(this.lifecycleGeneration);
|
|
2726
|
+
return;
|
|
2727
|
+
}
|
|
2003
2728
|
const queued = this.queuedTxCommitted.shift();
|
|
2004
2729
|
if (!queued) {
|
|
2005
2730
|
continue;
|
|
2006
2731
|
}
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2732
|
+
if (queued.storageCollectionId !== this.storageCollectionId) continue;
|
|
2733
|
+
try {
|
|
2734
|
+
await this.processCommittedTxUnsafe(
|
|
2735
|
+
queued.txCommitted,
|
|
2736
|
+
queued.lifecycleGeneration
|
|
2737
|
+
);
|
|
2738
|
+
} catch (error) {
|
|
2739
|
+
throw this.markTerminalFailure(error, queued.lifecycleGeneration);
|
|
2740
|
+
}
|
|
2011
2741
|
}
|
|
2012
2742
|
}
|
|
2013
2743
|
async processCommittedTxUnsafe(txCommitted, lifecycleGeneration = this.lifecycleGeneration) {
|
|
@@ -2045,7 +2775,7 @@ class PersistedCollectionRuntime {
|
|
|
2045
2775
|
const hasGapAcrossTerms = txCommitted.term > this.latestTerm && txCommitted.seq > 1;
|
|
2046
2776
|
const hasGap = observedButUnpublished || txCommitted.latestRowVersion > this.publicRowVersion + 1 || hasGapInCurrentTerm || hasGapAcrossTerms;
|
|
2047
2777
|
if (hasGap) {
|
|
2048
|
-
await this.recoverFromSeqGapUnsafe(lifecycleGeneration);
|
|
2778
|
+
if (!await this.recoverFromSeqGapUnsafe(lifecycleGeneration)) return;
|
|
2049
2779
|
if (lifecycleGeneration !== this.lifecycleGeneration) return;
|
|
2050
2780
|
if (txCommitted.latestRowVersion <= this.publicRowVersion) return;
|
|
2051
2781
|
}
|
|
@@ -2068,8 +2798,9 @@ class PersistedCollectionRuntime {
|
|
|
2068
2798
|
}
|
|
2069
2799
|
async rebaseDurableStreamFromSnapshotUnsafe(adapter, lifecycleGeneration) {
|
|
2070
2800
|
if (lifecycleGeneration !== this.lifecycleGeneration) return;
|
|
2071
|
-
const snapshot = await adapter.loadResumeSnapshot(this.
|
|
2072
|
-
includeRows: false
|
|
2801
|
+
const snapshot = await adapter.loadResumeSnapshot(this.storageTargetFor(), {
|
|
2802
|
+
includeRows: false,
|
|
2803
|
+
cacheGenerationClaimId: this.cacheGenerationClaim?.claimId
|
|
2073
2804
|
});
|
|
2074
2805
|
if (lifecycleGeneration !== this.lifecycleGeneration) return;
|
|
2075
2806
|
this.latestRowVersion = snapshot.latestRowVersion;
|
|
@@ -2081,19 +2812,26 @@ class PersistedCollectionRuntime {
|
|
|
2081
2812
|
);
|
|
2082
2813
|
}
|
|
2083
2814
|
async recoverFromSeqGapUnsafe(lifecycleGeneration) {
|
|
2084
|
-
if (lifecycleGeneration !== this.lifecycleGeneration) return;
|
|
2815
|
+
if (lifecycleGeneration !== this.lifecycleGeneration) return false;
|
|
2085
2816
|
if (this.persistence.coordinator.pullSince && this.latestRowVersion >= 0) {
|
|
2086
2817
|
let pullResponse;
|
|
2087
2818
|
try {
|
|
2088
2819
|
pullResponse = await this.persistence.coordinator.pullSince(
|
|
2089
|
-
this.
|
|
2090
|
-
this.publicRowVersion
|
|
2820
|
+
this.storageCollectionId,
|
|
2821
|
+
this.publicRowVersion,
|
|
2822
|
+
void 0,
|
|
2823
|
+
this.cacheGenerationClaim?.claimId
|
|
2091
2824
|
);
|
|
2092
2825
|
} catch (error) {
|
|
2093
2826
|
console.warn(`Failed pullSince recovery attempt:`, error);
|
|
2094
2827
|
}
|
|
2828
|
+
if (this.scopedRecoveryPending) return false;
|
|
2829
|
+
if (this.cacheGenerationClaim && !await this.renewCacheClaim(this.persistence.adapter)) {
|
|
2830
|
+
this.scheduleCacheClaimRecovery(lifecycleGeneration);
|
|
2831
|
+
return false;
|
|
2832
|
+
}
|
|
2095
2833
|
if (pullResponse) {
|
|
2096
|
-
if (lifecycleGeneration !== this.lifecycleGeneration) return;
|
|
2834
|
+
if (lifecycleGeneration !== this.lifecycleGeneration) return false;
|
|
2097
2835
|
if (pullResponse.ok) {
|
|
2098
2836
|
this.observeStreamPosition(
|
|
2099
2837
|
pullResponse.latestTerm,
|
|
@@ -2108,7 +2846,7 @@ class PersistedCollectionRuntime {
|
|
|
2108
2846
|
this.publicRowVersion,
|
|
2109
2847
|
pullResponse.latestRowVersion
|
|
2110
2848
|
);
|
|
2111
|
-
return;
|
|
2849
|
+
return true;
|
|
2112
2850
|
}
|
|
2113
2851
|
const deltas = pullResponse.deltas;
|
|
2114
2852
|
if (!deltas) {
|
|
@@ -2119,7 +2857,7 @@ class PersistedCollectionRuntime {
|
|
|
2119
2857
|
this.publicRowVersion,
|
|
2120
2858
|
pullResponse.latestRowVersion
|
|
2121
2859
|
);
|
|
2122
|
-
return;
|
|
2860
|
+
return true;
|
|
2123
2861
|
}
|
|
2124
2862
|
await this.runInHydrationScope(async (adapter) => {
|
|
2125
2863
|
for (const delta of deltas) {
|
|
@@ -2146,11 +2884,11 @@ class PersistedCollectionRuntime {
|
|
|
2146
2884
|
this.publicRowVersion,
|
|
2147
2885
|
pullResponse.latestRowVersion
|
|
2148
2886
|
);
|
|
2149
|
-
return;
|
|
2887
|
+
return true;
|
|
2150
2888
|
}
|
|
2151
2889
|
}
|
|
2152
2890
|
}
|
|
2153
|
-
if (lifecycleGeneration !== this.lifecycleGeneration) return;
|
|
2891
|
+
if (lifecycleGeneration !== this.lifecycleGeneration) return false;
|
|
2154
2892
|
await this.runInHydrationScope(
|
|
2155
2893
|
(adapter) => this.reloadActiveSubsetsUnsafe(adapter)
|
|
2156
2894
|
);
|
|
@@ -2163,12 +2901,48 @@ class PersistedCollectionRuntime {
|
|
|
2163
2901
|
this.queueRemoteSubsetEnsure(options);
|
|
2164
2902
|
}
|
|
2165
2903
|
}
|
|
2904
|
+
return true;
|
|
2905
|
+
}
|
|
2906
|
+
refreshScopedSubsets() {
|
|
2907
|
+
if (!this.remoteSubsetOwner) return;
|
|
2908
|
+
const lifecycleGeneration = this.lifecycleGeneration;
|
|
2909
|
+
const storageId = this.storageCollectionId;
|
|
2910
|
+
for (const [subsetKey, options] of this.activeSubsets) {
|
|
2911
|
+
if (options.signal?.aborted) continue;
|
|
2912
|
+
this.hydratedDemands.delete(db.getLoadSubsetDemandKey(options));
|
|
2913
|
+
const refreshOptions = { ...options, refetch: true };
|
|
2914
|
+
this.scopedRefreshes.set(refreshOptions, storageId);
|
|
2915
|
+
void Promise.resolve().then(() => {
|
|
2916
|
+
if (lifecycleGeneration !== this.lifecycleGeneration || this.activeSubsets.get(subsetKey) !== options) {
|
|
2917
|
+
return;
|
|
2918
|
+
}
|
|
2919
|
+
return this.persistence.coordinator.requestEnsureRemoteSubset(
|
|
2920
|
+
storageId,
|
|
2921
|
+
refreshOptions
|
|
2922
|
+
);
|
|
2923
|
+
}).catch((error) => {
|
|
2924
|
+
if (lifecycleGeneration === this.lifecycleGeneration && !options.signal?.aborted) {
|
|
2925
|
+
this.reportSyncError(error);
|
|
2926
|
+
}
|
|
2927
|
+
}).finally(() => {
|
|
2928
|
+
if (!this.scopedRefreshes.delete(refreshOptions)) return;
|
|
2929
|
+
void this.persistence.coordinator.requestReleaseRemoteSubset(storageId, refreshOptions).catch((error) => {
|
|
2930
|
+
if (lifecycleGeneration === this.lifecycleGeneration) {
|
|
2931
|
+
this.reportSyncError(error);
|
|
2932
|
+
}
|
|
2933
|
+
});
|
|
2934
|
+
});
|
|
2935
|
+
}
|
|
2166
2936
|
}
|
|
2167
2937
|
async truncateAndReloadUnsafe(adapter, lifecycleGeneration = this.lifecycleGeneration) {
|
|
2168
2938
|
if (lifecycleGeneration !== this.lifecycleGeneration) return;
|
|
2939
|
+
if (this.scopedRecovery) {
|
|
2940
|
+
this.refreshScopedSubsets();
|
|
2941
|
+
return;
|
|
2942
|
+
}
|
|
2169
2943
|
this.hydratedDemands.clear();
|
|
2170
2944
|
this.resetSequence++;
|
|
2171
|
-
if (this.syncControls.begin && this.syncControls.commit) {
|
|
2945
|
+
if (this.syncMode === `eager` && this.syncControls.begin && this.syncControls.commit) {
|
|
2172
2946
|
const applied = this.withInternalApply(() => {
|
|
2173
2947
|
this.syncControls.begin?.();
|
|
2174
2948
|
this.syncControls.truncate?.();
|
|
@@ -2177,9 +2951,18 @@ class PersistedCollectionRuntime {
|
|
|
2177
2951
|
await db.whenSyncAccepted(applied);
|
|
2178
2952
|
}
|
|
2179
2953
|
if (lifecycleGeneration !== this.lifecycleGeneration) return;
|
|
2180
|
-
await this.reloadActiveSubsetsUnsafe(
|
|
2954
|
+
await this.reloadActiveSubsetsUnsafe(
|
|
2955
|
+
adapter,
|
|
2956
|
+
false,
|
|
2957
|
+
this.syncMode === `eager`,
|
|
2958
|
+
true
|
|
2959
|
+
);
|
|
2181
2960
|
}
|
|
2182
2961
|
async invalidateFromCommittedTxUnsafe(txCommitted, adapter) {
|
|
2962
|
+
if (this.scopedRecovery) {
|
|
2963
|
+
this.refreshScopedSubsets();
|
|
2964
|
+
return;
|
|
2965
|
+
}
|
|
2183
2966
|
const reloadActiveSubsets = (skipUnchangedReplace = false) => this.runInHydrationScope(
|
|
2184
2967
|
(scopedAdapter) => this.reloadActiveSubsetsUnsafe(scopedAdapter, skipUnchangedReplace),
|
|
2185
2968
|
adapter
|
|
@@ -2196,6 +2979,21 @@ class PersistedCollectionRuntime {
|
|
|
2196
2979
|
const hasPaginatedSubset = Array.from(this.activeSubsets.values()).some(
|
|
2197
2980
|
(opt) => opt.limit != null || opt.offset != null || opt.cursor != null
|
|
2198
2981
|
);
|
|
2982
|
+
const changedKeys = new Set(txCommitted.changedRows.map((row) => row.key));
|
|
2983
|
+
const hasRepeatedChange = changedKeys.size !== txCommitted.changedRows.length;
|
|
2984
|
+
const hasChangedAndDeletedKey = txCommitted.deletedKeys.some(
|
|
2985
|
+
(key) => changedKeys.has(key)
|
|
2986
|
+
);
|
|
2987
|
+
const hasChangedPublicKey = txCommitted.changedRows.some(
|
|
2988
|
+
(row) => this.collection?.base.has(row.key)
|
|
2989
|
+
);
|
|
2990
|
+
const hasUnseenDemandedKey = this.activeSubsets.size > 0 && txCommitted.changedRows.some(
|
|
2991
|
+
(row) => !this.collection?.base.has(row.key) && !this.collection?._hasHydratedKey(row.key)
|
|
2992
|
+
);
|
|
2993
|
+
if (hasRepeatedChange || hasChangedAndDeletedKey || hasChangedPublicKey || this.syncMode === `on-demand` && hasUnseenDemandedKey) {
|
|
2994
|
+
await reloadActiveSubsets();
|
|
2995
|
+
return;
|
|
2996
|
+
}
|
|
2199
2997
|
if (!hasPaginatedSubset || changedKeyCount === 0) {
|
|
2200
2998
|
await this.applyTargetedInvalidationUnsafe(txCommitted);
|
|
2201
2999
|
return;
|
|
@@ -2212,20 +3010,33 @@ class PersistedCollectionRuntime {
|
|
|
2212
3010
|
key: changedKey,
|
|
2213
3011
|
value: newValue
|
|
2214
3012
|
} of txCommitted.changedRows) {
|
|
2215
|
-
const
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
3013
|
+
const key = changedKey;
|
|
3014
|
+
if (this.collection?._hasHydratedKey(key)) continue;
|
|
3015
|
+
const previous = this.collection?.base.get(key);
|
|
3016
|
+
if (previous === void 0 && this.activeSubsets.size === 0) continue;
|
|
3017
|
+
const completeValue = {
|
|
3018
|
+
...previous,
|
|
3019
|
+
...newValue
|
|
3020
|
+
};
|
|
3021
|
+
const currentlyDemanded = matchesAnySubset(
|
|
3022
|
+
completeValue,
|
|
3023
|
+
subsetEvaluators
|
|
3024
|
+
);
|
|
3025
|
+
const previouslyOutsideDemand = previous !== void 0 && !matchesAnySubset(previous, subsetEvaluators);
|
|
3026
|
+
if (currentlyDemanded || previouslyOutsideDemand) {
|
|
3027
|
+
this.syncControls.write?.({ type: `update`, value: completeValue });
|
|
3028
|
+
} else if (previous !== void 0) {
|
|
3029
|
+
this.syncControls.write?.({ type: `delete`, key });
|
|
2223
3030
|
}
|
|
2224
3031
|
}
|
|
2225
3032
|
for (const deletedKey of txCommitted.deletedKeys) {
|
|
2226
|
-
|
|
3033
|
+
const key = deletedKey;
|
|
3034
|
+
if (!this.collection?._hasHydratedKey(key)) {
|
|
3035
|
+
this.syncControls.write?.({ type: `delete`, key });
|
|
3036
|
+
}
|
|
2227
3037
|
}
|
|
2228
3038
|
txCommitted.rowMetadataMutations?.forEach((mutation) => {
|
|
3039
|
+
if (this.collection?._hasHydratedKey(mutation.key)) return;
|
|
2229
3040
|
if (mutation.type === `delete`) {
|
|
2230
3041
|
this.syncControls.metadata?.row.delete(mutation.key);
|
|
2231
3042
|
} else {
|
|
@@ -2249,10 +3060,25 @@ class PersistedCollectionRuntime {
|
|
|
2249
3060
|
});
|
|
2250
3061
|
await db.whenSyncAccepted(applied);
|
|
2251
3062
|
}
|
|
2252
|
-
async reloadActiveSubsetsUnsafe(adapter, skipUnchangedReplace = false) {
|
|
3063
|
+
async reloadActiveSubsetsUnsafe(adapter, skipUnchangedReplace = false, restoreResetBaseline = false, replayDemand = false) {
|
|
3064
|
+
if (this.scopedRecovery) {
|
|
3065
|
+
this.refreshScopedSubsets();
|
|
3066
|
+
return;
|
|
3067
|
+
}
|
|
2253
3068
|
const lifecycleGeneration = this.lifecycleGeneration;
|
|
2254
3069
|
const truncateGeneration = this.sourceTruncateGeneration;
|
|
2255
|
-
const activeSubsetOptions =
|
|
3070
|
+
const activeSubsetOptions = Array.from(this.activeSubsets.values());
|
|
3071
|
+
if (restoreResetBaseline && activeSubsetOptions.length === 0) {
|
|
3072
|
+
activeSubsetOptions.push({});
|
|
3073
|
+
}
|
|
3074
|
+
const evaluators = activeSubsetOptions.map(
|
|
3075
|
+
(options) => options.where ? db.compileSingleRowExpression(options.where) : null
|
|
3076
|
+
);
|
|
3077
|
+
const outsideDemandKeys = activeSubsetOptions.some(
|
|
3078
|
+
(options) => options.limit != null || options.offset != null || options.cursor != null
|
|
3079
|
+
) ? [] : Array.from(this.collection?.base ?? []).filter(
|
|
3080
|
+
([key, row]) => !this.collection?._hasHydratedKey(key) && !matchesAnySubset(row, evaluators)
|
|
3081
|
+
).map(([key]) => key);
|
|
2256
3082
|
this.hydratedDemands.clear();
|
|
2257
3083
|
this.hydrationSequence++;
|
|
2258
3084
|
const hydrationContext = { suppliedRowKeys: /* @__PURE__ */ new Set() };
|
|
@@ -2260,22 +3086,68 @@ class PersistedCollectionRuntime {
|
|
|
2260
3086
|
this.hydratingGeneration = lifecycleGeneration;
|
|
2261
3087
|
try {
|
|
2262
3088
|
const mergedRows = /* @__PURE__ */ new Map();
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
3089
|
+
let validationSnapshot;
|
|
3090
|
+
let collectionMetadata = [];
|
|
3091
|
+
try {
|
|
3092
|
+
validationSnapshot = (activeSubsetOptions.length === 0 || outsideDemandKeys.length > 0) && this.collection?.base.size ? await adapter.loadResumeSnapshot(this.storageTargetFor(), {
|
|
3093
|
+
requiredIndexSignatures: this.getRequiredIndexSignatures(),
|
|
3094
|
+
includeRows: true,
|
|
3095
|
+
cacheGenerationClaimId: this.cacheGenerationClaim?.claimId
|
|
3096
|
+
}) : void 0;
|
|
3097
|
+
collectionMetadata = validationSnapshot ? validationSnapshot.collectionMetadata : await this.loadCollectionMetadataSnapshot(adapter);
|
|
2267
3098
|
if (lifecycleGeneration !== this.lifecycleGeneration) return;
|
|
2268
|
-
for (const
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
3099
|
+
for (const options of activeSubsetOptions) {
|
|
3100
|
+
const subsetRows = await this.loadSubsetRowsUnsafe(options, adapter);
|
|
3101
|
+
if (lifecycleGeneration !== this.lifecycleGeneration) return;
|
|
3102
|
+
for (const row of subsetRows) {
|
|
3103
|
+
mergedRows.set(row.key, {
|
|
3104
|
+
value: row.value,
|
|
3105
|
+
metadata: row.metadata
|
|
3106
|
+
});
|
|
3107
|
+
}
|
|
3108
|
+
}
|
|
3109
|
+
if (outsideDemandKeys.length > 0 && validationSnapshot) {
|
|
3110
|
+
const durableRows = new Map(
|
|
3111
|
+
validationSnapshot.rows.map((row) => [row.key, row])
|
|
3112
|
+
);
|
|
3113
|
+
for (const key of outsideDemandKeys) {
|
|
3114
|
+
if (mergedRows.has(key)) continue;
|
|
3115
|
+
const row = durableRows.get(key);
|
|
3116
|
+
if (row) {
|
|
3117
|
+
mergedRows.set(key, {
|
|
3118
|
+
value: row.value,
|
|
3119
|
+
metadata: row.metadata
|
|
3120
|
+
});
|
|
3121
|
+
}
|
|
3122
|
+
}
|
|
3123
|
+
}
|
|
3124
|
+
} catch (error) {
|
|
3125
|
+
if (this.scopedRecovery) return;
|
|
3126
|
+
if (!this.cacheGenerationClaim || !this.hasExpiredCacheClaim()) {
|
|
3127
|
+
throw error;
|
|
2273
3128
|
}
|
|
3129
|
+
this.scheduleCacheClaimRecovery(lifecycleGeneration);
|
|
3130
|
+
return;
|
|
3131
|
+
}
|
|
3132
|
+
if (this.isScopedRecoveryPending()) return;
|
|
3133
|
+
if (this.cacheGenerationClaim && !await this.renewCacheClaim(adapter)) {
|
|
3134
|
+
this.scheduleCacheClaimRecovery(lifecycleGeneration);
|
|
3135
|
+
return;
|
|
2274
3136
|
}
|
|
3137
|
+
if (this.isScopedRecoveryPending() || lifecycleGeneration !== this.lifecycleGeneration)
|
|
3138
|
+
return;
|
|
2275
3139
|
for (const key of mergedRows.keys()) {
|
|
2276
3140
|
hydrationContext.suppliedRowKeys.add(key);
|
|
2277
3141
|
}
|
|
2278
|
-
if (
|
|
3142
|
+
if (activeSubsetOptions.length === 0) {
|
|
3143
|
+
await db.whenSyncAccepted(
|
|
3144
|
+
this.applyRowsToCollection(
|
|
3145
|
+
[],
|
|
3146
|
+
validationSnapshot?.rows ?? [],
|
|
3147
|
+
collectionMetadata
|
|
3148
|
+
)
|
|
3149
|
+
);
|
|
3150
|
+
} else if (skipUnchangedReplace && this.matchesCollectionSnapshot(mergedRows, collectionMetadata)) {
|
|
2279
3151
|
const orphanKeys = this.collection?._syncedRowMetadataKeysOutsideBase() ?? [];
|
|
2280
3152
|
if (orphanKeys.length > 0) {
|
|
2281
3153
|
const applied = this.withInternalApply(() => {
|
|
@@ -2294,7 +3166,8 @@ class PersistedCollectionRuntime {
|
|
|
2294
3166
|
value: row.value,
|
|
2295
3167
|
metadata: row.metadata
|
|
2296
3168
|
})),
|
|
2297
|
-
collectionMetadata
|
|
3169
|
+
collectionMetadata,
|
|
3170
|
+
replayDemand
|
|
2298
3171
|
);
|
|
2299
3172
|
await db.whenSyncAccepted(applied);
|
|
2300
3173
|
}
|
|
@@ -2322,7 +3195,12 @@ class PersistedCollectionRuntime {
|
|
|
2322
3195
|
return;
|
|
2323
3196
|
}
|
|
2324
3197
|
this.indexAddedUnsubscribe = this.collection.on(`index:added`, (event) => {
|
|
2325
|
-
void this.ensurePersistedIndex(event.index)
|
|
3198
|
+
void this.ensurePersistedIndex(event.index).catch((error) => {
|
|
3199
|
+
console.warn(
|
|
3200
|
+
`Failed to ensure persisted index after index:added:`,
|
|
3201
|
+
error
|
|
3202
|
+
);
|
|
3203
|
+
});
|
|
2326
3204
|
});
|
|
2327
3205
|
this.indexRemovedUnsubscribe = this.collection.on(
|
|
2328
3206
|
`index:removed`,
|
|
@@ -2338,20 +3216,69 @@ class PersistedCollectionRuntime {
|
|
|
2338
3216
|
}
|
|
2339
3217
|
const indexMetadata = indexMetadataSnapshot ?? collection?.getIndexMetadata() ?? [];
|
|
2340
3218
|
const completedLocalIndexSignatures = /* @__PURE__ */ new Set();
|
|
3219
|
+
const lifecycleGeneration = this.lifecycleGeneration;
|
|
2341
3220
|
for (const metadata of indexMetadata) {
|
|
2342
|
-
|
|
3221
|
+
const storageCollectionId = this.storageCollectionId;
|
|
3222
|
+
const claimId = this.cacheGenerationClaim?.claimId;
|
|
3223
|
+
const ensured = await this.ensureLocalPersistedIndex(metadata, adapter);
|
|
3224
|
+
if (ensured && await this.reconcilePersistedIndex(
|
|
3225
|
+
metadata,
|
|
3226
|
+
adapter,
|
|
3227
|
+
storageCollectionId,
|
|
3228
|
+
claimId,
|
|
3229
|
+
lifecycleGeneration,
|
|
3230
|
+
true
|
|
3231
|
+
)) {
|
|
2343
3232
|
completedLocalIndexSignatures.add(metadata.signature);
|
|
2344
3233
|
}
|
|
2345
3234
|
}
|
|
2346
3235
|
return completedLocalIndexSignatures;
|
|
2347
3236
|
}
|
|
2348
3237
|
async requestCoordinatorPersistedIndexes(indexMetadata, completedLocalIndexSignatures) {
|
|
3238
|
+
const lifecycleGeneration = this.lifecycleGeneration;
|
|
2349
3239
|
for (const metadata of indexMetadata) {
|
|
3240
|
+
if (!this.hasCurrentIndex(metadata)) continue;
|
|
3241
|
+
const storageCollectionId = this.storageCollectionId;
|
|
3242
|
+
const claimId = this.cacheGenerationClaim?.claimId;
|
|
2350
3243
|
await this.requestCoordinatorPersistedIndex(
|
|
2351
3244
|
metadata,
|
|
2352
3245
|
completedLocalIndexSignatures.has(metadata.signature)
|
|
2353
3246
|
);
|
|
3247
|
+
await this.reconcilePersistedIndex(
|
|
3248
|
+
metadata,
|
|
3249
|
+
this.persistence.adapter,
|
|
3250
|
+
storageCollectionId,
|
|
3251
|
+
claimId,
|
|
3252
|
+
lifecycleGeneration,
|
|
3253
|
+
true
|
|
3254
|
+
);
|
|
3255
|
+
}
|
|
3256
|
+
}
|
|
3257
|
+
/** Settle a late index operation against the Collection's current declaration. */
|
|
3258
|
+
async reconcilePersistedIndex(indexMetadata, adapter, storageCollectionId, claimId, lifecycleGeneration, indexPresent) {
|
|
3259
|
+
while (lifecycleGeneration === this.lifecycleGeneration && storageCollectionId === this.storageCollectionId) {
|
|
3260
|
+
const declared = this.hasCurrentIndex(indexMetadata);
|
|
3261
|
+
if (declared === indexPresent) return declared;
|
|
3262
|
+
if (declared) {
|
|
3263
|
+
if (!await this.ensureLocalPersistedIndex(indexMetadata, adapter)) {
|
|
3264
|
+
return false;
|
|
3265
|
+
}
|
|
3266
|
+
} else {
|
|
3267
|
+
if (!adapter.markIndexRemoved) return false;
|
|
3268
|
+
try {
|
|
3269
|
+
await adapter.markIndexRemoved(
|
|
3270
|
+
this.storageTargetFor(storageCollectionId, claimId),
|
|
3271
|
+
indexMetadata.signature,
|
|
3272
|
+
{ cacheGenerationClaimId: claimId }
|
|
3273
|
+
);
|
|
3274
|
+
} catch (error) {
|
|
3275
|
+
console.warn(`Failed to mark persisted index removed:`, error);
|
|
3276
|
+
return false;
|
|
3277
|
+
}
|
|
3278
|
+
}
|
|
3279
|
+
indexPresent = declared;
|
|
2354
3280
|
}
|
|
3281
|
+
return false;
|
|
2355
3282
|
}
|
|
2356
3283
|
buildPersistedIndexSpec(index) {
|
|
2357
3284
|
return {
|
|
@@ -2364,19 +3291,36 @@ class PersistedCollectionRuntime {
|
|
|
2364
3291
|
};
|
|
2365
3292
|
}
|
|
2366
3293
|
async ensurePersistedIndex(indexMetadata, adapter = this.persistence.adapter) {
|
|
3294
|
+
if (this.scopedRecoveryPending) return;
|
|
3295
|
+
const lifecycleGeneration = this.lifecycleGeneration;
|
|
3296
|
+
await this.ensureCacheClaimActive();
|
|
3297
|
+
if (lifecycleGeneration !== this.lifecycleGeneration || !this.hasCurrentIndex(indexMetadata))
|
|
3298
|
+
return;
|
|
2367
3299
|
const completedLocally = await this.ensureLocalPersistedIndex(
|
|
2368
3300
|
indexMetadata,
|
|
2369
3301
|
adapter
|
|
2370
3302
|
);
|
|
3303
|
+
if (lifecycleGeneration !== this.lifecycleGeneration) return;
|
|
3304
|
+
if (!this.hasCurrentIndex(indexMetadata)) {
|
|
3305
|
+
await this.markIndexRemoved(indexMetadata);
|
|
3306
|
+
return;
|
|
3307
|
+
}
|
|
2371
3308
|
await this.requestCoordinatorPersistedIndex(indexMetadata, completedLocally);
|
|
3309
|
+
if (lifecycleGeneration === this.lifecycleGeneration && !this.hasCurrentIndex(indexMetadata)) {
|
|
3310
|
+
await this.markIndexRemoved(indexMetadata);
|
|
3311
|
+
}
|
|
3312
|
+
}
|
|
3313
|
+
hasCurrentIndex(indexMetadata) {
|
|
3314
|
+
return this.collection?.getIndexMetadata().some((current) => current.signature === indexMetadata.signature) ?? false;
|
|
2372
3315
|
}
|
|
2373
3316
|
async ensureLocalPersistedIndex(indexMetadata, adapter) {
|
|
2374
3317
|
try {
|
|
2375
3318
|
const spec = this.buildPersistedIndexSpec(indexMetadata);
|
|
2376
3319
|
await adapter.ensureIndex(
|
|
2377
|
-
this.
|
|
3320
|
+
this.storageTargetFor(),
|
|
2378
3321
|
indexMetadata.signature,
|
|
2379
|
-
spec
|
|
3322
|
+
spec,
|
|
3323
|
+
{ cacheGenerationClaimId: this.cacheGenerationClaim?.claimId }
|
|
2380
3324
|
);
|
|
2381
3325
|
return true;
|
|
2382
3326
|
} catch (error) {
|
|
@@ -2388,11 +3332,12 @@ class PersistedCollectionRuntime {
|
|
|
2388
3332
|
try {
|
|
2389
3333
|
const spec = this.buildPersistedIndexSpec(indexMetadata);
|
|
2390
3334
|
await this.persistence.coordinator.requestEnsurePersistedIndex(
|
|
2391
|
-
this.
|
|
3335
|
+
this.storageCollectionId,
|
|
2392
3336
|
indexMetadata.signature,
|
|
2393
3337
|
spec,
|
|
2394
3338
|
completedLocally ? this.persistence.adapter : void 0,
|
|
2395
|
-
completedLocally
|
|
3339
|
+
completedLocally,
|
|
3340
|
+
this.cacheGenerationClaim?.claimId
|
|
2396
3341
|
);
|
|
2397
3342
|
} catch (error) {
|
|
2398
3343
|
console.warn(
|
|
@@ -2406,10 +3351,19 @@ class PersistedCollectionRuntime {
|
|
|
2406
3351
|
return;
|
|
2407
3352
|
}
|
|
2408
3353
|
try {
|
|
3354
|
+
if (this.scopedRecoveryPending) return;
|
|
3355
|
+
const lifecycleGeneration = this.lifecycleGeneration;
|
|
3356
|
+
await this.ensureCacheClaimActive();
|
|
3357
|
+
if (lifecycleGeneration !== this.lifecycleGeneration || this.hasCurrentIndex(indexMetadata))
|
|
3358
|
+
return;
|
|
2409
3359
|
await this.persistence.adapter.markIndexRemoved(
|
|
2410
|
-
this.
|
|
2411
|
-
indexMetadata.signature
|
|
3360
|
+
this.storageTargetFor(),
|
|
3361
|
+
indexMetadata.signature,
|
|
3362
|
+
{ cacheGenerationClaimId: this.cacheGenerationClaim?.claimId }
|
|
2412
3363
|
);
|
|
3364
|
+
if (lifecycleGeneration === this.lifecycleGeneration && this.hasCurrentIndex(indexMetadata)) {
|
|
3365
|
+
await this.ensurePersistedIndex(indexMetadata);
|
|
3366
|
+
}
|
|
2413
3367
|
} catch (error) {
|
|
2414
3368
|
console.warn(`Failed to mark persisted index removed:`, error);
|
|
2415
3369
|
}
|
|
@@ -2612,20 +3566,26 @@ function createWrappedSyncConfig(sourceSyncConfig, runtime, persistedReadiness)
|
|
|
2612
3566
|
throw error;
|
|
2613
3567
|
}
|
|
2614
3568
|
};
|
|
2615
|
-
runtime.setSyncControls({
|
|
2616
|
-
begin: params.begin,
|
|
2617
|
-
write: params.write,
|
|
2618
|
-
commit: params.commit,
|
|
2619
|
-
truncate: params.truncate,
|
|
2620
|
-
metadata: params.metadata ?? null,
|
|
2621
|
-
markError: params.markError
|
|
2622
|
-
});
|
|
2623
3569
|
runtime.setCollection(
|
|
2624
|
-
params.collection
|
|
3570
|
+
params.collection,
|
|
3571
|
+
{
|
|
3572
|
+
begin: params.begin,
|
|
3573
|
+
write: params.write,
|
|
3574
|
+
commit: params.commit,
|
|
3575
|
+
truncate: params.truncate,
|
|
3576
|
+
metadata: params.metadata ?? null,
|
|
3577
|
+
markError: params.markError
|
|
3578
|
+
}
|
|
2625
3579
|
);
|
|
2626
3580
|
const persistenceCapability = {
|
|
2627
3581
|
protocol: db.SYNC_PERSISTENCE_PROTOCOL,
|
|
2628
3582
|
version: db.SYNC_PERSISTENCE_VERSION,
|
|
3583
|
+
get managedCacheGeneration() {
|
|
3584
|
+
return runtime.hasManagedCacheGeneration();
|
|
3585
|
+
},
|
|
3586
|
+
get startupCacheGenerationRotated() {
|
|
3587
|
+
return runtime.hasRotatedStartupCacheGeneration();
|
|
3588
|
+
},
|
|
2629
3589
|
reserveCommitTurn: () => {
|
|
2630
3590
|
const openTransaction = getOpenTransaction();
|
|
2631
3591
|
if (openTransaction && !openTransaction.internal)
|
|
@@ -2639,6 +3599,7 @@ function createWrappedSyncConfig(sourceSyncConfig, runtime, persistedReadiness)
|
|
|
2639
3599
|
throw runtime.reportSyncError(error);
|
|
2640
3600
|
}
|
|
2641
3601
|
},
|
|
3602
|
+
startScopedRecovery: (resetMetadata) => startupState.cleanedUp ? Promise.resolve() : runtime.startScopedRecovery(resetMetadata),
|
|
2642
3603
|
scanPersistedRows: (options) => startupState.cleanedUp ? Promise.resolve([]) : runtime.scanPersistedRows(options),
|
|
2643
3604
|
resumeSnapshot: {
|
|
2644
3605
|
certify: async () => {
|
|
@@ -2662,6 +3623,7 @@ function createWrappedSyncConfig(sourceSyncConfig, runtime, persistedReadiness)
|
|
|
2662
3623
|
}
|
|
2663
3624
|
}
|
|
2664
3625
|
};
|
|
3626
|
+
let sourceSession = 0;
|
|
2665
3627
|
const wrappedParams = {
|
|
2666
3628
|
...params,
|
|
2667
3629
|
markError: reportUpstreamError,
|
|
@@ -2933,6 +3895,12 @@ function createWrappedSyncConfig(sourceSyncConfig, runtime, persistedReadiness)
|
|
|
2933
3895
|
settlePublicationAdmissionWaiters(openTransaction, error);
|
|
2934
3896
|
return createHandledRejection(error);
|
|
2935
3897
|
}
|
|
3898
|
+
if (runtime.isScopedRecoveryPending()) {
|
|
3899
|
+
settlePendingTransaction(openTransaction);
|
|
3900
|
+
const error = new db.SyncTransactionAbortedError();
|
|
3901
|
+
settlePublicationAdmissionWaiters(openTransaction, error);
|
|
3902
|
+
return createHandledRejection(error);
|
|
3903
|
+
}
|
|
2936
3904
|
try {
|
|
2937
3905
|
bindToCurrentHydration(openTransaction);
|
|
2938
3906
|
assertHydrationSequenceCurrent(openTransaction);
|
|
@@ -3007,48 +3975,150 @@ function createWrappedSyncConfig(sourceSyncConfig, runtime, persistedReadiness)
|
|
|
3007
3975
|
return visibleAfterDurable(openTransaction, applied);
|
|
3008
3976
|
}
|
|
3009
3977
|
};
|
|
3978
|
+
const sourceParamsFor = (session) => {
|
|
3979
|
+
const active = () => !startupState.cleanedUp && session === sourceSession;
|
|
3980
|
+
const metadata = wrappedParams.metadata;
|
|
3981
|
+
return {
|
|
3982
|
+
...wrappedParams,
|
|
3983
|
+
markReady: () => {
|
|
3984
|
+
if (active()) wrappedParams.markReady();
|
|
3985
|
+
},
|
|
3986
|
+
markError: (error) => {
|
|
3987
|
+
if (active()) wrappedParams.markError(error);
|
|
3988
|
+
},
|
|
3989
|
+
begin: () => {
|
|
3990
|
+
if (active()) wrappedParams.begin();
|
|
3991
|
+
},
|
|
3992
|
+
write: (message) => {
|
|
3993
|
+
if (active()) wrappedParams.write(message);
|
|
3994
|
+
},
|
|
3995
|
+
truncate: () => {
|
|
3996
|
+
if (active()) wrappedParams.truncate();
|
|
3997
|
+
},
|
|
3998
|
+
commit: (signal) => active() ? wrappedParams.commit(signal) : true,
|
|
3999
|
+
metadata: metadata && {
|
|
4000
|
+
...metadata,
|
|
4001
|
+
row: {
|
|
4002
|
+
get: (key) => active() ? metadata.row.get(key) : void 0,
|
|
4003
|
+
set: (key, value) => {
|
|
4004
|
+
if (active()) metadata.row.set(key, value);
|
|
4005
|
+
},
|
|
4006
|
+
delete: (key) => {
|
|
4007
|
+
if (active()) metadata.row.delete(key);
|
|
4008
|
+
}
|
|
4009
|
+
},
|
|
4010
|
+
collection: {
|
|
4011
|
+
get: (key) => active() ? metadata.collection.get(key) : void 0,
|
|
4012
|
+
set: (key, value) => {
|
|
4013
|
+
if (active()) metadata.collection.set(key, value);
|
|
4014
|
+
},
|
|
4015
|
+
delete: (key) => {
|
|
4016
|
+
if (active()) metadata.collection.delete(key);
|
|
4017
|
+
},
|
|
4018
|
+
list: (prefix) => active() ? metadata.collection.list(prefix) : []
|
|
4019
|
+
},
|
|
4020
|
+
persistence: {
|
|
4021
|
+
...metadata.persistence,
|
|
4022
|
+
reserveCommitTurn: () => {
|
|
4023
|
+
if (active()) metadata.persistence.reserveCommitTurn();
|
|
4024
|
+
},
|
|
4025
|
+
hydrateBaseline: () => active() ? metadata.persistence.hydrateBaseline() : Promise.resolve(),
|
|
4026
|
+
startScopedRecovery: (resetMetadata) => active() ? metadata.persistence.startScopedRecovery?.(
|
|
4027
|
+
resetMetadata
|
|
4028
|
+
) ?? Promise.resolve() : Promise.resolve(),
|
|
4029
|
+
scanPersistedRows: (options) => active() ? metadata.persistence.scanPersistedRows(options) : Promise.resolve([]),
|
|
4030
|
+
resumeSnapshot: {
|
|
4031
|
+
certify: () => active() ? metadata.persistence.resumeSnapshot.certify() : Promise.resolve(),
|
|
4032
|
+
getKeySetEvidence: () => active() ? metadata.persistence.resumeSnapshot.getKeySetEvidence() : void 0,
|
|
4033
|
+
expectCurrentCommit: () => {
|
|
4034
|
+
if (active())
|
|
4035
|
+
metadata.persistence.resumeSnapshot.expectCurrentCommit();
|
|
4036
|
+
}
|
|
4037
|
+
}
|
|
4038
|
+
}
|
|
4039
|
+
}
|
|
4040
|
+
};
|
|
4041
|
+
};
|
|
3010
4042
|
let sourceResult = {};
|
|
3011
4043
|
let sourceResultSettled = false;
|
|
3012
4044
|
let activeSourceSyncEntry;
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
if (startupState.cleanedUp) {
|
|
3018
|
-
sourceResultSettled = true;
|
|
3019
|
-
return sourceResult;
|
|
3020
|
-
}
|
|
4045
|
+
let sourceCleanupPromise;
|
|
4046
|
+
let customRestartSequence = 0;
|
|
4047
|
+
const enterSourceSession = () => {
|
|
4048
|
+
const session = ++sourceSession;
|
|
3021
4049
|
let settleSourceSyncEntry;
|
|
3022
4050
|
activeSourceSyncEntry = new Promise((resolve) => {
|
|
3023
4051
|
settleSourceSyncEntry = resolve;
|
|
3024
4052
|
});
|
|
3025
4053
|
try {
|
|
3026
4054
|
sourceResult = normalizeSyncFnResult(
|
|
3027
|
-
sourceSyncConfig.sync(
|
|
4055
|
+
sourceSyncConfig.sync(
|
|
4056
|
+
runtime.hasManagedCacheGeneration() ? sourceParamsFor(session) : wrappedParams
|
|
4057
|
+
)
|
|
3028
4058
|
);
|
|
4059
|
+
if (!startupState.cleanedUp && sourceResult.loadSubset) {
|
|
4060
|
+
const ownerSource = sourceResult;
|
|
4061
|
+
const loadSubset = async (options) => {
|
|
4062
|
+
if (startupState.cleanedUp || session !== sourceSession) {
|
|
4063
|
+
throw new Error(`persisted sync source is no longer active`);
|
|
4064
|
+
}
|
|
4065
|
+
await ownerSource.loadSubset?.(
|
|
4066
|
+
options
|
|
4067
|
+
);
|
|
4068
|
+
};
|
|
4069
|
+
runtime.registerRemoteSubsetOwner(
|
|
4070
|
+
Object.assign(loadSubset, {
|
|
4071
|
+
unloadSubset: (options) => ownerSource.unloadSubset?.(
|
|
4072
|
+
options
|
|
4073
|
+
),
|
|
4074
|
+
onError: (error) => {
|
|
4075
|
+
if (session === sourceSession) reportUpstreamError(error);
|
|
4076
|
+
}
|
|
4077
|
+
})
|
|
4078
|
+
);
|
|
4079
|
+
}
|
|
3029
4080
|
} finally {
|
|
3030
4081
|
activeSourceSyncEntry = void 0;
|
|
3031
4082
|
settleSourceSyncEntry();
|
|
3032
4083
|
}
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
);
|
|
3041
|
-
}
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
4084
|
+
};
|
|
4085
|
+
const restartCustomSource = (cacheRotated) => {
|
|
4086
|
+
const restartSequence = ++customRestartSequence;
|
|
4087
|
+
if (!sourceCleanupPromise) {
|
|
4088
|
+
sourceSession++;
|
|
4089
|
+
const abortError = new db.SyncTransactionAbortedError();
|
|
4090
|
+
for (const transaction of transactionStack) {
|
|
4091
|
+
settlePublicationAdmissionWaiters(transaction, abortError);
|
|
4092
|
+
}
|
|
4093
|
+
transactionStack.length = 0;
|
|
4094
|
+
const retiredResult = sourceResult;
|
|
4095
|
+
sourceCleanupPromise = Promise.resolve().then(
|
|
4096
|
+
() => retiredResult.cleanup?.()
|
|
4097
|
+
);
|
|
4098
|
+
}
|
|
4099
|
+
return Promise.all([sourceCleanupPromise, cacheRotated]).then(() => {
|
|
4100
|
+
if (startupState.cleanedUp || restartSequence !== customRestartSequence)
|
|
4101
|
+
return;
|
|
4102
|
+
sourceCleanupPromise = void 0;
|
|
4103
|
+
enterSourceSession();
|
|
4104
|
+
runtime.setSourceRestartAfterScopedRecovery(
|
|
4105
|
+
sourceResult.restartAfterScopedRecovery ?? (runtime.hasManagedCacheGeneration() ? restartCustomSource : void 0)
|
|
3049
4106
|
);
|
|
4107
|
+
});
|
|
4108
|
+
};
|
|
4109
|
+
fullStartPromise = runtime.ensureStarted();
|
|
4110
|
+
void fullStartPromise.catch(() => void 0);
|
|
4111
|
+
sourceResultPromise = (async () => {
|
|
4112
|
+
await runtime.ensureStartupMetadataLoaded();
|
|
4113
|
+
if (startupState.cleanedUp) {
|
|
4114
|
+
sourceResultSettled = true;
|
|
4115
|
+
return sourceResult;
|
|
3050
4116
|
}
|
|
4117
|
+
enterSourceSession();
|
|
3051
4118
|
sourceResultSettled = true;
|
|
4119
|
+
runtime.setSourceRestartAfterScopedRecovery(
|
|
4120
|
+
sourceResult.restartAfterScopedRecovery ?? (runtime.hasManagedCacheGeneration() ? restartCustomSource : void 0)
|
|
4121
|
+
);
|
|
3052
4122
|
return sourceResult;
|
|
3053
4123
|
})();
|
|
3054
4124
|
resolveSourceResultAssigned();
|
|
@@ -3058,6 +4128,7 @@ function createWrappedSyncConfig(sourceSyncConfig, runtime, persistedReadiness)
|
|
|
3058
4128
|
return {
|
|
3059
4129
|
cleanup: async () => {
|
|
3060
4130
|
startupState.cleanedUp = true;
|
|
4131
|
+
runtime.clearSourceRestartAfterScopedRecovery();
|
|
3061
4132
|
const sourceSyncEntry = activeSourceSyncEntry;
|
|
3062
4133
|
const cleanupError = new db.SyncTransactionAbortedError();
|
|
3063
4134
|
for (const acquisition of acquisitions.values()) {
|
|
@@ -3071,7 +4142,7 @@ function createWrappedSyncConfig(sourceSyncConfig, runtime, persistedReadiness)
|
|
|
3071
4142
|
transactionStack.length = 0;
|
|
3072
4143
|
let sourceCleanup;
|
|
3073
4144
|
try {
|
|
3074
|
-
sourceCleanup = sourceSyncEntry ? sourceSyncEntry.then(() => sourceResult.cleanup?.()) : Promise.resolve(sourceResult.cleanup?.());
|
|
4145
|
+
sourceCleanup = sourceCleanupPromise ?? (sourceSyncEntry ? sourceSyncEntry.then(() => sourceResult.cleanup?.()) : Promise.resolve(sourceResult.cleanup?.()));
|
|
3075
4146
|
} catch (error) {
|
|
3076
4147
|
sourceCleanup = Promise.reject(error);
|
|
3077
4148
|
}
|
|
@@ -3098,31 +4169,63 @@ function createWrappedSyncConfig(sourceSyncConfig, runtime, persistedReadiness)
|
|
|
3098
4169
|
loadSubset: (options) => {
|
|
3099
4170
|
const acquisition = { forwarded: false };
|
|
3100
4171
|
acquisitions.set(options, acquisition);
|
|
3101
|
-
const forwardUpstream = (
|
|
4172
|
+
const forwardUpstream = (loadOptions) => {
|
|
3102
4173
|
if (startupState.cleanedUp || acquisitions.get(options) !== acquisition) {
|
|
3103
4174
|
return true;
|
|
3104
4175
|
}
|
|
3105
|
-
|
|
4176
|
+
const currentSource = sourceResult;
|
|
4177
|
+
if (!currentSource.loadSubset) return true;
|
|
3106
4178
|
acquisition.forwarded = true;
|
|
4179
|
+
acquisition.source = currentSource;
|
|
3107
4180
|
try {
|
|
3108
|
-
return
|
|
4181
|
+
return currentSource.loadSubset(loadOptions);
|
|
3109
4182
|
} catch (error) {
|
|
3110
4183
|
acquisition.forwarded = false;
|
|
4184
|
+
acquisition.source = void 0;
|
|
3111
4185
|
throw error;
|
|
3112
4186
|
}
|
|
3113
4187
|
};
|
|
3114
4188
|
const openTransaction = getOpenTransaction();
|
|
3115
4189
|
const needsPublicationAdmission = openTransaction?.reservesCommitTurn === true;
|
|
4190
|
+
let hydrationStarted = false;
|
|
4191
|
+
const raceRetainedLoad = (work2, canAbortPromptly = () => true) => {
|
|
4192
|
+
const signal = options.signal;
|
|
4193
|
+
const abortable = !signal ? work2 : new Promise((resolve, reject) => {
|
|
4194
|
+
const abortError = () => signal.reason ?? new db.SyncTransactionAbortedError();
|
|
4195
|
+
const onAbort = () => {
|
|
4196
|
+
if (canAbortPromptly()) reject(abortError());
|
|
4197
|
+
};
|
|
4198
|
+
signal.addEventListener(`abort`, onAbort, { once: true });
|
|
4199
|
+
if (signal.aborted) onAbort();
|
|
4200
|
+
void work2.then(
|
|
4201
|
+
() => {
|
|
4202
|
+
signal.removeEventListener(`abort`, onAbort);
|
|
4203
|
+
if (signal.aborted) reject(abortError());
|
|
4204
|
+
else resolve();
|
|
4205
|
+
},
|
|
4206
|
+
(error) => {
|
|
4207
|
+
signal.removeEventListener(`abort`, onAbort);
|
|
4208
|
+
reject(signal.aborted ? abortError() : error);
|
|
4209
|
+
}
|
|
4210
|
+
);
|
|
4211
|
+
});
|
|
4212
|
+
return abortable.catch((error) => {
|
|
4213
|
+
if (startupState.cleanedUp || acquisitions.get(options) !== acquisition) {
|
|
4214
|
+
return;
|
|
4215
|
+
}
|
|
4216
|
+
throw error;
|
|
4217
|
+
});
|
|
4218
|
+
};
|
|
3116
4219
|
const hydrated = sourceResultSettled && !needsPublicationAdmission ? runtime.loadHydratedSubset(
|
|
3117
4220
|
options,
|
|
3118
|
-
(loadOptions) => forwardUpstream(
|
|
4221
|
+
(loadOptions) => forwardUpstream(loadOptions)
|
|
3119
4222
|
) : void 0;
|
|
3120
4223
|
if (hydrated !== void 0) {
|
|
3121
|
-
return hydrated;
|
|
4224
|
+
return hydrated === true ? true : raceRetainedLoad(hydrated);
|
|
3122
4225
|
}
|
|
3123
|
-
|
|
4226
|
+
const work = (async () => {
|
|
3124
4227
|
await fullStartPromise;
|
|
3125
|
-
|
|
4228
|
+
await sourceResultPromise;
|
|
3126
4229
|
if (startupState.cleanedUp || acquisitions.get(options) !== acquisition) {
|
|
3127
4230
|
return;
|
|
3128
4231
|
}
|
|
@@ -3139,9 +4242,14 @@ function createWrappedSyncConfig(sourceSyncConfig, runtime, persistedReadiness)
|
|
|
3139
4242
|
}
|
|
3140
4243
|
return runtime.loadSubset(
|
|
3141
4244
|
options,
|
|
3142
|
-
(loadOptions) => forwardUpstream(
|
|
4245
|
+
(loadOptions) => forwardUpstream(loadOptions),
|
|
4246
|
+
() => {
|
|
4247
|
+
hydrationStarted = true;
|
|
4248
|
+
},
|
|
4249
|
+
() => !startupState.cleanedUp && acquisitions.get(options) === acquisition
|
|
3143
4250
|
);
|
|
3144
4251
|
})();
|
|
4252
|
+
return raceRetainedLoad(work, () => !hydrationStarted);
|
|
3145
4253
|
},
|
|
3146
4254
|
unloadSubset: (options) => {
|
|
3147
4255
|
const acquisition = acquisitions.get(options);
|
|
@@ -3149,7 +4257,7 @@ function createWrappedSyncConfig(sourceSyncConfig, runtime, persistedReadiness)
|
|
|
3149
4257
|
acquisitions.delete(options);
|
|
3150
4258
|
runtime.unloadSubset(
|
|
3151
4259
|
options,
|
|
3152
|
-
acquisition?.forwarded ?
|
|
4260
|
+
acquisition?.forwarded ? acquisition.source?.unloadSubset : void 0
|
|
3153
4261
|
);
|
|
3154
4262
|
}
|
|
3155
4263
|
};
|
|
@@ -3159,16 +4267,16 @@ function createWrappedSyncConfig(sourceSyncConfig, runtime, persistedReadiness)
|
|
|
3159
4267
|
function createLoopbackSyncConfig(runtime) {
|
|
3160
4268
|
return {
|
|
3161
4269
|
sync: (params) => {
|
|
3162
|
-
runtime.setSyncControls({
|
|
3163
|
-
begin: params.begin,
|
|
3164
|
-
write: params.write,
|
|
3165
|
-
commit: params.commit,
|
|
3166
|
-
truncate: params.truncate,
|
|
3167
|
-
metadata: params.metadata ?? null,
|
|
3168
|
-
markError: params.markError
|
|
3169
|
-
});
|
|
3170
4270
|
runtime.setCollection(
|
|
3171
|
-
params.collection
|
|
4271
|
+
params.collection,
|
|
4272
|
+
{
|
|
4273
|
+
begin: params.begin,
|
|
4274
|
+
write: params.write,
|
|
4275
|
+
commit: params.commit,
|
|
4276
|
+
truncate: params.truncate,
|
|
4277
|
+
metadata: params.metadata ?? null,
|
|
4278
|
+
markError: params.markError
|
|
4279
|
+
}
|
|
3172
4280
|
);
|
|
3173
4281
|
void runtime.ensureStarted().then(() => {
|
|
3174
4282
|
if (runtime.getCurrentTerminalFailure()) return;
|
|
@@ -3198,6 +4306,19 @@ function persistedCollectionOptions(options) {
|
|
|
3198
4306
|
);
|
|
3199
4307
|
}
|
|
3200
4308
|
const persistedReadinessTracker = networkTimeoutMs !== void 0 ? new PersistedReadinessTracker(networkTimeoutMs) : void 0;
|
|
4309
|
+
let claimed = false;
|
|
4310
|
+
let claimedRuntime;
|
|
4311
|
+
const claimOptions = () => {
|
|
4312
|
+
if (claimed) {
|
|
4313
|
+
throw new errors.InvalidPersistedCollectionConfigError(
|
|
4314
|
+
`Persisted options can create only one Collection. Create fresh options for each Collection.`
|
|
4315
|
+
);
|
|
4316
|
+
}
|
|
4317
|
+
claimed = true;
|
|
4318
|
+
return () => {
|
|
4319
|
+
if (!claimedRuntime?.hasBoundCollection()) claimed = false;
|
|
4320
|
+
};
|
|
4321
|
+
};
|
|
3201
4322
|
if (hasOwnSyncKey(options)) {
|
|
3202
4323
|
if (!isValidSyncConfig(options.sync)) {
|
|
3203
4324
|
throw new errors.InvalidSyncConfigError(
|
|
@@ -3224,8 +4345,10 @@ function persistedCollectionOptions(options) {
|
|
|
3224
4345
|
persistence2,
|
|
3225
4346
|
syncOptions.syncMode ?? `eager`,
|
|
3226
4347
|
collectionId2,
|
|
3227
|
-
persistedReadinessTracker
|
|
4348
|
+
persistedReadinessTracker,
|
|
4349
|
+
claimOptions
|
|
3228
4350
|
);
|
|
4351
|
+
claimedRuntime = runtime2;
|
|
3229
4352
|
const result2 = {
|
|
3230
4353
|
...syncOptions,
|
|
3231
4354
|
id: collectionId2,
|
|
@@ -3242,7 +4365,8 @@ function persistedCollectionOptions(options) {
|
|
|
3242
4365
|
() => persistedCollectionOptions({
|
|
3243
4366
|
...options,
|
|
3244
4367
|
id: collectionId2
|
|
3245
|
-
})
|
|
4368
|
+
}),
|
|
4369
|
+
claimOptions
|
|
3246
4370
|
);
|
|
3247
4371
|
}
|
|
3248
4372
|
const {
|
|
@@ -3265,8 +4389,10 @@ function persistedCollectionOptions(options) {
|
|
|
3265
4389
|
persistence,
|
|
3266
4390
|
localOnlyOptions.syncMode ?? `eager`,
|
|
3267
4391
|
localOnlyOptions.id ?? DEFAULT_DB_NAME,
|
|
3268
|
-
persistedReadinessTracker
|
|
4392
|
+
persistedReadinessTracker,
|
|
4393
|
+
claimOptions
|
|
3269
4394
|
);
|
|
4395
|
+
claimedRuntime = runtime;
|
|
3270
4396
|
const wrappedOnInsert = async (params) => {
|
|
3271
4397
|
const lifecycleGeneration = runtime.getLifecycleGeneration();
|
|
3272
4398
|
const handlerResult = localOnlyOptions.onInsert ? await localOnlyOptions.onInsert(
|
|
@@ -3330,7 +4456,8 @@ function persistedCollectionOptions(options) {
|
|
|
3330
4456
|
() => persistedCollectionOptions({
|
|
3331
4457
|
...options,
|
|
3332
4458
|
id: collectionId
|
|
3333
|
-
})
|
|
4459
|
+
}),
|
|
4460
|
+
claimOptions
|
|
3334
4461
|
);
|
|
3335
4462
|
}
|
|
3336
4463
|
function encodePersistedStorageKey(key) {
|
|
@@ -3392,8 +4519,11 @@ exports.SQLITE_DRIVER_SHARED_LOGICAL_SCHEDULING_KEY = SQLITE_DRIVER_SHARED_LOGIC
|
|
|
3392
4519
|
exports.SingleProcessCoordinator = SingleProcessCoordinator;
|
|
3393
4520
|
exports.createPersistedTableName = createPersistedTableName;
|
|
3394
4521
|
exports.decodePersistedStorageKey = decodePersistedStorageKey;
|
|
4522
|
+
exports.eagerStorageTarget = eagerStorageTarget;
|
|
3395
4523
|
exports.encodePersistedStorageKey = encodePersistedStorageKey;
|
|
3396
4524
|
exports.forwardSQLiteDriverSharedLogicalScheduling = forwardSQLiteDriverSharedLogicalScheduling;
|
|
4525
|
+
exports.managedStorageTarget = managedStorageTarget;
|
|
3397
4526
|
exports.persistedCollectionOptions = persistedCollectionOptions;
|
|
4527
|
+
exports.resolvePersistedStorageTarget = resolvePersistedStorageTarget;
|
|
3398
4528
|
exports.validatePersistedCollectionCoordinator = validatePersistedCollectionCoordinator;
|
|
3399
4529
|
//# sourceMappingURL=persisted.cjs.map
|