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