@tanstack/db-sqlite-persistence-core 0.4.7 → 0.4.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -3
- package/dist/cjs/broadcast-coordinator.cjs +282 -43
- package/dist/cjs/broadcast-coordinator.cjs.map +1 -1
- package/dist/cjs/broadcast-coordinator.d.cts +25 -8
- package/dist/cjs/committed-tx-anchor.cjs +9 -0
- package/dist/cjs/committed-tx-anchor.cjs.map +1 -0
- package/dist/cjs/committed-tx-anchor.d.cts +1 -0
- package/dist/cjs/index.cjs +4 -0
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/persisted.cjs +1599 -313
- package/dist/cjs/persisted.cjs.map +1 -1
- package/dist/cjs/persisted.d.cts +106 -13
- package/dist/cjs/remote-subset-wire.cjs +11 -0
- package/dist/cjs/remote-subset-wire.cjs.map +1 -1
- package/dist/cjs/remote-subset-wire.d.cts +1 -0
- package/dist/cjs/sqlite-core-adapter.cjs +705 -64
- package/dist/cjs/sqlite-core-adapter.cjs.map +1 -1
- package/dist/cjs/sqlite-core-adapter.d.cts +53 -10
- package/dist/esm/broadcast-coordinator.d.ts +25 -8
- package/dist/esm/broadcast-coordinator.js +283 -44
- package/dist/esm/broadcast-coordinator.js.map +1 -1
- package/dist/esm/committed-tx-anchor.d.ts +1 -0
- package/dist/esm/committed-tx-anchor.js +9 -0
- package/dist/esm/committed-tx-anchor.js.map +1 -0
- package/dist/esm/index.js +6 -2
- package/dist/esm/persisted.d.ts +106 -13
- package/dist/esm/persisted.js +1601 -315
- package/dist/esm/persisted.js.map +1 -1
- package/dist/esm/remote-subset-wire.d.ts +1 -0
- package/dist/esm/remote-subset-wire.js +11 -0
- package/dist/esm/remote-subset-wire.js.map +1 -1
- package/dist/esm/sqlite-core-adapter.d.ts +53 -10
- package/dist/esm/sqlite-core-adapter.js +708 -67
- package/dist/esm/sqlite-core-adapter.js.map +1 -1
- package/package.json +2 -2
- package/src/broadcast-coordinator.ts +415 -62
- package/src/committed-tx-anchor.ts +13 -0
- package/src/persisted.ts +2283 -370
- package/src/remote-subset-wire.ts +12 -0
- package/src/sqlite-core-adapter.ts +891 -68
package/dist/esm/persisted.js
CHANGED
|
@@ -1,8 +1,60 @@
|
|
|
1
|
-
import { safeRandomUUID, withCollectionConfigFactory, whenSyncAccepted, SyncTransactionAbortedError, getLoadSubsetDemandKey, compileSingleRowExpression, toBooleanPredicate, SYNC_PERSISTENCE_VERSION, SYNC_PERSISTENCE_PROTOCOL,
|
|
2
|
-
import { InvalidPersistedCollectionConfigError, DuplicateRemoteSubsetOwnerError, toPersistedCollectionDurabilityError, InvalidPersistedStorageKeyEncodingError, InvalidPersistedStorageKeyError, InvalidSyncConfigError, InvalidPersistedCollectionCoordinatorError, PersistedCollectionDurabilityError, InvalidPersistenceAdapterError } from "./errors.js";
|
|
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
|
+
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;
|
|
@@ -391,7 +446,7 @@ function isTxCommittedPayload(payload) {
|
|
|
391
446
|
return false;
|
|
392
447
|
}
|
|
393
448
|
if (payload.requiresFullReload) {
|
|
394
|
-
return true;
|
|
449
|
+
return payload.reconciliationReload === void 0 || payload.reconciliationReload === true;
|
|
395
450
|
}
|
|
396
451
|
return Array.isArray(payload.changedRows) && Array.isArray(payload.deletedKeys) && (payload.rowMetadataMutations === void 0 || Array.isArray(payload.rowMetadataMutations)) && (payload.collectionMetadataMutations === void 0 || Array.isArray(payload.collectionMetadataMutations));
|
|
397
452
|
}
|
|
@@ -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,15 +511,184 @@ 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;
|
|
524
|
+
this.latestTerm = 0;
|
|
525
|
+
this.latestSeq = 0;
|
|
526
|
+
this.latestRowVersion = 0;
|
|
527
|
+
this.publicRowVersion = 0;
|
|
528
|
+
this.localTerm = 1;
|
|
529
|
+
this.localSeq = 0;
|
|
530
|
+
this.localRowVersion = 0;
|
|
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++;
|
|
457
657
|
this.latestTerm = 0;
|
|
458
658
|
this.latestSeq = 0;
|
|
459
659
|
this.latestRowVersion = 0;
|
|
460
660
|
this.localTerm = 1;
|
|
461
661
|
this.localSeq = 0;
|
|
462
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;
|
|
463
692
|
}
|
|
464
693
|
setSyncControls(syncControls) {
|
|
465
694
|
this.advanceLifecycle();
|
|
@@ -471,6 +700,12 @@ class PersistedCollectionRuntime {
|
|
|
471
700
|
commit: commit ? (signal) => this.trackAppliedReceipt(commit(signal)) : null
|
|
472
701
|
};
|
|
473
702
|
}
|
|
703
|
+
setSourceRestartAfterScopedRecovery(restart) {
|
|
704
|
+
this.restartSourceAfterScopedRecovery = restart ?? null;
|
|
705
|
+
}
|
|
706
|
+
clearSourceRestartAfterScopedRecovery() {
|
|
707
|
+
this.restartSourceAfterScopedRecovery = void 0;
|
|
708
|
+
}
|
|
474
709
|
reportSyncError(error) {
|
|
475
710
|
const markError = this.syncControls.markError;
|
|
476
711
|
if (this.syncErrorReported) return this.reportedSyncError;
|
|
@@ -485,8 +720,9 @@ class PersistedCollectionRuntime {
|
|
|
485
720
|
}
|
|
486
721
|
registerRemoteSubsetOwner(owner) {
|
|
487
722
|
this.remoteSubsetOwnerUnsubscribe?.();
|
|
723
|
+
this.remoteSubsetOwner = owner;
|
|
488
724
|
this.remoteSubsetOwnerUnsubscribe = this.persistence.coordinator.registerRemoteSubsetOwner(
|
|
489
|
-
this.
|
|
725
|
+
this.storageCollectionId,
|
|
490
726
|
owner
|
|
491
727
|
);
|
|
492
728
|
}
|
|
@@ -511,6 +747,23 @@ class PersistedCollectionRuntime {
|
|
|
511
747
|
)
|
|
512
748
|
);
|
|
513
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
|
+
}
|
|
514
767
|
clearSyncControls() {
|
|
515
768
|
this.syncControls = {
|
|
516
769
|
begin: null,
|
|
@@ -569,17 +822,46 @@ class PersistedCollectionRuntime {
|
|
|
569
822
|
isApplyingInternally() {
|
|
570
823
|
return this.internalApplyDepth > 0;
|
|
571
824
|
}
|
|
572
|
-
|
|
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);
|
|
573
845
|
if (this.collection === collection) {
|
|
846
|
+
this.boundCollection ??= collection;
|
|
574
847
|
return;
|
|
575
848
|
}
|
|
576
849
|
this.collection = collection;
|
|
577
|
-
|
|
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;
|
|
578
860
|
}
|
|
579
861
|
getLeadershipState() {
|
|
580
862
|
return {
|
|
581
863
|
nodeId: this.persistence.coordinator.getNodeId(),
|
|
582
|
-
isLeader: this.persistence.coordinator.isLeader(this.
|
|
864
|
+
isLeader: this.persistence.coordinator.isLeader(this.storageCollectionId)
|
|
583
865
|
};
|
|
584
866
|
}
|
|
585
867
|
runInHydrationScope(task, adapter = this.persistence.adapter) {
|
|
@@ -594,6 +876,7 @@ class PersistedCollectionRuntime {
|
|
|
594
876
|
return this.startPromise;
|
|
595
877
|
}
|
|
596
878
|
const lifecycleGeneration = this.lifecycleGeneration;
|
|
879
|
+
this.startupCacheGenerationRotated = false;
|
|
597
880
|
const isRestart = this.hasAttemptedStartup;
|
|
598
881
|
this.hasAttemptedStartup = true;
|
|
599
882
|
let resolveStartupMetadata;
|
|
@@ -604,63 +887,98 @@ class PersistedCollectionRuntime {
|
|
|
604
887
|
});
|
|
605
888
|
void this.startupMetadataPromise.catch(() => void 0);
|
|
606
889
|
this.startPromise = (async () => {
|
|
890
|
+
await this.claimCacheGeneration(lifecycleGeneration);
|
|
891
|
+
if (lifecycleGeneration !== this.lifecycleGeneration) {
|
|
892
|
+
resolveStartupMetadata();
|
|
893
|
+
return;
|
|
894
|
+
}
|
|
895
|
+
let metadataExpired = false;
|
|
607
896
|
const loadStartupMetadata = async (adapter) => {
|
|
608
897
|
if (lifecycleGeneration !== this.lifecycleGeneration) {
|
|
609
898
|
resolveStartupMetadata();
|
|
610
899
|
return false;
|
|
611
900
|
}
|
|
612
901
|
try {
|
|
613
|
-
await this.loadStartupMetadataInternal(
|
|
902
|
+
if (!await this.loadStartupMetadataInternal(
|
|
903
|
+
lifecycleGeneration,
|
|
904
|
+
adapter
|
|
905
|
+
)) {
|
|
906
|
+
metadataExpired = true;
|
|
907
|
+
return false;
|
|
908
|
+
}
|
|
614
909
|
return lifecycleGeneration === this.lifecycleGeneration;
|
|
615
910
|
} catch (error) {
|
|
911
|
+
if (this.cacheGenerationClaim && this.hasExpiredCacheClaim()) {
|
|
912
|
+
metadataExpired = true;
|
|
913
|
+
return false;
|
|
914
|
+
}
|
|
616
915
|
rejectStartupMetadata(error);
|
|
617
916
|
throw error;
|
|
618
917
|
}
|
|
619
918
|
};
|
|
620
919
|
let startup;
|
|
621
920
|
const scheduleStartupAsOneHydrate = this.persistence.adapter.runInHydrationScope !== void 0 && (!isRestart || (this.persistence.adapter.isHydrationScopeScheduled?.() ?? true));
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
adapter
|
|
629
|
-
|
|
630
|
-
|
|
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;
|
|
631
938
|
});
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
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
|
|
647
960
|
);
|
|
648
961
|
if (lifecycleGeneration === this.lifecycleGeneration) {
|
|
649
962
|
await this.flushQueuedTxCommittedUnsafe();
|
|
650
963
|
}
|
|
651
|
-
return result;
|
|
652
|
-
});
|
|
653
|
-
} else {
|
|
654
|
-
startup = await this.startInternal(
|
|
655
|
-
lifecycleGeneration,
|
|
656
|
-
this.persistence.adapter,
|
|
657
|
-
resolveStartupMetadata
|
|
658
|
-
);
|
|
659
|
-
if (lifecycleGeneration === this.lifecycleGeneration) {
|
|
660
|
-
await this.flushQueuedTxCommittedUnsafe();
|
|
661
964
|
}
|
|
662
965
|
}
|
|
663
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();
|
|
664
982
|
}
|
|
665
983
|
if (startup !== void 0 && lifecycleGeneration === this.lifecycleGeneration) {
|
|
666
984
|
await this.requestCoordinatorPersistedIndexes(
|
|
@@ -696,6 +1014,7 @@ class PersistedCollectionRuntime {
|
|
|
696
1014
|
await this.ensureStarted();
|
|
697
1015
|
if (lifecycleGeneration !== this.lifecycleGeneration) return;
|
|
698
1016
|
if (this.syncMode !== `on-demand`) return;
|
|
1017
|
+
if (this.scopedRecovery) return;
|
|
699
1018
|
const appliedCursor = await this.applyMutex.run(async () => {
|
|
700
1019
|
const result = await this.runInHydrationScope(
|
|
701
1020
|
(adapter) => this.hydrateBaseline(lifecycleGeneration, adapter)
|
|
@@ -705,6 +1024,10 @@ class PersistedCollectionRuntime {
|
|
|
705
1024
|
}
|
|
706
1025
|
return result;
|
|
707
1026
|
});
|
|
1027
|
+
if (appliedCursor === false) {
|
|
1028
|
+
await this.ensureCacheClaimActive();
|
|
1029
|
+
return;
|
|
1030
|
+
}
|
|
708
1031
|
if (appliedCursor !== void 0 && lifecycleGeneration === this.lifecycleGeneration) {
|
|
709
1032
|
await this.waitForAppliedReceiptsAfter(appliedCursor, `accepted`);
|
|
710
1033
|
}
|
|
@@ -721,14 +1044,32 @@ class PersistedCollectionRuntime {
|
|
|
721
1044
|
this.resumeCertificationPromise = (async () => {
|
|
722
1045
|
await this.ensureStarted();
|
|
723
1046
|
if (lifecycleGeneration !== this.lifecycleGeneration) return;
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
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;
|
|
732
1073
|
this.bindResumeSnapshotEvidence(snapshot);
|
|
733
1074
|
})();
|
|
734
1075
|
return this.resumeCertificationPromise;
|
|
@@ -736,6 +1077,162 @@ class PersistedCollectionRuntime {
|
|
|
736
1077
|
getKeySetEvidence() {
|
|
737
1078
|
return this.persistedKeySetEvidence;
|
|
738
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
|
+
}
|
|
739
1236
|
getResumeGenerationOwner() {
|
|
740
1237
|
return this.resumeGenerationOwner;
|
|
741
1238
|
}
|
|
@@ -744,7 +1241,7 @@ class PersistedCollectionRuntime {
|
|
|
744
1241
|
const baseline = {};
|
|
745
1242
|
this.activeSubsets.set(this.getSubsetKey(baseline), baseline);
|
|
746
1243
|
const appliedCursor = this.appliedReceiptSequence;
|
|
747
|
-
await this.hydrateSubsetUnsafe(
|
|
1244
|
+
const hydrated = await this.hydrateSubsetUnsafe(
|
|
748
1245
|
baseline,
|
|
749
1246
|
{
|
|
750
1247
|
lifecycleGeneration,
|
|
@@ -752,6 +1249,7 @@ class PersistedCollectionRuntime {
|
|
|
752
1249
|
},
|
|
753
1250
|
adapter
|
|
754
1251
|
);
|
|
1252
|
+
if (!hydrated) return false;
|
|
755
1253
|
return lifecycleGeneration === this.lifecycleGeneration ? appliedCursor : void 0;
|
|
756
1254
|
}
|
|
757
1255
|
async ensureStartupMetadataLoaded() {
|
|
@@ -771,19 +1269,33 @@ class PersistedCollectionRuntime {
|
|
|
771
1269
|
adapter
|
|
772
1270
|
);
|
|
773
1271
|
if (lifecycleGeneration !== this.lifecycleGeneration) return void 0;
|
|
774
|
-
|
|
1272
|
+
if (!this.cacheGenerationClaim || this.syncMode !== `on-demand`) {
|
|
1273
|
+
onStartupMetadataLoaded();
|
|
1274
|
+
}
|
|
775
1275
|
const appliedCursor = this.syncMode !== `on-demand` ? await this.hydrateBaseline(lifecycleGeneration, adapter) : void 0;
|
|
776
1276
|
return lifecycleGeneration === this.lifecycleGeneration ? {
|
|
777
|
-
appliedCursor,
|
|
1277
|
+
appliedCursor: appliedCursor === false ? void 0 : appliedCursor,
|
|
778
1278
|
indexBootstrapSnapshot,
|
|
779
1279
|
completedLocalIndexSignatures
|
|
780
1280
|
} : void 0;
|
|
781
1281
|
}
|
|
782
1282
|
async loadStartupMetadataInternal(lifecycleGeneration, adapter) {
|
|
783
|
-
const
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
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;
|
|
787
1299
|
this.persistedResumeGeneration = this.getResumeSnapshotGeneration(snapshot);
|
|
788
1300
|
this.persistedKeySetEvidence = snapshot.keySet;
|
|
789
1301
|
this.observeStreamPosition(
|
|
@@ -795,36 +1307,42 @@ class PersistedCollectionRuntime {
|
|
|
795
1307
|
snapshot.collectionMetadata
|
|
796
1308
|
);
|
|
797
1309
|
await whenSyncAccepted(applied);
|
|
1310
|
+
return true;
|
|
798
1311
|
}
|
|
799
1312
|
async loadCollectionMetadataSnapshot(adapter) {
|
|
800
1313
|
if (!adapter.loadCollectionMetadata) {
|
|
801
1314
|
return [];
|
|
802
1315
|
}
|
|
803
|
-
return adapter.loadCollectionMetadata(this.
|
|
1316
|
+
return adapter.loadCollectionMetadata(this.storageTargetFor(), {
|
|
1317
|
+
cacheGenerationClaimId: this.cacheGenerationClaim?.claimId
|
|
1318
|
+
});
|
|
804
1319
|
}
|
|
805
1320
|
replaceCollectionMetadataSnapshot(collectionMetadata) {
|
|
806
1321
|
if (!this.syncControls.begin || !this.syncControls.commit || !this.syncControls.metadata) {
|
|
807
1322
|
return true;
|
|
808
1323
|
}
|
|
809
|
-
const nextMetadata = new Map(
|
|
810
|
-
collectionMetadata.map(({ key, value }) => [key, value])
|
|
811
|
-
);
|
|
812
|
-
const currentKeys = this.syncControls.metadata.collection.list().map(({ key }) => key);
|
|
813
1324
|
return this.withInternalApply(() => {
|
|
814
1325
|
this.syncControls.begin?.();
|
|
815
|
-
|
|
816
|
-
if (!nextMetadata.has(key)) {
|
|
817
|
-
this.syncControls.metadata?.collection.delete(key);
|
|
818
|
-
}
|
|
819
|
-
});
|
|
820
|
-
nextMetadata.forEach((value, key) => {
|
|
821
|
-
this.syncControls.metadata?.collection.set(key, value);
|
|
822
|
-
});
|
|
1326
|
+
this.applyCollectionMetadataSnapshot(collectionMetadata);
|
|
823
1327
|
return this.syncControls.commit?.() ?? true;
|
|
824
1328
|
});
|
|
825
1329
|
}
|
|
826
|
-
|
|
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) {
|
|
827
1342
|
this.throwIfTerminal();
|
|
1343
|
+
await this.ensureCacheClaimActive();
|
|
1344
|
+
if (options.signal?.aborted || isDemandRetained && !isDemandRetained())
|
|
1345
|
+
return;
|
|
828
1346
|
const lifecycleGeneration = this.lifecycleGeneration;
|
|
829
1347
|
const subsetKey = this.getSubsetKey(options);
|
|
830
1348
|
const truncateGeneration = this.sourceTruncateGeneration;
|
|
@@ -833,25 +1351,52 @@ class PersistedCollectionRuntime {
|
|
|
833
1351
|
this.activeSubsets.set(subsetKey, options);
|
|
834
1352
|
const appliedCursor = this.appliedReceiptSequence;
|
|
835
1353
|
try {
|
|
836
|
-
await this.applyMutex.run(
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
{
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
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
|
+
}
|
|
852
1379
|
}
|
|
853
1380
|
}
|
|
854
|
-
|
|
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
|
+
}
|
|
855
1400
|
if (lifecycleGeneration !== this.lifecycleGeneration) return;
|
|
856
1401
|
await this.waitForAppliedReceiptsAfter(appliedCursor);
|
|
857
1402
|
if (!routeRemoteDemandDuringHydration && this.canRouteRemoteDemandThroughCoordinator()) {
|
|
@@ -864,13 +1409,16 @@ class PersistedCollectionRuntime {
|
|
|
864
1409
|
throw error;
|
|
865
1410
|
}
|
|
866
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
|
+
}
|
|
867
1415
|
return;
|
|
868
1416
|
}
|
|
869
1417
|
if (this.canRouteRemoteDemandThroughCoordinator()) {
|
|
870
1418
|
try {
|
|
871
1419
|
await this.persistence.coordinator.requestEnsureRemoteSubset(
|
|
872
|
-
this.
|
|
873
|
-
options
|
|
1420
|
+
this.storageCollectionId,
|
|
1421
|
+
this.rotatedAcquisitions.get(options) ?? options
|
|
874
1422
|
);
|
|
875
1423
|
} catch (error) {
|
|
876
1424
|
if (options.signal?.aborted || typeof error === `object` && error !== null && `name` in error && error.name === `AbortError`) {
|
|
@@ -880,6 +1428,7 @@ class PersistedCollectionRuntime {
|
|
|
880
1428
|
this.queueRemoteSubsetEnsure(options);
|
|
881
1429
|
throw error;
|
|
882
1430
|
}
|
|
1431
|
+
await this.applyLeaderCommitsBeforeReply(options);
|
|
883
1432
|
return;
|
|
884
1433
|
}
|
|
885
1434
|
if (truncateGeneration === this.sourceTruncateGeneration && this.activeSubsets.get(subsetKey) === options) {
|
|
@@ -897,7 +1446,7 @@ class PersistedCollectionRuntime {
|
|
|
897
1446
|
loadHydratedSubset(options, upstreamLoadSubset) {
|
|
898
1447
|
const failure = this.getCurrentTerminalFailure();
|
|
899
1448
|
if (failure) return Promise.reject(failure.error);
|
|
900
|
-
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))) {
|
|
901
1450
|
return;
|
|
902
1451
|
}
|
|
903
1452
|
this.activeSubsets.set(this.getSubsetKey(options), options);
|
|
@@ -937,13 +1486,15 @@ class PersistedCollectionRuntime {
|
|
|
937
1486
|
const subsetKey = this.getSubsetKey(options);
|
|
938
1487
|
this.activeSubsets.delete(subsetKey);
|
|
939
1488
|
this.pendingRemoteSubsetEnsures.delete(subsetKey);
|
|
1489
|
+
const remoteOptions = this.rotatedAcquisitions.get(options) ?? options;
|
|
1490
|
+
this.rotatedAcquisitions.delete(options);
|
|
940
1491
|
const demandKey = getLoadSubsetDemandKey(options);
|
|
941
1492
|
const stillActive = Array.from(this.activeSubsets.values()).some(
|
|
942
1493
|
(active) => getLoadSubsetDemandKey(active) === demandKey
|
|
943
1494
|
);
|
|
944
1495
|
if (!stillActive) this.hydratedDemands.delete(demandKey);
|
|
945
1496
|
if (this.mode === `sync-present`) {
|
|
946
|
-
void this.persistence.coordinator.requestReleaseRemoteSubset(this.
|
|
1497
|
+
void this.persistence.coordinator.requestReleaseRemoteSubset(this.storageCollectionId, remoteOptions).catch((error) => {
|
|
947
1498
|
this.reportSyncError(error);
|
|
948
1499
|
});
|
|
949
1500
|
}
|
|
@@ -960,9 +1511,10 @@ class PersistedCollectionRuntime {
|
|
|
960
1511
|
}
|
|
961
1512
|
async forceReloadSubset(options) {
|
|
962
1513
|
this.throwIfTerminal();
|
|
1514
|
+
await this.ensureCacheClaimActive();
|
|
963
1515
|
const lifecycleGeneration = this.lifecycleGeneration;
|
|
964
|
-
await this.applyMutex.run(async () => {
|
|
965
|
-
await this.runInHydrationScope(
|
|
1516
|
+
const hydrated = await this.applyMutex.run(async () => {
|
|
1517
|
+
const result = await this.runInHydrationScope(
|
|
966
1518
|
(adapter) => this.hydrateSubsetUnsafe(
|
|
967
1519
|
options,
|
|
968
1520
|
{
|
|
@@ -975,7 +1527,9 @@ class PersistedCollectionRuntime {
|
|
|
975
1527
|
if (lifecycleGeneration === this.lifecycleGeneration) {
|
|
976
1528
|
await this.flushQueuedTxCommittedUnsafe();
|
|
977
1529
|
}
|
|
1530
|
+
return result;
|
|
978
1531
|
});
|
|
1532
|
+
if (!hydrated) await this.ensureCacheClaimActive();
|
|
979
1533
|
}
|
|
980
1534
|
queueHydrationBufferedTransaction(transaction) {
|
|
981
1535
|
const failure = this.getCurrentTerminalFailure();
|
|
@@ -983,11 +1537,13 @@ class PersistedCollectionRuntime {
|
|
|
983
1537
|
transaction.rejectApplied?.(failure.error);
|
|
984
1538
|
return;
|
|
985
1539
|
}
|
|
1540
|
+
transaction.admittedStorageCollectionId = this.storageCollectionId;
|
|
986
1541
|
this.queuedHydrationTransactions.push(transaction);
|
|
987
1542
|
}
|
|
988
1543
|
applyHydrationBufferedTransaction(transaction) {
|
|
989
1544
|
const failure = this.getCurrentTerminalFailure();
|
|
990
1545
|
if (failure) return Promise.reject(failure.error);
|
|
1546
|
+
transaction.admittedStorageCollectionId = this.storageCollectionId;
|
|
991
1547
|
return this.applyMutex.run(async () => {
|
|
992
1548
|
await this.applyBufferedSyncTransactionUnsafe(transaction);
|
|
993
1549
|
});
|
|
@@ -1092,7 +1648,10 @@ class PersistedCollectionRuntime {
|
|
|
1092
1648
|
for (const options of this.activeSubsets.values()) {
|
|
1093
1649
|
attempt(() => {
|
|
1094
1650
|
remoteReleases.push(
|
|
1095
|
-
this.persistence.coordinator.requestReleaseRemoteSubset(
|
|
1651
|
+
this.persistence.coordinator.requestReleaseRemoteSubset(
|
|
1652
|
+
this.storageCollectionId,
|
|
1653
|
+
this.rotatedAcquisitions.get(options) ?? options
|
|
1654
|
+
).catch((error) => {
|
|
1096
1655
|
this.reportSyncError(error);
|
|
1097
1656
|
})
|
|
1098
1657
|
);
|
|
@@ -1103,6 +1662,7 @@ class PersistedCollectionRuntime {
|
|
|
1103
1662
|
this.coordinatorUnsubscribe = null;
|
|
1104
1663
|
attempt(() => this.remoteSubsetOwnerUnsubscribe?.());
|
|
1105
1664
|
this.remoteSubsetOwnerUnsubscribe = null;
|
|
1665
|
+
this.remoteSubsetOwner = void 0;
|
|
1106
1666
|
attempt(() => this.indexAddedUnsubscribe?.());
|
|
1107
1667
|
this.indexAddedUnsubscribe = null;
|
|
1108
1668
|
attempt(() => this.indexRemovedUnsubscribe?.());
|
|
@@ -1112,6 +1672,16 @@ class PersistedCollectionRuntime {
|
|
|
1112
1672
|
this.remoteEnsureRetryTimer = null;
|
|
1113
1673
|
}
|
|
1114
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();
|
|
1115
1685
|
this.activeSubsets.clear();
|
|
1116
1686
|
this.hydratedDemands.clear();
|
|
1117
1687
|
for (const transaction of this.queuedHydrationTransactions) {
|
|
@@ -1121,6 +1691,15 @@ class PersistedCollectionRuntime {
|
|
|
1121
1691
|
this.queuedTxCommitted.length = 0;
|
|
1122
1692
|
this.clearSyncControls();
|
|
1123
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
|
+
}
|
|
1124
1703
|
return Promise.all(remoteReleases).then(() => {
|
|
1125
1704
|
if (failures.length === 1) throw failures[0];
|
|
1126
1705
|
if (failures.length > 1) {
|
|
@@ -1132,8 +1711,16 @@ class PersistedCollectionRuntime {
|
|
|
1132
1711
|
}
|
|
1133
1712
|
advanceLifecycle() {
|
|
1134
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;
|
|
1135
1720
|
this.persistedReadiness?.set({ status: `loading` });
|
|
1136
1721
|
this.startupSettled = false;
|
|
1722
|
+
this.publicRowVersion = 0;
|
|
1723
|
+
this.resetTermFence = void 0;
|
|
1137
1724
|
this.hydratedDemands.clear();
|
|
1138
1725
|
this.startupMetadataPromise = null;
|
|
1139
1726
|
this.startPromise = null;
|
|
@@ -1159,25 +1746,44 @@ class PersistedCollectionRuntime {
|
|
|
1159
1746
|
return this.collection.getIndexMetadata().map((metadata) => metadata.signature);
|
|
1160
1747
|
}
|
|
1161
1748
|
loadSubsetRowsUnsafe(options, adapter) {
|
|
1162
|
-
|
|
1163
|
-
|
|
1749
|
+
if (this.scopedRecovery) return Promise.resolve([]);
|
|
1750
|
+
return adapter.loadSubset(this.storageTargetFor(), options, {
|
|
1751
|
+
requiredIndexSignatures: this.getRequiredIndexSignatures(),
|
|
1752
|
+
cacheGenerationClaimId: this.cacheGenerationClaim?.claimId
|
|
1164
1753
|
});
|
|
1165
1754
|
}
|
|
1166
1755
|
async scanPersistedRowsUnsafe(options) {
|
|
1167
1756
|
if (!this.persistence.adapter.scanRows) {
|
|
1168
1757
|
return [];
|
|
1169
1758
|
}
|
|
1170
|
-
return this.persistence.adapter.scanRows(
|
|
1171
|
-
this.
|
|
1172
|
-
|
|
1173
|
-
);
|
|
1759
|
+
return this.persistence.adapter.scanRows(this.storageTargetFor(), options, {
|
|
1760
|
+
cacheGenerationClaimId: this.cacheGenerationClaim?.claimId
|
|
1761
|
+
});
|
|
1174
1762
|
}
|
|
1175
1763
|
async scanPersistedRows(options) {
|
|
1176
|
-
|
|
1177
|
-
|
|
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
|
+
}
|
|
1178
1783
|
}
|
|
1179
1784
|
async hydrateSubsetUnsafe(options, config, adapter) {
|
|
1180
1785
|
let rowsLoaded = false;
|
|
1786
|
+
let boundRowVersion;
|
|
1181
1787
|
let replayFailure;
|
|
1182
1788
|
const resetSequence = this.resetSequence;
|
|
1183
1789
|
this.hydrationSequence++;
|
|
@@ -1188,38 +1794,68 @@ class PersistedCollectionRuntime {
|
|
|
1188
1794
|
this.hydratingGeneration = config.lifecycleGeneration;
|
|
1189
1795
|
try {
|
|
1190
1796
|
let rows;
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
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
|
+
);
|
|
1196
1807
|
rows = snapshot.rows;
|
|
1197
|
-
if (config.lifecycleGeneration !== this.lifecycleGeneration)
|
|
1198
|
-
|
|
1199
|
-
this.
|
|
1808
|
+
if (config.lifecycleGeneration !== this.lifecycleGeneration)
|
|
1809
|
+
return true;
|
|
1810
|
+
if (resetSequence !== this.resetSequence) return true;
|
|
1811
|
+
bindResumeSnapshot = () => this.bindResumeSnapshotEvidence(snapshot);
|
|
1812
|
+
boundRowVersion = snapshot.latestRowVersion;
|
|
1200
1813
|
} else {
|
|
1201
1814
|
rows = await this.loadSubsetRowsUnsafe(options, adapter);
|
|
1202
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;
|
|
1203
1818
|
rowsLoaded = true;
|
|
1204
|
-
if (config.lifecycleGeneration !== this.lifecycleGeneration) return;
|
|
1205
|
-
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?.();
|
|
1206
1827
|
if (!config.bindKeySetEvidence || this.persistedKeySetEvidence?.status !== `incompatible`) {
|
|
1207
1828
|
for (const row of rows) {
|
|
1208
1829
|
hydrationContext.suppliedRowKeys.add(row.key);
|
|
1209
1830
|
}
|
|
1210
|
-
const applied = this.applyRowsToCollection(rows);
|
|
1831
|
+
const applied = this.applyRowsToCollection(rows, validationRows);
|
|
1211
1832
|
await whenSyncAccepted(applied);
|
|
1833
|
+
if (boundRowVersion !== void 0) {
|
|
1834
|
+
this.publicRowVersion = Math.max(
|
|
1835
|
+
this.publicRowVersion,
|
|
1836
|
+
boundRowVersion
|
|
1837
|
+
);
|
|
1838
|
+
}
|
|
1212
1839
|
}
|
|
1213
1840
|
} finally {
|
|
1214
1841
|
if (this.activeHydrationContext === hydrationContext && this.hydratingGeneration === config.lifecycleGeneration) {
|
|
1215
1842
|
this.hydratingGeneration = null;
|
|
1216
1843
|
}
|
|
1217
1844
|
}
|
|
1218
|
-
if (config.lifecycleGeneration !== this.lifecycleGeneration) return;
|
|
1219
|
-
|
|
1220
|
-
|
|
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;
|
|
1221
1850
|
} catch (error) {
|
|
1851
|
+
if (config.requestLocalLoadFailure && !rowsLoaded && this.cacheGenerationClaim && this.hasExpiredCacheClaim()) {
|
|
1852
|
+
return false;
|
|
1853
|
+
}
|
|
1222
1854
|
if (config.requestLocalLoadFailure && !rowsLoaded) {
|
|
1855
|
+
if (this.scopedRecoveryPending) {
|
|
1856
|
+
this.recoverQueuedAfterLocalLoadFailure = true;
|
|
1857
|
+
throw error;
|
|
1858
|
+
}
|
|
1223
1859
|
this.hydratingGeneration = config.lifecycleGeneration;
|
|
1224
1860
|
try {
|
|
1225
1861
|
await this.recoverBufferedTransactionsAfterLocalLoadFailureUnsafe(
|
|
@@ -1248,6 +1884,7 @@ class PersistedCollectionRuntime {
|
|
|
1248
1884
|
if (replayFailure && config.rejectBufferedReplayFailure) {
|
|
1249
1885
|
throw replayFailure.reason;
|
|
1250
1886
|
}
|
|
1887
|
+
return true;
|
|
1251
1888
|
}
|
|
1252
1889
|
async recoverBufferedTransactionsAfterLocalLoadFailureUnsafe(lifecycleGeneration, adapter) {
|
|
1253
1890
|
let snapshotRows;
|
|
@@ -1282,18 +1919,15 @@ class PersistedCollectionRuntime {
|
|
|
1282
1919
|
const key = operation.key;
|
|
1283
1920
|
if (operation.type === `update` && transaction.partialUpdateOperationIndexes.has(index) && getPresence(key) === `unknown`) {
|
|
1284
1921
|
if (snapshotRows === void 0) {
|
|
1285
|
-
const snapshot = await adapter.loadResumeSnapshot(
|
|
1286
|
-
this.
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
}
|
|
1291
|
-
);
|
|
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
|
+
});
|
|
1292
1927
|
this.throwIfLifecycleReplaced(lifecycleGeneration);
|
|
1293
1928
|
if (transaction.signal?.aborted) break;
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
);
|
|
1929
|
+
const rows = snapshot && !this.scopedRecovery ? snapshot.rows : [];
|
|
1930
|
+
snapshotRows = new Map(rows.map((row) => [row.key, row]));
|
|
1297
1931
|
}
|
|
1298
1932
|
const baseline = snapshotRows.get(key);
|
|
1299
1933
|
if (baseline === void 0) {
|
|
@@ -1343,22 +1977,65 @@ class PersistedCollectionRuntime {
|
|
|
1343
1977
|
}
|
|
1344
1978
|
this.queuedHydrationTransactions.length = 0;
|
|
1345
1979
|
}
|
|
1346
|
-
applyRowsToCollection(rows) {
|
|
1980
|
+
applyRowsToCollection(rows, validationRows, collectionMetadata) {
|
|
1347
1981
|
if (!this.syncControls.begin || !this.syncControls.write || !this.syncControls.commit) {
|
|
1348
1982
|
return true;
|
|
1349
1983
|
}
|
|
1350
1984
|
return this.withInternalApply(() => {
|
|
1351
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
|
+
}
|
|
1352
2021
|
for (const row of rows) {
|
|
1353
|
-
if (this.collection?._hasHydratedKey(row.key)) {
|
|
2022
|
+
if (validationRows && this.collection?.base.has(row.key) || this.collection?._hasHydratedKey(row.key)) {
|
|
1354
2023
|
continue;
|
|
1355
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
|
+
}
|
|
1356
2031
|
this.syncControls.write?.({
|
|
1357
2032
|
type: `update`,
|
|
1358
2033
|
value: row.value,
|
|
1359
2034
|
metadata: row.metadata
|
|
1360
2035
|
});
|
|
1361
2036
|
}
|
|
2037
|
+
if (collectionMetadata)
|
|
2038
|
+
this.applyCollectionMetadataSnapshot(collectionMetadata);
|
|
1362
2039
|
return this.syncControls.commit?.() ?? true;
|
|
1363
2040
|
});
|
|
1364
2041
|
}
|
|
@@ -1372,7 +2049,9 @@ class PersistedCollectionRuntime {
|
|
|
1372
2049
|
}
|
|
1373
2050
|
isExpectedResumeGeneration(generation) {
|
|
1374
2051
|
const expected = this.persistedResumeGeneration;
|
|
1375
|
-
return expected !== void 0 && expected.
|
|
2052
|
+
return expected !== void 0 && expected.latestRowVersion === generation.latestRowVersion && expected.resetEpoch === generation.resetEpoch && (expected.latestTerm === generation.latestTerm && expected.latestSeq === generation.latestSeq || // A no-write election reserves a new term at sequence zero. It does
|
|
2053
|
+
// not change the persisted rows or the source resume cursor.
|
|
2054
|
+
generation.latestTerm > expected.latestTerm && generation.latestSeq === 0);
|
|
1376
2055
|
}
|
|
1377
2056
|
bindResumeSnapshotEvidence(snapshot) {
|
|
1378
2057
|
const generation = this.getResumeSnapshotGeneration(snapshot);
|
|
@@ -1385,35 +2064,69 @@ class PersistedCollectionRuntime {
|
|
|
1385
2064
|
);
|
|
1386
2065
|
this.persistedKeySetEvidence = this.isExpectedResumeGeneration(generation) || mayAcceptUnownedGeneration ? snapshot.keySet : { status: `incompatible` };
|
|
1387
2066
|
}
|
|
1388
|
-
replaceCollectionSnapshot(rows, collectionMetadata) {
|
|
2067
|
+
replaceCollectionSnapshot(rows, collectionMetadata, replayDemand) {
|
|
1389
2068
|
if (!this.syncControls.begin || !this.syncControls.write || !this.syncControls.commit || !this.syncControls.metadata) {
|
|
1390
2069
|
return true;
|
|
1391
2070
|
}
|
|
1392
|
-
const
|
|
1393
|
-
|
|
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
|
+
)
|
|
1394
2076
|
);
|
|
1395
|
-
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);
|
|
1396
2080
|
return this.withInternalApply(() => {
|
|
1397
2081
|
this.syncControls.begin?.();
|
|
1398
|
-
|
|
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
|
+
}
|
|
1399
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
|
+
}
|
|
1400
2099
|
this.syncControls.write?.({
|
|
1401
2100
|
type: `update`,
|
|
1402
2101
|
value: row.value,
|
|
1403
2102
|
metadata: row.metadata
|
|
1404
2103
|
});
|
|
1405
2104
|
}
|
|
1406
|
-
|
|
1407
|
-
if (!nextMetadata.has(key)) {
|
|
1408
|
-
this.syncControls.metadata?.collection.delete(key);
|
|
1409
|
-
}
|
|
1410
|
-
});
|
|
1411
|
-
nextMetadata.forEach((value, key) => {
|
|
1412
|
-
this.syncControls.metadata?.collection.set(key, value);
|
|
1413
|
-
});
|
|
2105
|
+
this.applyCollectionMetadataSnapshot(collectionMetadata);
|
|
1414
2106
|
return this.syncControls.commit?.() ?? true;
|
|
1415
2107
|
});
|
|
1416
2108
|
}
|
|
2109
|
+
matchesCollectionSnapshot(rows, collectionMetadata) {
|
|
2110
|
+
if (!this.collection || !this.syncControls.metadata) return false;
|
|
2111
|
+
const base = this.collection.base;
|
|
2112
|
+
if (base.size !== rows.size) return false;
|
|
2113
|
+
for (const [key, row] of rows) {
|
|
2114
|
+
if (!base.has(key) || !equalPersistedSnapshotValues(base.get(key), row.value) || !equalPersistedSnapshotValues(
|
|
2115
|
+
this.syncControls.metadata.row.get(key),
|
|
2116
|
+
row.metadata
|
|
2117
|
+
)) {
|
|
2118
|
+
return false;
|
|
2119
|
+
}
|
|
2120
|
+
}
|
|
2121
|
+
const currentMetadata = this.syncControls.metadata.collection.list();
|
|
2122
|
+
if (currentMetadata.length !== collectionMetadata.length) return false;
|
|
2123
|
+
const expectedMetadata = new Map(
|
|
2124
|
+
collectionMetadata.map(({ key, value }) => [key, value])
|
|
2125
|
+
);
|
|
2126
|
+
return currentMetadata.every(
|
|
2127
|
+
({ key, value }) => expectedMetadata.has(key) && equalPersistedSnapshotValues(expectedMetadata.get(key), value)
|
|
2128
|
+
);
|
|
2129
|
+
}
|
|
1417
2130
|
async flushQueuedHydrationTransactionsUnsafe(adapter) {
|
|
1418
2131
|
let operationFailure;
|
|
1419
2132
|
while (this.queuedHydrationTransactions.length > 0) {
|
|
@@ -1441,6 +2154,31 @@ class PersistedCollectionRuntime {
|
|
|
1441
2154
|
async applyBufferedSyncTransactionUnsafe(transaction, scopedAdapter) {
|
|
1442
2155
|
this.throwIfTerminal();
|
|
1443
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
|
+
}
|
|
1444
2182
|
const abortedBeforeApplication = transaction.signal?.aborted === true;
|
|
1445
2183
|
let abortedDuringApplication = false;
|
|
1446
2184
|
let applicationReturned = false;
|
|
@@ -1498,16 +2236,56 @@ class PersistedCollectionRuntime {
|
|
|
1498
2236
|
if (!transaction.truncate && transaction.operations.length === 0 && transaction.rowMetadataWrites.size === 0 && transaction.collectionMetadataWrites.size === 0) {
|
|
1499
2237
|
return;
|
|
1500
2238
|
}
|
|
2239
|
+
const anchor = this.persistedResumeGeneration && {
|
|
2240
|
+
latestRowVersion: this.latestRowVersion,
|
|
2241
|
+
resetEpoch: this.persistedResumeGeneration.resetEpoch
|
|
2242
|
+
};
|
|
1501
2243
|
const streamPosition = this.nextLocalStreamPosition();
|
|
1502
2244
|
const tx = this.createPersistedTxFromOperations(transaction, streamPosition);
|
|
1503
|
-
const
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
2245
|
+
const storageCollectionId = this.storageCollectionId;
|
|
2246
|
+
let response;
|
|
2247
|
+
try {
|
|
2248
|
+
response = await this.persistence.coordinator.requestApplyCommittedTx(
|
|
2249
|
+
storageCollectionId,
|
|
2250
|
+
tx,
|
|
2251
|
+
// A hydration-scoped adapter is already inside the shared scheduler.
|
|
2252
|
+
// Ordinary source commits enter it before taking the writer lock.
|
|
2253
|
+
scopedAdapter
|
|
2254
|
+
);
|
|
2255
|
+
} catch (error) {
|
|
2256
|
+
if (!(error instanceof IndeterminateCommitError) || !anchor || !this.persistence.coordinator.reconcileCommittedTx) {
|
|
2257
|
+
throw error;
|
|
2258
|
+
}
|
|
2259
|
+
let reconciled;
|
|
2260
|
+
try {
|
|
2261
|
+
reconciled = await this.persistence.coordinator.reconcileCommittedTx(
|
|
2262
|
+
storageCollectionId,
|
|
2263
|
+
tx,
|
|
2264
|
+
anchor,
|
|
2265
|
+
scopedAdapter
|
|
2266
|
+
);
|
|
2267
|
+
} catch (reconcileError) {
|
|
2268
|
+
if (reconcileError instanceof PersistedCollectionDurabilityError) {
|
|
2269
|
+
throw reconcileError;
|
|
2270
|
+
}
|
|
2271
|
+
throw error;
|
|
2272
|
+
}
|
|
2273
|
+
if (!reconciled.ok) {
|
|
2274
|
+
if (reconciled.code === `PERSISTENCE_ERROR`) {
|
|
2275
|
+
throw new PersistedCollectionDurabilityError(
|
|
2276
|
+
`Failed to reconcile collection "${this.collectionId}": ${reconciled.error}`,
|
|
2277
|
+
{
|
|
2278
|
+
cause: reconciled,
|
|
2279
|
+
code: reconciled.sourceCode ?? reconciled.code,
|
|
2280
|
+
path: reconciled.path
|
|
2281
|
+
}
|
|
2282
|
+
);
|
|
2283
|
+
}
|
|
2284
|
+
throw error;
|
|
2285
|
+
}
|
|
2286
|
+
this.recordDurableSourceCommit(transaction, reconciled.committed);
|
|
2287
|
+
return;
|
|
2288
|
+
}
|
|
1511
2289
|
if (!response.ok) {
|
|
1512
2290
|
if (response.code === `PERSISTENCE_ERROR`) {
|
|
1513
2291
|
const error = new PersistedCollectionDurabilityError(
|
|
@@ -1524,18 +2302,24 @@ class PersistedCollectionRuntime {
|
|
|
1524
2302
|
`failed to apply external sync transaction through coordinator: ${response.error}`
|
|
1525
2303
|
);
|
|
1526
2304
|
}
|
|
2305
|
+
this.recordDurableSourceCommit(transaction, {
|
|
2306
|
+
term: response.term,
|
|
2307
|
+
seq: response.seq,
|
|
2308
|
+
rowVersion: response.latestRowVersion
|
|
2309
|
+
});
|
|
2310
|
+
}
|
|
2311
|
+
recordDurableSourceCommit(transaction, position) {
|
|
1527
2312
|
if (transaction.lifecycleGeneration !== this.lifecycleGeneration) return;
|
|
1528
|
-
this.observeStreamPosition(
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
);
|
|
2313
|
+
this.observeStreamPosition(position.term, position.seq, position.rowVersion);
|
|
2314
|
+
if (position.rowVersion === this.publicRowVersion + 1) {
|
|
2315
|
+
this.publicRowVersion = position.rowVersion;
|
|
2316
|
+
}
|
|
1533
2317
|
if (transaction.expectedResumeGenerationOwner === this.resumeGenerationOwner && this.persistedResumeGeneration !== void 0) {
|
|
1534
2318
|
this.persistedResumeGeneration = {
|
|
1535
2319
|
...this.persistedResumeGeneration,
|
|
1536
|
-
latestTerm:
|
|
1537
|
-
latestSeq:
|
|
1538
|
-
latestRowVersion:
|
|
2320
|
+
latestTerm: position.term,
|
|
2321
|
+
latestSeq: position.seq,
|
|
2322
|
+
latestRowVersion: position.rowVersion
|
|
1539
2323
|
};
|
|
1540
2324
|
}
|
|
1541
2325
|
}
|
|
@@ -1543,7 +2327,10 @@ class PersistedCollectionRuntime {
|
|
|
1543
2327
|
this.throwIfTerminal();
|
|
1544
2328
|
this.throwIfLifecycleReplaced(lifecycleGeneration);
|
|
1545
2329
|
try {
|
|
1546
|
-
await this.persistence.adapter.applyCommittedTx(
|
|
2330
|
+
await this.persistence.adapter.applyCommittedTx(
|
|
2331
|
+
this.storageTargetFor(),
|
|
2332
|
+
tx
|
|
2333
|
+
);
|
|
1547
2334
|
} catch (error) {
|
|
1548
2335
|
throw this.markTerminalFailure(
|
|
1549
2336
|
toPersistedCollectionDurabilityError(this.collectionId, error),
|
|
@@ -1558,6 +2345,7 @@ class PersistedCollectionRuntime {
|
|
|
1558
2345
|
term: streamPosition.term,
|
|
1559
2346
|
seq: streamPosition.seq,
|
|
1560
2347
|
rowVersion: streamPosition.rowVersion,
|
|
2348
|
+
cacheGenerationClaimId: this.cacheGenerationClaim?.claimId,
|
|
1561
2349
|
truncate: transaction.truncate,
|
|
1562
2350
|
mutations: transaction.operations.map(
|
|
1563
2351
|
(operation) => operation.type === `update` ? {
|
|
@@ -1592,6 +2380,7 @@ class PersistedCollectionRuntime {
|
|
|
1592
2380
|
term: streamPosition.term,
|
|
1593
2381
|
seq: streamPosition.seq,
|
|
1594
2382
|
rowVersion: streamPosition.rowVersion,
|
|
2383
|
+
cacheGenerationClaimId: this.cacheGenerationClaim?.claimId,
|
|
1595
2384
|
mutations: mutations.map((mutation) => {
|
|
1596
2385
|
if (mutation.type === `delete`) {
|
|
1597
2386
|
return {
|
|
@@ -1651,14 +2440,16 @@ class PersistedCollectionRuntime {
|
|
|
1651
2440
|
async persistCollectionMutationsUnsafe(mutations, lifecycleGeneration) {
|
|
1652
2441
|
this.throwIfLifecycleReplaced(lifecycleGeneration);
|
|
1653
2442
|
if (this.persistence.coordinator.requestApplyLocalMutations) {
|
|
2443
|
+
const target = this.storageTargetFor();
|
|
1654
2444
|
const envelopeMutations = mutations.map(
|
|
1655
2445
|
(mutation) => toPersistedMutationEnvelope(
|
|
1656
2446
|
mutation
|
|
1657
2447
|
)
|
|
1658
2448
|
);
|
|
1659
2449
|
const response = await this.persistence.coordinator.requestApplyLocalMutations(
|
|
1660
|
-
this.
|
|
1661
|
-
envelopeMutations
|
|
2450
|
+
this.storageCollectionId,
|
|
2451
|
+
envelopeMutations,
|
|
2452
|
+
target.kind === `managed` ? target.claimId : void 0
|
|
1662
2453
|
);
|
|
1663
2454
|
this.throwIfLifecycleReplaced(lifecycleGeneration);
|
|
1664
2455
|
if (!response.ok) {
|
|
@@ -1755,12 +2546,12 @@ class PersistedCollectionRuntime {
|
|
|
1755
2546
|
const envelope = {
|
|
1756
2547
|
v: 1,
|
|
1757
2548
|
dbName: this.dbName,
|
|
1758
|
-
collectionId: this.
|
|
2549
|
+
collectionId: this.storageCollectionId,
|
|
1759
2550
|
senderId: this.persistence.coordinator.getNodeId(),
|
|
1760
2551
|
ts: Date.now(),
|
|
1761
2552
|
payload: txCommitted
|
|
1762
2553
|
};
|
|
1763
|
-
this.persistence.coordinator.publish(this.
|
|
2554
|
+
this.persistence.coordinator.publish(this.storageCollectionId, envelope);
|
|
1764
2555
|
}
|
|
1765
2556
|
observeStreamPosition(term, seq, rowVersion) {
|
|
1766
2557
|
if (term > this.latestTerm || term === this.latestTerm && seq > this.latestSeq) {
|
|
@@ -1802,7 +2593,7 @@ class PersistedCollectionRuntime {
|
|
|
1802
2593
|
if (this.getCurrentTerminalFailure() || this.mode !== `sync-present` || this.persistence.coordinator instanceof SingleProcessCoordinator) {
|
|
1803
2594
|
return false;
|
|
1804
2595
|
}
|
|
1805
|
-
return !this.persistence.coordinator.isLeader(this.
|
|
2596
|
+
return !this.persistence.coordinator.isLeader(this.storageCollectionId) || this.remoteSubsetOwnerUnsubscribe !== null;
|
|
1806
2597
|
}
|
|
1807
2598
|
queueRemoteSubsetEnsure(options) {
|
|
1808
2599
|
if (options.signal?.aborted || !this.canRouteRemoteDemandThroughCoordinator() || this.activeSubsets.get(this.getSubsetKey(options)) !== options) {
|
|
@@ -1841,8 +2632,8 @@ class PersistedCollectionRuntime {
|
|
|
1841
2632
|
}
|
|
1842
2633
|
try {
|
|
1843
2634
|
await this.persistence.coordinator.requestEnsureRemoteSubset(
|
|
1844
|
-
this.
|
|
1845
|
-
options
|
|
2635
|
+
this.storageCollectionId,
|
|
2636
|
+
this.rotatedAcquisitions.get(options) ?? options
|
|
1846
2637
|
);
|
|
1847
2638
|
this.pendingRemoteSubsetEnsures.delete(subsetKey);
|
|
1848
2639
|
} catch (error) {
|
|
@@ -1860,18 +2651,19 @@ class PersistedCollectionRuntime {
|
|
|
1860
2651
|
return;
|
|
1861
2652
|
}
|
|
1862
2653
|
this.persistence.coordinator.setAdapterForCollection?.(
|
|
1863
|
-
this.
|
|
1864
|
-
this.persistence.adapter
|
|
2654
|
+
this.storageCollectionId,
|
|
2655
|
+
this.persistence.adapter,
|
|
2656
|
+
this.cacheGenerationClaim?.claimId
|
|
1865
2657
|
);
|
|
1866
2658
|
this.coordinatorUnsubscribe = this.persistence.coordinator.subscribe(
|
|
1867
|
-
this.
|
|
2659
|
+
this.storageCollectionId,
|
|
1868
2660
|
(message) => {
|
|
1869
2661
|
this.onCoordinatorMessage(message);
|
|
1870
2662
|
}
|
|
1871
2663
|
);
|
|
1872
2664
|
}
|
|
1873
2665
|
onCoordinatorMessage(message) {
|
|
1874
|
-
if (message.collectionId !== this.
|
|
2666
|
+
if (message.collectionId !== this.storageCollectionId) {
|
|
1875
2667
|
return;
|
|
1876
2668
|
}
|
|
1877
2669
|
if (this.getCurrentTerminalFailure()) {
|
|
@@ -1880,17 +2672,24 @@ class PersistedCollectionRuntime {
|
|
|
1880
2672
|
const { payload } = message;
|
|
1881
2673
|
const lifecycleGeneration = this.lifecycleGeneration;
|
|
1882
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
|
+
}
|
|
1883
2680
|
if (isTxCommittedPayload(payload)) {
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
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);
|
|
1888
2691
|
});
|
|
1889
|
-
return;
|
|
1890
2692
|
}
|
|
1891
|
-
void this.applyMutex.run(() => this.processCommittedTxUnsafe(payload, lifecycleGeneration)).catch((error) => {
|
|
1892
|
-
this.markTerminalFailure(error, lifecycleGeneration);
|
|
1893
|
-
});
|
|
1894
2693
|
return;
|
|
1895
2694
|
}
|
|
1896
2695
|
if (isSelf) {
|
|
@@ -1898,9 +2697,18 @@ class PersistedCollectionRuntime {
|
|
|
1898
2697
|
}
|
|
1899
2698
|
if (isCollectionResetPayload(payload)) {
|
|
1900
2699
|
void this.applyMutex.run(async () => {
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
2700
|
+
if (message.collectionId !== this.storageCollectionId) return;
|
|
2701
|
+
await this.runInHydrationScope(async (adapter) => {
|
|
2702
|
+
await this.truncateAndReloadUnsafe(adapter, lifecycleGeneration);
|
|
2703
|
+
await this.rebaseDurableStreamFromSnapshotUnsafe(
|
|
2704
|
+
adapter,
|
|
2705
|
+
lifecycleGeneration
|
|
2706
|
+
);
|
|
2707
|
+
if (lifecycleGeneration === this.lifecycleGeneration) {
|
|
2708
|
+
this.publicRowVersion = 0;
|
|
2709
|
+
this.resetTermFence = this.latestTerm;
|
|
2710
|
+
}
|
|
2711
|
+
});
|
|
1904
2712
|
if (lifecycleGeneration === this.lifecycleGeneration) {
|
|
1905
2713
|
await this.flushQueuedTxCommittedUnsafe();
|
|
1906
2714
|
}
|
|
@@ -1911,33 +2719,63 @@ class PersistedCollectionRuntime {
|
|
|
1911
2719
|
}
|
|
1912
2720
|
async flushQueuedTxCommittedUnsafe() {
|
|
1913
2721
|
while (this.queuedTxCommitted.length > 0) {
|
|
2722
|
+
if (this.hasExpiredCacheClaim()) {
|
|
2723
|
+
this.scheduleCacheClaimRecovery(this.lifecycleGeneration);
|
|
2724
|
+
return;
|
|
2725
|
+
}
|
|
1914
2726
|
const queued = this.queuedTxCommitted.shift();
|
|
1915
2727
|
if (!queued) {
|
|
1916
2728
|
continue;
|
|
1917
2729
|
}
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
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
|
+
}
|
|
1922
2739
|
}
|
|
1923
2740
|
}
|
|
1924
2741
|
async processCommittedTxUnsafe(txCommitted, lifecycleGeneration = this.lifecycleGeneration) {
|
|
1925
2742
|
if (lifecycleGeneration !== this.lifecycleGeneration) return;
|
|
1926
|
-
if (
|
|
1927
|
-
|
|
2743
|
+
if (this.resetTermFence !== void 0) {
|
|
2744
|
+
if (txCommitted.term <= this.resetTermFence) {
|
|
2745
|
+
await this.runInHydrationScope(
|
|
2746
|
+
(adapter) => this.reloadActiveSubsetsUnsafe(adapter)
|
|
2747
|
+
);
|
|
2748
|
+
return;
|
|
2749
|
+
}
|
|
1928
2750
|
}
|
|
1929
|
-
if (txCommitted.
|
|
2751
|
+
if (txCommitted.requiresFullReload && txCommitted.reconciliationReload) {
|
|
2752
|
+
await this.invalidateFromCommittedTxUnsafe(
|
|
2753
|
+
txCommitted,
|
|
2754
|
+
this.persistence.adapter
|
|
2755
|
+
);
|
|
2756
|
+
if (lifecycleGeneration === this.lifecycleGeneration) {
|
|
2757
|
+
this.observeStreamPosition(
|
|
2758
|
+
txCommitted.term,
|
|
2759
|
+
txCommitted.seq,
|
|
2760
|
+
txCommitted.latestRowVersion
|
|
2761
|
+
);
|
|
2762
|
+
this.publicRowVersion = Math.max(
|
|
2763
|
+
this.publicRowVersion,
|
|
2764
|
+
txCommitted.latestRowVersion
|
|
2765
|
+
);
|
|
2766
|
+
await this.flushQueuedTxCommittedUnsafe();
|
|
2767
|
+
}
|
|
1930
2768
|
return;
|
|
1931
2769
|
}
|
|
2770
|
+
if (txCommitted.latestRowVersion <= this.publicRowVersion) return;
|
|
2771
|
+
const observedButUnpublished = txCommitted.term < this.latestTerm || txCommitted.term === this.latestTerm && txCommitted.seq <= this.latestSeq;
|
|
1932
2772
|
const hasGapInCurrentTerm = txCommitted.term === this.latestTerm && txCommitted.seq > this.latestSeq + 1;
|
|
1933
2773
|
const hasGapAcrossTerms = txCommitted.term > this.latestTerm && txCommitted.seq > 1;
|
|
1934
|
-
const hasGap = hasGapInCurrentTerm || hasGapAcrossTerms;
|
|
2774
|
+
const hasGap = observedButUnpublished || txCommitted.latestRowVersion > this.publicRowVersion + 1 || hasGapInCurrentTerm || hasGapAcrossTerms;
|
|
1935
2775
|
if (hasGap) {
|
|
1936
|
-
await this.recoverFromSeqGapUnsafe(lifecycleGeneration);
|
|
2776
|
+
if (!await this.recoverFromSeqGapUnsafe(lifecycleGeneration)) return;
|
|
1937
2777
|
if (lifecycleGeneration !== this.lifecycleGeneration) return;
|
|
1938
|
-
if (txCommitted.
|
|
1939
|
-
return;
|
|
1940
|
-
}
|
|
2778
|
+
if (txCommitted.latestRowVersion <= this.publicRowVersion) return;
|
|
1941
2779
|
}
|
|
1942
2780
|
this.observeStreamPosition(
|
|
1943
2781
|
txCommitted.term,
|
|
@@ -1949,23 +2787,49 @@ class PersistedCollectionRuntime {
|
|
|
1949
2787
|
this.persistence.adapter
|
|
1950
2788
|
);
|
|
1951
2789
|
if (lifecycleGeneration === this.lifecycleGeneration) {
|
|
2790
|
+
this.publicRowVersion = Math.max(
|
|
2791
|
+
this.publicRowVersion,
|
|
2792
|
+
txCommitted.latestRowVersion
|
|
2793
|
+
);
|
|
1952
2794
|
await this.flushQueuedTxCommittedUnsafe();
|
|
1953
2795
|
}
|
|
1954
2796
|
}
|
|
1955
|
-
async
|
|
2797
|
+
async rebaseDurableStreamFromSnapshotUnsafe(adapter, lifecycleGeneration) {
|
|
2798
|
+
if (lifecycleGeneration !== this.lifecycleGeneration) return;
|
|
2799
|
+
const snapshot = await adapter.loadResumeSnapshot(this.storageTargetFor(), {
|
|
2800
|
+
includeRows: false,
|
|
2801
|
+
cacheGenerationClaimId: this.cacheGenerationClaim?.claimId
|
|
2802
|
+
});
|
|
1956
2803
|
if (lifecycleGeneration !== this.lifecycleGeneration) return;
|
|
2804
|
+
this.latestRowVersion = snapshot.latestRowVersion;
|
|
2805
|
+
this.localRowVersion = snapshot.latestRowVersion;
|
|
2806
|
+
this.observeStreamPosition(
|
|
2807
|
+
snapshot.latestTerm,
|
|
2808
|
+
snapshot.latestSeq,
|
|
2809
|
+
snapshot.latestRowVersion
|
|
2810
|
+
);
|
|
2811
|
+
}
|
|
2812
|
+
async recoverFromSeqGapUnsafe(lifecycleGeneration) {
|
|
2813
|
+
if (lifecycleGeneration !== this.lifecycleGeneration) return false;
|
|
1957
2814
|
if (this.persistence.coordinator.pullSince && this.latestRowVersion >= 0) {
|
|
1958
2815
|
let pullResponse;
|
|
1959
2816
|
try {
|
|
1960
2817
|
pullResponse = await this.persistence.coordinator.pullSince(
|
|
1961
|
-
this.
|
|
1962
|
-
this.
|
|
2818
|
+
this.storageCollectionId,
|
|
2819
|
+
this.publicRowVersion,
|
|
2820
|
+
void 0,
|
|
2821
|
+
this.cacheGenerationClaim?.claimId
|
|
1963
2822
|
);
|
|
1964
2823
|
} catch (error) {
|
|
1965
2824
|
console.warn(`Failed pullSince recovery attempt:`, error);
|
|
1966
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
|
+
}
|
|
1967
2831
|
if (pullResponse) {
|
|
1968
|
-
if (lifecycleGeneration !== this.lifecycleGeneration) return;
|
|
2832
|
+
if (lifecycleGeneration !== this.lifecycleGeneration) return false;
|
|
1969
2833
|
if (pullResponse.ok) {
|
|
1970
2834
|
this.observeStreamPosition(
|
|
1971
2835
|
pullResponse.latestTerm,
|
|
@@ -1976,14 +2840,22 @@ class PersistedCollectionRuntime {
|
|
|
1976
2840
|
await this.runInHydrationScope(
|
|
1977
2841
|
(adapter) => this.reloadActiveSubsetsUnsafe(adapter)
|
|
1978
2842
|
);
|
|
1979
|
-
|
|
2843
|
+
this.publicRowVersion = Math.max(
|
|
2844
|
+
this.publicRowVersion,
|
|
2845
|
+
pullResponse.latestRowVersion
|
|
2846
|
+
);
|
|
2847
|
+
return true;
|
|
1980
2848
|
}
|
|
1981
2849
|
const deltas = pullResponse.deltas;
|
|
1982
2850
|
if (!deltas) {
|
|
1983
2851
|
await this.runInHydrationScope(
|
|
1984
2852
|
(adapter) => this.reloadActiveSubsetsUnsafe(adapter)
|
|
1985
2853
|
);
|
|
1986
|
-
|
|
2854
|
+
this.publicRowVersion = Math.max(
|
|
2855
|
+
this.publicRowVersion,
|
|
2856
|
+
pullResponse.latestRowVersion
|
|
2857
|
+
);
|
|
2858
|
+
return true;
|
|
1987
2859
|
}
|
|
1988
2860
|
await this.runInHydrationScope(async (adapter) => {
|
|
1989
2861
|
for (const delta of deltas) {
|
|
@@ -2006,25 +2878,69 @@ class PersistedCollectionRuntime {
|
|
|
2006
2878
|
if (lifecycleGeneration !== this.lifecycleGeneration) return;
|
|
2007
2879
|
}
|
|
2008
2880
|
});
|
|
2009
|
-
|
|
2881
|
+
this.publicRowVersion = Math.max(
|
|
2882
|
+
this.publicRowVersion,
|
|
2883
|
+
pullResponse.latestRowVersion
|
|
2884
|
+
);
|
|
2885
|
+
return true;
|
|
2010
2886
|
}
|
|
2011
2887
|
}
|
|
2012
2888
|
}
|
|
2013
|
-
if (lifecycleGeneration !== this.lifecycleGeneration) return;
|
|
2889
|
+
if (lifecycleGeneration !== this.lifecycleGeneration) return false;
|
|
2014
2890
|
await this.runInHydrationScope(
|
|
2015
|
-
(adapter) => this.
|
|
2891
|
+
(adapter) => this.reloadActiveSubsetsUnsafe(adapter)
|
|
2892
|
+
);
|
|
2893
|
+
this.publicRowVersion = Math.max(
|
|
2894
|
+
this.publicRowVersion,
|
|
2895
|
+
this.latestRowVersion
|
|
2016
2896
|
);
|
|
2017
2897
|
if (this.mode === `sync-present`) {
|
|
2018
2898
|
for (const options of this.activeSubsets.values()) {
|
|
2019
2899
|
this.queueRemoteSubsetEnsure(options);
|
|
2020
2900
|
}
|
|
2021
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
|
+
}
|
|
2022
2934
|
}
|
|
2023
2935
|
async truncateAndReloadUnsafe(adapter, lifecycleGeneration = this.lifecycleGeneration) {
|
|
2024
2936
|
if (lifecycleGeneration !== this.lifecycleGeneration) return;
|
|
2937
|
+
if (this.scopedRecovery) {
|
|
2938
|
+
this.refreshScopedSubsets();
|
|
2939
|
+
return;
|
|
2940
|
+
}
|
|
2025
2941
|
this.hydratedDemands.clear();
|
|
2026
2942
|
this.resetSequence++;
|
|
2027
|
-
if (this.syncControls.begin && this.syncControls.commit) {
|
|
2943
|
+
if (this.syncMode === `eager` && this.syncControls.begin && this.syncControls.commit) {
|
|
2028
2944
|
const applied = this.withInternalApply(() => {
|
|
2029
2945
|
this.syncControls.begin?.();
|
|
2030
2946
|
this.syncControls.truncate?.();
|
|
@@ -2033,15 +2949,24 @@ class PersistedCollectionRuntime {
|
|
|
2033
2949
|
await whenSyncAccepted(applied);
|
|
2034
2950
|
}
|
|
2035
2951
|
if (lifecycleGeneration !== this.lifecycleGeneration) return;
|
|
2036
|
-
await this.reloadActiveSubsetsUnsafe(
|
|
2952
|
+
await this.reloadActiveSubsetsUnsafe(
|
|
2953
|
+
adapter,
|
|
2954
|
+
false,
|
|
2955
|
+
this.syncMode === `eager`,
|
|
2956
|
+
true
|
|
2957
|
+
);
|
|
2037
2958
|
}
|
|
2038
2959
|
async invalidateFromCommittedTxUnsafe(txCommitted, adapter) {
|
|
2039
|
-
|
|
2040
|
-
|
|
2960
|
+
if (this.scopedRecovery) {
|
|
2961
|
+
this.refreshScopedSubsets();
|
|
2962
|
+
return;
|
|
2963
|
+
}
|
|
2964
|
+
const reloadActiveSubsets = (skipUnchangedReplace = false) => this.runInHydrationScope(
|
|
2965
|
+
(scopedAdapter) => this.reloadActiveSubsetsUnsafe(scopedAdapter, skipUnchangedReplace),
|
|
2041
2966
|
adapter
|
|
2042
2967
|
);
|
|
2043
2968
|
if (txCommitted.requiresFullReload) {
|
|
2044
|
-
await reloadActiveSubsets();
|
|
2969
|
+
await reloadActiveSubsets(txCommitted.reconciliationReload === true);
|
|
2045
2970
|
return;
|
|
2046
2971
|
}
|
|
2047
2972
|
const changedKeyCount = txCommitted.changedRows.length + txCommitted.deletedKeys.length;
|
|
@@ -2052,6 +2977,21 @@ class PersistedCollectionRuntime {
|
|
|
2052
2977
|
const hasPaginatedSubset = Array.from(this.activeSubsets.values()).some(
|
|
2053
2978
|
(opt) => opt.limit != null || opt.offset != null || opt.cursor != null
|
|
2054
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
|
+
}
|
|
2055
2995
|
if (!hasPaginatedSubset || changedKeyCount === 0) {
|
|
2056
2996
|
await this.applyTargetedInvalidationUnsafe(txCommitted);
|
|
2057
2997
|
return;
|
|
@@ -2068,20 +3008,33 @@ class PersistedCollectionRuntime {
|
|
|
2068
3008
|
key: changedKey,
|
|
2069
3009
|
value: newValue
|
|
2070
3010
|
} of txCommitted.changedRows) {
|
|
2071
|
-
const
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
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 });
|
|
2079
3028
|
}
|
|
2080
3029
|
}
|
|
2081
3030
|
for (const deletedKey of txCommitted.deletedKeys) {
|
|
2082
|
-
|
|
3031
|
+
const key = deletedKey;
|
|
3032
|
+
if (!this.collection?._hasHydratedKey(key)) {
|
|
3033
|
+
this.syncControls.write?.({ type: `delete`, key });
|
|
3034
|
+
}
|
|
2083
3035
|
}
|
|
2084
3036
|
txCommitted.rowMetadataMutations?.forEach((mutation) => {
|
|
3037
|
+
if (this.collection?._hasHydratedKey(mutation.key)) return;
|
|
2085
3038
|
if (mutation.type === `delete`) {
|
|
2086
3039
|
this.syncControls.metadata?.row.delete(mutation.key);
|
|
2087
3040
|
} else {
|
|
@@ -2105,10 +3058,25 @@ class PersistedCollectionRuntime {
|
|
|
2105
3058
|
});
|
|
2106
3059
|
await whenSyncAccepted(applied);
|
|
2107
3060
|
}
|
|
2108
|
-
async reloadActiveSubsetsUnsafe(adapter) {
|
|
3061
|
+
async reloadActiveSubsetsUnsafe(adapter, skipUnchangedReplace = false, restoreResetBaseline = false, replayDemand = false) {
|
|
3062
|
+
if (this.scopedRecovery) {
|
|
3063
|
+
this.refreshScopedSubsets();
|
|
3064
|
+
return;
|
|
3065
|
+
}
|
|
2109
3066
|
const lifecycleGeneration = this.lifecycleGeneration;
|
|
2110
3067
|
const truncateGeneration = this.sourceTruncateGeneration;
|
|
2111
|
-
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);
|
|
2112
3080
|
this.hydratedDemands.clear();
|
|
2113
3081
|
this.hydrationSequence++;
|
|
2114
3082
|
const hydrationContext = { suppliedRowKeys: /* @__PURE__ */ new Set() };
|
|
@@ -2116,30 +3084,91 @@ class PersistedCollectionRuntime {
|
|
|
2116
3084
|
this.hydratingGeneration = lifecycleGeneration;
|
|
2117
3085
|
try {
|
|
2118
3086
|
const mergedRows = /* @__PURE__ */ new Map();
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
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);
|
|
2123
3096
|
if (lifecycleGeneration !== this.lifecycleGeneration) return;
|
|
2124
|
-
for (const
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
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;
|
|
2129
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;
|
|
2130
3134
|
}
|
|
3135
|
+
if (this.isScopedRecoveryPending() || lifecycleGeneration !== this.lifecycleGeneration)
|
|
3136
|
+
return;
|
|
2131
3137
|
for (const key of mergedRows.keys()) {
|
|
2132
3138
|
hydrationContext.suppliedRowKeys.add(key);
|
|
2133
3139
|
}
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
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)) {
|
|
3149
|
+
const orphanKeys = this.collection?._syncedRowMetadataKeysOutsideBase() ?? [];
|
|
3150
|
+
if (orphanKeys.length > 0) {
|
|
3151
|
+
const applied = this.withInternalApply(() => {
|
|
3152
|
+
this.syncControls.begin?.();
|
|
3153
|
+
for (const key of orphanKeys) {
|
|
3154
|
+
this.syncControls.metadata?.row.delete(key);
|
|
3155
|
+
}
|
|
3156
|
+
return this.syncControls.commit?.() ?? true;
|
|
3157
|
+
});
|
|
3158
|
+
await whenSyncAccepted(applied);
|
|
3159
|
+
}
|
|
3160
|
+
} else {
|
|
3161
|
+
const applied = this.replaceCollectionSnapshot(
|
|
3162
|
+
Array.from(mergedRows.entries()).map(([key, row]) => ({
|
|
3163
|
+
key,
|
|
3164
|
+
value: row.value,
|
|
3165
|
+
metadata: row.metadata
|
|
3166
|
+
})),
|
|
3167
|
+
collectionMetadata,
|
|
3168
|
+
replayDemand
|
|
3169
|
+
);
|
|
3170
|
+
await whenSyncAccepted(applied);
|
|
3171
|
+
}
|
|
2143
3172
|
} finally {
|
|
2144
3173
|
if (this.activeHydrationContext === hydrationContext && this.hydratingGeneration === lifecycleGeneration) {
|
|
2145
3174
|
this.hydratingGeneration = null;
|
|
@@ -2164,7 +3193,12 @@ class PersistedCollectionRuntime {
|
|
|
2164
3193
|
return;
|
|
2165
3194
|
}
|
|
2166
3195
|
this.indexAddedUnsubscribe = this.collection.on(`index:added`, (event) => {
|
|
2167
|
-
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
|
+
});
|
|
2168
3202
|
});
|
|
2169
3203
|
this.indexRemovedUnsubscribe = this.collection.on(
|
|
2170
3204
|
`index:removed`,
|
|
@@ -2180,20 +3214,69 @@ class PersistedCollectionRuntime {
|
|
|
2180
3214
|
}
|
|
2181
3215
|
const indexMetadata = indexMetadataSnapshot ?? collection?.getIndexMetadata() ?? [];
|
|
2182
3216
|
const completedLocalIndexSignatures = /* @__PURE__ */ new Set();
|
|
3217
|
+
const lifecycleGeneration = this.lifecycleGeneration;
|
|
2183
3218
|
for (const metadata of indexMetadata) {
|
|
2184
|
-
|
|
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
|
+
)) {
|
|
2185
3230
|
completedLocalIndexSignatures.add(metadata.signature);
|
|
2186
3231
|
}
|
|
2187
3232
|
}
|
|
2188
3233
|
return completedLocalIndexSignatures;
|
|
2189
3234
|
}
|
|
2190
3235
|
async requestCoordinatorPersistedIndexes(indexMetadata, completedLocalIndexSignatures) {
|
|
3236
|
+
const lifecycleGeneration = this.lifecycleGeneration;
|
|
2191
3237
|
for (const metadata of indexMetadata) {
|
|
3238
|
+
if (!this.hasCurrentIndex(metadata)) continue;
|
|
3239
|
+
const storageCollectionId = this.storageCollectionId;
|
|
3240
|
+
const claimId = this.cacheGenerationClaim?.claimId;
|
|
2192
3241
|
await this.requestCoordinatorPersistedIndex(
|
|
2193
3242
|
metadata,
|
|
2194
3243
|
completedLocalIndexSignatures.has(metadata.signature)
|
|
2195
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;
|
|
2196
3278
|
}
|
|
3279
|
+
return false;
|
|
2197
3280
|
}
|
|
2198
3281
|
buildPersistedIndexSpec(index) {
|
|
2199
3282
|
return {
|
|
@@ -2206,19 +3289,36 @@ class PersistedCollectionRuntime {
|
|
|
2206
3289
|
};
|
|
2207
3290
|
}
|
|
2208
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;
|
|
2209
3297
|
const completedLocally = await this.ensureLocalPersistedIndex(
|
|
2210
3298
|
indexMetadata,
|
|
2211
3299
|
adapter
|
|
2212
3300
|
);
|
|
3301
|
+
if (lifecycleGeneration !== this.lifecycleGeneration) return;
|
|
3302
|
+
if (!this.hasCurrentIndex(indexMetadata)) {
|
|
3303
|
+
await this.markIndexRemoved(indexMetadata);
|
|
3304
|
+
return;
|
|
3305
|
+
}
|
|
2213
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;
|
|
2214
3313
|
}
|
|
2215
3314
|
async ensureLocalPersistedIndex(indexMetadata, adapter) {
|
|
2216
3315
|
try {
|
|
2217
3316
|
const spec = this.buildPersistedIndexSpec(indexMetadata);
|
|
2218
3317
|
await adapter.ensureIndex(
|
|
2219
|
-
this.
|
|
3318
|
+
this.storageTargetFor(),
|
|
2220
3319
|
indexMetadata.signature,
|
|
2221
|
-
spec
|
|
3320
|
+
spec,
|
|
3321
|
+
{ cacheGenerationClaimId: this.cacheGenerationClaim?.claimId }
|
|
2222
3322
|
);
|
|
2223
3323
|
return true;
|
|
2224
3324
|
} catch (error) {
|
|
@@ -2230,11 +3330,12 @@ class PersistedCollectionRuntime {
|
|
|
2230
3330
|
try {
|
|
2231
3331
|
const spec = this.buildPersistedIndexSpec(indexMetadata);
|
|
2232
3332
|
await this.persistence.coordinator.requestEnsurePersistedIndex(
|
|
2233
|
-
this.
|
|
3333
|
+
this.storageCollectionId,
|
|
2234
3334
|
indexMetadata.signature,
|
|
2235
3335
|
spec,
|
|
2236
3336
|
completedLocally ? this.persistence.adapter : void 0,
|
|
2237
|
-
completedLocally
|
|
3337
|
+
completedLocally,
|
|
3338
|
+
this.cacheGenerationClaim?.claimId
|
|
2238
3339
|
);
|
|
2239
3340
|
} catch (error) {
|
|
2240
3341
|
console.warn(
|
|
@@ -2248,10 +3349,19 @@ class PersistedCollectionRuntime {
|
|
|
2248
3349
|
return;
|
|
2249
3350
|
}
|
|
2250
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;
|
|
2251
3357
|
await this.persistence.adapter.markIndexRemoved(
|
|
2252
|
-
this.
|
|
2253
|
-
indexMetadata.signature
|
|
3358
|
+
this.storageTargetFor(),
|
|
3359
|
+
indexMetadata.signature,
|
|
3360
|
+
{ cacheGenerationClaimId: this.cacheGenerationClaim?.claimId }
|
|
2254
3361
|
);
|
|
3362
|
+
if (lifecycleGeneration === this.lifecycleGeneration && this.hasCurrentIndex(indexMetadata)) {
|
|
3363
|
+
await this.ensurePersistedIndex(indexMetadata);
|
|
3364
|
+
}
|
|
2255
3365
|
} catch (error) {
|
|
2256
3366
|
console.warn(`Failed to mark persisted index removed:`, error);
|
|
2257
3367
|
}
|
|
@@ -2454,20 +3564,26 @@ function createWrappedSyncConfig(sourceSyncConfig, runtime, persistedReadiness)
|
|
|
2454
3564
|
throw error;
|
|
2455
3565
|
}
|
|
2456
3566
|
};
|
|
2457
|
-
runtime.setSyncControls({
|
|
2458
|
-
begin: params.begin,
|
|
2459
|
-
write: params.write,
|
|
2460
|
-
commit: params.commit,
|
|
2461
|
-
truncate: params.truncate,
|
|
2462
|
-
metadata: params.metadata ?? null,
|
|
2463
|
-
markError: params.markError
|
|
2464
|
-
});
|
|
2465
3567
|
runtime.setCollection(
|
|
2466
|
-
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
|
+
}
|
|
2467
3577
|
);
|
|
2468
3578
|
const persistenceCapability = {
|
|
2469
3579
|
protocol: SYNC_PERSISTENCE_PROTOCOL,
|
|
2470
3580
|
version: SYNC_PERSISTENCE_VERSION,
|
|
3581
|
+
get managedCacheGeneration() {
|
|
3582
|
+
return runtime.hasManagedCacheGeneration();
|
|
3583
|
+
},
|
|
3584
|
+
get startupCacheGenerationRotated() {
|
|
3585
|
+
return runtime.hasRotatedStartupCacheGeneration();
|
|
3586
|
+
},
|
|
2471
3587
|
reserveCommitTurn: () => {
|
|
2472
3588
|
const openTransaction = getOpenTransaction();
|
|
2473
3589
|
if (openTransaction && !openTransaction.internal)
|
|
@@ -2481,6 +3597,7 @@ function createWrappedSyncConfig(sourceSyncConfig, runtime, persistedReadiness)
|
|
|
2481
3597
|
throw runtime.reportSyncError(error);
|
|
2482
3598
|
}
|
|
2483
3599
|
},
|
|
3600
|
+
startScopedRecovery: (resetMetadata) => startupState.cleanedUp ? Promise.resolve() : runtime.startScopedRecovery(resetMetadata),
|
|
2484
3601
|
scanPersistedRows: (options) => startupState.cleanedUp ? Promise.resolve([]) : runtime.scanPersistedRows(options),
|
|
2485
3602
|
resumeSnapshot: {
|
|
2486
3603
|
certify: async () => {
|
|
@@ -2504,6 +3621,7 @@ function createWrappedSyncConfig(sourceSyncConfig, runtime, persistedReadiness)
|
|
|
2504
3621
|
}
|
|
2505
3622
|
}
|
|
2506
3623
|
};
|
|
3624
|
+
let sourceSession = 0;
|
|
2507
3625
|
const wrappedParams = {
|
|
2508
3626
|
...params,
|
|
2509
3627
|
markError: reportUpstreamError,
|
|
@@ -2775,6 +3893,12 @@ function createWrappedSyncConfig(sourceSyncConfig, runtime, persistedReadiness)
|
|
|
2775
3893
|
settlePublicationAdmissionWaiters(openTransaction, error);
|
|
2776
3894
|
return createHandledRejection(error);
|
|
2777
3895
|
}
|
|
3896
|
+
if (runtime.isScopedRecoveryPending()) {
|
|
3897
|
+
settlePendingTransaction(openTransaction);
|
|
3898
|
+
const error = new SyncTransactionAbortedError();
|
|
3899
|
+
settlePublicationAdmissionWaiters(openTransaction, error);
|
|
3900
|
+
return createHandledRejection(error);
|
|
3901
|
+
}
|
|
2778
3902
|
try {
|
|
2779
3903
|
bindToCurrentHydration(openTransaction);
|
|
2780
3904
|
assertHydrationSequenceCurrent(openTransaction);
|
|
@@ -2849,48 +3973,150 @@ function createWrappedSyncConfig(sourceSyncConfig, runtime, persistedReadiness)
|
|
|
2849
3973
|
return visibleAfterDurable(openTransaction, applied);
|
|
2850
3974
|
}
|
|
2851
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
|
+
};
|
|
2852
4040
|
let sourceResult = {};
|
|
2853
4041
|
let sourceResultSettled = false;
|
|
2854
4042
|
let activeSourceSyncEntry;
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
if (startupState.cleanedUp) {
|
|
2860
|
-
sourceResultSettled = true;
|
|
2861
|
-
return sourceResult;
|
|
2862
|
-
}
|
|
4043
|
+
let sourceCleanupPromise;
|
|
4044
|
+
let customRestartSequence = 0;
|
|
4045
|
+
const enterSourceSession = () => {
|
|
4046
|
+
const session = ++sourceSession;
|
|
2863
4047
|
let settleSourceSyncEntry;
|
|
2864
4048
|
activeSourceSyncEntry = new Promise((resolve) => {
|
|
2865
4049
|
settleSourceSyncEntry = resolve;
|
|
2866
4050
|
});
|
|
2867
4051
|
try {
|
|
2868
4052
|
sourceResult = normalizeSyncFnResult(
|
|
2869
|
-
sourceSyncConfig.sync(
|
|
4053
|
+
sourceSyncConfig.sync(
|
|
4054
|
+
runtime.hasManagedCacheGeneration() ? sourceParamsFor(session) : wrappedParams
|
|
4055
|
+
)
|
|
2870
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
|
+
}
|
|
2871
4078
|
} finally {
|
|
2872
4079
|
activeSourceSyncEntry = void 0;
|
|
2873
4080
|
settleSourceSyncEntry();
|
|
2874
4081
|
}
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
);
|
|
2883
|
-
}
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
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)
|
|
2891
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;
|
|
2892
4114
|
}
|
|
4115
|
+
enterSourceSession();
|
|
2893
4116
|
sourceResultSettled = true;
|
|
4117
|
+
runtime.setSourceRestartAfterScopedRecovery(
|
|
4118
|
+
sourceResult.restartAfterScopedRecovery ?? (runtime.hasManagedCacheGeneration() ? restartCustomSource : void 0)
|
|
4119
|
+
);
|
|
2894
4120
|
return sourceResult;
|
|
2895
4121
|
})();
|
|
2896
4122
|
resolveSourceResultAssigned();
|
|
@@ -2900,6 +4126,7 @@ function createWrappedSyncConfig(sourceSyncConfig, runtime, persistedReadiness)
|
|
|
2900
4126
|
return {
|
|
2901
4127
|
cleanup: async () => {
|
|
2902
4128
|
startupState.cleanedUp = true;
|
|
4129
|
+
runtime.clearSourceRestartAfterScopedRecovery();
|
|
2903
4130
|
const sourceSyncEntry = activeSourceSyncEntry;
|
|
2904
4131
|
const cleanupError = new SyncTransactionAbortedError();
|
|
2905
4132
|
for (const acquisition of acquisitions.values()) {
|
|
@@ -2913,7 +4140,7 @@ function createWrappedSyncConfig(sourceSyncConfig, runtime, persistedReadiness)
|
|
|
2913
4140
|
transactionStack.length = 0;
|
|
2914
4141
|
let sourceCleanup;
|
|
2915
4142
|
try {
|
|
2916
|
-
sourceCleanup = sourceSyncEntry ? sourceSyncEntry.then(() => sourceResult.cleanup?.()) : Promise.resolve(sourceResult.cleanup?.());
|
|
4143
|
+
sourceCleanup = sourceCleanupPromise ?? (sourceSyncEntry ? sourceSyncEntry.then(() => sourceResult.cleanup?.()) : Promise.resolve(sourceResult.cleanup?.()));
|
|
2917
4144
|
} catch (error) {
|
|
2918
4145
|
sourceCleanup = Promise.reject(error);
|
|
2919
4146
|
}
|
|
@@ -2940,31 +4167,63 @@ function createWrappedSyncConfig(sourceSyncConfig, runtime, persistedReadiness)
|
|
|
2940
4167
|
loadSubset: (options) => {
|
|
2941
4168
|
const acquisition = { forwarded: false };
|
|
2942
4169
|
acquisitions.set(options, acquisition);
|
|
2943
|
-
const forwardUpstream = (
|
|
4170
|
+
const forwardUpstream = (loadOptions) => {
|
|
2944
4171
|
if (startupState.cleanedUp || acquisitions.get(options) !== acquisition) {
|
|
2945
4172
|
return true;
|
|
2946
4173
|
}
|
|
2947
|
-
|
|
4174
|
+
const currentSource = sourceResult;
|
|
4175
|
+
if (!currentSource.loadSubset) return true;
|
|
2948
4176
|
acquisition.forwarded = true;
|
|
4177
|
+
acquisition.source = currentSource;
|
|
2949
4178
|
try {
|
|
2950
|
-
return
|
|
4179
|
+
return currentSource.loadSubset(loadOptions);
|
|
2951
4180
|
} catch (error) {
|
|
2952
4181
|
acquisition.forwarded = false;
|
|
4182
|
+
acquisition.source = void 0;
|
|
2953
4183
|
throw error;
|
|
2954
4184
|
}
|
|
2955
4185
|
};
|
|
2956
4186
|
const openTransaction = getOpenTransaction();
|
|
2957
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
|
+
};
|
|
2958
4217
|
const hydrated = sourceResultSettled && !needsPublicationAdmission ? runtime.loadHydratedSubset(
|
|
2959
4218
|
options,
|
|
2960
|
-
(loadOptions) => forwardUpstream(
|
|
4219
|
+
(loadOptions) => forwardUpstream(loadOptions)
|
|
2961
4220
|
) : void 0;
|
|
2962
4221
|
if (hydrated !== void 0) {
|
|
2963
|
-
return hydrated;
|
|
4222
|
+
return hydrated === true ? true : raceRetainedLoad(hydrated);
|
|
2964
4223
|
}
|
|
2965
|
-
|
|
4224
|
+
const work = (async () => {
|
|
2966
4225
|
await fullStartPromise;
|
|
2967
|
-
|
|
4226
|
+
await sourceResultPromise;
|
|
2968
4227
|
if (startupState.cleanedUp || acquisitions.get(options) !== acquisition) {
|
|
2969
4228
|
return;
|
|
2970
4229
|
}
|
|
@@ -2981,9 +4240,14 @@ function createWrappedSyncConfig(sourceSyncConfig, runtime, persistedReadiness)
|
|
|
2981
4240
|
}
|
|
2982
4241
|
return runtime.loadSubset(
|
|
2983
4242
|
options,
|
|
2984
|
-
(loadOptions) => forwardUpstream(
|
|
4243
|
+
(loadOptions) => forwardUpstream(loadOptions),
|
|
4244
|
+
() => {
|
|
4245
|
+
hydrationStarted = true;
|
|
4246
|
+
},
|
|
4247
|
+
() => !startupState.cleanedUp && acquisitions.get(options) === acquisition
|
|
2985
4248
|
);
|
|
2986
4249
|
})();
|
|
4250
|
+
return raceRetainedLoad(work, () => !hydrationStarted);
|
|
2987
4251
|
},
|
|
2988
4252
|
unloadSubset: (options) => {
|
|
2989
4253
|
const acquisition = acquisitions.get(options);
|
|
@@ -2991,7 +4255,7 @@ function createWrappedSyncConfig(sourceSyncConfig, runtime, persistedReadiness)
|
|
|
2991
4255
|
acquisitions.delete(options);
|
|
2992
4256
|
runtime.unloadSubset(
|
|
2993
4257
|
options,
|
|
2994
|
-
acquisition?.forwarded ?
|
|
4258
|
+
acquisition?.forwarded ? acquisition.source?.unloadSubset : void 0
|
|
2995
4259
|
);
|
|
2996
4260
|
}
|
|
2997
4261
|
};
|
|
@@ -3001,16 +4265,16 @@ function createWrappedSyncConfig(sourceSyncConfig, runtime, persistedReadiness)
|
|
|
3001
4265
|
function createLoopbackSyncConfig(runtime) {
|
|
3002
4266
|
return {
|
|
3003
4267
|
sync: (params) => {
|
|
3004
|
-
runtime.setSyncControls({
|
|
3005
|
-
begin: params.begin,
|
|
3006
|
-
write: params.write,
|
|
3007
|
-
commit: params.commit,
|
|
3008
|
-
truncate: params.truncate,
|
|
3009
|
-
metadata: params.metadata ?? null,
|
|
3010
|
-
markError: params.markError
|
|
3011
|
-
});
|
|
3012
4268
|
runtime.setCollection(
|
|
3013
|
-
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
|
+
}
|
|
3014
4278
|
);
|
|
3015
4279
|
void runtime.ensureStarted().then(() => {
|
|
3016
4280
|
if (runtime.getCurrentTerminalFailure()) return;
|
|
@@ -3040,6 +4304,19 @@ function persistedCollectionOptions(options) {
|
|
|
3040
4304
|
);
|
|
3041
4305
|
}
|
|
3042
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
|
+
};
|
|
3043
4320
|
if (hasOwnSyncKey(options)) {
|
|
3044
4321
|
if (!isValidSyncConfig(options.sync)) {
|
|
3045
4322
|
throw new InvalidSyncConfigError(
|
|
@@ -3066,8 +4343,10 @@ function persistedCollectionOptions(options) {
|
|
|
3066
4343
|
persistence2,
|
|
3067
4344
|
syncOptions.syncMode ?? `eager`,
|
|
3068
4345
|
collectionId2,
|
|
3069
|
-
persistedReadinessTracker
|
|
4346
|
+
persistedReadinessTracker,
|
|
4347
|
+
claimOptions
|
|
3070
4348
|
);
|
|
4349
|
+
claimedRuntime = runtime2;
|
|
3071
4350
|
const result2 = {
|
|
3072
4351
|
...syncOptions,
|
|
3073
4352
|
id: collectionId2,
|
|
@@ -3084,7 +4363,8 @@ function persistedCollectionOptions(options) {
|
|
|
3084
4363
|
() => persistedCollectionOptions({
|
|
3085
4364
|
...options,
|
|
3086
4365
|
id: collectionId2
|
|
3087
|
-
})
|
|
4366
|
+
}),
|
|
4367
|
+
claimOptions
|
|
3088
4368
|
);
|
|
3089
4369
|
}
|
|
3090
4370
|
const {
|
|
@@ -3107,8 +4387,10 @@ function persistedCollectionOptions(options) {
|
|
|
3107
4387
|
persistence,
|
|
3108
4388
|
localOnlyOptions.syncMode ?? `eager`,
|
|
3109
4389
|
localOnlyOptions.id ?? DEFAULT_DB_NAME,
|
|
3110
|
-
persistedReadinessTracker
|
|
4390
|
+
persistedReadinessTracker,
|
|
4391
|
+
claimOptions
|
|
3111
4392
|
);
|
|
4393
|
+
claimedRuntime = runtime;
|
|
3112
4394
|
const wrappedOnInsert = async (params) => {
|
|
3113
4395
|
const lifecycleGeneration = runtime.getLifecycleGeneration();
|
|
3114
4396
|
const handlerResult = localOnlyOptions.onInsert ? await localOnlyOptions.onInsert(
|
|
@@ -3172,7 +4454,8 @@ function persistedCollectionOptions(options) {
|
|
|
3172
4454
|
() => persistedCollectionOptions({
|
|
3173
4455
|
...options,
|
|
3174
4456
|
id: collectionId
|
|
3175
|
-
})
|
|
4457
|
+
}),
|
|
4458
|
+
claimOptions
|
|
3176
4459
|
);
|
|
3177
4460
|
}
|
|
3178
4461
|
function encodePersistedStorageKey(key) {
|
|
@@ -3235,9 +4518,12 @@ export {
|
|
|
3235
4518
|
SingleProcessCoordinator,
|
|
3236
4519
|
createPersistedTableName,
|
|
3237
4520
|
decodePersistedStorageKey,
|
|
4521
|
+
eagerStorageTarget,
|
|
3238
4522
|
encodePersistedStorageKey,
|
|
3239
4523
|
forwardSQLiteDriverSharedLogicalScheduling,
|
|
4524
|
+
managedStorageTarget,
|
|
3240
4525
|
persistedCollectionOptions,
|
|
4526
|
+
resolvePersistedStorageTarget,
|
|
3241
4527
|
validatePersistedCollectionCoordinator
|
|
3242
4528
|
};
|
|
3243
4529
|
//# sourceMappingURL=persisted.js.map
|