@tanstack/db-sqlite-persistence-core 0.4.7 → 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 +32 -3
- package/dist/cjs/broadcast-coordinator.cjs +282 -43
- package/dist/cjs/broadcast-coordinator.cjs.map +1 -1
- package/dist/cjs/broadcast-coordinator.d.cts +25 -8
- package/dist/cjs/committed-tx-anchor.cjs +9 -0
- package/dist/cjs/committed-tx-anchor.cjs.map +1 -0
- package/dist/cjs/committed-tx-anchor.d.cts +1 -0
- package/dist/cjs/index.cjs +4 -0
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/persisted.cjs +1599 -313
- package/dist/cjs/persisted.cjs.map +1 -1
- package/dist/cjs/persisted.d.cts +106 -13
- 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 +705 -64
- package/dist/cjs/sqlite-core-adapter.cjs.map +1 -1
- package/dist/cjs/sqlite-core-adapter.d.cts +53 -10
- package/dist/esm/broadcast-coordinator.d.ts +25 -8
- package/dist/esm/broadcast-coordinator.js +283 -44
- package/dist/esm/broadcast-coordinator.js.map +1 -1
- package/dist/esm/committed-tx-anchor.d.ts +1 -0
- package/dist/esm/committed-tx-anchor.js +9 -0
- package/dist/esm/committed-tx-anchor.js.map +1 -0
- package/dist/esm/index.js +6 -2
- package/dist/esm/persisted.d.ts +106 -13
- package/dist/esm/persisted.js +1601 -315
- 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 +53 -10
- package/dist/esm/sqlite-core-adapter.js +708 -67
- package/dist/esm/sqlite-core-adapter.js.map +1 -1
- package/package.json +2 -2
- package/src/broadcast-coordinator.ts +415 -62
- package/src/committed-tx-anchor.ts +13 -0
- package/src/persisted.ts +2283 -370
- package/src/remote-subset-wire.ts +12 -0
- package/src/sqlite-core-adapter.ts +891 -68
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;
|
|
@@ -393,7 +448,7 @@ function isTxCommittedPayload(payload) {
|
|
|
393
448
|
return false;
|
|
394
449
|
}
|
|
395
450
|
if (payload.requiresFullReload) {
|
|
396
|
-
return true;
|
|
451
|
+
return payload.reconciliationReload === void 0 || payload.reconciliationReload === true;
|
|
397
452
|
}
|
|
398
453
|
return Array.isArray(payload.changedRows) && Array.isArray(payload.deletedKeys) && (payload.rowMetadataMutations === void 0 || Array.isArray(payload.rowMetadataMutations)) && (payload.collectionMetadataMutations === void 0 || Array.isArray(payload.collectionMetadataMutations));
|
|
399
454
|
}
|
|
@@ -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,15 +513,184 @@ 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;
|
|
526
|
+
this.latestTerm = 0;
|
|
527
|
+
this.latestSeq = 0;
|
|
528
|
+
this.latestRowVersion = 0;
|
|
529
|
+
this.publicRowVersion = 0;
|
|
530
|
+
this.localTerm = 1;
|
|
531
|
+
this.localSeq = 0;
|
|
532
|
+
this.localRowVersion = 0;
|
|
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++;
|
|
459
659
|
this.latestTerm = 0;
|
|
460
660
|
this.latestSeq = 0;
|
|
461
661
|
this.latestRowVersion = 0;
|
|
462
662
|
this.localTerm = 1;
|
|
463
663
|
this.localSeq = 0;
|
|
464
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;
|
|
465
694
|
}
|
|
466
695
|
setSyncControls(syncControls) {
|
|
467
696
|
this.advanceLifecycle();
|
|
@@ -473,6 +702,12 @@ class PersistedCollectionRuntime {
|
|
|
473
702
|
commit: commit ? (signal) => this.trackAppliedReceipt(commit(signal)) : null
|
|
474
703
|
};
|
|
475
704
|
}
|
|
705
|
+
setSourceRestartAfterScopedRecovery(restart) {
|
|
706
|
+
this.restartSourceAfterScopedRecovery = restart ?? null;
|
|
707
|
+
}
|
|
708
|
+
clearSourceRestartAfterScopedRecovery() {
|
|
709
|
+
this.restartSourceAfterScopedRecovery = void 0;
|
|
710
|
+
}
|
|
476
711
|
reportSyncError(error) {
|
|
477
712
|
const markError = this.syncControls.markError;
|
|
478
713
|
if (this.syncErrorReported) return this.reportedSyncError;
|
|
@@ -487,8 +722,9 @@ class PersistedCollectionRuntime {
|
|
|
487
722
|
}
|
|
488
723
|
registerRemoteSubsetOwner(owner) {
|
|
489
724
|
this.remoteSubsetOwnerUnsubscribe?.();
|
|
725
|
+
this.remoteSubsetOwner = owner;
|
|
490
726
|
this.remoteSubsetOwnerUnsubscribe = this.persistence.coordinator.registerRemoteSubsetOwner(
|
|
491
|
-
this.
|
|
727
|
+
this.storageCollectionId,
|
|
492
728
|
owner
|
|
493
729
|
);
|
|
494
730
|
}
|
|
@@ -513,6 +749,23 @@ class PersistedCollectionRuntime {
|
|
|
513
749
|
)
|
|
514
750
|
);
|
|
515
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
|
+
}
|
|
516
769
|
clearSyncControls() {
|
|
517
770
|
this.syncControls = {
|
|
518
771
|
begin: null,
|
|
@@ -571,17 +824,46 @@ class PersistedCollectionRuntime {
|
|
|
571
824
|
isApplyingInternally() {
|
|
572
825
|
return this.internalApplyDepth > 0;
|
|
573
826
|
}
|
|
574
|
-
|
|
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);
|
|
575
847
|
if (this.collection === collection) {
|
|
848
|
+
this.boundCollection ??= collection;
|
|
576
849
|
return;
|
|
577
850
|
}
|
|
578
851
|
this.collection = collection;
|
|
579
|
-
|
|
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;
|
|
580
862
|
}
|
|
581
863
|
getLeadershipState() {
|
|
582
864
|
return {
|
|
583
865
|
nodeId: this.persistence.coordinator.getNodeId(),
|
|
584
|
-
isLeader: this.persistence.coordinator.isLeader(this.
|
|
866
|
+
isLeader: this.persistence.coordinator.isLeader(this.storageCollectionId)
|
|
585
867
|
};
|
|
586
868
|
}
|
|
587
869
|
runInHydrationScope(task, adapter = this.persistence.adapter) {
|
|
@@ -596,6 +878,7 @@ class PersistedCollectionRuntime {
|
|
|
596
878
|
return this.startPromise;
|
|
597
879
|
}
|
|
598
880
|
const lifecycleGeneration = this.lifecycleGeneration;
|
|
881
|
+
this.startupCacheGenerationRotated = false;
|
|
599
882
|
const isRestart = this.hasAttemptedStartup;
|
|
600
883
|
this.hasAttemptedStartup = true;
|
|
601
884
|
let resolveStartupMetadata;
|
|
@@ -606,63 +889,98 @@ class PersistedCollectionRuntime {
|
|
|
606
889
|
});
|
|
607
890
|
void this.startupMetadataPromise.catch(() => void 0);
|
|
608
891
|
this.startPromise = (async () => {
|
|
892
|
+
await this.claimCacheGeneration(lifecycleGeneration);
|
|
893
|
+
if (lifecycleGeneration !== this.lifecycleGeneration) {
|
|
894
|
+
resolveStartupMetadata();
|
|
895
|
+
return;
|
|
896
|
+
}
|
|
897
|
+
let metadataExpired = false;
|
|
609
898
|
const loadStartupMetadata = async (adapter) => {
|
|
610
899
|
if (lifecycleGeneration !== this.lifecycleGeneration) {
|
|
611
900
|
resolveStartupMetadata();
|
|
612
901
|
return false;
|
|
613
902
|
}
|
|
614
903
|
try {
|
|
615
|
-
await this.loadStartupMetadataInternal(
|
|
904
|
+
if (!await this.loadStartupMetadataInternal(
|
|
905
|
+
lifecycleGeneration,
|
|
906
|
+
adapter
|
|
907
|
+
)) {
|
|
908
|
+
metadataExpired = true;
|
|
909
|
+
return false;
|
|
910
|
+
}
|
|
616
911
|
return lifecycleGeneration === this.lifecycleGeneration;
|
|
617
912
|
} catch (error) {
|
|
913
|
+
if (this.cacheGenerationClaim && this.hasExpiredCacheClaim()) {
|
|
914
|
+
metadataExpired = true;
|
|
915
|
+
return false;
|
|
916
|
+
}
|
|
618
917
|
rejectStartupMetadata(error);
|
|
619
918
|
throw error;
|
|
620
919
|
}
|
|
621
920
|
};
|
|
622
921
|
let startup;
|
|
623
922
|
const scheduleStartupAsOneHydrate = this.persistence.adapter.runInHydrationScope !== void 0 && (!isRestart || (this.persistence.adapter.isHydrationScopeScheduled?.() ?? true));
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
adapter
|
|
631
|
-
|
|
632
|
-
|
|
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;
|
|
633
940
|
});
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
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
|
|
649
962
|
);
|
|
650
963
|
if (lifecycleGeneration === this.lifecycleGeneration) {
|
|
651
964
|
await this.flushQueuedTxCommittedUnsafe();
|
|
652
965
|
}
|
|
653
|
-
return result;
|
|
654
|
-
});
|
|
655
|
-
} else {
|
|
656
|
-
startup = await this.startInternal(
|
|
657
|
-
lifecycleGeneration,
|
|
658
|
-
this.persistence.adapter,
|
|
659
|
-
resolveStartupMetadata
|
|
660
|
-
);
|
|
661
|
-
if (lifecycleGeneration === this.lifecycleGeneration) {
|
|
662
|
-
await this.flushQueuedTxCommittedUnsafe();
|
|
663
966
|
}
|
|
664
967
|
}
|
|
665
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();
|
|
666
984
|
}
|
|
667
985
|
if (startup !== void 0 && lifecycleGeneration === this.lifecycleGeneration) {
|
|
668
986
|
await this.requestCoordinatorPersistedIndexes(
|
|
@@ -698,6 +1016,7 @@ class PersistedCollectionRuntime {
|
|
|
698
1016
|
await this.ensureStarted();
|
|
699
1017
|
if (lifecycleGeneration !== this.lifecycleGeneration) return;
|
|
700
1018
|
if (this.syncMode !== `on-demand`) return;
|
|
1019
|
+
if (this.scopedRecovery) return;
|
|
701
1020
|
const appliedCursor = await this.applyMutex.run(async () => {
|
|
702
1021
|
const result = await this.runInHydrationScope(
|
|
703
1022
|
(adapter) => this.hydrateBaseline(lifecycleGeneration, adapter)
|
|
@@ -707,6 +1026,10 @@ class PersistedCollectionRuntime {
|
|
|
707
1026
|
}
|
|
708
1027
|
return result;
|
|
709
1028
|
});
|
|
1029
|
+
if (appliedCursor === false) {
|
|
1030
|
+
await this.ensureCacheClaimActive();
|
|
1031
|
+
return;
|
|
1032
|
+
}
|
|
710
1033
|
if (appliedCursor !== void 0 && lifecycleGeneration === this.lifecycleGeneration) {
|
|
711
1034
|
await this.waitForAppliedReceiptsAfter(appliedCursor, `accepted`);
|
|
712
1035
|
}
|
|
@@ -723,14 +1046,32 @@ class PersistedCollectionRuntime {
|
|
|
723
1046
|
this.resumeCertificationPromise = (async () => {
|
|
724
1047
|
await this.ensureStarted();
|
|
725
1048
|
if (lifecycleGeneration !== this.lifecycleGeneration) return;
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
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;
|
|
734
1075
|
this.bindResumeSnapshotEvidence(snapshot);
|
|
735
1076
|
})();
|
|
736
1077
|
return this.resumeCertificationPromise;
|
|
@@ -738,6 +1079,162 @@ class PersistedCollectionRuntime {
|
|
|
738
1079
|
getKeySetEvidence() {
|
|
739
1080
|
return this.persistedKeySetEvidence;
|
|
740
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
|
+
}
|
|
741
1238
|
getResumeGenerationOwner() {
|
|
742
1239
|
return this.resumeGenerationOwner;
|
|
743
1240
|
}
|
|
@@ -746,7 +1243,7 @@ class PersistedCollectionRuntime {
|
|
|
746
1243
|
const baseline = {};
|
|
747
1244
|
this.activeSubsets.set(this.getSubsetKey(baseline), baseline);
|
|
748
1245
|
const appliedCursor = this.appliedReceiptSequence;
|
|
749
|
-
await this.hydrateSubsetUnsafe(
|
|
1246
|
+
const hydrated = await this.hydrateSubsetUnsafe(
|
|
750
1247
|
baseline,
|
|
751
1248
|
{
|
|
752
1249
|
lifecycleGeneration,
|
|
@@ -754,6 +1251,7 @@ class PersistedCollectionRuntime {
|
|
|
754
1251
|
},
|
|
755
1252
|
adapter
|
|
756
1253
|
);
|
|
1254
|
+
if (!hydrated) return false;
|
|
757
1255
|
return lifecycleGeneration === this.lifecycleGeneration ? appliedCursor : void 0;
|
|
758
1256
|
}
|
|
759
1257
|
async ensureStartupMetadataLoaded() {
|
|
@@ -773,19 +1271,33 @@ class PersistedCollectionRuntime {
|
|
|
773
1271
|
adapter
|
|
774
1272
|
);
|
|
775
1273
|
if (lifecycleGeneration !== this.lifecycleGeneration) return void 0;
|
|
776
|
-
|
|
1274
|
+
if (!this.cacheGenerationClaim || this.syncMode !== `on-demand`) {
|
|
1275
|
+
onStartupMetadataLoaded();
|
|
1276
|
+
}
|
|
777
1277
|
const appliedCursor = this.syncMode !== `on-demand` ? await this.hydrateBaseline(lifecycleGeneration, adapter) : void 0;
|
|
778
1278
|
return lifecycleGeneration === this.lifecycleGeneration ? {
|
|
779
|
-
appliedCursor,
|
|
1279
|
+
appliedCursor: appliedCursor === false ? void 0 : appliedCursor,
|
|
780
1280
|
indexBootstrapSnapshot,
|
|
781
1281
|
completedLocalIndexSignatures
|
|
782
1282
|
} : void 0;
|
|
783
1283
|
}
|
|
784
1284
|
async loadStartupMetadataInternal(lifecycleGeneration, adapter) {
|
|
785
|
-
const
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
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;
|
|
789
1301
|
this.persistedResumeGeneration = this.getResumeSnapshotGeneration(snapshot);
|
|
790
1302
|
this.persistedKeySetEvidence = snapshot.keySet;
|
|
791
1303
|
this.observeStreamPosition(
|
|
@@ -797,36 +1309,42 @@ class PersistedCollectionRuntime {
|
|
|
797
1309
|
snapshot.collectionMetadata
|
|
798
1310
|
);
|
|
799
1311
|
await db.whenSyncAccepted(applied);
|
|
1312
|
+
return true;
|
|
800
1313
|
}
|
|
801
1314
|
async loadCollectionMetadataSnapshot(adapter) {
|
|
802
1315
|
if (!adapter.loadCollectionMetadata) {
|
|
803
1316
|
return [];
|
|
804
1317
|
}
|
|
805
|
-
return adapter.loadCollectionMetadata(this.
|
|
1318
|
+
return adapter.loadCollectionMetadata(this.storageTargetFor(), {
|
|
1319
|
+
cacheGenerationClaimId: this.cacheGenerationClaim?.claimId
|
|
1320
|
+
});
|
|
806
1321
|
}
|
|
807
1322
|
replaceCollectionMetadataSnapshot(collectionMetadata) {
|
|
808
1323
|
if (!this.syncControls.begin || !this.syncControls.commit || !this.syncControls.metadata) {
|
|
809
1324
|
return true;
|
|
810
1325
|
}
|
|
811
|
-
const nextMetadata = new Map(
|
|
812
|
-
collectionMetadata.map(({ key, value }) => [key, value])
|
|
813
|
-
);
|
|
814
|
-
const currentKeys = this.syncControls.metadata.collection.list().map(({ key }) => key);
|
|
815
1326
|
return this.withInternalApply(() => {
|
|
816
1327
|
this.syncControls.begin?.();
|
|
817
|
-
|
|
818
|
-
if (!nextMetadata.has(key)) {
|
|
819
|
-
this.syncControls.metadata?.collection.delete(key);
|
|
820
|
-
}
|
|
821
|
-
});
|
|
822
|
-
nextMetadata.forEach((value, key) => {
|
|
823
|
-
this.syncControls.metadata?.collection.set(key, value);
|
|
824
|
-
});
|
|
1328
|
+
this.applyCollectionMetadataSnapshot(collectionMetadata);
|
|
825
1329
|
return this.syncControls.commit?.() ?? true;
|
|
826
1330
|
});
|
|
827
1331
|
}
|
|
828
|
-
|
|
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) {
|
|
829
1344
|
this.throwIfTerminal();
|
|
1345
|
+
await this.ensureCacheClaimActive();
|
|
1346
|
+
if (options.signal?.aborted || isDemandRetained && !isDemandRetained())
|
|
1347
|
+
return;
|
|
830
1348
|
const lifecycleGeneration = this.lifecycleGeneration;
|
|
831
1349
|
const subsetKey = this.getSubsetKey(options);
|
|
832
1350
|
const truncateGeneration = this.sourceTruncateGeneration;
|
|
@@ -835,25 +1353,52 @@ class PersistedCollectionRuntime {
|
|
|
835
1353
|
this.activeSubsets.set(subsetKey, options);
|
|
836
1354
|
const appliedCursor = this.appliedReceiptSequence;
|
|
837
1355
|
try {
|
|
838
|
-
await this.applyMutex.run(
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
{
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
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
|
+
}
|
|
854
1381
|
}
|
|
855
1382
|
}
|
|
856
|
-
|
|
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
|
+
}
|
|
857
1402
|
if (lifecycleGeneration !== this.lifecycleGeneration) return;
|
|
858
1403
|
await this.waitForAppliedReceiptsAfter(appliedCursor);
|
|
859
1404
|
if (!routeRemoteDemandDuringHydration && this.canRouteRemoteDemandThroughCoordinator()) {
|
|
@@ -866,13 +1411,16 @@ class PersistedCollectionRuntime {
|
|
|
866
1411
|
throw error;
|
|
867
1412
|
}
|
|
868
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
|
+
}
|
|
869
1417
|
return;
|
|
870
1418
|
}
|
|
871
1419
|
if (this.canRouteRemoteDemandThroughCoordinator()) {
|
|
872
1420
|
try {
|
|
873
1421
|
await this.persistence.coordinator.requestEnsureRemoteSubset(
|
|
874
|
-
this.
|
|
875
|
-
options
|
|
1422
|
+
this.storageCollectionId,
|
|
1423
|
+
this.rotatedAcquisitions.get(options) ?? options
|
|
876
1424
|
);
|
|
877
1425
|
} catch (error) {
|
|
878
1426
|
if (options.signal?.aborted || typeof error === `object` && error !== null && `name` in error && error.name === `AbortError`) {
|
|
@@ -882,6 +1430,7 @@ class PersistedCollectionRuntime {
|
|
|
882
1430
|
this.queueRemoteSubsetEnsure(options);
|
|
883
1431
|
throw error;
|
|
884
1432
|
}
|
|
1433
|
+
await this.applyLeaderCommitsBeforeReply(options);
|
|
885
1434
|
return;
|
|
886
1435
|
}
|
|
887
1436
|
if (truncateGeneration === this.sourceTruncateGeneration && this.activeSubsets.get(subsetKey) === options) {
|
|
@@ -899,7 +1448,7 @@ class PersistedCollectionRuntime {
|
|
|
899
1448
|
loadHydratedSubset(options, upstreamLoadSubset) {
|
|
900
1449
|
const failure = this.getCurrentTerminalFailure();
|
|
901
1450
|
if (failure) return Promise.reject(failure.error);
|
|
902
|
-
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))) {
|
|
903
1452
|
return;
|
|
904
1453
|
}
|
|
905
1454
|
this.activeSubsets.set(this.getSubsetKey(options), options);
|
|
@@ -939,13 +1488,15 @@ class PersistedCollectionRuntime {
|
|
|
939
1488
|
const subsetKey = this.getSubsetKey(options);
|
|
940
1489
|
this.activeSubsets.delete(subsetKey);
|
|
941
1490
|
this.pendingRemoteSubsetEnsures.delete(subsetKey);
|
|
1491
|
+
const remoteOptions = this.rotatedAcquisitions.get(options) ?? options;
|
|
1492
|
+
this.rotatedAcquisitions.delete(options);
|
|
942
1493
|
const demandKey = db.getLoadSubsetDemandKey(options);
|
|
943
1494
|
const stillActive = Array.from(this.activeSubsets.values()).some(
|
|
944
1495
|
(active) => db.getLoadSubsetDemandKey(active) === demandKey
|
|
945
1496
|
);
|
|
946
1497
|
if (!stillActive) this.hydratedDemands.delete(demandKey);
|
|
947
1498
|
if (this.mode === `sync-present`) {
|
|
948
|
-
void this.persistence.coordinator.requestReleaseRemoteSubset(this.
|
|
1499
|
+
void this.persistence.coordinator.requestReleaseRemoteSubset(this.storageCollectionId, remoteOptions).catch((error) => {
|
|
949
1500
|
this.reportSyncError(error);
|
|
950
1501
|
});
|
|
951
1502
|
}
|
|
@@ -962,9 +1513,10 @@ class PersistedCollectionRuntime {
|
|
|
962
1513
|
}
|
|
963
1514
|
async forceReloadSubset(options) {
|
|
964
1515
|
this.throwIfTerminal();
|
|
1516
|
+
await this.ensureCacheClaimActive();
|
|
965
1517
|
const lifecycleGeneration = this.lifecycleGeneration;
|
|
966
|
-
await this.applyMutex.run(async () => {
|
|
967
|
-
await this.runInHydrationScope(
|
|
1518
|
+
const hydrated = await this.applyMutex.run(async () => {
|
|
1519
|
+
const result = await this.runInHydrationScope(
|
|
968
1520
|
(adapter) => this.hydrateSubsetUnsafe(
|
|
969
1521
|
options,
|
|
970
1522
|
{
|
|
@@ -977,7 +1529,9 @@ class PersistedCollectionRuntime {
|
|
|
977
1529
|
if (lifecycleGeneration === this.lifecycleGeneration) {
|
|
978
1530
|
await this.flushQueuedTxCommittedUnsafe();
|
|
979
1531
|
}
|
|
1532
|
+
return result;
|
|
980
1533
|
});
|
|
1534
|
+
if (!hydrated) await this.ensureCacheClaimActive();
|
|
981
1535
|
}
|
|
982
1536
|
queueHydrationBufferedTransaction(transaction) {
|
|
983
1537
|
const failure = this.getCurrentTerminalFailure();
|
|
@@ -985,11 +1539,13 @@ class PersistedCollectionRuntime {
|
|
|
985
1539
|
transaction.rejectApplied?.(failure.error);
|
|
986
1540
|
return;
|
|
987
1541
|
}
|
|
1542
|
+
transaction.admittedStorageCollectionId = this.storageCollectionId;
|
|
988
1543
|
this.queuedHydrationTransactions.push(transaction);
|
|
989
1544
|
}
|
|
990
1545
|
applyHydrationBufferedTransaction(transaction) {
|
|
991
1546
|
const failure = this.getCurrentTerminalFailure();
|
|
992
1547
|
if (failure) return Promise.reject(failure.error);
|
|
1548
|
+
transaction.admittedStorageCollectionId = this.storageCollectionId;
|
|
993
1549
|
return this.applyMutex.run(async () => {
|
|
994
1550
|
await this.applyBufferedSyncTransactionUnsafe(transaction);
|
|
995
1551
|
});
|
|
@@ -1094,7 +1650,10 @@ class PersistedCollectionRuntime {
|
|
|
1094
1650
|
for (const options of this.activeSubsets.values()) {
|
|
1095
1651
|
attempt(() => {
|
|
1096
1652
|
remoteReleases.push(
|
|
1097
|
-
this.persistence.coordinator.requestReleaseRemoteSubset(
|
|
1653
|
+
this.persistence.coordinator.requestReleaseRemoteSubset(
|
|
1654
|
+
this.storageCollectionId,
|
|
1655
|
+
this.rotatedAcquisitions.get(options) ?? options
|
|
1656
|
+
).catch((error) => {
|
|
1098
1657
|
this.reportSyncError(error);
|
|
1099
1658
|
})
|
|
1100
1659
|
);
|
|
@@ -1105,6 +1664,7 @@ class PersistedCollectionRuntime {
|
|
|
1105
1664
|
this.coordinatorUnsubscribe = null;
|
|
1106
1665
|
attempt(() => this.remoteSubsetOwnerUnsubscribe?.());
|
|
1107
1666
|
this.remoteSubsetOwnerUnsubscribe = null;
|
|
1667
|
+
this.remoteSubsetOwner = void 0;
|
|
1108
1668
|
attempt(() => this.indexAddedUnsubscribe?.());
|
|
1109
1669
|
this.indexAddedUnsubscribe = null;
|
|
1110
1670
|
attempt(() => this.indexRemovedUnsubscribe?.());
|
|
@@ -1114,6 +1674,16 @@ class PersistedCollectionRuntime {
|
|
|
1114
1674
|
this.remoteEnsureRetryTimer = null;
|
|
1115
1675
|
}
|
|
1116
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();
|
|
1117
1687
|
this.activeSubsets.clear();
|
|
1118
1688
|
this.hydratedDemands.clear();
|
|
1119
1689
|
for (const transaction of this.queuedHydrationTransactions) {
|
|
@@ -1123,6 +1693,15 @@ class PersistedCollectionRuntime {
|
|
|
1123
1693
|
this.queuedTxCommitted.length = 0;
|
|
1124
1694
|
this.clearSyncControls();
|
|
1125
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
|
+
}
|
|
1126
1705
|
return Promise.all(remoteReleases).then(() => {
|
|
1127
1706
|
if (failures.length === 1) throw failures[0];
|
|
1128
1707
|
if (failures.length > 1) {
|
|
@@ -1134,8 +1713,16 @@ class PersistedCollectionRuntime {
|
|
|
1134
1713
|
}
|
|
1135
1714
|
advanceLifecycle() {
|
|
1136
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;
|
|
1137
1722
|
this.persistedReadiness?.set({ status: `loading` });
|
|
1138
1723
|
this.startupSettled = false;
|
|
1724
|
+
this.publicRowVersion = 0;
|
|
1725
|
+
this.resetTermFence = void 0;
|
|
1139
1726
|
this.hydratedDemands.clear();
|
|
1140
1727
|
this.startupMetadataPromise = null;
|
|
1141
1728
|
this.startPromise = null;
|
|
@@ -1161,25 +1748,44 @@ class PersistedCollectionRuntime {
|
|
|
1161
1748
|
return this.collection.getIndexMetadata().map((metadata) => metadata.signature);
|
|
1162
1749
|
}
|
|
1163
1750
|
loadSubsetRowsUnsafe(options, adapter) {
|
|
1164
|
-
|
|
1165
|
-
|
|
1751
|
+
if (this.scopedRecovery) return Promise.resolve([]);
|
|
1752
|
+
return adapter.loadSubset(this.storageTargetFor(), options, {
|
|
1753
|
+
requiredIndexSignatures: this.getRequiredIndexSignatures(),
|
|
1754
|
+
cacheGenerationClaimId: this.cacheGenerationClaim?.claimId
|
|
1166
1755
|
});
|
|
1167
1756
|
}
|
|
1168
1757
|
async scanPersistedRowsUnsafe(options) {
|
|
1169
1758
|
if (!this.persistence.adapter.scanRows) {
|
|
1170
1759
|
return [];
|
|
1171
1760
|
}
|
|
1172
|
-
return this.persistence.adapter.scanRows(
|
|
1173
|
-
this.
|
|
1174
|
-
|
|
1175
|
-
);
|
|
1761
|
+
return this.persistence.adapter.scanRows(this.storageTargetFor(), options, {
|
|
1762
|
+
cacheGenerationClaimId: this.cacheGenerationClaim?.claimId
|
|
1763
|
+
});
|
|
1176
1764
|
}
|
|
1177
1765
|
async scanPersistedRows(options) {
|
|
1178
|
-
|
|
1179
|
-
|
|
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
|
+
}
|
|
1180
1785
|
}
|
|
1181
1786
|
async hydrateSubsetUnsafe(options, config, adapter) {
|
|
1182
1787
|
let rowsLoaded = false;
|
|
1788
|
+
let boundRowVersion;
|
|
1183
1789
|
let replayFailure;
|
|
1184
1790
|
const resetSequence = this.resetSequence;
|
|
1185
1791
|
this.hydrationSequence++;
|
|
@@ -1190,38 +1796,68 @@ class PersistedCollectionRuntime {
|
|
|
1190
1796
|
this.hydratingGeneration = config.lifecycleGeneration;
|
|
1191
1797
|
try {
|
|
1192
1798
|
let rows;
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
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
|
+
);
|
|
1198
1809
|
rows = snapshot.rows;
|
|
1199
|
-
if (config.lifecycleGeneration !== this.lifecycleGeneration)
|
|
1200
|
-
|
|
1201
|
-
this.
|
|
1810
|
+
if (config.lifecycleGeneration !== this.lifecycleGeneration)
|
|
1811
|
+
return true;
|
|
1812
|
+
if (resetSequence !== this.resetSequence) return true;
|
|
1813
|
+
bindResumeSnapshot = () => this.bindResumeSnapshotEvidence(snapshot);
|
|
1814
|
+
boundRowVersion = snapshot.latestRowVersion;
|
|
1202
1815
|
} else {
|
|
1203
1816
|
rows = await this.loadSubsetRowsUnsafe(options, adapter);
|
|
1204
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;
|
|
1205
1820
|
rowsLoaded = true;
|
|
1206
|
-
if (config.lifecycleGeneration !== this.lifecycleGeneration) return;
|
|
1207
|
-
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?.();
|
|
1208
1829
|
if (!config.bindKeySetEvidence || this.persistedKeySetEvidence?.status !== `incompatible`) {
|
|
1209
1830
|
for (const row of rows) {
|
|
1210
1831
|
hydrationContext.suppliedRowKeys.add(row.key);
|
|
1211
1832
|
}
|
|
1212
|
-
const applied = this.applyRowsToCollection(rows);
|
|
1833
|
+
const applied = this.applyRowsToCollection(rows, validationRows);
|
|
1213
1834
|
await db.whenSyncAccepted(applied);
|
|
1835
|
+
if (boundRowVersion !== void 0) {
|
|
1836
|
+
this.publicRowVersion = Math.max(
|
|
1837
|
+
this.publicRowVersion,
|
|
1838
|
+
boundRowVersion
|
|
1839
|
+
);
|
|
1840
|
+
}
|
|
1214
1841
|
}
|
|
1215
1842
|
} finally {
|
|
1216
1843
|
if (this.activeHydrationContext === hydrationContext && this.hydratingGeneration === config.lifecycleGeneration) {
|
|
1217
1844
|
this.hydratingGeneration = null;
|
|
1218
1845
|
}
|
|
1219
1846
|
}
|
|
1220
|
-
if (config.lifecycleGeneration !== this.lifecycleGeneration) return;
|
|
1221
|
-
|
|
1222
|
-
|
|
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;
|
|
1223
1852
|
} catch (error) {
|
|
1853
|
+
if (config.requestLocalLoadFailure && !rowsLoaded && this.cacheGenerationClaim && this.hasExpiredCacheClaim()) {
|
|
1854
|
+
return false;
|
|
1855
|
+
}
|
|
1224
1856
|
if (config.requestLocalLoadFailure && !rowsLoaded) {
|
|
1857
|
+
if (this.scopedRecoveryPending) {
|
|
1858
|
+
this.recoverQueuedAfterLocalLoadFailure = true;
|
|
1859
|
+
throw error;
|
|
1860
|
+
}
|
|
1225
1861
|
this.hydratingGeneration = config.lifecycleGeneration;
|
|
1226
1862
|
try {
|
|
1227
1863
|
await this.recoverBufferedTransactionsAfterLocalLoadFailureUnsafe(
|
|
@@ -1250,6 +1886,7 @@ class PersistedCollectionRuntime {
|
|
|
1250
1886
|
if (replayFailure && config.rejectBufferedReplayFailure) {
|
|
1251
1887
|
throw replayFailure.reason;
|
|
1252
1888
|
}
|
|
1889
|
+
return true;
|
|
1253
1890
|
}
|
|
1254
1891
|
async recoverBufferedTransactionsAfterLocalLoadFailureUnsafe(lifecycleGeneration, adapter) {
|
|
1255
1892
|
let snapshotRows;
|
|
@@ -1284,18 +1921,15 @@ class PersistedCollectionRuntime {
|
|
|
1284
1921
|
const key = operation.key;
|
|
1285
1922
|
if (operation.type === `update` && transaction.partialUpdateOperationIndexes.has(index) && getPresence(key) === `unknown`) {
|
|
1286
1923
|
if (snapshotRows === void 0) {
|
|
1287
|
-
const snapshot = await adapter.loadResumeSnapshot(
|
|
1288
|
-
this.
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
}
|
|
1293
|
-
);
|
|
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
|
+
});
|
|
1294
1929
|
this.throwIfLifecycleReplaced(lifecycleGeneration);
|
|
1295
1930
|
if (transaction.signal?.aborted) break;
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
);
|
|
1931
|
+
const rows = snapshot && !this.scopedRecovery ? snapshot.rows : [];
|
|
1932
|
+
snapshotRows = new Map(rows.map((row) => [row.key, row]));
|
|
1299
1933
|
}
|
|
1300
1934
|
const baseline = snapshotRows.get(key);
|
|
1301
1935
|
if (baseline === void 0) {
|
|
@@ -1345,22 +1979,65 @@ class PersistedCollectionRuntime {
|
|
|
1345
1979
|
}
|
|
1346
1980
|
this.queuedHydrationTransactions.length = 0;
|
|
1347
1981
|
}
|
|
1348
|
-
applyRowsToCollection(rows) {
|
|
1982
|
+
applyRowsToCollection(rows, validationRows, collectionMetadata) {
|
|
1349
1983
|
if (!this.syncControls.begin || !this.syncControls.write || !this.syncControls.commit) {
|
|
1350
1984
|
return true;
|
|
1351
1985
|
}
|
|
1352
1986
|
return this.withInternalApply(() => {
|
|
1353
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
|
+
}
|
|
1354
2023
|
for (const row of rows) {
|
|
1355
|
-
if (this.collection?._hasHydratedKey(row.key)) {
|
|
2024
|
+
if (validationRows && this.collection?.base.has(row.key) || this.collection?._hasHydratedKey(row.key)) {
|
|
1356
2025
|
continue;
|
|
1357
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
|
+
}
|
|
1358
2033
|
this.syncControls.write?.({
|
|
1359
2034
|
type: `update`,
|
|
1360
2035
|
value: row.value,
|
|
1361
2036
|
metadata: row.metadata
|
|
1362
2037
|
});
|
|
1363
2038
|
}
|
|
2039
|
+
if (collectionMetadata)
|
|
2040
|
+
this.applyCollectionMetadataSnapshot(collectionMetadata);
|
|
1364
2041
|
return this.syncControls.commit?.() ?? true;
|
|
1365
2042
|
});
|
|
1366
2043
|
}
|
|
@@ -1374,7 +2051,9 @@ class PersistedCollectionRuntime {
|
|
|
1374
2051
|
}
|
|
1375
2052
|
isExpectedResumeGeneration(generation) {
|
|
1376
2053
|
const expected = this.persistedResumeGeneration;
|
|
1377
|
-
return expected !== void 0 && expected.
|
|
2054
|
+
return expected !== void 0 && expected.latestRowVersion === generation.latestRowVersion && expected.resetEpoch === generation.resetEpoch && (expected.latestTerm === generation.latestTerm && expected.latestSeq === generation.latestSeq || // A no-write election reserves a new term at sequence zero. It does
|
|
2055
|
+
// not change the persisted rows or the source resume cursor.
|
|
2056
|
+
generation.latestTerm > expected.latestTerm && generation.latestSeq === 0);
|
|
1378
2057
|
}
|
|
1379
2058
|
bindResumeSnapshotEvidence(snapshot) {
|
|
1380
2059
|
const generation = this.getResumeSnapshotGeneration(snapshot);
|
|
@@ -1387,35 +2066,69 @@ class PersistedCollectionRuntime {
|
|
|
1387
2066
|
);
|
|
1388
2067
|
this.persistedKeySetEvidence = this.isExpectedResumeGeneration(generation) || mayAcceptUnownedGeneration ? snapshot.keySet : { status: `incompatible` };
|
|
1389
2068
|
}
|
|
1390
|
-
replaceCollectionSnapshot(rows, collectionMetadata) {
|
|
2069
|
+
replaceCollectionSnapshot(rows, collectionMetadata, replayDemand) {
|
|
1391
2070
|
if (!this.syncControls.begin || !this.syncControls.write || !this.syncControls.commit || !this.syncControls.metadata) {
|
|
1392
2071
|
return true;
|
|
1393
2072
|
}
|
|
1394
|
-
const
|
|
1395
|
-
|
|
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
|
+
)
|
|
1396
2078
|
);
|
|
1397
|
-
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);
|
|
1398
2082
|
return this.withInternalApply(() => {
|
|
1399
2083
|
this.syncControls.begin?.();
|
|
1400
|
-
|
|
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
|
+
}
|
|
1401
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
|
+
}
|
|
1402
2101
|
this.syncControls.write?.({
|
|
1403
2102
|
type: `update`,
|
|
1404
2103
|
value: row.value,
|
|
1405
2104
|
metadata: row.metadata
|
|
1406
2105
|
});
|
|
1407
2106
|
}
|
|
1408
|
-
|
|
1409
|
-
if (!nextMetadata.has(key)) {
|
|
1410
|
-
this.syncControls.metadata?.collection.delete(key);
|
|
1411
|
-
}
|
|
1412
|
-
});
|
|
1413
|
-
nextMetadata.forEach((value, key) => {
|
|
1414
|
-
this.syncControls.metadata?.collection.set(key, value);
|
|
1415
|
-
});
|
|
2107
|
+
this.applyCollectionMetadataSnapshot(collectionMetadata);
|
|
1416
2108
|
return this.syncControls.commit?.() ?? true;
|
|
1417
2109
|
});
|
|
1418
2110
|
}
|
|
2111
|
+
matchesCollectionSnapshot(rows, collectionMetadata) {
|
|
2112
|
+
if (!this.collection || !this.syncControls.metadata) return false;
|
|
2113
|
+
const base = this.collection.base;
|
|
2114
|
+
if (base.size !== rows.size) return false;
|
|
2115
|
+
for (const [key, row] of rows) {
|
|
2116
|
+
if (!base.has(key) || !db.equalPersistedSnapshotValues(base.get(key), row.value) || !db.equalPersistedSnapshotValues(
|
|
2117
|
+
this.syncControls.metadata.row.get(key),
|
|
2118
|
+
row.metadata
|
|
2119
|
+
)) {
|
|
2120
|
+
return false;
|
|
2121
|
+
}
|
|
2122
|
+
}
|
|
2123
|
+
const currentMetadata = this.syncControls.metadata.collection.list();
|
|
2124
|
+
if (currentMetadata.length !== collectionMetadata.length) return false;
|
|
2125
|
+
const expectedMetadata = new Map(
|
|
2126
|
+
collectionMetadata.map(({ key, value }) => [key, value])
|
|
2127
|
+
);
|
|
2128
|
+
return currentMetadata.every(
|
|
2129
|
+
({ key, value }) => expectedMetadata.has(key) && db.equalPersistedSnapshotValues(expectedMetadata.get(key), value)
|
|
2130
|
+
);
|
|
2131
|
+
}
|
|
1419
2132
|
async flushQueuedHydrationTransactionsUnsafe(adapter) {
|
|
1420
2133
|
let operationFailure;
|
|
1421
2134
|
while (this.queuedHydrationTransactions.length > 0) {
|
|
@@ -1443,6 +2156,31 @@ class PersistedCollectionRuntime {
|
|
|
1443
2156
|
async applyBufferedSyncTransactionUnsafe(transaction, scopedAdapter) {
|
|
1444
2157
|
this.throwIfTerminal();
|
|
1445
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
|
+
}
|
|
1446
2184
|
const abortedBeforeApplication = transaction.signal?.aborted === true;
|
|
1447
2185
|
let abortedDuringApplication = false;
|
|
1448
2186
|
let applicationReturned = false;
|
|
@@ -1500,16 +2238,56 @@ class PersistedCollectionRuntime {
|
|
|
1500
2238
|
if (!transaction.truncate && transaction.operations.length === 0 && transaction.rowMetadataWrites.size === 0 && transaction.collectionMetadataWrites.size === 0) {
|
|
1501
2239
|
return;
|
|
1502
2240
|
}
|
|
2241
|
+
const anchor = this.persistedResumeGeneration && {
|
|
2242
|
+
latestRowVersion: this.latestRowVersion,
|
|
2243
|
+
resetEpoch: this.persistedResumeGeneration.resetEpoch
|
|
2244
|
+
};
|
|
1503
2245
|
const streamPosition = this.nextLocalStreamPosition();
|
|
1504
2246
|
const tx = this.createPersistedTxFromOperations(transaction, streamPosition);
|
|
1505
|
-
const
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
2247
|
+
const storageCollectionId = this.storageCollectionId;
|
|
2248
|
+
let response;
|
|
2249
|
+
try {
|
|
2250
|
+
response = await this.persistence.coordinator.requestApplyCommittedTx(
|
|
2251
|
+
storageCollectionId,
|
|
2252
|
+
tx,
|
|
2253
|
+
// A hydration-scoped adapter is already inside the shared scheduler.
|
|
2254
|
+
// Ordinary source commits enter it before taking the writer lock.
|
|
2255
|
+
scopedAdapter
|
|
2256
|
+
);
|
|
2257
|
+
} catch (error) {
|
|
2258
|
+
if (!(error instanceof errors.IndeterminateCommitError) || !anchor || !this.persistence.coordinator.reconcileCommittedTx) {
|
|
2259
|
+
throw error;
|
|
2260
|
+
}
|
|
2261
|
+
let reconciled;
|
|
2262
|
+
try {
|
|
2263
|
+
reconciled = await this.persistence.coordinator.reconcileCommittedTx(
|
|
2264
|
+
storageCollectionId,
|
|
2265
|
+
tx,
|
|
2266
|
+
anchor,
|
|
2267
|
+
scopedAdapter
|
|
2268
|
+
);
|
|
2269
|
+
} catch (reconcileError) {
|
|
2270
|
+
if (reconcileError instanceof errors.PersistedCollectionDurabilityError) {
|
|
2271
|
+
throw reconcileError;
|
|
2272
|
+
}
|
|
2273
|
+
throw error;
|
|
2274
|
+
}
|
|
2275
|
+
if (!reconciled.ok) {
|
|
2276
|
+
if (reconciled.code === `PERSISTENCE_ERROR`) {
|
|
2277
|
+
throw new errors.PersistedCollectionDurabilityError(
|
|
2278
|
+
`Failed to reconcile collection "${this.collectionId}": ${reconciled.error}`,
|
|
2279
|
+
{
|
|
2280
|
+
cause: reconciled,
|
|
2281
|
+
code: reconciled.sourceCode ?? reconciled.code,
|
|
2282
|
+
path: reconciled.path
|
|
2283
|
+
}
|
|
2284
|
+
);
|
|
2285
|
+
}
|
|
2286
|
+
throw error;
|
|
2287
|
+
}
|
|
2288
|
+
this.recordDurableSourceCommit(transaction, reconciled.committed);
|
|
2289
|
+
return;
|
|
2290
|
+
}
|
|
1513
2291
|
if (!response.ok) {
|
|
1514
2292
|
if (response.code === `PERSISTENCE_ERROR`) {
|
|
1515
2293
|
const error = new errors.PersistedCollectionDurabilityError(
|
|
@@ -1526,18 +2304,24 @@ class PersistedCollectionRuntime {
|
|
|
1526
2304
|
`failed to apply external sync transaction through coordinator: ${response.error}`
|
|
1527
2305
|
);
|
|
1528
2306
|
}
|
|
2307
|
+
this.recordDurableSourceCommit(transaction, {
|
|
2308
|
+
term: response.term,
|
|
2309
|
+
seq: response.seq,
|
|
2310
|
+
rowVersion: response.latestRowVersion
|
|
2311
|
+
});
|
|
2312
|
+
}
|
|
2313
|
+
recordDurableSourceCommit(transaction, position) {
|
|
1529
2314
|
if (transaction.lifecycleGeneration !== this.lifecycleGeneration) return;
|
|
1530
|
-
this.observeStreamPosition(
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
);
|
|
2315
|
+
this.observeStreamPosition(position.term, position.seq, position.rowVersion);
|
|
2316
|
+
if (position.rowVersion === this.publicRowVersion + 1) {
|
|
2317
|
+
this.publicRowVersion = position.rowVersion;
|
|
2318
|
+
}
|
|
1535
2319
|
if (transaction.expectedResumeGenerationOwner === this.resumeGenerationOwner && this.persistedResumeGeneration !== void 0) {
|
|
1536
2320
|
this.persistedResumeGeneration = {
|
|
1537
2321
|
...this.persistedResumeGeneration,
|
|
1538
|
-
latestTerm:
|
|
1539
|
-
latestSeq:
|
|
1540
|
-
latestRowVersion:
|
|
2322
|
+
latestTerm: position.term,
|
|
2323
|
+
latestSeq: position.seq,
|
|
2324
|
+
latestRowVersion: position.rowVersion
|
|
1541
2325
|
};
|
|
1542
2326
|
}
|
|
1543
2327
|
}
|
|
@@ -1545,7 +2329,10 @@ class PersistedCollectionRuntime {
|
|
|
1545
2329
|
this.throwIfTerminal();
|
|
1546
2330
|
this.throwIfLifecycleReplaced(lifecycleGeneration);
|
|
1547
2331
|
try {
|
|
1548
|
-
await this.persistence.adapter.applyCommittedTx(
|
|
2332
|
+
await this.persistence.adapter.applyCommittedTx(
|
|
2333
|
+
this.storageTargetFor(),
|
|
2334
|
+
tx
|
|
2335
|
+
);
|
|
1549
2336
|
} catch (error) {
|
|
1550
2337
|
throw this.markTerminalFailure(
|
|
1551
2338
|
errors.toPersistedCollectionDurabilityError(this.collectionId, error),
|
|
@@ -1560,6 +2347,7 @@ class PersistedCollectionRuntime {
|
|
|
1560
2347
|
term: streamPosition.term,
|
|
1561
2348
|
seq: streamPosition.seq,
|
|
1562
2349
|
rowVersion: streamPosition.rowVersion,
|
|
2350
|
+
cacheGenerationClaimId: this.cacheGenerationClaim?.claimId,
|
|
1563
2351
|
truncate: transaction.truncate,
|
|
1564
2352
|
mutations: transaction.operations.map(
|
|
1565
2353
|
(operation) => operation.type === `update` ? {
|
|
@@ -1594,6 +2382,7 @@ class PersistedCollectionRuntime {
|
|
|
1594
2382
|
term: streamPosition.term,
|
|
1595
2383
|
seq: streamPosition.seq,
|
|
1596
2384
|
rowVersion: streamPosition.rowVersion,
|
|
2385
|
+
cacheGenerationClaimId: this.cacheGenerationClaim?.claimId,
|
|
1597
2386
|
mutations: mutations.map((mutation) => {
|
|
1598
2387
|
if (mutation.type === `delete`) {
|
|
1599
2388
|
return {
|
|
@@ -1653,14 +2442,16 @@ class PersistedCollectionRuntime {
|
|
|
1653
2442
|
async persistCollectionMutationsUnsafe(mutations, lifecycleGeneration) {
|
|
1654
2443
|
this.throwIfLifecycleReplaced(lifecycleGeneration);
|
|
1655
2444
|
if (this.persistence.coordinator.requestApplyLocalMutations) {
|
|
2445
|
+
const target = this.storageTargetFor();
|
|
1656
2446
|
const envelopeMutations = mutations.map(
|
|
1657
2447
|
(mutation) => toPersistedMutationEnvelope(
|
|
1658
2448
|
mutation
|
|
1659
2449
|
)
|
|
1660
2450
|
);
|
|
1661
2451
|
const response = await this.persistence.coordinator.requestApplyLocalMutations(
|
|
1662
|
-
this.
|
|
1663
|
-
envelopeMutations
|
|
2452
|
+
this.storageCollectionId,
|
|
2453
|
+
envelopeMutations,
|
|
2454
|
+
target.kind === `managed` ? target.claimId : void 0
|
|
1664
2455
|
);
|
|
1665
2456
|
this.throwIfLifecycleReplaced(lifecycleGeneration);
|
|
1666
2457
|
if (!response.ok) {
|
|
@@ -1757,12 +2548,12 @@ class PersistedCollectionRuntime {
|
|
|
1757
2548
|
const envelope = {
|
|
1758
2549
|
v: 1,
|
|
1759
2550
|
dbName: this.dbName,
|
|
1760
|
-
collectionId: this.
|
|
2551
|
+
collectionId: this.storageCollectionId,
|
|
1761
2552
|
senderId: this.persistence.coordinator.getNodeId(),
|
|
1762
2553
|
ts: Date.now(),
|
|
1763
2554
|
payload: txCommitted
|
|
1764
2555
|
};
|
|
1765
|
-
this.persistence.coordinator.publish(this.
|
|
2556
|
+
this.persistence.coordinator.publish(this.storageCollectionId, envelope);
|
|
1766
2557
|
}
|
|
1767
2558
|
observeStreamPosition(term, seq, rowVersion) {
|
|
1768
2559
|
if (term > this.latestTerm || term === this.latestTerm && seq > this.latestSeq) {
|
|
@@ -1804,7 +2595,7 @@ class PersistedCollectionRuntime {
|
|
|
1804
2595
|
if (this.getCurrentTerminalFailure() || this.mode !== `sync-present` || this.persistence.coordinator instanceof SingleProcessCoordinator) {
|
|
1805
2596
|
return false;
|
|
1806
2597
|
}
|
|
1807
|
-
return !this.persistence.coordinator.isLeader(this.
|
|
2598
|
+
return !this.persistence.coordinator.isLeader(this.storageCollectionId) || this.remoteSubsetOwnerUnsubscribe !== null;
|
|
1808
2599
|
}
|
|
1809
2600
|
queueRemoteSubsetEnsure(options) {
|
|
1810
2601
|
if (options.signal?.aborted || !this.canRouteRemoteDemandThroughCoordinator() || this.activeSubsets.get(this.getSubsetKey(options)) !== options) {
|
|
@@ -1843,8 +2634,8 @@ class PersistedCollectionRuntime {
|
|
|
1843
2634
|
}
|
|
1844
2635
|
try {
|
|
1845
2636
|
await this.persistence.coordinator.requestEnsureRemoteSubset(
|
|
1846
|
-
this.
|
|
1847
|
-
options
|
|
2637
|
+
this.storageCollectionId,
|
|
2638
|
+
this.rotatedAcquisitions.get(options) ?? options
|
|
1848
2639
|
);
|
|
1849
2640
|
this.pendingRemoteSubsetEnsures.delete(subsetKey);
|
|
1850
2641
|
} catch (error) {
|
|
@@ -1862,18 +2653,19 @@ class PersistedCollectionRuntime {
|
|
|
1862
2653
|
return;
|
|
1863
2654
|
}
|
|
1864
2655
|
this.persistence.coordinator.setAdapterForCollection?.(
|
|
1865
|
-
this.
|
|
1866
|
-
this.persistence.adapter
|
|
2656
|
+
this.storageCollectionId,
|
|
2657
|
+
this.persistence.adapter,
|
|
2658
|
+
this.cacheGenerationClaim?.claimId
|
|
1867
2659
|
);
|
|
1868
2660
|
this.coordinatorUnsubscribe = this.persistence.coordinator.subscribe(
|
|
1869
|
-
this.
|
|
2661
|
+
this.storageCollectionId,
|
|
1870
2662
|
(message) => {
|
|
1871
2663
|
this.onCoordinatorMessage(message);
|
|
1872
2664
|
}
|
|
1873
2665
|
);
|
|
1874
2666
|
}
|
|
1875
2667
|
onCoordinatorMessage(message) {
|
|
1876
|
-
if (message.collectionId !== this.
|
|
2668
|
+
if (message.collectionId !== this.storageCollectionId) {
|
|
1877
2669
|
return;
|
|
1878
2670
|
}
|
|
1879
2671
|
if (this.getCurrentTerminalFailure()) {
|
|
@@ -1882,17 +2674,24 @@ class PersistedCollectionRuntime {
|
|
|
1882
2674
|
const { payload } = message;
|
|
1883
2675
|
const lifecycleGeneration = this.lifecycleGeneration;
|
|
1884
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
|
+
}
|
|
1885
2682
|
if (isTxCommittedPayload(payload)) {
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
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);
|
|
1890
2693
|
});
|
|
1891
|
-
return;
|
|
1892
2694
|
}
|
|
1893
|
-
void this.applyMutex.run(() => this.processCommittedTxUnsafe(payload, lifecycleGeneration)).catch((error) => {
|
|
1894
|
-
this.markTerminalFailure(error, lifecycleGeneration);
|
|
1895
|
-
});
|
|
1896
2695
|
return;
|
|
1897
2696
|
}
|
|
1898
2697
|
if (isSelf) {
|
|
@@ -1900,9 +2699,18 @@ class PersistedCollectionRuntime {
|
|
|
1900
2699
|
}
|
|
1901
2700
|
if (isCollectionResetPayload(payload)) {
|
|
1902
2701
|
void this.applyMutex.run(async () => {
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
2702
|
+
if (message.collectionId !== this.storageCollectionId) return;
|
|
2703
|
+
await this.runInHydrationScope(async (adapter) => {
|
|
2704
|
+
await this.truncateAndReloadUnsafe(adapter, lifecycleGeneration);
|
|
2705
|
+
await this.rebaseDurableStreamFromSnapshotUnsafe(
|
|
2706
|
+
adapter,
|
|
2707
|
+
lifecycleGeneration
|
|
2708
|
+
);
|
|
2709
|
+
if (lifecycleGeneration === this.lifecycleGeneration) {
|
|
2710
|
+
this.publicRowVersion = 0;
|
|
2711
|
+
this.resetTermFence = this.latestTerm;
|
|
2712
|
+
}
|
|
2713
|
+
});
|
|
1906
2714
|
if (lifecycleGeneration === this.lifecycleGeneration) {
|
|
1907
2715
|
await this.flushQueuedTxCommittedUnsafe();
|
|
1908
2716
|
}
|
|
@@ -1913,33 +2721,63 @@ class PersistedCollectionRuntime {
|
|
|
1913
2721
|
}
|
|
1914
2722
|
async flushQueuedTxCommittedUnsafe() {
|
|
1915
2723
|
while (this.queuedTxCommitted.length > 0) {
|
|
2724
|
+
if (this.hasExpiredCacheClaim()) {
|
|
2725
|
+
this.scheduleCacheClaimRecovery(this.lifecycleGeneration);
|
|
2726
|
+
return;
|
|
2727
|
+
}
|
|
1916
2728
|
const queued = this.queuedTxCommitted.shift();
|
|
1917
2729
|
if (!queued) {
|
|
1918
2730
|
continue;
|
|
1919
2731
|
}
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
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
|
+
}
|
|
1924
2741
|
}
|
|
1925
2742
|
}
|
|
1926
2743
|
async processCommittedTxUnsafe(txCommitted, lifecycleGeneration = this.lifecycleGeneration) {
|
|
1927
2744
|
if (lifecycleGeneration !== this.lifecycleGeneration) return;
|
|
1928
|
-
if (
|
|
1929
|
-
|
|
2745
|
+
if (this.resetTermFence !== void 0) {
|
|
2746
|
+
if (txCommitted.term <= this.resetTermFence) {
|
|
2747
|
+
await this.runInHydrationScope(
|
|
2748
|
+
(adapter) => this.reloadActiveSubsetsUnsafe(adapter)
|
|
2749
|
+
);
|
|
2750
|
+
return;
|
|
2751
|
+
}
|
|
1930
2752
|
}
|
|
1931
|
-
if (txCommitted.
|
|
2753
|
+
if (txCommitted.requiresFullReload && txCommitted.reconciliationReload) {
|
|
2754
|
+
await this.invalidateFromCommittedTxUnsafe(
|
|
2755
|
+
txCommitted,
|
|
2756
|
+
this.persistence.adapter
|
|
2757
|
+
);
|
|
2758
|
+
if (lifecycleGeneration === this.lifecycleGeneration) {
|
|
2759
|
+
this.observeStreamPosition(
|
|
2760
|
+
txCommitted.term,
|
|
2761
|
+
txCommitted.seq,
|
|
2762
|
+
txCommitted.latestRowVersion
|
|
2763
|
+
);
|
|
2764
|
+
this.publicRowVersion = Math.max(
|
|
2765
|
+
this.publicRowVersion,
|
|
2766
|
+
txCommitted.latestRowVersion
|
|
2767
|
+
);
|
|
2768
|
+
await this.flushQueuedTxCommittedUnsafe();
|
|
2769
|
+
}
|
|
1932
2770
|
return;
|
|
1933
2771
|
}
|
|
2772
|
+
if (txCommitted.latestRowVersion <= this.publicRowVersion) return;
|
|
2773
|
+
const observedButUnpublished = txCommitted.term < this.latestTerm || txCommitted.term === this.latestTerm && txCommitted.seq <= this.latestSeq;
|
|
1934
2774
|
const hasGapInCurrentTerm = txCommitted.term === this.latestTerm && txCommitted.seq > this.latestSeq + 1;
|
|
1935
2775
|
const hasGapAcrossTerms = txCommitted.term > this.latestTerm && txCommitted.seq > 1;
|
|
1936
|
-
const hasGap = hasGapInCurrentTerm || hasGapAcrossTerms;
|
|
2776
|
+
const hasGap = observedButUnpublished || txCommitted.latestRowVersion > this.publicRowVersion + 1 || hasGapInCurrentTerm || hasGapAcrossTerms;
|
|
1937
2777
|
if (hasGap) {
|
|
1938
|
-
await this.recoverFromSeqGapUnsafe(lifecycleGeneration);
|
|
2778
|
+
if (!await this.recoverFromSeqGapUnsafe(lifecycleGeneration)) return;
|
|
1939
2779
|
if (lifecycleGeneration !== this.lifecycleGeneration) return;
|
|
1940
|
-
if (txCommitted.
|
|
1941
|
-
return;
|
|
1942
|
-
}
|
|
2780
|
+
if (txCommitted.latestRowVersion <= this.publicRowVersion) return;
|
|
1943
2781
|
}
|
|
1944
2782
|
this.observeStreamPosition(
|
|
1945
2783
|
txCommitted.term,
|
|
@@ -1951,23 +2789,49 @@ class PersistedCollectionRuntime {
|
|
|
1951
2789
|
this.persistence.adapter
|
|
1952
2790
|
);
|
|
1953
2791
|
if (lifecycleGeneration === this.lifecycleGeneration) {
|
|
2792
|
+
this.publicRowVersion = Math.max(
|
|
2793
|
+
this.publicRowVersion,
|
|
2794
|
+
txCommitted.latestRowVersion
|
|
2795
|
+
);
|
|
1954
2796
|
await this.flushQueuedTxCommittedUnsafe();
|
|
1955
2797
|
}
|
|
1956
2798
|
}
|
|
1957
|
-
async
|
|
2799
|
+
async rebaseDurableStreamFromSnapshotUnsafe(adapter, lifecycleGeneration) {
|
|
2800
|
+
if (lifecycleGeneration !== this.lifecycleGeneration) return;
|
|
2801
|
+
const snapshot = await adapter.loadResumeSnapshot(this.storageTargetFor(), {
|
|
2802
|
+
includeRows: false,
|
|
2803
|
+
cacheGenerationClaimId: this.cacheGenerationClaim?.claimId
|
|
2804
|
+
});
|
|
1958
2805
|
if (lifecycleGeneration !== this.lifecycleGeneration) return;
|
|
2806
|
+
this.latestRowVersion = snapshot.latestRowVersion;
|
|
2807
|
+
this.localRowVersion = snapshot.latestRowVersion;
|
|
2808
|
+
this.observeStreamPosition(
|
|
2809
|
+
snapshot.latestTerm,
|
|
2810
|
+
snapshot.latestSeq,
|
|
2811
|
+
snapshot.latestRowVersion
|
|
2812
|
+
);
|
|
2813
|
+
}
|
|
2814
|
+
async recoverFromSeqGapUnsafe(lifecycleGeneration) {
|
|
2815
|
+
if (lifecycleGeneration !== this.lifecycleGeneration) return false;
|
|
1959
2816
|
if (this.persistence.coordinator.pullSince && this.latestRowVersion >= 0) {
|
|
1960
2817
|
let pullResponse;
|
|
1961
2818
|
try {
|
|
1962
2819
|
pullResponse = await this.persistence.coordinator.pullSince(
|
|
1963
|
-
this.
|
|
1964
|
-
this.
|
|
2820
|
+
this.storageCollectionId,
|
|
2821
|
+
this.publicRowVersion,
|
|
2822
|
+
void 0,
|
|
2823
|
+
this.cacheGenerationClaim?.claimId
|
|
1965
2824
|
);
|
|
1966
2825
|
} catch (error) {
|
|
1967
2826
|
console.warn(`Failed pullSince recovery attempt:`, error);
|
|
1968
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
|
+
}
|
|
1969
2833
|
if (pullResponse) {
|
|
1970
|
-
if (lifecycleGeneration !== this.lifecycleGeneration) return;
|
|
2834
|
+
if (lifecycleGeneration !== this.lifecycleGeneration) return false;
|
|
1971
2835
|
if (pullResponse.ok) {
|
|
1972
2836
|
this.observeStreamPosition(
|
|
1973
2837
|
pullResponse.latestTerm,
|
|
@@ -1978,14 +2842,22 @@ class PersistedCollectionRuntime {
|
|
|
1978
2842
|
await this.runInHydrationScope(
|
|
1979
2843
|
(adapter) => this.reloadActiveSubsetsUnsafe(adapter)
|
|
1980
2844
|
);
|
|
1981
|
-
|
|
2845
|
+
this.publicRowVersion = Math.max(
|
|
2846
|
+
this.publicRowVersion,
|
|
2847
|
+
pullResponse.latestRowVersion
|
|
2848
|
+
);
|
|
2849
|
+
return true;
|
|
1982
2850
|
}
|
|
1983
2851
|
const deltas = pullResponse.deltas;
|
|
1984
2852
|
if (!deltas) {
|
|
1985
2853
|
await this.runInHydrationScope(
|
|
1986
2854
|
(adapter) => this.reloadActiveSubsetsUnsafe(adapter)
|
|
1987
2855
|
);
|
|
1988
|
-
|
|
2856
|
+
this.publicRowVersion = Math.max(
|
|
2857
|
+
this.publicRowVersion,
|
|
2858
|
+
pullResponse.latestRowVersion
|
|
2859
|
+
);
|
|
2860
|
+
return true;
|
|
1989
2861
|
}
|
|
1990
2862
|
await this.runInHydrationScope(async (adapter) => {
|
|
1991
2863
|
for (const delta of deltas) {
|
|
@@ -2008,25 +2880,69 @@ class PersistedCollectionRuntime {
|
|
|
2008
2880
|
if (lifecycleGeneration !== this.lifecycleGeneration) return;
|
|
2009
2881
|
}
|
|
2010
2882
|
});
|
|
2011
|
-
|
|
2883
|
+
this.publicRowVersion = Math.max(
|
|
2884
|
+
this.publicRowVersion,
|
|
2885
|
+
pullResponse.latestRowVersion
|
|
2886
|
+
);
|
|
2887
|
+
return true;
|
|
2012
2888
|
}
|
|
2013
2889
|
}
|
|
2014
2890
|
}
|
|
2015
|
-
if (lifecycleGeneration !== this.lifecycleGeneration) return;
|
|
2891
|
+
if (lifecycleGeneration !== this.lifecycleGeneration) return false;
|
|
2016
2892
|
await this.runInHydrationScope(
|
|
2017
|
-
(adapter) => this.
|
|
2893
|
+
(adapter) => this.reloadActiveSubsetsUnsafe(adapter)
|
|
2894
|
+
);
|
|
2895
|
+
this.publicRowVersion = Math.max(
|
|
2896
|
+
this.publicRowVersion,
|
|
2897
|
+
this.latestRowVersion
|
|
2018
2898
|
);
|
|
2019
2899
|
if (this.mode === `sync-present`) {
|
|
2020
2900
|
for (const options of this.activeSubsets.values()) {
|
|
2021
2901
|
this.queueRemoteSubsetEnsure(options);
|
|
2022
2902
|
}
|
|
2023
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
|
+
}
|
|
2024
2936
|
}
|
|
2025
2937
|
async truncateAndReloadUnsafe(adapter, lifecycleGeneration = this.lifecycleGeneration) {
|
|
2026
2938
|
if (lifecycleGeneration !== this.lifecycleGeneration) return;
|
|
2939
|
+
if (this.scopedRecovery) {
|
|
2940
|
+
this.refreshScopedSubsets();
|
|
2941
|
+
return;
|
|
2942
|
+
}
|
|
2027
2943
|
this.hydratedDemands.clear();
|
|
2028
2944
|
this.resetSequence++;
|
|
2029
|
-
if (this.syncControls.begin && this.syncControls.commit) {
|
|
2945
|
+
if (this.syncMode === `eager` && this.syncControls.begin && this.syncControls.commit) {
|
|
2030
2946
|
const applied = this.withInternalApply(() => {
|
|
2031
2947
|
this.syncControls.begin?.();
|
|
2032
2948
|
this.syncControls.truncate?.();
|
|
@@ -2035,15 +2951,24 @@ class PersistedCollectionRuntime {
|
|
|
2035
2951
|
await db.whenSyncAccepted(applied);
|
|
2036
2952
|
}
|
|
2037
2953
|
if (lifecycleGeneration !== this.lifecycleGeneration) return;
|
|
2038
|
-
await this.reloadActiveSubsetsUnsafe(
|
|
2954
|
+
await this.reloadActiveSubsetsUnsafe(
|
|
2955
|
+
adapter,
|
|
2956
|
+
false,
|
|
2957
|
+
this.syncMode === `eager`,
|
|
2958
|
+
true
|
|
2959
|
+
);
|
|
2039
2960
|
}
|
|
2040
2961
|
async invalidateFromCommittedTxUnsafe(txCommitted, adapter) {
|
|
2041
|
-
|
|
2042
|
-
|
|
2962
|
+
if (this.scopedRecovery) {
|
|
2963
|
+
this.refreshScopedSubsets();
|
|
2964
|
+
return;
|
|
2965
|
+
}
|
|
2966
|
+
const reloadActiveSubsets = (skipUnchangedReplace = false) => this.runInHydrationScope(
|
|
2967
|
+
(scopedAdapter) => this.reloadActiveSubsetsUnsafe(scopedAdapter, skipUnchangedReplace),
|
|
2043
2968
|
adapter
|
|
2044
2969
|
);
|
|
2045
2970
|
if (txCommitted.requiresFullReload) {
|
|
2046
|
-
await reloadActiveSubsets();
|
|
2971
|
+
await reloadActiveSubsets(txCommitted.reconciliationReload === true);
|
|
2047
2972
|
return;
|
|
2048
2973
|
}
|
|
2049
2974
|
const changedKeyCount = txCommitted.changedRows.length + txCommitted.deletedKeys.length;
|
|
@@ -2054,6 +2979,21 @@ class PersistedCollectionRuntime {
|
|
|
2054
2979
|
const hasPaginatedSubset = Array.from(this.activeSubsets.values()).some(
|
|
2055
2980
|
(opt) => opt.limit != null || opt.offset != null || opt.cursor != null
|
|
2056
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
|
+
}
|
|
2057
2997
|
if (!hasPaginatedSubset || changedKeyCount === 0) {
|
|
2058
2998
|
await this.applyTargetedInvalidationUnsafe(txCommitted);
|
|
2059
2999
|
return;
|
|
@@ -2070,20 +3010,33 @@ class PersistedCollectionRuntime {
|
|
|
2070
3010
|
key: changedKey,
|
|
2071
3011
|
value: newValue
|
|
2072
3012
|
} of txCommitted.changedRows) {
|
|
2073
|
-
const
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
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 });
|
|
2081
3030
|
}
|
|
2082
3031
|
}
|
|
2083
3032
|
for (const deletedKey of txCommitted.deletedKeys) {
|
|
2084
|
-
|
|
3033
|
+
const key = deletedKey;
|
|
3034
|
+
if (!this.collection?._hasHydratedKey(key)) {
|
|
3035
|
+
this.syncControls.write?.({ type: `delete`, key });
|
|
3036
|
+
}
|
|
2085
3037
|
}
|
|
2086
3038
|
txCommitted.rowMetadataMutations?.forEach((mutation) => {
|
|
3039
|
+
if (this.collection?._hasHydratedKey(mutation.key)) return;
|
|
2087
3040
|
if (mutation.type === `delete`) {
|
|
2088
3041
|
this.syncControls.metadata?.row.delete(mutation.key);
|
|
2089
3042
|
} else {
|
|
@@ -2107,10 +3060,25 @@ class PersistedCollectionRuntime {
|
|
|
2107
3060
|
});
|
|
2108
3061
|
await db.whenSyncAccepted(applied);
|
|
2109
3062
|
}
|
|
2110
|
-
async reloadActiveSubsetsUnsafe(adapter) {
|
|
3063
|
+
async reloadActiveSubsetsUnsafe(adapter, skipUnchangedReplace = false, restoreResetBaseline = false, replayDemand = false) {
|
|
3064
|
+
if (this.scopedRecovery) {
|
|
3065
|
+
this.refreshScopedSubsets();
|
|
3066
|
+
return;
|
|
3067
|
+
}
|
|
2111
3068
|
const lifecycleGeneration = this.lifecycleGeneration;
|
|
2112
3069
|
const truncateGeneration = this.sourceTruncateGeneration;
|
|
2113
|
-
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);
|
|
2114
3082
|
this.hydratedDemands.clear();
|
|
2115
3083
|
this.hydrationSequence++;
|
|
2116
3084
|
const hydrationContext = { suppliedRowKeys: /* @__PURE__ */ new Set() };
|
|
@@ -2118,30 +3086,91 @@ class PersistedCollectionRuntime {
|
|
|
2118
3086
|
this.hydratingGeneration = lifecycleGeneration;
|
|
2119
3087
|
try {
|
|
2120
3088
|
const mergedRows = /* @__PURE__ */ new Map();
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
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);
|
|
2125
3098
|
if (lifecycleGeneration !== this.lifecycleGeneration) return;
|
|
2126
|
-
for (const
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
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;
|
|
2131
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;
|
|
2132
3136
|
}
|
|
3137
|
+
if (this.isScopedRecoveryPending() || lifecycleGeneration !== this.lifecycleGeneration)
|
|
3138
|
+
return;
|
|
2133
3139
|
for (const key of mergedRows.keys()) {
|
|
2134
3140
|
hydrationContext.suppliedRowKeys.add(key);
|
|
2135
3141
|
}
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
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)) {
|
|
3151
|
+
const orphanKeys = this.collection?._syncedRowMetadataKeysOutsideBase() ?? [];
|
|
3152
|
+
if (orphanKeys.length > 0) {
|
|
3153
|
+
const applied = this.withInternalApply(() => {
|
|
3154
|
+
this.syncControls.begin?.();
|
|
3155
|
+
for (const key of orphanKeys) {
|
|
3156
|
+
this.syncControls.metadata?.row.delete(key);
|
|
3157
|
+
}
|
|
3158
|
+
return this.syncControls.commit?.() ?? true;
|
|
3159
|
+
});
|
|
3160
|
+
await db.whenSyncAccepted(applied);
|
|
3161
|
+
}
|
|
3162
|
+
} else {
|
|
3163
|
+
const applied = this.replaceCollectionSnapshot(
|
|
3164
|
+
Array.from(mergedRows.entries()).map(([key, row]) => ({
|
|
3165
|
+
key,
|
|
3166
|
+
value: row.value,
|
|
3167
|
+
metadata: row.metadata
|
|
3168
|
+
})),
|
|
3169
|
+
collectionMetadata,
|
|
3170
|
+
replayDemand
|
|
3171
|
+
);
|
|
3172
|
+
await db.whenSyncAccepted(applied);
|
|
3173
|
+
}
|
|
2145
3174
|
} finally {
|
|
2146
3175
|
if (this.activeHydrationContext === hydrationContext && this.hydratingGeneration === lifecycleGeneration) {
|
|
2147
3176
|
this.hydratingGeneration = null;
|
|
@@ -2166,7 +3195,12 @@ class PersistedCollectionRuntime {
|
|
|
2166
3195
|
return;
|
|
2167
3196
|
}
|
|
2168
3197
|
this.indexAddedUnsubscribe = this.collection.on(`index:added`, (event) => {
|
|
2169
|
-
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
|
+
});
|
|
2170
3204
|
});
|
|
2171
3205
|
this.indexRemovedUnsubscribe = this.collection.on(
|
|
2172
3206
|
`index:removed`,
|
|
@@ -2182,20 +3216,69 @@ class PersistedCollectionRuntime {
|
|
|
2182
3216
|
}
|
|
2183
3217
|
const indexMetadata = indexMetadataSnapshot ?? collection?.getIndexMetadata() ?? [];
|
|
2184
3218
|
const completedLocalIndexSignatures = /* @__PURE__ */ new Set();
|
|
3219
|
+
const lifecycleGeneration = this.lifecycleGeneration;
|
|
2185
3220
|
for (const metadata of indexMetadata) {
|
|
2186
|
-
|
|
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
|
+
)) {
|
|
2187
3232
|
completedLocalIndexSignatures.add(metadata.signature);
|
|
2188
3233
|
}
|
|
2189
3234
|
}
|
|
2190
3235
|
return completedLocalIndexSignatures;
|
|
2191
3236
|
}
|
|
2192
3237
|
async requestCoordinatorPersistedIndexes(indexMetadata, completedLocalIndexSignatures) {
|
|
3238
|
+
const lifecycleGeneration = this.lifecycleGeneration;
|
|
2193
3239
|
for (const metadata of indexMetadata) {
|
|
3240
|
+
if (!this.hasCurrentIndex(metadata)) continue;
|
|
3241
|
+
const storageCollectionId = this.storageCollectionId;
|
|
3242
|
+
const claimId = this.cacheGenerationClaim?.claimId;
|
|
2194
3243
|
await this.requestCoordinatorPersistedIndex(
|
|
2195
3244
|
metadata,
|
|
2196
3245
|
completedLocalIndexSignatures.has(metadata.signature)
|
|
2197
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;
|
|
2198
3280
|
}
|
|
3281
|
+
return false;
|
|
2199
3282
|
}
|
|
2200
3283
|
buildPersistedIndexSpec(index) {
|
|
2201
3284
|
return {
|
|
@@ -2208,19 +3291,36 @@ class PersistedCollectionRuntime {
|
|
|
2208
3291
|
};
|
|
2209
3292
|
}
|
|
2210
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;
|
|
2211
3299
|
const completedLocally = await this.ensureLocalPersistedIndex(
|
|
2212
3300
|
indexMetadata,
|
|
2213
3301
|
adapter
|
|
2214
3302
|
);
|
|
3303
|
+
if (lifecycleGeneration !== this.lifecycleGeneration) return;
|
|
3304
|
+
if (!this.hasCurrentIndex(indexMetadata)) {
|
|
3305
|
+
await this.markIndexRemoved(indexMetadata);
|
|
3306
|
+
return;
|
|
3307
|
+
}
|
|
2215
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;
|
|
2216
3315
|
}
|
|
2217
3316
|
async ensureLocalPersistedIndex(indexMetadata, adapter) {
|
|
2218
3317
|
try {
|
|
2219
3318
|
const spec = this.buildPersistedIndexSpec(indexMetadata);
|
|
2220
3319
|
await adapter.ensureIndex(
|
|
2221
|
-
this.
|
|
3320
|
+
this.storageTargetFor(),
|
|
2222
3321
|
indexMetadata.signature,
|
|
2223
|
-
spec
|
|
3322
|
+
spec,
|
|
3323
|
+
{ cacheGenerationClaimId: this.cacheGenerationClaim?.claimId }
|
|
2224
3324
|
);
|
|
2225
3325
|
return true;
|
|
2226
3326
|
} catch (error) {
|
|
@@ -2232,11 +3332,12 @@ class PersistedCollectionRuntime {
|
|
|
2232
3332
|
try {
|
|
2233
3333
|
const spec = this.buildPersistedIndexSpec(indexMetadata);
|
|
2234
3334
|
await this.persistence.coordinator.requestEnsurePersistedIndex(
|
|
2235
|
-
this.
|
|
3335
|
+
this.storageCollectionId,
|
|
2236
3336
|
indexMetadata.signature,
|
|
2237
3337
|
spec,
|
|
2238
3338
|
completedLocally ? this.persistence.adapter : void 0,
|
|
2239
|
-
completedLocally
|
|
3339
|
+
completedLocally,
|
|
3340
|
+
this.cacheGenerationClaim?.claimId
|
|
2240
3341
|
);
|
|
2241
3342
|
} catch (error) {
|
|
2242
3343
|
console.warn(
|
|
@@ -2250,10 +3351,19 @@ class PersistedCollectionRuntime {
|
|
|
2250
3351
|
return;
|
|
2251
3352
|
}
|
|
2252
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;
|
|
2253
3359
|
await this.persistence.adapter.markIndexRemoved(
|
|
2254
|
-
this.
|
|
2255
|
-
indexMetadata.signature
|
|
3360
|
+
this.storageTargetFor(),
|
|
3361
|
+
indexMetadata.signature,
|
|
3362
|
+
{ cacheGenerationClaimId: this.cacheGenerationClaim?.claimId }
|
|
2256
3363
|
);
|
|
3364
|
+
if (lifecycleGeneration === this.lifecycleGeneration && this.hasCurrentIndex(indexMetadata)) {
|
|
3365
|
+
await this.ensurePersistedIndex(indexMetadata);
|
|
3366
|
+
}
|
|
2257
3367
|
} catch (error) {
|
|
2258
3368
|
console.warn(`Failed to mark persisted index removed:`, error);
|
|
2259
3369
|
}
|
|
@@ -2456,20 +3566,26 @@ function createWrappedSyncConfig(sourceSyncConfig, runtime, persistedReadiness)
|
|
|
2456
3566
|
throw error;
|
|
2457
3567
|
}
|
|
2458
3568
|
};
|
|
2459
|
-
runtime.setSyncControls({
|
|
2460
|
-
begin: params.begin,
|
|
2461
|
-
write: params.write,
|
|
2462
|
-
commit: params.commit,
|
|
2463
|
-
truncate: params.truncate,
|
|
2464
|
-
metadata: params.metadata ?? null,
|
|
2465
|
-
markError: params.markError
|
|
2466
|
-
});
|
|
2467
3569
|
runtime.setCollection(
|
|
2468
|
-
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
|
+
}
|
|
2469
3579
|
);
|
|
2470
3580
|
const persistenceCapability = {
|
|
2471
3581
|
protocol: db.SYNC_PERSISTENCE_PROTOCOL,
|
|
2472
3582
|
version: db.SYNC_PERSISTENCE_VERSION,
|
|
3583
|
+
get managedCacheGeneration() {
|
|
3584
|
+
return runtime.hasManagedCacheGeneration();
|
|
3585
|
+
},
|
|
3586
|
+
get startupCacheGenerationRotated() {
|
|
3587
|
+
return runtime.hasRotatedStartupCacheGeneration();
|
|
3588
|
+
},
|
|
2473
3589
|
reserveCommitTurn: () => {
|
|
2474
3590
|
const openTransaction = getOpenTransaction();
|
|
2475
3591
|
if (openTransaction && !openTransaction.internal)
|
|
@@ -2483,6 +3599,7 @@ function createWrappedSyncConfig(sourceSyncConfig, runtime, persistedReadiness)
|
|
|
2483
3599
|
throw runtime.reportSyncError(error);
|
|
2484
3600
|
}
|
|
2485
3601
|
},
|
|
3602
|
+
startScopedRecovery: (resetMetadata) => startupState.cleanedUp ? Promise.resolve() : runtime.startScopedRecovery(resetMetadata),
|
|
2486
3603
|
scanPersistedRows: (options) => startupState.cleanedUp ? Promise.resolve([]) : runtime.scanPersistedRows(options),
|
|
2487
3604
|
resumeSnapshot: {
|
|
2488
3605
|
certify: async () => {
|
|
@@ -2506,6 +3623,7 @@ function createWrappedSyncConfig(sourceSyncConfig, runtime, persistedReadiness)
|
|
|
2506
3623
|
}
|
|
2507
3624
|
}
|
|
2508
3625
|
};
|
|
3626
|
+
let sourceSession = 0;
|
|
2509
3627
|
const wrappedParams = {
|
|
2510
3628
|
...params,
|
|
2511
3629
|
markError: reportUpstreamError,
|
|
@@ -2777,6 +3895,12 @@ function createWrappedSyncConfig(sourceSyncConfig, runtime, persistedReadiness)
|
|
|
2777
3895
|
settlePublicationAdmissionWaiters(openTransaction, error);
|
|
2778
3896
|
return createHandledRejection(error);
|
|
2779
3897
|
}
|
|
3898
|
+
if (runtime.isScopedRecoveryPending()) {
|
|
3899
|
+
settlePendingTransaction(openTransaction);
|
|
3900
|
+
const error = new db.SyncTransactionAbortedError();
|
|
3901
|
+
settlePublicationAdmissionWaiters(openTransaction, error);
|
|
3902
|
+
return createHandledRejection(error);
|
|
3903
|
+
}
|
|
2780
3904
|
try {
|
|
2781
3905
|
bindToCurrentHydration(openTransaction);
|
|
2782
3906
|
assertHydrationSequenceCurrent(openTransaction);
|
|
@@ -2851,48 +3975,150 @@ function createWrappedSyncConfig(sourceSyncConfig, runtime, persistedReadiness)
|
|
|
2851
3975
|
return visibleAfterDurable(openTransaction, applied);
|
|
2852
3976
|
}
|
|
2853
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
|
+
};
|
|
2854
4042
|
let sourceResult = {};
|
|
2855
4043
|
let sourceResultSettled = false;
|
|
2856
4044
|
let activeSourceSyncEntry;
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
if (startupState.cleanedUp) {
|
|
2862
|
-
sourceResultSettled = true;
|
|
2863
|
-
return sourceResult;
|
|
2864
|
-
}
|
|
4045
|
+
let sourceCleanupPromise;
|
|
4046
|
+
let customRestartSequence = 0;
|
|
4047
|
+
const enterSourceSession = () => {
|
|
4048
|
+
const session = ++sourceSession;
|
|
2865
4049
|
let settleSourceSyncEntry;
|
|
2866
4050
|
activeSourceSyncEntry = new Promise((resolve) => {
|
|
2867
4051
|
settleSourceSyncEntry = resolve;
|
|
2868
4052
|
});
|
|
2869
4053
|
try {
|
|
2870
4054
|
sourceResult = normalizeSyncFnResult(
|
|
2871
|
-
sourceSyncConfig.sync(
|
|
4055
|
+
sourceSyncConfig.sync(
|
|
4056
|
+
runtime.hasManagedCacheGeneration() ? sourceParamsFor(session) : wrappedParams
|
|
4057
|
+
)
|
|
2872
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
|
+
}
|
|
2873
4080
|
} finally {
|
|
2874
4081
|
activeSourceSyncEntry = void 0;
|
|
2875
4082
|
settleSourceSyncEntry();
|
|
2876
4083
|
}
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
);
|
|
2885
|
-
}
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
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)
|
|
2893
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;
|
|
2894
4116
|
}
|
|
4117
|
+
enterSourceSession();
|
|
2895
4118
|
sourceResultSettled = true;
|
|
4119
|
+
runtime.setSourceRestartAfterScopedRecovery(
|
|
4120
|
+
sourceResult.restartAfterScopedRecovery ?? (runtime.hasManagedCacheGeneration() ? restartCustomSource : void 0)
|
|
4121
|
+
);
|
|
2896
4122
|
return sourceResult;
|
|
2897
4123
|
})();
|
|
2898
4124
|
resolveSourceResultAssigned();
|
|
@@ -2902,6 +4128,7 @@ function createWrappedSyncConfig(sourceSyncConfig, runtime, persistedReadiness)
|
|
|
2902
4128
|
return {
|
|
2903
4129
|
cleanup: async () => {
|
|
2904
4130
|
startupState.cleanedUp = true;
|
|
4131
|
+
runtime.clearSourceRestartAfterScopedRecovery();
|
|
2905
4132
|
const sourceSyncEntry = activeSourceSyncEntry;
|
|
2906
4133
|
const cleanupError = new db.SyncTransactionAbortedError();
|
|
2907
4134
|
for (const acquisition of acquisitions.values()) {
|
|
@@ -2915,7 +4142,7 @@ function createWrappedSyncConfig(sourceSyncConfig, runtime, persistedReadiness)
|
|
|
2915
4142
|
transactionStack.length = 0;
|
|
2916
4143
|
let sourceCleanup;
|
|
2917
4144
|
try {
|
|
2918
|
-
sourceCleanup = sourceSyncEntry ? sourceSyncEntry.then(() => sourceResult.cleanup?.()) : Promise.resolve(sourceResult.cleanup?.());
|
|
4145
|
+
sourceCleanup = sourceCleanupPromise ?? (sourceSyncEntry ? sourceSyncEntry.then(() => sourceResult.cleanup?.()) : Promise.resolve(sourceResult.cleanup?.()));
|
|
2919
4146
|
} catch (error) {
|
|
2920
4147
|
sourceCleanup = Promise.reject(error);
|
|
2921
4148
|
}
|
|
@@ -2942,31 +4169,63 @@ function createWrappedSyncConfig(sourceSyncConfig, runtime, persistedReadiness)
|
|
|
2942
4169
|
loadSubset: (options) => {
|
|
2943
4170
|
const acquisition = { forwarded: false };
|
|
2944
4171
|
acquisitions.set(options, acquisition);
|
|
2945
|
-
const forwardUpstream = (
|
|
4172
|
+
const forwardUpstream = (loadOptions) => {
|
|
2946
4173
|
if (startupState.cleanedUp || acquisitions.get(options) !== acquisition) {
|
|
2947
4174
|
return true;
|
|
2948
4175
|
}
|
|
2949
|
-
|
|
4176
|
+
const currentSource = sourceResult;
|
|
4177
|
+
if (!currentSource.loadSubset) return true;
|
|
2950
4178
|
acquisition.forwarded = true;
|
|
4179
|
+
acquisition.source = currentSource;
|
|
2951
4180
|
try {
|
|
2952
|
-
return
|
|
4181
|
+
return currentSource.loadSubset(loadOptions);
|
|
2953
4182
|
} catch (error) {
|
|
2954
4183
|
acquisition.forwarded = false;
|
|
4184
|
+
acquisition.source = void 0;
|
|
2955
4185
|
throw error;
|
|
2956
4186
|
}
|
|
2957
4187
|
};
|
|
2958
4188
|
const openTransaction = getOpenTransaction();
|
|
2959
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
|
+
};
|
|
2960
4219
|
const hydrated = sourceResultSettled && !needsPublicationAdmission ? runtime.loadHydratedSubset(
|
|
2961
4220
|
options,
|
|
2962
|
-
(loadOptions) => forwardUpstream(
|
|
4221
|
+
(loadOptions) => forwardUpstream(loadOptions)
|
|
2963
4222
|
) : void 0;
|
|
2964
4223
|
if (hydrated !== void 0) {
|
|
2965
|
-
return hydrated;
|
|
4224
|
+
return hydrated === true ? true : raceRetainedLoad(hydrated);
|
|
2966
4225
|
}
|
|
2967
|
-
|
|
4226
|
+
const work = (async () => {
|
|
2968
4227
|
await fullStartPromise;
|
|
2969
|
-
|
|
4228
|
+
await sourceResultPromise;
|
|
2970
4229
|
if (startupState.cleanedUp || acquisitions.get(options) !== acquisition) {
|
|
2971
4230
|
return;
|
|
2972
4231
|
}
|
|
@@ -2983,9 +4242,14 @@ function createWrappedSyncConfig(sourceSyncConfig, runtime, persistedReadiness)
|
|
|
2983
4242
|
}
|
|
2984
4243
|
return runtime.loadSubset(
|
|
2985
4244
|
options,
|
|
2986
|
-
(loadOptions) => forwardUpstream(
|
|
4245
|
+
(loadOptions) => forwardUpstream(loadOptions),
|
|
4246
|
+
() => {
|
|
4247
|
+
hydrationStarted = true;
|
|
4248
|
+
},
|
|
4249
|
+
() => !startupState.cleanedUp && acquisitions.get(options) === acquisition
|
|
2987
4250
|
);
|
|
2988
4251
|
})();
|
|
4252
|
+
return raceRetainedLoad(work, () => !hydrationStarted);
|
|
2989
4253
|
},
|
|
2990
4254
|
unloadSubset: (options) => {
|
|
2991
4255
|
const acquisition = acquisitions.get(options);
|
|
@@ -2993,7 +4257,7 @@ function createWrappedSyncConfig(sourceSyncConfig, runtime, persistedReadiness)
|
|
|
2993
4257
|
acquisitions.delete(options);
|
|
2994
4258
|
runtime.unloadSubset(
|
|
2995
4259
|
options,
|
|
2996
|
-
acquisition?.forwarded ?
|
|
4260
|
+
acquisition?.forwarded ? acquisition.source?.unloadSubset : void 0
|
|
2997
4261
|
);
|
|
2998
4262
|
}
|
|
2999
4263
|
};
|
|
@@ -3003,16 +4267,16 @@ function createWrappedSyncConfig(sourceSyncConfig, runtime, persistedReadiness)
|
|
|
3003
4267
|
function createLoopbackSyncConfig(runtime) {
|
|
3004
4268
|
return {
|
|
3005
4269
|
sync: (params) => {
|
|
3006
|
-
runtime.setSyncControls({
|
|
3007
|
-
begin: params.begin,
|
|
3008
|
-
write: params.write,
|
|
3009
|
-
commit: params.commit,
|
|
3010
|
-
truncate: params.truncate,
|
|
3011
|
-
metadata: params.metadata ?? null,
|
|
3012
|
-
markError: params.markError
|
|
3013
|
-
});
|
|
3014
4270
|
runtime.setCollection(
|
|
3015
|
-
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
|
+
}
|
|
3016
4280
|
);
|
|
3017
4281
|
void runtime.ensureStarted().then(() => {
|
|
3018
4282
|
if (runtime.getCurrentTerminalFailure()) return;
|
|
@@ -3042,6 +4306,19 @@ function persistedCollectionOptions(options) {
|
|
|
3042
4306
|
);
|
|
3043
4307
|
}
|
|
3044
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
|
+
};
|
|
3045
4322
|
if (hasOwnSyncKey(options)) {
|
|
3046
4323
|
if (!isValidSyncConfig(options.sync)) {
|
|
3047
4324
|
throw new errors.InvalidSyncConfigError(
|
|
@@ -3068,8 +4345,10 @@ function persistedCollectionOptions(options) {
|
|
|
3068
4345
|
persistence2,
|
|
3069
4346
|
syncOptions.syncMode ?? `eager`,
|
|
3070
4347
|
collectionId2,
|
|
3071
|
-
persistedReadinessTracker
|
|
4348
|
+
persistedReadinessTracker,
|
|
4349
|
+
claimOptions
|
|
3072
4350
|
);
|
|
4351
|
+
claimedRuntime = runtime2;
|
|
3073
4352
|
const result2 = {
|
|
3074
4353
|
...syncOptions,
|
|
3075
4354
|
id: collectionId2,
|
|
@@ -3086,7 +4365,8 @@ function persistedCollectionOptions(options) {
|
|
|
3086
4365
|
() => persistedCollectionOptions({
|
|
3087
4366
|
...options,
|
|
3088
4367
|
id: collectionId2
|
|
3089
|
-
})
|
|
4368
|
+
}),
|
|
4369
|
+
claimOptions
|
|
3090
4370
|
);
|
|
3091
4371
|
}
|
|
3092
4372
|
const {
|
|
@@ -3109,8 +4389,10 @@ function persistedCollectionOptions(options) {
|
|
|
3109
4389
|
persistence,
|
|
3110
4390
|
localOnlyOptions.syncMode ?? `eager`,
|
|
3111
4391
|
localOnlyOptions.id ?? DEFAULT_DB_NAME,
|
|
3112
|
-
persistedReadinessTracker
|
|
4392
|
+
persistedReadinessTracker,
|
|
4393
|
+
claimOptions
|
|
3113
4394
|
);
|
|
4395
|
+
claimedRuntime = runtime;
|
|
3114
4396
|
const wrappedOnInsert = async (params) => {
|
|
3115
4397
|
const lifecycleGeneration = runtime.getLifecycleGeneration();
|
|
3116
4398
|
const handlerResult = localOnlyOptions.onInsert ? await localOnlyOptions.onInsert(
|
|
@@ -3174,7 +4456,8 @@ function persistedCollectionOptions(options) {
|
|
|
3174
4456
|
() => persistedCollectionOptions({
|
|
3175
4457
|
...options,
|
|
3176
4458
|
id: collectionId
|
|
3177
|
-
})
|
|
4459
|
+
}),
|
|
4460
|
+
claimOptions
|
|
3178
4461
|
);
|
|
3179
4462
|
}
|
|
3180
4463
|
function encodePersistedStorageKey(key) {
|
|
@@ -3236,8 +4519,11 @@ exports.SQLITE_DRIVER_SHARED_LOGICAL_SCHEDULING_KEY = SQLITE_DRIVER_SHARED_LOGIC
|
|
|
3236
4519
|
exports.SingleProcessCoordinator = SingleProcessCoordinator;
|
|
3237
4520
|
exports.createPersistedTableName = createPersistedTableName;
|
|
3238
4521
|
exports.decodePersistedStorageKey = decodePersistedStorageKey;
|
|
4522
|
+
exports.eagerStorageTarget = eagerStorageTarget;
|
|
3239
4523
|
exports.encodePersistedStorageKey = encodePersistedStorageKey;
|
|
3240
4524
|
exports.forwardSQLiteDriverSharedLogicalScheduling = forwardSQLiteDriverSharedLogicalScheduling;
|
|
4525
|
+
exports.managedStorageTarget = managedStorageTarget;
|
|
3241
4526
|
exports.persistedCollectionOptions = persistedCollectionOptions;
|
|
4527
|
+
exports.resolvePersistedStorageTarget = resolvePersistedStorageTarget;
|
|
3242
4528
|
exports.validatePersistedCollectionCoordinator = validatePersistedCollectionCoordinator;
|
|
3243
4529
|
//# sourceMappingURL=persisted.cjs.map
|