@tanstack/db-sqlite-persistence-core 0.2.23 → 0.4.1
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 +258 -7
- package/dist/cjs/broadcast-coordinator.cjs +1689 -0
- package/dist/cjs/broadcast-coordinator.cjs.map +1 -0
- package/dist/cjs/broadcast-coordinator.d.cts +116 -0
- package/dist/cjs/errors.cjs +73 -0
- package/dist/cjs/errors.cjs.map +1 -1
- package/dist/cjs/errors.d.cts +44 -0
- package/dist/cjs/index.cjs +19 -0
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.cts +2 -0
- package/dist/cjs/persisted.cjs +1782 -394
- package/dist/cjs/persisted.cjs.map +1 -1
- package/dist/cjs/persisted.d.cts +176 -11
- package/dist/cjs/remote-subset-owner.cjs +20 -0
- package/dist/cjs/remote-subset-owner.cjs.map +1 -0
- package/dist/cjs/remote-subset-owner.d.cts +7 -0
- package/dist/cjs/remote-subset-wire.cjs +681 -0
- package/dist/cjs/remote-subset-wire.cjs.map +1 -0
- package/dist/cjs/remote-subset-wire.d.cts +59 -0
- package/dist/cjs/sqlite-core-adapter.cjs +969 -364
- package/dist/cjs/sqlite-core-adapter.cjs.map +1 -1
- package/dist/cjs/sqlite-core-adapter.d.cts +45 -1
- package/dist/cjs/sqlite-value.cjs +31 -0
- package/dist/cjs/sqlite-value.cjs.map +1 -0
- package/dist/cjs/sqlite-value.d.cts +10 -0
- package/dist/esm/broadcast-coordinator.d.ts +116 -0
- package/dist/esm/broadcast-coordinator.js +1689 -0
- package/dist/esm/broadcast-coordinator.js.map +1 -0
- package/dist/esm/errors.d.ts +44 -0
- package/dist/esm/errors.js +74 -1
- package/dist/esm/errors.js.map +1 -1
- package/dist/esm/index.d.ts +2 -0
- package/dist/esm/index.js +21 -2
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/persisted.d.ts +176 -11
- package/dist/esm/persisted.js +1784 -396
- package/dist/esm/persisted.js.map +1 -1
- package/dist/esm/remote-subset-owner.d.ts +7 -0
- package/dist/esm/remote-subset-owner.js +20 -0
- package/dist/esm/remote-subset-owner.js.map +1 -0
- package/dist/esm/remote-subset-wire.d.ts +59 -0
- package/dist/esm/remote-subset-wire.js +681 -0
- package/dist/esm/remote-subset-wire.js.map +1 -0
- package/dist/esm/sqlite-core-adapter.d.ts +45 -1
- package/dist/esm/sqlite-core-adapter.js +955 -350
- package/dist/esm/sqlite-core-adapter.js.map +1 -1
- package/dist/esm/sqlite-value.d.ts +10 -0
- package/dist/esm/sqlite-value.js +31 -0
- package/dist/esm/sqlite-value.js.map +1 -0
- package/package.json +12 -2
- package/src/broadcast-coordinator.ts +2456 -0
- package/src/errors.ts +120 -0
- package/src/index.ts +2 -0
- package/src/persisted.ts +2617 -442
- package/src/remote-subset-owner.ts +36 -0
- package/src/remote-subset-wire.ts +946 -0
- package/src/sqlite-core-adapter.ts +1297 -410
- package/src/sqlite-value.ts +30 -0
package/dist/esm/persisted.js
CHANGED
|
@@ -1,19 +1,87 @@
|
|
|
1
|
-
import { safeRandomUUID, withCollectionConfigFactory, SyncTransactionAbortedError, compileSingleRowExpression, toBooleanPredicate } from "@tanstack/db";
|
|
2
|
-
import {
|
|
1
|
+
import { safeRandomUUID, withCollectionConfigFactory, SyncTransactionAbortedError, getLoadSubsetDemandKey, compileSingleRowExpression, toBooleanPredicate, SYNC_PERSISTENCE_VERSION, SYNC_PERSISTENCE_PROTOCOL, PERSISTED_READINESS, NoPendingSyncTransactionCommitError, NoPendingSyncTransactionWriteError } from "@tanstack/db";
|
|
2
|
+
import { InvalidPersistedCollectionConfigError, DuplicateRemoteSubsetOwnerError, toPersistedCollectionDurabilityError, InvalidPersistedCollectionCoordinatorError, InvalidSyncConfigError, InvalidPersistedStorageKeyError, InvalidPersistedStorageKeyEncodingError, PersistedCollectionDurabilityError, InvalidPersistenceAdapterError } from "./errors.js";
|
|
3
|
+
import { serializeSQLiteBigInt } from "./sqlite-value.js";
|
|
4
|
+
import { toTransportedLoadSubsetOptions, toProcessLocalLoadSubsetOptions } from "./remote-subset-wire.js";
|
|
5
|
+
import { reportRemoteSubsetOwnerError, unloadRemoteSubsetOwner } from "./remote-subset-owner.js";
|
|
6
|
+
const SQLITE_DRIVER_SHARED_LOGICAL_SCHEDULING_KEY = /* @__PURE__ */ Symbol.for(
|
|
7
|
+
`tanstack-db.sqlite-driver-supports-shared-logical-scheduling`
|
|
8
|
+
);
|
|
9
|
+
function forwardSQLiteDriverSharedLogicalScheduling(source, wrapper) {
|
|
10
|
+
const key = source[SQLITE_DRIVER_SHARED_LOGICAL_SCHEDULING_KEY];
|
|
11
|
+
if (key) {
|
|
12
|
+
Object.defineProperty(
|
|
13
|
+
wrapper,
|
|
14
|
+
SQLITE_DRIVER_SHARED_LOGICAL_SCHEDULING_KEY,
|
|
15
|
+
{ value: key }
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
return wrapper;
|
|
19
|
+
}
|
|
20
|
+
class PersistedReadinessTracker {
|
|
21
|
+
constructor(networkTimeoutMs) {
|
|
22
|
+
this.networkTimeoutMs = networkTimeoutMs;
|
|
23
|
+
this.snapshot = { status: `loading` };
|
|
24
|
+
this.listeners = /* @__PURE__ */ new Set();
|
|
25
|
+
this.getSnapshot = () => this.snapshot;
|
|
26
|
+
this.getOrStartNetworkDeadline = () => {
|
|
27
|
+
return this.networkDeadlineAt ??= Date.now() + this.networkTimeoutMs;
|
|
28
|
+
};
|
|
29
|
+
this.subscribe = (listener) => {
|
|
30
|
+
this.listeners.add(listener);
|
|
31
|
+
return () => this.listeners.delete(listener);
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
set(snapshot) {
|
|
35
|
+
const deadlineRestarted = snapshot.status === `loading` && this.networkDeadlineAt !== void 0;
|
|
36
|
+
if (deadlineRestarted) {
|
|
37
|
+
this.networkDeadlineAt = Date.now() + this.networkTimeoutMs;
|
|
38
|
+
}
|
|
39
|
+
if (this.snapshot.status === snapshot.status && this.snapshot.error === snapshot.error && !deadlineRestarted) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
this.snapshot = snapshot;
|
|
43
|
+
for (const listener of this.listeners) listener();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
const DEFAULT_NETWORK_TIMEOUT_MS = 3e3;
|
|
47
|
+
const MAX_TIMER_TIMEOUT_MS = 2147483647;
|
|
48
|
+
function getNetworkTimeoutMs(initialRender) {
|
|
49
|
+
if (initialRender === void 0) return void 0;
|
|
50
|
+
const strategy = initialRender.strategy;
|
|
51
|
+
if (strategy !== `network-first`) {
|
|
52
|
+
throw new InvalidPersistedCollectionConfigError(
|
|
53
|
+
`initialRender.strategy must be "network-first"`
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
const timeoutMs = initialRender.networkTimeoutMs ?? DEFAULT_NETWORK_TIMEOUT_MS;
|
|
57
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs < 0 || timeoutMs > MAX_TIMER_TIMEOUT_MS) {
|
|
58
|
+
throw new InvalidPersistedCollectionConfigError(
|
|
59
|
+
`initialRender.networkTimeoutMs must be between 0 and ${MAX_TIMER_TIMEOUT_MS}`
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
return timeoutMs;
|
|
63
|
+
}
|
|
3
64
|
const REQUIRED_COORDINATOR_METHODS = [
|
|
4
65
|
`getNodeId`,
|
|
5
66
|
`subscribe`,
|
|
6
67
|
`publish`,
|
|
7
68
|
`isLeader`,
|
|
8
69
|
`ensureLeadership`,
|
|
9
|
-
`
|
|
70
|
+
`requestEnsureRemoteSubset`,
|
|
71
|
+
`requestReleaseRemoteSubset`,
|
|
72
|
+
`registerRemoteSubsetOwner`,
|
|
73
|
+
`requestEnsurePersistedIndex`,
|
|
74
|
+
`requestApplyCommittedTx`
|
|
10
75
|
];
|
|
11
|
-
const REQUIRED_ADAPTER_METHODS = [`loadSubset`, `applyCommittedTx`, `ensureIndex`];
|
|
76
|
+
const REQUIRED_ADAPTER_METHODS = [`loadSubset`, `loadResumeSnapshot`, `applyCommittedTx`, `ensureIndex`];
|
|
12
77
|
const TARGETED_INVALIDATION_KEY_LIMIT = 128;
|
|
13
78
|
const DEFAULT_DB_NAME = `tanstack-db`;
|
|
14
79
|
const REMOTE_ENSURE_RETRY_DELAY_MS = 50;
|
|
15
80
|
class SingleProcessCoordinator {
|
|
16
81
|
constructor(nodeId = safeRandomUUID()) {
|
|
82
|
+
this.collectionAdapters = /* @__PURE__ */ new Map();
|
|
83
|
+
this.remoteSubsetOwners = /* @__PURE__ */ new Map();
|
|
84
|
+
this.remoteSubsetAcquisitions = /* @__PURE__ */ new Map();
|
|
17
85
|
this.nodeId = nodeId;
|
|
18
86
|
}
|
|
19
87
|
getNodeId() {
|
|
@@ -30,10 +98,105 @@ class SingleProcessCoordinator {
|
|
|
30
98
|
}
|
|
31
99
|
async ensureLeadership() {
|
|
32
100
|
}
|
|
33
|
-
async requestEnsureRemoteSubset() {
|
|
101
|
+
async requestEnsureRemoteSubset(collectionId, options) {
|
|
102
|
+
const transported = toTransportedLoadSubsetOptions(options);
|
|
103
|
+
const localOptions = toProcessLocalLoadSubsetOptions(options, transported);
|
|
104
|
+
const owner = this.remoteSubsetOwners.get(collectionId);
|
|
105
|
+
if (!owner) {
|
|
106
|
+
throw new InvalidPersistedCollectionConfigError(
|
|
107
|
+
`SingleProcessCoordinator has no remote subset owner configured for collection "${collectionId}"`
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
let acquisitions = this.remoteSubsetAcquisitions.get(collectionId);
|
|
111
|
+
if (!acquisitions) {
|
|
112
|
+
acquisitions = /* @__PURE__ */ new Map();
|
|
113
|
+
this.remoteSubsetAcquisitions.set(collectionId, acquisitions);
|
|
114
|
+
}
|
|
115
|
+
const existing = acquisitions.get(options);
|
|
116
|
+
if (existing) {
|
|
117
|
+
await existing.load;
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
let resolveLoad;
|
|
121
|
+
let rejectLoad;
|
|
122
|
+
const load = new Promise((resolve, reject) => {
|
|
123
|
+
resolveLoad = resolve;
|
|
124
|
+
rejectLoad = reject;
|
|
125
|
+
});
|
|
126
|
+
acquisitions.set(options, { owner, options: localOptions, load });
|
|
127
|
+
try {
|
|
128
|
+
const ownerLoad = owner(localOptions);
|
|
129
|
+
void Promise.resolve(ownerLoad).then(resolveLoad, (error) => {
|
|
130
|
+
reportRemoteSubsetOwnerError(owner, error);
|
|
131
|
+
rejectLoad(error);
|
|
132
|
+
});
|
|
133
|
+
} catch (error) {
|
|
134
|
+
reportRemoteSubsetOwnerError(owner, error);
|
|
135
|
+
rejectLoad(error);
|
|
136
|
+
}
|
|
137
|
+
await load;
|
|
138
|
+
}
|
|
139
|
+
async requestReleaseRemoteSubset(collectionId, options) {
|
|
140
|
+
const acquisitions = this.remoteSubsetAcquisitions.get(collectionId);
|
|
141
|
+
const acquisition = acquisitions?.get(options);
|
|
142
|
+
if (!acquisition) return;
|
|
143
|
+
acquisitions.delete(options);
|
|
144
|
+
if (acquisitions.size === 0) {
|
|
145
|
+
this.remoteSubsetAcquisitions.delete(collectionId);
|
|
146
|
+
}
|
|
147
|
+
try {
|
|
148
|
+
await acquisition.load;
|
|
149
|
+
} catch {
|
|
150
|
+
}
|
|
151
|
+
await unloadRemoteSubsetOwner(acquisition.owner, acquisition.options);
|
|
152
|
+
}
|
|
153
|
+
registerRemoteSubsetOwner(collectionId, owner) {
|
|
154
|
+
if (this.remoteSubsetOwners.has(collectionId)) {
|
|
155
|
+
throw new DuplicateRemoteSubsetOwnerError(collectionId);
|
|
156
|
+
}
|
|
157
|
+
this.remoteSubsetOwners.set(collectionId, owner);
|
|
158
|
+
return () => {
|
|
159
|
+
if (this.remoteSubsetOwners.get(collectionId) !== owner) return;
|
|
160
|
+
const acquisitions = this.remoteSubsetAcquisitions.get(collectionId);
|
|
161
|
+
this.remoteSubsetAcquisitions.delete(collectionId);
|
|
162
|
+
this.remoteSubsetOwners.delete(collectionId);
|
|
163
|
+
for (const acquisition of acquisitions?.values() ?? []) {
|
|
164
|
+
void (async () => {
|
|
165
|
+
try {
|
|
166
|
+
await acquisition.load;
|
|
167
|
+
} catch {
|
|
168
|
+
}
|
|
169
|
+
await unloadRemoteSubsetOwner(owner, acquisition.options);
|
|
170
|
+
})().catch(() => void 0);
|
|
171
|
+
}
|
|
172
|
+
};
|
|
34
173
|
}
|
|
35
174
|
async requestEnsurePersistedIndex() {
|
|
36
175
|
}
|
|
176
|
+
setAdapterForCollection(collectionId, adapter) {
|
|
177
|
+
this.collectionAdapters.set(collectionId, adapter);
|
|
178
|
+
}
|
|
179
|
+
async requestApplyCommittedTx(collectionId, tx) {
|
|
180
|
+
const adapter = this.collectionAdapters.get(collectionId);
|
|
181
|
+
if (!adapter) {
|
|
182
|
+
throw new InvalidPersistedCollectionConfigError(
|
|
183
|
+
`SingleProcessCoordinator has no persistence adapter configured for collection "${collectionId}"`
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
try {
|
|
187
|
+
await adapter.applyCommittedTx(collectionId, tx);
|
|
188
|
+
} catch (error) {
|
|
189
|
+
throw toPersistedCollectionDurabilityError(collectionId, error);
|
|
190
|
+
}
|
|
191
|
+
return {
|
|
192
|
+
type: `rpc:applyCommittedTx:res`,
|
|
193
|
+
rpcId: safeRandomUUID(),
|
|
194
|
+
ok: true,
|
|
195
|
+
term: tx.term,
|
|
196
|
+
seq: tx.seq,
|
|
197
|
+
latestRowVersion: tx.rowVersion
|
|
198
|
+
};
|
|
199
|
+
}
|
|
37
200
|
pullSince() {
|
|
38
201
|
return Promise.resolve({
|
|
39
202
|
type: `rpc:pullSince:res`,
|
|
@@ -63,25 +226,38 @@ function validatePersistenceAdapter(adapter) {
|
|
|
63
226
|
}
|
|
64
227
|
}
|
|
65
228
|
}
|
|
66
|
-
function resolvePersistence(persistence) {
|
|
229
|
+
function resolvePersistence(persistence, collectionId) {
|
|
67
230
|
validatePersistenceAdapter(persistence.adapter);
|
|
68
231
|
const coordinator = persistence.coordinator ?? new SingleProcessCoordinator();
|
|
232
|
+
if (coordinator instanceof SingleProcessCoordinator) {
|
|
233
|
+
coordinator.setAdapterForCollection(collectionId, persistence.adapter);
|
|
234
|
+
}
|
|
69
235
|
validatePersistedCollectionCoordinator(coordinator);
|
|
70
236
|
return {
|
|
71
237
|
...persistence,
|
|
72
238
|
coordinator
|
|
73
239
|
};
|
|
74
240
|
}
|
|
75
|
-
function resolvePersistenceForMode(persistence, mode) {
|
|
241
|
+
function resolvePersistenceForMode(persistence, mode, collectionId) {
|
|
76
242
|
const modeSpecificPersistence = persistence.resolvePersistenceForMode?.(mode);
|
|
77
|
-
return resolvePersistence(
|
|
243
|
+
return resolvePersistence(
|
|
244
|
+
modeSpecificPersistence ?? persistence,
|
|
245
|
+
collectionId
|
|
246
|
+
);
|
|
78
247
|
}
|
|
79
248
|
function resolvePersistenceForCollection(persistence, options) {
|
|
80
249
|
const collectionSpecificPersistence = persistence.resolvePersistenceForCollection?.(options);
|
|
81
250
|
if (collectionSpecificPersistence) {
|
|
82
|
-
return resolvePersistence(
|
|
251
|
+
return resolvePersistence(
|
|
252
|
+
collectionSpecificPersistence,
|
|
253
|
+
options.collectionId
|
|
254
|
+
);
|
|
83
255
|
}
|
|
84
|
-
return resolvePersistenceForMode(
|
|
256
|
+
return resolvePersistenceForMode(
|
|
257
|
+
persistence,
|
|
258
|
+
options.mode,
|
|
259
|
+
options.collectionId
|
|
260
|
+
);
|
|
85
261
|
}
|
|
86
262
|
function hasOwnSyncKey(options) {
|
|
87
263
|
return Object.prototype.hasOwnProperty.call(options, `sync`);
|
|
@@ -98,14 +274,47 @@ function isValidSyncConfig(value) {
|
|
|
98
274
|
class ApplyMutex {
|
|
99
275
|
constructor() {
|
|
100
276
|
this.queue = Promise.resolve();
|
|
277
|
+
this.pending = 0;
|
|
278
|
+
}
|
|
279
|
+
reserve() {
|
|
280
|
+
const runImmediately = this.pending === 0;
|
|
281
|
+
const predecessor = this.queue;
|
|
282
|
+
this.pending++;
|
|
283
|
+
let releaseReservation;
|
|
284
|
+
const reservation = new Promise((resolve) => {
|
|
285
|
+
releaseReservation = resolve;
|
|
286
|
+
});
|
|
287
|
+
this.queue = runImmediately ? reservation : predecessor.then(() => reservation);
|
|
288
|
+
let used = false;
|
|
289
|
+
return {
|
|
290
|
+
run: (task) => {
|
|
291
|
+
if (used) {
|
|
292
|
+
return Promise.reject(
|
|
293
|
+
new Error(`an apply-mutex reservation can only run once`)
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
used = true;
|
|
297
|
+
let taskPromise;
|
|
298
|
+
if (runImmediately) {
|
|
299
|
+
try {
|
|
300
|
+
taskPromise = Promise.resolve(task());
|
|
301
|
+
} catch (error) {
|
|
302
|
+
taskPromise = Promise.reject(error);
|
|
303
|
+
}
|
|
304
|
+
} else {
|
|
305
|
+
taskPromise = predecessor.then(() => task());
|
|
306
|
+
}
|
|
307
|
+
const markComplete = () => {
|
|
308
|
+
this.pending--;
|
|
309
|
+
releaseReservation();
|
|
310
|
+
};
|
|
311
|
+
void taskPromise.then(markComplete, markComplete);
|
|
312
|
+
return taskPromise;
|
|
313
|
+
}
|
|
314
|
+
};
|
|
101
315
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
this.queue = taskPromise.then(
|
|
105
|
-
() => void 0,
|
|
106
|
-
() => void 0
|
|
107
|
-
);
|
|
108
|
-
return taskPromise;
|
|
316
|
+
run(task) {
|
|
317
|
+
return this.reserve().run(task);
|
|
109
318
|
}
|
|
110
319
|
}
|
|
111
320
|
function toStableSerializable(value) {
|
|
@@ -118,7 +327,7 @@ function toStableSerializable(value) {
|
|
|
118
327
|
case `boolean`:
|
|
119
328
|
return value;
|
|
120
329
|
case `bigint`:
|
|
121
|
-
return value
|
|
330
|
+
return serializeSQLiteBigInt(value);
|
|
122
331
|
case `function`:
|
|
123
332
|
case `symbol`:
|
|
124
333
|
case `undefined`:
|
|
@@ -198,40 +407,52 @@ function toPersistedMutationEnvelope(mutation) {
|
|
|
198
407
|
};
|
|
199
408
|
}
|
|
200
409
|
class PersistedCollectionRuntime {
|
|
201
|
-
constructor(mode, collectionId, persistence, syncMode, dbName) {
|
|
410
|
+
constructor(mode, collectionId, persistence, syncMode, dbName, persistedReadiness) {
|
|
202
411
|
this.mode = mode;
|
|
203
412
|
this.collectionId = collectionId;
|
|
204
413
|
this.persistence = persistence;
|
|
205
414
|
this.syncMode = syncMode;
|
|
206
415
|
this.dbName = dbName;
|
|
416
|
+
this.persistedReadiness = persistedReadiness;
|
|
207
417
|
this.applyMutex = new ApplyMutex();
|
|
208
418
|
this.activeSubsets = /* @__PURE__ */ new Map();
|
|
419
|
+
this.hydrationSequence = 0;
|
|
420
|
+
this.resetSequence = 0;
|
|
209
421
|
this.pendingRemoteSubsetEnsures = /* @__PURE__ */ new Map();
|
|
210
422
|
this.queuedHydrationTransactions = [];
|
|
211
423
|
this.queuedTxCommitted = [];
|
|
212
424
|
this.requestIds = /* @__PURE__ */ new WeakMap();
|
|
425
|
+
this.hydratedDemands = /* @__PURE__ */ new Set();
|
|
213
426
|
this.collection = null;
|
|
214
427
|
this.syncControls = {
|
|
215
428
|
begin: null,
|
|
216
429
|
write: null,
|
|
217
430
|
commit: null,
|
|
218
431
|
truncate: null,
|
|
219
|
-
metadata: null
|
|
432
|
+
metadata: null,
|
|
433
|
+
markError: null
|
|
220
434
|
};
|
|
221
|
-
this.started = false;
|
|
222
435
|
this.startupMetadataPromise = null;
|
|
223
436
|
this.startPromise = null;
|
|
437
|
+
this.hasAttemptedStartup = false;
|
|
224
438
|
this.resumeBaselinePromise = null;
|
|
439
|
+
this.resumeCertificationPromise = null;
|
|
440
|
+
this.resumeGenerationOwner = /* @__PURE__ */ Symbol(`persisted resume generation owner`);
|
|
225
441
|
this.lifecycleGeneration = 0;
|
|
226
442
|
this.internalApplyDepth = 0;
|
|
443
|
+
this.sourcePublicationWaitDepth = 0;
|
|
227
444
|
this.appliedReceiptSequence = 0;
|
|
445
|
+
this.syncErrorReported = false;
|
|
228
446
|
this.pendingAppliedReceipts = /* @__PURE__ */ new Map();
|
|
229
447
|
this.hydratingGeneration = null;
|
|
230
448
|
this.coordinatorUnsubscribe = null;
|
|
449
|
+
this.remoteSubsetOwnerUnsubscribe = null;
|
|
231
450
|
this.indexAddedUnsubscribe = null;
|
|
232
451
|
this.indexRemovedUnsubscribe = null;
|
|
233
452
|
this.remoteEnsureRetryTimer = null;
|
|
234
453
|
this.nextRequestId = 0;
|
|
454
|
+
this.startupSettled = false;
|
|
455
|
+
this.sourceTruncateGeneration = 0;
|
|
235
456
|
this.latestTerm = 0;
|
|
236
457
|
this.latestSeq = 0;
|
|
237
458
|
this.latestRowVersion = 0;
|
|
@@ -241,12 +462,33 @@ class PersistedCollectionRuntime {
|
|
|
241
462
|
}
|
|
242
463
|
setSyncControls(syncControls) {
|
|
243
464
|
this.advanceLifecycle();
|
|
465
|
+
this.syncErrorReported = false;
|
|
466
|
+
this.reportedSyncError = void 0;
|
|
244
467
|
const commit = syncControls.commit;
|
|
245
468
|
this.syncControls = {
|
|
246
469
|
...syncControls,
|
|
247
470
|
commit: commit ? (signal) => this.trackAppliedReceipt(commit(signal)) : null
|
|
248
471
|
};
|
|
249
472
|
}
|
|
473
|
+
reportSyncError(error) {
|
|
474
|
+
const markError = this.syncControls.markError;
|
|
475
|
+
if (this.syncErrorReported) return this.reportedSyncError;
|
|
476
|
+
if (!markError) return error;
|
|
477
|
+
this.syncErrorReported = true;
|
|
478
|
+
this.reportedSyncError = error;
|
|
479
|
+
try {
|
|
480
|
+
markError(error);
|
|
481
|
+
} catch {
|
|
482
|
+
}
|
|
483
|
+
return error;
|
|
484
|
+
}
|
|
485
|
+
registerRemoteSubsetOwner(owner) {
|
|
486
|
+
this.remoteSubsetOwnerUnsubscribe?.();
|
|
487
|
+
this.remoteSubsetOwnerUnsubscribe = this.persistence.coordinator.registerRemoteSubsetOwner(
|
|
488
|
+
this.collectionId,
|
|
489
|
+
owner
|
|
490
|
+
);
|
|
491
|
+
}
|
|
250
492
|
trackAppliedReceipt(receipt) {
|
|
251
493
|
const sequence = ++this.appliedReceiptSequence;
|
|
252
494
|
if (receipt === true) {
|
|
@@ -271,12 +513,55 @@ class PersistedCollectionRuntime {
|
|
|
271
513
|
write: null,
|
|
272
514
|
commit: null,
|
|
273
515
|
truncate: null,
|
|
274
|
-
metadata: null
|
|
516
|
+
metadata: null,
|
|
517
|
+
markError: null
|
|
275
518
|
};
|
|
276
519
|
}
|
|
277
520
|
isHydratingNow() {
|
|
278
521
|
return this.hydratingGeneration === this.lifecycleGeneration;
|
|
279
522
|
}
|
|
523
|
+
getActiveHydrationContext() {
|
|
524
|
+
return this.activeHydrationContext;
|
|
525
|
+
}
|
|
526
|
+
getHydrationSequence() {
|
|
527
|
+
return this.hydrationSequence;
|
|
528
|
+
}
|
|
529
|
+
getCurrentTerminalFailure() {
|
|
530
|
+
const failure = this.terminalFailure;
|
|
531
|
+
return failure?.lifecycleGeneration === this.lifecycleGeneration ? failure : void 0;
|
|
532
|
+
}
|
|
533
|
+
getLifecycleGeneration() {
|
|
534
|
+
return this.lifecycleGeneration;
|
|
535
|
+
}
|
|
536
|
+
throwIfLifecycleReplaced(lifecycleGeneration) {
|
|
537
|
+
if (lifecycleGeneration !== this.lifecycleGeneration) {
|
|
538
|
+
throw new SyncTransactionAbortedError();
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
throwIfTerminal() {
|
|
542
|
+
const failure = this.getCurrentTerminalFailure();
|
|
543
|
+
if (failure) throw failure.error;
|
|
544
|
+
}
|
|
545
|
+
markTerminalFailure(error, lifecycleGeneration = this.lifecycleGeneration) {
|
|
546
|
+
if (lifecycleGeneration !== this.lifecycleGeneration) {
|
|
547
|
+
return error;
|
|
548
|
+
}
|
|
549
|
+
const existing = this.getCurrentTerminalFailure();
|
|
550
|
+
if (existing) {
|
|
551
|
+
return existing.error;
|
|
552
|
+
}
|
|
553
|
+
this.terminalFailure = { lifecycleGeneration, error };
|
|
554
|
+
this.persistedReadiness?.set({ status: `error`, error });
|
|
555
|
+
this.pendingRemoteSubsetEnsures.clear();
|
|
556
|
+
this.queuedTxCommitted.length = 0;
|
|
557
|
+
if (this.remoteEnsureRetryTimer !== null) {
|
|
558
|
+
clearTimeout(this.remoteEnsureRetryTimer);
|
|
559
|
+
this.remoteEnsureRetryTimer = null;
|
|
560
|
+
}
|
|
561
|
+
this.rejectQueuedHydrationTransactions(error);
|
|
562
|
+
this.reportSyncError(error);
|
|
563
|
+
return error;
|
|
564
|
+
}
|
|
280
565
|
isApplyingInternally() {
|
|
281
566
|
return this.internalApplyDepth > 0;
|
|
282
567
|
}
|
|
@@ -293,15 +578,109 @@ class PersistedCollectionRuntime {
|
|
|
293
578
|
isLeader: this.persistence.coordinator.isLeader(this.collectionId)
|
|
294
579
|
};
|
|
295
580
|
}
|
|
581
|
+
runInHydrationScope(task, adapter = this.persistence.adapter) {
|
|
582
|
+
if (adapter.runInHydrationScope) {
|
|
583
|
+
return adapter.runInHydrationScope(task);
|
|
584
|
+
}
|
|
585
|
+
return Promise.resolve().then(() => task(adapter));
|
|
586
|
+
}
|
|
296
587
|
async ensureStarted() {
|
|
588
|
+
this.throwIfTerminal();
|
|
297
589
|
if (this.startPromise) {
|
|
298
590
|
return this.startPromise;
|
|
299
591
|
}
|
|
300
592
|
const lifecycleGeneration = this.lifecycleGeneration;
|
|
301
|
-
|
|
593
|
+
const isRestart = this.hasAttemptedStartup;
|
|
594
|
+
this.hasAttemptedStartup = true;
|
|
595
|
+
let resolveStartupMetadata;
|
|
596
|
+
let rejectStartupMetadata;
|
|
597
|
+
this.startupMetadataPromise = new Promise((resolve, reject) => {
|
|
598
|
+
resolveStartupMetadata = resolve;
|
|
599
|
+
rejectStartupMetadata = reject;
|
|
600
|
+
});
|
|
601
|
+
void this.startupMetadataPromise.catch(() => void 0);
|
|
602
|
+
this.startPromise = (async () => {
|
|
603
|
+
const loadStartupMetadata = async (adapter) => {
|
|
604
|
+
if (lifecycleGeneration !== this.lifecycleGeneration) {
|
|
605
|
+
resolveStartupMetadata();
|
|
606
|
+
return false;
|
|
607
|
+
}
|
|
608
|
+
try {
|
|
609
|
+
await this.loadStartupMetadataInternal(lifecycleGeneration, adapter);
|
|
610
|
+
return lifecycleGeneration === this.lifecycleGeneration;
|
|
611
|
+
} catch (error) {
|
|
612
|
+
rejectStartupMetadata(error);
|
|
613
|
+
throw error;
|
|
614
|
+
}
|
|
615
|
+
};
|
|
616
|
+
let startup;
|
|
617
|
+
const scheduleStartupAsOneHydrate = this.persistence.adapter.runInHydrationScope !== void 0 && (!isRestart || (this.persistence.adapter.isHydrationScopeScheduled?.() ?? true));
|
|
618
|
+
if (scheduleStartupAsOneHydrate) {
|
|
619
|
+
startup = await this.applyMutex.run(async () => {
|
|
620
|
+
const result = await this.runInHydrationScope(async (adapter) => {
|
|
621
|
+
if (!await loadStartupMetadata(adapter)) return void 0;
|
|
622
|
+
return this.startInternal(
|
|
623
|
+
lifecycleGeneration,
|
|
624
|
+
adapter,
|
|
625
|
+
resolveStartupMetadata
|
|
626
|
+
);
|
|
627
|
+
});
|
|
628
|
+
if (lifecycleGeneration === this.lifecycleGeneration) {
|
|
629
|
+
await this.flushQueuedTxCommittedUnsafe();
|
|
630
|
+
}
|
|
631
|
+
return result;
|
|
632
|
+
});
|
|
633
|
+
} else {
|
|
634
|
+
if (await loadStartupMetadata(this.persistence.adapter)) {
|
|
635
|
+
if (this.persistence.adapter.isHydrationScopeScheduled?.()) {
|
|
636
|
+
startup = await this.applyMutex.run(async () => {
|
|
637
|
+
const result = await this.runInHydrationScope(
|
|
638
|
+
(adapter) => this.startInternal(
|
|
639
|
+
lifecycleGeneration,
|
|
640
|
+
adapter,
|
|
641
|
+
resolveStartupMetadata
|
|
642
|
+
)
|
|
643
|
+
);
|
|
644
|
+
if (lifecycleGeneration === this.lifecycleGeneration) {
|
|
645
|
+
await this.flushQueuedTxCommittedUnsafe();
|
|
646
|
+
}
|
|
647
|
+
return result;
|
|
648
|
+
});
|
|
649
|
+
} else {
|
|
650
|
+
startup = await this.startInternal(
|
|
651
|
+
lifecycleGeneration,
|
|
652
|
+
this.persistence.adapter,
|
|
653
|
+
resolveStartupMetadata
|
|
654
|
+
);
|
|
655
|
+
if (lifecycleGeneration === this.lifecycleGeneration) {
|
|
656
|
+
await this.flushQueuedTxCommittedUnsafe();
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
if (startup !== void 0 && lifecycleGeneration === this.lifecycleGeneration) {
|
|
662
|
+
await this.requestCoordinatorPersistedIndexes(
|
|
663
|
+
startup.indexBootstrapSnapshot,
|
|
664
|
+
startup.completedLocalIndexSignatures
|
|
665
|
+
);
|
|
666
|
+
if (startup.appliedCursor !== void 0 && lifecycleGeneration === this.lifecycleGeneration) {
|
|
667
|
+
await this.waitForAppliedReceiptsAfter(startup.appliedCursor);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
if (lifecycleGeneration === this.lifecycleGeneration) {
|
|
671
|
+
this.startupSettled = true;
|
|
672
|
+
this.persistedReadiness?.set({ status: `ready` });
|
|
673
|
+
}
|
|
674
|
+
resolveStartupMetadata();
|
|
675
|
+
})().catch((error) => {
|
|
676
|
+
rejectStartupMetadata(error);
|
|
677
|
+
throw this.markTerminalFailure(error, lifecycleGeneration);
|
|
678
|
+
});
|
|
679
|
+
void this.startPromise.catch(() => void 0);
|
|
302
680
|
return this.startPromise;
|
|
303
681
|
}
|
|
304
682
|
ensureResumeBaselineHydrated() {
|
|
683
|
+
this.throwIfTerminal();
|
|
305
684
|
if (this.resumeBaselinePromise) {
|
|
306
685
|
return this.resumeBaselinePromise;
|
|
307
686
|
}
|
|
@@ -310,79 +689,122 @@ class PersistedCollectionRuntime {
|
|
|
310
689
|
await this.ensureStarted();
|
|
311
690
|
if (lifecycleGeneration !== this.lifecycleGeneration) return;
|
|
312
691
|
if (this.syncMode !== `on-demand`) return;
|
|
313
|
-
await this.
|
|
314
|
-
|
|
692
|
+
const appliedCursor = await this.applyMutex.run(async () => {
|
|
693
|
+
const result = await this.runInHydrationScope(
|
|
694
|
+
(adapter) => this.hydrateBaseline(lifecycleGeneration, adapter)
|
|
695
|
+
);
|
|
696
|
+
if (lifecycleGeneration === this.lifecycleGeneration) {
|
|
697
|
+
await this.flushQueuedTxCommittedUnsafe();
|
|
698
|
+
}
|
|
699
|
+
return result;
|
|
700
|
+
});
|
|
701
|
+
if (appliedCursor !== void 0 && lifecycleGeneration === this.lifecycleGeneration) {
|
|
702
|
+
await this.waitForAppliedReceiptsAfter(appliedCursor);
|
|
703
|
+
}
|
|
704
|
+
})().catch((error) => {
|
|
705
|
+
throw this.markTerminalFailure(error, lifecycleGeneration);
|
|
706
|
+
});
|
|
315
707
|
return this.resumeBaselinePromise;
|
|
316
708
|
}
|
|
317
|
-
|
|
318
|
-
if (
|
|
709
|
+
ensureResumeBaselineCertified() {
|
|
710
|
+
if (this.resumeCertificationPromise) {
|
|
711
|
+
return this.resumeCertificationPromise;
|
|
712
|
+
}
|
|
713
|
+
const lifecycleGeneration = this.lifecycleGeneration;
|
|
714
|
+
this.resumeCertificationPromise = (async () => {
|
|
715
|
+
await this.ensureStarted();
|
|
716
|
+
if (lifecycleGeneration !== this.lifecycleGeneration) return;
|
|
717
|
+
const snapshot = await this.persistence.adapter.loadResumeSnapshot(
|
|
718
|
+
this.collectionId,
|
|
719
|
+
{
|
|
720
|
+
requiredIndexSignatures: this.getRequiredIndexSignatures(),
|
|
721
|
+
includeRows: false
|
|
722
|
+
}
|
|
723
|
+
);
|
|
724
|
+
if (lifecycleGeneration !== this.lifecycleGeneration) return;
|
|
725
|
+
this.bindResumeSnapshotEvidence(snapshot);
|
|
726
|
+
})();
|
|
727
|
+
return this.resumeCertificationPromise;
|
|
728
|
+
}
|
|
729
|
+
getKeySetEvidence() {
|
|
730
|
+
return this.persistedKeySetEvidence;
|
|
731
|
+
}
|
|
732
|
+
getResumeGenerationOwner() {
|
|
733
|
+
return this.resumeGenerationOwner;
|
|
734
|
+
}
|
|
735
|
+
async hydrateBaseline(lifecycleGeneration, adapter) {
|
|
736
|
+
if (lifecycleGeneration !== this.lifecycleGeneration) return void 0;
|
|
319
737
|
const baseline = {};
|
|
320
738
|
this.activeSubsets.set(this.getSubsetKey(baseline), baseline);
|
|
321
739
|
const appliedCursor = this.appliedReceiptSequence;
|
|
322
|
-
await this.
|
|
323
|
-
|
|
324
|
-
|
|
740
|
+
await this.hydrateSubsetUnsafe(
|
|
741
|
+
baseline,
|
|
742
|
+
{
|
|
325
743
|
requestRemoteEnsure: false,
|
|
326
|
-
lifecycleGeneration
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
744
|
+
lifecycleGeneration,
|
|
745
|
+
bindKeySetEvidence: true
|
|
746
|
+
},
|
|
747
|
+
adapter
|
|
748
|
+
);
|
|
749
|
+
return lifecycleGeneration === this.lifecycleGeneration ? appliedCursor : void 0;
|
|
331
750
|
}
|
|
332
751
|
async ensureStartupMetadataLoaded() {
|
|
752
|
+
this.throwIfTerminal();
|
|
333
753
|
if (this.startupMetadataPromise) {
|
|
334
754
|
return this.startupMetadataPromise;
|
|
335
755
|
}
|
|
336
|
-
|
|
337
|
-
this.startupMetadataPromise = this.loadStartupMetadataInternal(lifecycleGeneration);
|
|
756
|
+
void this.ensureStarted();
|
|
338
757
|
return this.startupMetadataPromise;
|
|
339
758
|
}
|
|
340
|
-
async startInternal(lifecycleGeneration) {
|
|
341
|
-
if (this.
|
|
342
|
-
return;
|
|
343
|
-
}
|
|
344
|
-
this.started = true;
|
|
345
|
-
await this.ensureStartupMetadataLoaded();
|
|
346
|
-
if (lifecycleGeneration !== this.lifecycleGeneration) return;
|
|
759
|
+
async startInternal(lifecycleGeneration, adapter, onStartupMetadataLoaded) {
|
|
760
|
+
if (lifecycleGeneration !== this.lifecycleGeneration) return void 0;
|
|
347
761
|
const indexBootstrapSnapshot = this.collection?.getIndexMetadata() ?? [];
|
|
348
762
|
this.attachIndexLifecycleListeners();
|
|
349
|
-
await this.bootstrapPersistedIndexes(
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
}
|
|
367
|
-
const collectionMetadata = await this.loadCollectionMetadataSnapshot();
|
|
763
|
+
const completedLocalIndexSignatures = await this.bootstrapPersistedIndexes(
|
|
764
|
+
indexBootstrapSnapshot,
|
|
765
|
+
adapter
|
|
766
|
+
);
|
|
767
|
+
if (lifecycleGeneration !== this.lifecycleGeneration) return void 0;
|
|
768
|
+
onStartupMetadataLoaded();
|
|
769
|
+
const appliedCursor = this.syncMode !== `on-demand` ? await this.hydrateBaseline(lifecycleGeneration, adapter) : void 0;
|
|
770
|
+
return lifecycleGeneration === this.lifecycleGeneration ? {
|
|
771
|
+
appliedCursor,
|
|
772
|
+
indexBootstrapSnapshot,
|
|
773
|
+
completedLocalIndexSignatures
|
|
774
|
+
} : void 0;
|
|
775
|
+
}
|
|
776
|
+
async loadStartupMetadataInternal(lifecycleGeneration, adapter) {
|
|
777
|
+
const snapshot = await adapter.loadResumeSnapshot(this.collectionId, {
|
|
778
|
+
includeRows: false
|
|
779
|
+
});
|
|
368
780
|
if (lifecycleGeneration !== this.lifecycleGeneration) return;
|
|
369
|
-
this.
|
|
781
|
+
this.persistedResumeGeneration = this.getResumeSnapshotGeneration(snapshot);
|
|
782
|
+
this.persistedKeySetEvidence = snapshot.keySet;
|
|
783
|
+
this.observeStreamPosition(
|
|
784
|
+
snapshot.latestTerm,
|
|
785
|
+
snapshot.latestSeq,
|
|
786
|
+
snapshot.latestRowVersion
|
|
787
|
+
);
|
|
788
|
+
const applied = this.replaceCollectionMetadataSnapshot(
|
|
789
|
+
snapshot.collectionMetadata
|
|
790
|
+
);
|
|
791
|
+
if (applied !== true) await applied;
|
|
370
792
|
}
|
|
371
|
-
async loadCollectionMetadataSnapshot() {
|
|
372
|
-
if (!
|
|
793
|
+
async loadCollectionMetadataSnapshot(adapter) {
|
|
794
|
+
if (!adapter.loadCollectionMetadata) {
|
|
373
795
|
return [];
|
|
374
796
|
}
|
|
375
|
-
return
|
|
797
|
+
return adapter.loadCollectionMetadata(this.collectionId);
|
|
376
798
|
}
|
|
377
799
|
replaceCollectionMetadataSnapshot(collectionMetadata) {
|
|
378
800
|
if (!this.syncControls.begin || !this.syncControls.commit || !this.syncControls.metadata) {
|
|
379
|
-
return;
|
|
801
|
+
return true;
|
|
380
802
|
}
|
|
381
803
|
const nextMetadata = new Map(
|
|
382
804
|
collectionMetadata.map(({ key, value }) => [key, value])
|
|
383
805
|
);
|
|
384
806
|
const currentKeys = this.syncControls.metadata.collection.list().map(({ key }) => key);
|
|
385
|
-
this.withInternalApply(() => {
|
|
807
|
+
return this.withInternalApply(() => {
|
|
386
808
|
this.syncControls.begin?.({ immediate: true });
|
|
387
809
|
currentKeys.forEach((key) => {
|
|
388
810
|
if (!nextMetadata.has(key)) {
|
|
@@ -392,56 +814,182 @@ class PersistedCollectionRuntime {
|
|
|
392
814
|
nextMetadata.forEach((value, key) => {
|
|
393
815
|
this.syncControls.metadata?.collection.set(key, value);
|
|
394
816
|
});
|
|
395
|
-
this.syncControls.commit?.();
|
|
817
|
+
return this.syncControls.commit?.() ?? true;
|
|
396
818
|
});
|
|
397
819
|
}
|
|
398
820
|
async loadSubset(options, upstreamLoadSubset) {
|
|
821
|
+
this.throwIfTerminal();
|
|
399
822
|
const lifecycleGeneration = this.lifecycleGeneration;
|
|
400
|
-
this.
|
|
823
|
+
const subsetKey = this.getSubsetKey(options);
|
|
824
|
+
const truncateGeneration = this.sourceTruncateGeneration;
|
|
825
|
+
const routeRemoteDemandDuringHydration = this.canRouteRemoteDemandThroughCoordinator();
|
|
826
|
+
this.activeSubsets.set(subsetKey, options);
|
|
401
827
|
const appliedCursor = this.appliedReceiptSequence;
|
|
402
|
-
|
|
403
|
-
() =>
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
828
|
+
try {
|
|
829
|
+
await this.applyMutex.run(async () => {
|
|
830
|
+
try {
|
|
831
|
+
await this.runInHydrationScope(
|
|
832
|
+
(adapter) => this.hydrateSubsetUnsafe(
|
|
833
|
+
options,
|
|
834
|
+
{
|
|
835
|
+
requestRemoteEnsure: this.mode === `sync-present` && !routeRemoteDemandDuringHydration,
|
|
836
|
+
lifecycleGeneration,
|
|
837
|
+
requestLocalLoadFailure: true,
|
|
838
|
+
rejectBufferedReplayFailure: true
|
|
839
|
+
},
|
|
840
|
+
adapter
|
|
841
|
+
)
|
|
842
|
+
);
|
|
843
|
+
} finally {
|
|
844
|
+
if (lifecycleGeneration === this.lifecycleGeneration && !this.getCurrentTerminalFailure()) {
|
|
845
|
+
await this.flushQueuedTxCommittedUnsafe();
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
});
|
|
849
|
+
if (lifecycleGeneration !== this.lifecycleGeneration) return;
|
|
850
|
+
await this.waitForAppliedReceiptsAfter(appliedCursor);
|
|
851
|
+
} catch (error) {
|
|
852
|
+
if (this.activeSubsets.get(subsetKey) === options) {
|
|
853
|
+
this.activeSubsets.delete(subsetKey);
|
|
854
|
+
}
|
|
855
|
+
throw error;
|
|
856
|
+
}
|
|
857
|
+
if (options.signal?.aborted || this.activeSubsets.get(this.getSubsetKey(options)) !== options) {
|
|
858
|
+
return;
|
|
859
|
+
}
|
|
860
|
+
if (this.canRouteRemoteDemandThroughCoordinator()) {
|
|
411
861
|
try {
|
|
412
|
-
await
|
|
862
|
+
await this.persistence.coordinator.requestEnsureRemoteSubset(
|
|
863
|
+
this.collectionId,
|
|
864
|
+
options
|
|
865
|
+
);
|
|
413
866
|
} catch (error) {
|
|
414
867
|
if (options.signal?.aborted || typeof error === `object` && error !== null && `name` in error && error.name === `AbortError`) {
|
|
415
868
|
this.pendingRemoteSubsetEnsures.delete(this.getSubsetKey(options));
|
|
416
869
|
throw error;
|
|
417
870
|
}
|
|
418
|
-
console.warn(`Failed to trigger remote subset load:`, error);
|
|
419
871
|
this.queueRemoteSubsetEnsure(options);
|
|
420
872
|
throw error;
|
|
421
873
|
}
|
|
874
|
+
return;
|
|
875
|
+
}
|
|
876
|
+
if (truncateGeneration === this.sourceTruncateGeneration && this.activeSubsets.get(subsetKey) === options) {
|
|
877
|
+
this.hydratedDemands.add(getLoadSubsetDemandKey(options));
|
|
878
|
+
}
|
|
879
|
+
if (upstreamLoadSubset) {
|
|
880
|
+
try {
|
|
881
|
+
await upstreamLoadSubset(options);
|
|
882
|
+
} catch (error) {
|
|
883
|
+
this.noteUpstreamLoadFailure(options, error);
|
|
884
|
+
throw error;
|
|
885
|
+
}
|
|
422
886
|
}
|
|
423
887
|
}
|
|
888
|
+
loadHydratedSubset(options, upstreamLoadSubset) {
|
|
889
|
+
const failure = this.getCurrentTerminalFailure();
|
|
890
|
+
if (failure) return Promise.reject(failure.error);
|
|
891
|
+
if (!this.startupSettled || this.isHydratingNow() || this.canRouteRemoteDemandThroughCoordinator() || options.refetch === true || !this.hydratedDemands.has(getLoadSubsetDemandKey(options))) {
|
|
892
|
+
return;
|
|
893
|
+
}
|
|
894
|
+
this.activeSubsets.set(this.getSubsetKey(options), options);
|
|
895
|
+
let result;
|
|
896
|
+
try {
|
|
897
|
+
result = upstreamLoadSubset?.(options) ?? true;
|
|
898
|
+
} catch (error) {
|
|
899
|
+
this.noteUpstreamLoadFailure(options, error);
|
|
900
|
+
return Promise.reject(error);
|
|
901
|
+
}
|
|
902
|
+
if (result === true) {
|
|
903
|
+
return true;
|
|
904
|
+
}
|
|
905
|
+
return result.then(
|
|
906
|
+
() => {
|
|
907
|
+
this.queueRemoteSubsetEnsure(options);
|
|
908
|
+
},
|
|
909
|
+
(error) => {
|
|
910
|
+
this.noteUpstreamLoadFailure(options, error);
|
|
911
|
+
throw error;
|
|
912
|
+
}
|
|
913
|
+
);
|
|
914
|
+
}
|
|
915
|
+
noteUpstreamLoadFailure(options, error) {
|
|
916
|
+
if (options.signal?.aborted || typeof error === `object` && error !== null && `name` in error && error.name === `AbortError`) {
|
|
917
|
+
this.pendingRemoteSubsetEnsures.delete(this.getSubsetKey(options));
|
|
918
|
+
return;
|
|
919
|
+
}
|
|
920
|
+
console.warn(`Failed to trigger remote subset load:`, error);
|
|
921
|
+
this.queueRemoteSubsetEnsure(options);
|
|
922
|
+
}
|
|
923
|
+
noteSourceTruncate() {
|
|
924
|
+
this.sourceTruncateGeneration++;
|
|
925
|
+
this.hydratedDemands.clear();
|
|
926
|
+
}
|
|
424
927
|
unloadSubset(options, upstreamUnloadSubset) {
|
|
425
|
-
this.
|
|
426
|
-
this.
|
|
427
|
-
|
|
928
|
+
const subsetKey = this.getSubsetKey(options);
|
|
929
|
+
this.activeSubsets.delete(subsetKey);
|
|
930
|
+
this.pendingRemoteSubsetEnsures.delete(subsetKey);
|
|
931
|
+
const demandKey = getLoadSubsetDemandKey(options);
|
|
932
|
+
const stillActive = Array.from(this.activeSubsets.values()).some(
|
|
933
|
+
(active) => getLoadSubsetDemandKey(active) === demandKey
|
|
934
|
+
);
|
|
935
|
+
if (!stillActive) this.hydratedDemands.delete(demandKey);
|
|
936
|
+
if (this.mode === `sync-present`) {
|
|
937
|
+
void this.persistence.coordinator.requestReleaseRemoteSubset(this.collectionId, options).catch((error) => {
|
|
938
|
+
this.reportSyncError(error);
|
|
939
|
+
});
|
|
940
|
+
}
|
|
941
|
+
if (upstreamUnloadSubset) {
|
|
942
|
+
try {
|
|
943
|
+
const result = upstreamUnloadSubset(options);
|
|
944
|
+
void Promise.resolve(result).catch((error) => {
|
|
945
|
+
this.reportSyncError(error);
|
|
946
|
+
});
|
|
947
|
+
} catch (error) {
|
|
948
|
+
this.reportSyncError(error);
|
|
949
|
+
}
|
|
950
|
+
}
|
|
428
951
|
}
|
|
429
952
|
async forceReloadSubset(options) {
|
|
953
|
+
this.throwIfTerminal();
|
|
430
954
|
const lifecycleGeneration = this.lifecycleGeneration;
|
|
431
|
-
await this.applyMutex.run(
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
955
|
+
await this.applyMutex.run(async () => {
|
|
956
|
+
await this.runInHydrationScope(
|
|
957
|
+
(adapter) => this.hydrateSubsetUnsafe(
|
|
958
|
+
options,
|
|
959
|
+
{
|
|
960
|
+
requestRemoteEnsure: false,
|
|
961
|
+
lifecycleGeneration,
|
|
962
|
+
rejectBufferedReplayFailure: true
|
|
963
|
+
},
|
|
964
|
+
adapter
|
|
965
|
+
)
|
|
966
|
+
);
|
|
967
|
+
if (lifecycleGeneration === this.lifecycleGeneration) {
|
|
968
|
+
await this.flushQueuedTxCommittedUnsafe();
|
|
969
|
+
}
|
|
970
|
+
});
|
|
437
971
|
}
|
|
438
972
|
queueHydrationBufferedTransaction(transaction) {
|
|
973
|
+
const failure = this.getCurrentTerminalFailure();
|
|
974
|
+
if (failure) {
|
|
975
|
+
transaction.rejectApplied?.(failure.error);
|
|
976
|
+
return;
|
|
977
|
+
}
|
|
439
978
|
this.queuedHydrationTransactions.push(transaction);
|
|
440
979
|
}
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
)
|
|
980
|
+
applyHydrationBufferedTransaction(transaction) {
|
|
981
|
+
const failure = this.getCurrentTerminalFailure();
|
|
982
|
+
if (failure) return Promise.reject(failure.error);
|
|
983
|
+
if (transaction.beginOptions?.immediate && this.sourcePublicationWaitDepth > 0) {
|
|
984
|
+
return Promise.reject(
|
|
985
|
+
new InvalidPersistedCollectionConfigError(
|
|
986
|
+
`immediate persisted source replay cannot enter while an earlier source publication is waiting`
|
|
987
|
+
)
|
|
988
|
+
);
|
|
989
|
+
}
|
|
990
|
+
return this.applyMutex.run(async () => {
|
|
991
|
+
await this.applyBufferedSyncTransactionUnsafe(transaction);
|
|
992
|
+
});
|
|
445
993
|
}
|
|
446
994
|
normalizeSyncWriteMessage(message) {
|
|
447
995
|
if (!this.collection) {
|
|
@@ -453,10 +1001,6 @@ class PersistedCollectionRuntime {
|
|
|
453
1001
|
const key2 = message.key;
|
|
454
1002
|
const previousValue = this.collection.get(key2) ?? {};
|
|
455
1003
|
return {
|
|
456
|
-
forwardMessage: {
|
|
457
|
-
type: `delete`,
|
|
458
|
-
key: key2
|
|
459
|
-
},
|
|
460
1004
|
operation: {
|
|
461
1005
|
type: `delete`,
|
|
462
1006
|
key: key2,
|
|
@@ -468,10 +1012,6 @@ class PersistedCollectionRuntime {
|
|
|
468
1012
|
const key2 = this.collection.getKeyFromItem(message.value);
|
|
469
1013
|
const previousValue = this.collection.get(key2) ?? message.value;
|
|
470
1014
|
return {
|
|
471
|
-
forwardMessage: {
|
|
472
|
-
type: `delete`,
|
|
473
|
-
key: key2
|
|
474
|
-
},
|
|
475
1015
|
operation: {
|
|
476
1016
|
type: `delete`,
|
|
477
1017
|
key: key2,
|
|
@@ -481,11 +1021,6 @@ class PersistedCollectionRuntime {
|
|
|
481
1021
|
}
|
|
482
1022
|
const key = this.collection.getKeyFromItem(message.value);
|
|
483
1023
|
return {
|
|
484
|
-
forwardMessage: {
|
|
485
|
-
type: `update`,
|
|
486
|
-
value: message.value,
|
|
487
|
-
metadata: message.metadata
|
|
488
|
-
},
|
|
489
1024
|
operation: {
|
|
490
1025
|
type: `update`,
|
|
491
1026
|
key,
|
|
@@ -494,25 +1029,45 @@ class PersistedCollectionRuntime {
|
|
|
494
1029
|
}
|
|
495
1030
|
};
|
|
496
1031
|
}
|
|
497
|
-
async persistAndConfirmCollectionMutations(mutations) {
|
|
1032
|
+
async persistAndConfirmCollectionMutations(mutations, lifecycleGeneration = this.lifecycleGeneration) {
|
|
1033
|
+
this.throwIfTerminal();
|
|
1034
|
+
this.throwIfLifecycleReplaced(lifecycleGeneration);
|
|
498
1035
|
if (mutations.length === 0) {
|
|
499
1036
|
return;
|
|
500
1037
|
}
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
1038
|
+
try {
|
|
1039
|
+
await this.applyMutex.run(async () => {
|
|
1040
|
+
await this.ensureStartupMetadataLoaded();
|
|
1041
|
+
this.throwIfLifecycleReplaced(lifecycleGeneration);
|
|
1042
|
+
const acceptedMutationIds = await this.persistCollectionMutationsUnsafe(
|
|
1043
|
+
mutations,
|
|
1044
|
+
lifecycleGeneration
|
|
1045
|
+
);
|
|
1046
|
+
this.throwIfLifecycleReplaced(lifecycleGeneration);
|
|
1047
|
+
const acceptedMutationIdSet = new Set(acceptedMutationIds);
|
|
1048
|
+
const acceptedMutations = mutations.filter(
|
|
1049
|
+
(mutation) => acceptedMutationIdSet.has(mutation.mutationId)
|
|
510
1050
|
);
|
|
1051
|
+
if (acceptedMutations.length !== mutations.length) {
|
|
1052
|
+
throw new Error(
|
|
1053
|
+
`persistence coordinator accepted ${acceptedMutations.length} of ${mutations.length} mutations; partial acceptance is not supported`
|
|
1054
|
+
);
|
|
1055
|
+
}
|
|
1056
|
+
try {
|
|
1057
|
+
await this.confirmMutationsSyncUnsafe(acceptedMutations);
|
|
1058
|
+
} catch (error) {
|
|
1059
|
+
throw this.markTerminalFailure(error, lifecycleGeneration);
|
|
1060
|
+
}
|
|
1061
|
+
});
|
|
1062
|
+
} catch (error) {
|
|
1063
|
+
if (error instanceof PersistedCollectionDurabilityError) {
|
|
1064
|
+
throw this.markTerminalFailure(error, lifecycleGeneration);
|
|
511
1065
|
}
|
|
512
|
-
|
|
513
|
-
}
|
|
1066
|
+
throw error;
|
|
1067
|
+
}
|
|
514
1068
|
}
|
|
515
1069
|
async acceptTransactionMutations(transaction) {
|
|
1070
|
+
this.throwIfTerminal();
|
|
516
1071
|
const collectionMutations = this.filterMutationsForCollection(
|
|
517
1072
|
transaction.mutations
|
|
518
1073
|
);
|
|
@@ -523,11 +1078,33 @@ class PersistedCollectionRuntime {
|
|
|
523
1078
|
}
|
|
524
1079
|
cleanup() {
|
|
525
1080
|
this.advanceLifecycle();
|
|
526
|
-
|
|
1081
|
+
const remoteReleases = [];
|
|
1082
|
+
const failures = [];
|
|
1083
|
+
const attempt = (callback) => {
|
|
1084
|
+
try {
|
|
1085
|
+
callback();
|
|
1086
|
+
} catch (error) {
|
|
1087
|
+
failures.push(error);
|
|
1088
|
+
}
|
|
1089
|
+
};
|
|
1090
|
+
if (this.mode === `sync-present`) {
|
|
1091
|
+
for (const options of this.activeSubsets.values()) {
|
|
1092
|
+
attempt(() => {
|
|
1093
|
+
remoteReleases.push(
|
|
1094
|
+
this.persistence.coordinator.requestReleaseRemoteSubset(this.collectionId, options).catch((error) => {
|
|
1095
|
+
this.reportSyncError(error);
|
|
1096
|
+
})
|
|
1097
|
+
);
|
|
1098
|
+
});
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
attempt(() => this.coordinatorUnsubscribe?.());
|
|
527
1102
|
this.coordinatorUnsubscribe = null;
|
|
528
|
-
this.
|
|
1103
|
+
attempt(() => this.remoteSubsetOwnerUnsubscribe?.());
|
|
1104
|
+
this.remoteSubsetOwnerUnsubscribe = null;
|
|
1105
|
+
attempt(() => this.indexAddedUnsubscribe?.());
|
|
529
1106
|
this.indexAddedUnsubscribe = null;
|
|
530
|
-
this.indexRemovedUnsubscribe?.();
|
|
1107
|
+
attempt(() => this.indexRemovedUnsubscribe?.());
|
|
531
1108
|
this.indexRemovedUnsubscribe = null;
|
|
532
1109
|
if (this.remoteEnsureRetryTimer !== null) {
|
|
533
1110
|
clearTimeout(this.remoteEnsureRetryTimer);
|
|
@@ -535,6 +1112,7 @@ class PersistedCollectionRuntime {
|
|
|
535
1112
|
}
|
|
536
1113
|
this.pendingRemoteSubsetEnsures.clear();
|
|
537
1114
|
this.activeSubsets.clear();
|
|
1115
|
+
this.hydratedDemands.clear();
|
|
538
1116
|
for (const transaction of this.queuedHydrationTransactions) {
|
|
539
1117
|
transaction.rejectApplied?.(new SyncTransactionAbortedError());
|
|
540
1118
|
}
|
|
@@ -542,13 +1120,28 @@ class PersistedCollectionRuntime {
|
|
|
542
1120
|
this.queuedTxCommitted.length = 0;
|
|
543
1121
|
this.clearSyncControls();
|
|
544
1122
|
this.collection = null;
|
|
1123
|
+
return Promise.all(remoteReleases).then(() => {
|
|
1124
|
+
if (failures.length === 1) throw failures[0];
|
|
1125
|
+
if (failures.length > 1) {
|
|
1126
|
+
throw new AggregateError(failures, `Persistence cleanup failed`, {
|
|
1127
|
+
cause: failures[0]
|
|
1128
|
+
});
|
|
1129
|
+
}
|
|
1130
|
+
});
|
|
545
1131
|
}
|
|
546
1132
|
advanceLifecycle() {
|
|
547
1133
|
this.lifecycleGeneration++;
|
|
548
|
-
this.
|
|
1134
|
+
this.persistedReadiness?.set({ status: `loading` });
|
|
1135
|
+
this.startupSettled = false;
|
|
1136
|
+
this.hydratedDemands.clear();
|
|
549
1137
|
this.startupMetadataPromise = null;
|
|
550
1138
|
this.startPromise = null;
|
|
551
1139
|
this.resumeBaselinePromise = null;
|
|
1140
|
+
this.terminalFailure = void 0;
|
|
1141
|
+
this.resumeCertificationPromise = null;
|
|
1142
|
+
this.persistedKeySetEvidence = void 0;
|
|
1143
|
+
this.persistedResumeGeneration = void 0;
|
|
1144
|
+
this.resumeGenerationOwner = /* @__PURE__ */ Symbol(`persisted resume generation owner`);
|
|
552
1145
|
}
|
|
553
1146
|
withInternalApply(task) {
|
|
554
1147
|
this.internalApplyDepth++;
|
|
@@ -564,8 +1157,8 @@ class PersistedCollectionRuntime {
|
|
|
564
1157
|
}
|
|
565
1158
|
return this.collection.getIndexMetadata().map((metadata) => metadata.signature);
|
|
566
1159
|
}
|
|
567
|
-
loadSubsetRowsUnsafe(options) {
|
|
568
|
-
return
|
|
1160
|
+
loadSubsetRowsUnsafe(options, adapter) {
|
|
1161
|
+
return adapter.loadSubset(this.collectionId, options, {
|
|
569
1162
|
requiredIndexSignatures: this.getRequiredIndexSignatures()
|
|
570
1163
|
});
|
|
571
1164
|
}
|
|
@@ -579,30 +1172,184 @@ class PersistedCollectionRuntime {
|
|
|
579
1172
|
);
|
|
580
1173
|
}
|
|
581
1174
|
async scanPersistedRows(options) {
|
|
1175
|
+
this.throwIfTerminal();
|
|
582
1176
|
return this.applyMutex.run(() => this.scanPersistedRowsUnsafe(options));
|
|
583
1177
|
}
|
|
584
|
-
async hydrateSubsetUnsafe(options, config) {
|
|
585
|
-
|
|
1178
|
+
async hydrateSubsetUnsafe(options, config, adapter) {
|
|
1179
|
+
let rowsLoaded = false;
|
|
1180
|
+
let replayFailure;
|
|
1181
|
+
const resetSequence = this.resetSequence;
|
|
1182
|
+
this.hydrationSequence++;
|
|
1183
|
+
const hydrationContext = { suppliedRowKeys: /* @__PURE__ */ new Set() };
|
|
1184
|
+
this.activeHydrationContext = hydrationContext;
|
|
586
1185
|
try {
|
|
587
|
-
|
|
1186
|
+
this.throwIfTerminal();
|
|
1187
|
+
this.hydratingGeneration = config.lifecycleGeneration;
|
|
1188
|
+
try {
|
|
1189
|
+
let rows;
|
|
1190
|
+
if (config.bindKeySetEvidence) {
|
|
1191
|
+
const snapshot = await adapter.loadResumeSnapshot(this.collectionId, {
|
|
1192
|
+
requiredIndexSignatures: this.getRequiredIndexSignatures(),
|
|
1193
|
+
includeRows: true
|
|
1194
|
+
});
|
|
1195
|
+
rows = snapshot.rows;
|
|
1196
|
+
if (config.lifecycleGeneration !== this.lifecycleGeneration) return;
|
|
1197
|
+
if (resetSequence !== this.resetSequence) return;
|
|
1198
|
+
this.bindResumeSnapshotEvidence(snapshot);
|
|
1199
|
+
} else {
|
|
1200
|
+
rows = await this.loadSubsetRowsUnsafe(options, adapter);
|
|
1201
|
+
}
|
|
1202
|
+
rowsLoaded = true;
|
|
1203
|
+
if (config.lifecycleGeneration !== this.lifecycleGeneration) return;
|
|
1204
|
+
if (resetSequence !== this.resetSequence) return;
|
|
1205
|
+
if (!config.bindKeySetEvidence || this.persistedKeySetEvidence?.status !== `incompatible`) {
|
|
1206
|
+
for (const row of rows) {
|
|
1207
|
+
hydrationContext.suppliedRowKeys.add(row.key);
|
|
1208
|
+
}
|
|
1209
|
+
const applied = this.applyRowsToCollection(rows);
|
|
1210
|
+
if (applied !== true) await applied;
|
|
1211
|
+
}
|
|
1212
|
+
} finally {
|
|
1213
|
+
if (this.activeHydrationContext === hydrationContext && this.hydratingGeneration === config.lifecycleGeneration) {
|
|
1214
|
+
this.hydratingGeneration = null;
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
if (config.lifecycleGeneration !== this.lifecycleGeneration) return;
|
|
1218
|
+
replayFailure = await this.flushQueuedHydrationTransactionsUnsafe(adapter);
|
|
588
1219
|
if (config.lifecycleGeneration !== this.lifecycleGeneration) return;
|
|
589
|
-
|
|
1220
|
+
if (config.requestRemoteEnsure && !replayFailure) {
|
|
1221
|
+
this.queueRemoteSubsetEnsure(options);
|
|
1222
|
+
}
|
|
1223
|
+
} catch (error) {
|
|
1224
|
+
if (config.requestLocalLoadFailure && !rowsLoaded) {
|
|
1225
|
+
this.hydratingGeneration = config.lifecycleGeneration;
|
|
1226
|
+
try {
|
|
1227
|
+
await this.recoverBufferedTransactionsAfterLocalLoadFailureUnsafe(
|
|
1228
|
+
config.lifecycleGeneration,
|
|
1229
|
+
adapter
|
|
1230
|
+
);
|
|
1231
|
+
} catch (baselineError) {
|
|
1232
|
+
const terminalError = this.markTerminalFailure(
|
|
1233
|
+
baselineError,
|
|
1234
|
+
config.lifecycleGeneration
|
|
1235
|
+
);
|
|
1236
|
+
throw terminalError;
|
|
1237
|
+
} finally {
|
|
1238
|
+
if (this.hydratingGeneration === config.lifecycleGeneration) {
|
|
1239
|
+
this.hydratingGeneration = null;
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
throw error;
|
|
1243
|
+
}
|
|
1244
|
+
throw this.markTerminalFailure(error, config.lifecycleGeneration);
|
|
590
1245
|
} finally {
|
|
591
|
-
if (this.
|
|
592
|
-
this.
|
|
1246
|
+
if (this.activeHydrationContext === hydrationContext) {
|
|
1247
|
+
this.activeHydrationContext = void 0;
|
|
593
1248
|
}
|
|
594
1249
|
}
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
if (config.requestRemoteEnsure) {
|
|
598
|
-
this.queueRemoteSubsetEnsure(options);
|
|
1250
|
+
if (replayFailure && config.rejectBufferedReplayFailure) {
|
|
1251
|
+
throw replayFailure.reason;
|
|
599
1252
|
}
|
|
600
1253
|
}
|
|
1254
|
+
async recoverBufferedTransactionsAfterLocalLoadFailureUnsafe(lifecycleGeneration, adapter) {
|
|
1255
|
+
let snapshotRows;
|
|
1256
|
+
const recoveredPresence = /* @__PURE__ */ new Map();
|
|
1257
|
+
let snapshotInvalidatedByTruncate = false;
|
|
1258
|
+
while (this.queuedHydrationTransactions.length > 0) {
|
|
1259
|
+
const transaction = this.queuedHydrationTransactions.shift();
|
|
1260
|
+
if (!transaction) continue;
|
|
1261
|
+
try {
|
|
1262
|
+
this.throwIfLifecycleReplaced(lifecycleGeneration);
|
|
1263
|
+
const baselineRows = /* @__PURE__ */ new Map();
|
|
1264
|
+
const transactionPresence = /* @__PURE__ */ new Map();
|
|
1265
|
+
const transactionStartsWithTruncate = transaction.truncate;
|
|
1266
|
+
const getPresence = (key) => {
|
|
1267
|
+
const known = transactionPresence.get(key);
|
|
1268
|
+
if (known !== void 0) return known;
|
|
1269
|
+
if (transactionStartsWithTruncate) {
|
|
1270
|
+
transactionPresence.set(key, `absent`);
|
|
1271
|
+
return `absent`;
|
|
1272
|
+
}
|
|
1273
|
+
const recovered = recoveredPresence.get(key);
|
|
1274
|
+
if (recovered !== void 0) {
|
|
1275
|
+
transactionPresence.set(key, recovered);
|
|
1276
|
+
return recovered;
|
|
1277
|
+
}
|
|
1278
|
+
const present = this.collection?._hasHydratedKey(key) === true ? `present` : snapshotInvalidatedByTruncate ? `absent` : `unknown`;
|
|
1279
|
+
transactionPresence.set(key, present);
|
|
1280
|
+
return present;
|
|
1281
|
+
};
|
|
1282
|
+
if (!transaction.signal?.aborted) {
|
|
1283
|
+
for (const [index, operation] of transaction.operations.entries()) {
|
|
1284
|
+
const key = operation.key;
|
|
1285
|
+
if (operation.type === `update` && transaction.partialUpdateOperationIndexes.has(index) && getPresence(key) === `unknown`) {
|
|
1286
|
+
if (snapshotRows === void 0) {
|
|
1287
|
+
const snapshot = await adapter.loadResumeSnapshot(
|
|
1288
|
+
this.collectionId,
|
|
1289
|
+
{
|
|
1290
|
+
requiredIndexSignatures: this.getRequiredIndexSignatures(),
|
|
1291
|
+
includeRows: true
|
|
1292
|
+
}
|
|
1293
|
+
);
|
|
1294
|
+
this.throwIfLifecycleReplaced(lifecycleGeneration);
|
|
1295
|
+
if (transaction.signal?.aborted) break;
|
|
1296
|
+
snapshotRows = new Map(
|
|
1297
|
+
snapshot.rows.map((row) => [row.key, row])
|
|
1298
|
+
);
|
|
1299
|
+
}
|
|
1300
|
+
const baseline = snapshotRows.get(key);
|
|
1301
|
+
if (baseline === void 0) {
|
|
1302
|
+
transactionPresence.set(key, `absent`);
|
|
1303
|
+
} else {
|
|
1304
|
+
baselineRows.set(key, baseline);
|
|
1305
|
+
transactionPresence.set(key, `present`);
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
transactionPresence.set(
|
|
1309
|
+
key,
|
|
1310
|
+
operation.type === `delete` ? `absent` : `present`
|
|
1311
|
+
);
|
|
1312
|
+
}
|
|
1313
|
+
if (baselineRows.size > 0) {
|
|
1314
|
+
if (!transaction.signal?.aborted) {
|
|
1315
|
+
transaction.prependHydrationRows([...baselineRows.values()]);
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
const transactionOutcome = await this.applyBufferedSyncTransactionUnsafe(transaction, adapter);
|
|
1320
|
+
if (transactionOutcome.applied) {
|
|
1321
|
+
if (transaction.truncate) {
|
|
1322
|
+
snapshotInvalidatedByTruncate = true;
|
|
1323
|
+
recoveredPresence.clear();
|
|
1324
|
+
}
|
|
1325
|
+
for (const operation of transaction.operations) {
|
|
1326
|
+
recoveredPresence.set(
|
|
1327
|
+
operation.key,
|
|
1328
|
+
operation.type === `delete` ? `absent` : `present`
|
|
1329
|
+
);
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
} catch (error) {
|
|
1333
|
+
transaction.rejectApplied?.(error);
|
|
1334
|
+
for (const abandoned of this.queuedHydrationTransactions) {
|
|
1335
|
+
abandoned.rejectApplied?.(error);
|
|
1336
|
+
}
|
|
1337
|
+
this.queuedHydrationTransactions.length = 0;
|
|
1338
|
+
throw error;
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
rejectQueuedHydrationTransactions(error) {
|
|
1343
|
+
for (const transaction of this.queuedHydrationTransactions) {
|
|
1344
|
+
transaction.rejectApplied?.(error);
|
|
1345
|
+
}
|
|
1346
|
+
this.queuedHydrationTransactions.length = 0;
|
|
1347
|
+
}
|
|
601
1348
|
applyRowsToCollection(rows) {
|
|
602
1349
|
if (!this.syncControls.begin || !this.syncControls.write || !this.syncControls.commit) {
|
|
603
|
-
return;
|
|
1350
|
+
return true;
|
|
604
1351
|
}
|
|
605
|
-
this.withInternalApply(() => {
|
|
1352
|
+
return this.withInternalApply(() => {
|
|
606
1353
|
this.syncControls.begin?.({ immediate: true });
|
|
607
1354
|
for (const row of rows) {
|
|
608
1355
|
if (this.collection?._hasHydratedKey(row.key)) {
|
|
@@ -614,18 +1361,41 @@ class PersistedCollectionRuntime {
|
|
|
614
1361
|
metadata: row.metadata
|
|
615
1362
|
});
|
|
616
1363
|
}
|
|
617
|
-
this.syncControls.commit?.();
|
|
1364
|
+
return this.syncControls.commit?.() ?? true;
|
|
618
1365
|
});
|
|
619
1366
|
}
|
|
1367
|
+
getResumeSnapshotGeneration(snapshot) {
|
|
1368
|
+
return {
|
|
1369
|
+
latestTerm: snapshot.latestTerm,
|
|
1370
|
+
latestSeq: snapshot.latestSeq,
|
|
1371
|
+
latestRowVersion: snapshot.latestRowVersion,
|
|
1372
|
+
resetEpoch: snapshot.resetEpoch
|
|
1373
|
+
};
|
|
1374
|
+
}
|
|
1375
|
+
isExpectedResumeGeneration(generation) {
|
|
1376
|
+
const expected = this.persistedResumeGeneration;
|
|
1377
|
+
return expected !== void 0 && expected.latestTerm === generation.latestTerm && expected.latestSeq === generation.latestSeq && expected.latestRowVersion === generation.latestRowVersion && expected.resetEpoch === generation.resetEpoch;
|
|
1378
|
+
}
|
|
1379
|
+
bindResumeSnapshotEvidence(snapshot) {
|
|
1380
|
+
const generation = this.getResumeSnapshotGeneration(snapshot);
|
|
1381
|
+
const previousEvidenceStatus = this.persistedKeySetEvidence?.status;
|
|
1382
|
+
const mayAcceptUnownedGeneration = this.mode === `sync-present` && previousEvidenceStatus !== `consistent` && previousEvidenceStatus !== `incompatible` || this.mode === `sync-absent` && previousEvidenceStatus === `consistent` && snapshot.keySet?.status === `consistent`;
|
|
1383
|
+
this.observeStreamPosition(
|
|
1384
|
+
snapshot.latestTerm,
|
|
1385
|
+
snapshot.latestSeq,
|
|
1386
|
+
snapshot.latestRowVersion
|
|
1387
|
+
);
|
|
1388
|
+
this.persistedKeySetEvidence = this.isExpectedResumeGeneration(generation) || mayAcceptUnownedGeneration ? snapshot.keySet : { status: `incompatible` };
|
|
1389
|
+
}
|
|
620
1390
|
replaceCollectionSnapshot(rows, collectionMetadata) {
|
|
621
1391
|
if (!this.syncControls.begin || !this.syncControls.write || !this.syncControls.commit || !this.syncControls.metadata) {
|
|
622
|
-
return;
|
|
1392
|
+
return true;
|
|
623
1393
|
}
|
|
624
1394
|
const nextMetadata = new Map(
|
|
625
1395
|
collectionMetadata.map(({ key, value }) => [key, value])
|
|
626
1396
|
);
|
|
627
1397
|
const currentKeys = this.syncControls.metadata.collection.list().map(({ key }) => key);
|
|
628
|
-
this.withInternalApply(() => {
|
|
1398
|
+
return this.withInternalApply(() => {
|
|
629
1399
|
this.syncControls.begin?.({ immediate: true });
|
|
630
1400
|
this.syncControls.truncate?.();
|
|
631
1401
|
for (const row of rows) {
|
|
@@ -643,17 +1413,22 @@ class PersistedCollectionRuntime {
|
|
|
643
1413
|
nextMetadata.forEach((value, key) => {
|
|
644
1414
|
this.syncControls.metadata?.collection.set(key, value);
|
|
645
1415
|
});
|
|
646
|
-
this.syncControls.commit?.();
|
|
1416
|
+
return this.syncControls.commit?.() ?? true;
|
|
647
1417
|
});
|
|
648
1418
|
}
|
|
649
|
-
async flushQueuedHydrationTransactionsUnsafe() {
|
|
1419
|
+
async flushQueuedHydrationTransactionsUnsafe(adapter) {
|
|
1420
|
+
let operationFailure;
|
|
650
1421
|
while (this.queuedHydrationTransactions.length > 0) {
|
|
651
1422
|
const transaction = this.queuedHydrationTransactions.shift();
|
|
652
1423
|
if (!transaction) {
|
|
653
1424
|
continue;
|
|
654
1425
|
}
|
|
655
1426
|
try {
|
|
656
|
-
await this.applyBufferedSyncTransactionUnsafe(
|
|
1427
|
+
const outcome = await this.applyBufferedSyncTransactionUnsafe(
|
|
1428
|
+
transaction,
|
|
1429
|
+
adapter
|
|
1430
|
+
);
|
|
1431
|
+
operationFailure ??= outcome.hydrationFailure;
|
|
657
1432
|
} catch (error) {
|
|
658
1433
|
transaction.rejectApplied?.(error);
|
|
659
1434
|
for (const abandoned of this.queuedHydrationTransactions) {
|
|
@@ -663,103 +1438,126 @@ class PersistedCollectionRuntime {
|
|
|
663
1438
|
throw error;
|
|
664
1439
|
}
|
|
665
1440
|
}
|
|
1441
|
+
return operationFailure;
|
|
666
1442
|
}
|
|
667
|
-
async applyBufferedSyncTransactionUnsafe(transaction) {
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
if (!begin || !write || !commit) {
|
|
674
|
-
transaction.rejectApplied?.(new SyncTransactionAbortedError());
|
|
675
|
-
return;
|
|
676
|
-
}
|
|
677
|
-
const applyToCollection = () => {
|
|
678
|
-
begin();
|
|
679
|
-
if (transaction.truncate) {
|
|
680
|
-
truncate?.();
|
|
681
|
-
}
|
|
682
|
-
for (const operation of transaction.operations) {
|
|
683
|
-
if (operation.type === `delete`) {
|
|
684
|
-
write({
|
|
685
|
-
type: `delete`,
|
|
686
|
-
key: operation.key
|
|
687
|
-
});
|
|
688
|
-
} else {
|
|
689
|
-
write({
|
|
690
|
-
type: `update`,
|
|
691
|
-
value: operation.value,
|
|
692
|
-
metadata: operation.metadata
|
|
693
|
-
});
|
|
694
|
-
}
|
|
695
|
-
}
|
|
696
|
-
for (const [key, metadataWrite] of transaction.rowMetadataWrites) {
|
|
697
|
-
if (metadataWrite.type === `delete`) {
|
|
698
|
-
metadata?.row.delete(key);
|
|
699
|
-
} else {
|
|
700
|
-
metadata?.row.set(key, metadataWrite.value);
|
|
701
|
-
}
|
|
702
|
-
}
|
|
703
|
-
for (const [key, metadataWrite] of transaction.collectionMetadataWrites) {
|
|
704
|
-
if (metadataWrite.type === `delete`) {
|
|
705
|
-
metadata?.collection.delete(key);
|
|
706
|
-
} else {
|
|
707
|
-
metadata?.collection.set(key, metadataWrite.value);
|
|
708
|
-
}
|
|
709
|
-
}
|
|
710
|
-
return commit(transaction.signal);
|
|
711
|
-
};
|
|
1443
|
+
async applyBufferedSyncTransactionUnsafe(transaction, scopedAdapter) {
|
|
1444
|
+
this.throwIfTerminal();
|
|
1445
|
+
this.throwIfLifecycleReplaced(transaction.lifecycleGeneration);
|
|
1446
|
+
const abortedBeforeApplication = transaction.signal?.aborted === true;
|
|
1447
|
+
let abortedDuringApplication = false;
|
|
1448
|
+
let applicationReturned = false;
|
|
712
1449
|
try {
|
|
713
|
-
const applied = transaction.internal ? this.withInternalApply(applyToCollection) : applyToCollection();
|
|
1450
|
+
const applied = transaction.internal ? this.withInternalApply(transaction.applyToCollection) : transaction.applyToCollection();
|
|
1451
|
+
applicationReturned = true;
|
|
1452
|
+
abortedDuringApplication = transaction.signal?.aborted === true;
|
|
714
1453
|
if (applied !== true) {
|
|
715
|
-
|
|
1454
|
+
this.sourcePublicationWaitDepth++;
|
|
1455
|
+
try {
|
|
1456
|
+
await applied;
|
|
1457
|
+
} finally {
|
|
1458
|
+
this.sourcePublicationWaitDepth--;
|
|
1459
|
+
}
|
|
716
1460
|
}
|
|
1461
|
+
this.throwIfLifecycleReplaced(transaction.lifecycleGeneration);
|
|
717
1462
|
if (!transaction.internal) {
|
|
718
|
-
await this.persistAndBroadcastExternalSyncTransactionUnsafe(
|
|
1463
|
+
await this.persistAndBroadcastExternalSyncTransactionUnsafe(
|
|
1464
|
+
transaction,
|
|
1465
|
+
scopedAdapter
|
|
1466
|
+
);
|
|
719
1467
|
}
|
|
720
1468
|
transaction.resolveApplied?.();
|
|
1469
|
+
return { applied: true };
|
|
721
1470
|
} catch (error) {
|
|
722
|
-
|
|
723
|
-
|
|
1471
|
+
if (!applicationReturned) {
|
|
1472
|
+
abortedDuringApplication = transaction.signal?.aborted === true;
|
|
1473
|
+
}
|
|
1474
|
+
const aborted = transaction.signal?.aborted || error instanceof SyncTransactionAbortedError;
|
|
1475
|
+
const shouldFailStop = aborted && transaction.shouldFailStopOnAbort?.() === true;
|
|
1476
|
+
const terminalError = this.classifyBufferedTransactionFailure(
|
|
1477
|
+
transaction,
|
|
1478
|
+
error
|
|
1479
|
+
);
|
|
1480
|
+
transaction.rejectApplied?.(terminalError);
|
|
1481
|
+
if (aborted && !shouldFailStop && transaction.rejectApplied) {
|
|
1482
|
+
return {
|
|
1483
|
+
applied: false,
|
|
1484
|
+
hydrationFailure: !abortedBeforeApplication && abortedDuringApplication ? { reason: terminalError } : void 0
|
|
1485
|
+
};
|
|
1486
|
+
}
|
|
1487
|
+
throw terminalError;
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
classifyBufferedTransactionFailure(transaction, error) {
|
|
1491
|
+
if (error instanceof PersistedCollectionDurabilityError) {
|
|
1492
|
+
return this.markTerminalFailure(error, transaction.lifecycleGeneration);
|
|
1493
|
+
}
|
|
1494
|
+
const aborted = transaction.signal?.aborted || error instanceof SyncTransactionAbortedError;
|
|
1495
|
+
if (aborted && transaction.shouldFailStopOnAbort?.() !== true) {
|
|
1496
|
+
return error;
|
|
724
1497
|
}
|
|
1498
|
+
return this.markTerminalFailure(error, transaction.lifecycleGeneration);
|
|
725
1499
|
}
|
|
726
|
-
async persistAndBroadcastExternalSyncTransactionUnsafe(transaction) {
|
|
1500
|
+
async persistAndBroadcastExternalSyncTransactionUnsafe(transaction, scopedAdapter) {
|
|
727
1501
|
if (transaction.internal) {
|
|
728
1502
|
return;
|
|
729
1503
|
}
|
|
730
|
-
|
|
731
|
-
if (!transaction.truncate && transaction.operations.length === 0 && transaction.rowMetadataWrites.size === 0 && transaction.collectionMetadataWrites.size === 0) {
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
1504
|
+
this.throwIfLifecycleReplaced(transaction.lifecycleGeneration);
|
|
1505
|
+
if (!transaction.truncate && transaction.operations.length === 0 && transaction.rowMetadataWrites.size === 0 && transaction.collectionMetadataWrites.size === 0) {
|
|
1506
|
+
return;
|
|
1507
|
+
}
|
|
1508
|
+
const streamPosition = this.nextLocalStreamPosition();
|
|
1509
|
+
const tx = this.createPersistedTxFromOperations(transaction, streamPosition);
|
|
1510
|
+
const response = await this.persistence.coordinator.requestApplyCommittedTx(
|
|
1511
|
+
this.collectionId,
|
|
1512
|
+
tx,
|
|
1513
|
+
// A hydration-scoped adapter is already inside the shared scheduler.
|
|
1514
|
+
// Ordinary source commits pass no scoped adapter so the coordinator
|
|
1515
|
+
// enters that scheduler before taking the database-wide writer lock.
|
|
1516
|
+
scopedAdapter
|
|
1517
|
+
);
|
|
1518
|
+
if (!response.ok) {
|
|
1519
|
+
if (response.code === `PERSISTENCE_ERROR`) {
|
|
1520
|
+
const error = new PersistedCollectionDurabilityError(
|
|
1521
|
+
`Failed to durably persist collection "${this.collectionId}": ${response.error}`,
|
|
1522
|
+
{
|
|
1523
|
+
cause: response,
|
|
1524
|
+
code: response.sourceCode ?? response.code,
|
|
1525
|
+
path: response.path
|
|
1526
|
+
}
|
|
1527
|
+
);
|
|
1528
|
+
throw error;
|
|
1529
|
+
}
|
|
1530
|
+
throw new Error(
|
|
1531
|
+
`failed to apply external sync transaction through coordinator: ${response.error}`
|
|
1532
|
+
);
|
|
1533
|
+
}
|
|
1534
|
+
if (transaction.lifecycleGeneration !== this.lifecycleGeneration) return;
|
|
1535
|
+
this.observeStreamPosition(
|
|
1536
|
+
response.term,
|
|
1537
|
+
response.seq,
|
|
1538
|
+
response.latestRowVersion
|
|
1539
|
+
);
|
|
1540
|
+
if (transaction.expectedResumeGenerationOwner === this.resumeGenerationOwner && this.persistedResumeGeneration !== void 0) {
|
|
1541
|
+
this.persistedResumeGeneration = {
|
|
1542
|
+
...this.persistedResumeGeneration,
|
|
1543
|
+
latestTerm: response.term,
|
|
1544
|
+
latestSeq: response.seq,
|
|
1545
|
+
latestRowVersion: response.latestRowVersion
|
|
1546
|
+
};
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1549
|
+
async applyCommittedTx(tx, lifecycleGeneration) {
|
|
1550
|
+
this.throwIfTerminal();
|
|
1551
|
+
this.throwIfLifecycleReplaced(lifecycleGeneration);
|
|
1552
|
+
try {
|
|
1553
|
+
await this.persistence.adapter.applyCommittedTx(this.collectionId, tx);
|
|
1554
|
+
} catch (error) {
|
|
1555
|
+
throw this.markTerminalFailure(
|
|
1556
|
+
toPersistedCollectionDurabilityError(this.collectionId, error),
|
|
1557
|
+
lifecycleGeneration
|
|
742
1558
|
);
|
|
743
|
-
return;
|
|
744
1559
|
}
|
|
745
|
-
|
|
746
|
-
await this.persistence.adapter.applyCommittedTx(this.collectionId, tx);
|
|
747
|
-
this.publishTxCommittedEvent(
|
|
748
|
-
this.createTxCommittedPayload({
|
|
749
|
-
term: tx.term,
|
|
750
|
-
seq: tx.seq,
|
|
751
|
-
txId: tx.txId,
|
|
752
|
-
latestRowVersion: tx.rowVersion,
|
|
753
|
-
requiresFullReload: transaction.truncate,
|
|
754
|
-
changedRows: transaction.operations.filter((operation) => operation.type === `update`).map((operation) => ({
|
|
755
|
-
key: operation.key,
|
|
756
|
-
value: operation.value
|
|
757
|
-
})),
|
|
758
|
-
deletedKeys: transaction.operations.filter((operation) => operation.type === `delete`).map((operation) => operation.key),
|
|
759
|
-
rowMetadataMutations: tx.rowMetadataMutations,
|
|
760
|
-
collectionMetadataMutations: tx.collectionMetadataMutations
|
|
761
|
-
})
|
|
762
|
-
);
|
|
1560
|
+
this.throwIfLifecycleReplaced(lifecycleGeneration);
|
|
763
1561
|
}
|
|
764
1562
|
createPersistedTxFromOperations(transaction, streamPosition) {
|
|
765
1563
|
return {
|
|
@@ -824,11 +1622,11 @@ class PersistedCollectionRuntime {
|
|
|
824
1622
|
})
|
|
825
1623
|
};
|
|
826
1624
|
}
|
|
827
|
-
confirmMutationsSyncUnsafe(mutations) {
|
|
1625
|
+
async confirmMutationsSyncUnsafe(mutations) {
|
|
828
1626
|
if (!this.syncControls.begin || !this.syncControls.write || !this.syncControls.commit) {
|
|
829
1627
|
return;
|
|
830
1628
|
}
|
|
831
|
-
this.withInternalApply(() => {
|
|
1629
|
+
const applied = this.withInternalApply(() => {
|
|
832
1630
|
this.syncControls.begin?.({ immediate: true });
|
|
833
1631
|
for (const mutation of mutations) {
|
|
834
1632
|
if (mutation.type === `delete`) {
|
|
@@ -843,8 +1641,11 @@ class PersistedCollectionRuntime {
|
|
|
843
1641
|
});
|
|
844
1642
|
}
|
|
845
1643
|
}
|
|
846
|
-
this.syncControls.commit?.();
|
|
1644
|
+
return this.syncControls.commit?.() ?? true;
|
|
847
1645
|
});
|
|
1646
|
+
if (applied !== true) {
|
|
1647
|
+
await applied;
|
|
1648
|
+
}
|
|
848
1649
|
}
|
|
849
1650
|
filterMutationsForCollection(mutations) {
|
|
850
1651
|
const collection = this.collection;
|
|
@@ -855,7 +1656,8 @@ class PersistedCollectionRuntime {
|
|
|
855
1656
|
return mutation.collection.id === this.collectionId;
|
|
856
1657
|
});
|
|
857
1658
|
}
|
|
858
|
-
async persistCollectionMutationsUnsafe(mutations) {
|
|
1659
|
+
async persistCollectionMutationsUnsafe(mutations, lifecycleGeneration) {
|
|
1660
|
+
this.throwIfLifecycleReplaced(lifecycleGeneration);
|
|
859
1661
|
if (this.persistence.coordinator.requestApplyLocalMutations) {
|
|
860
1662
|
const envelopeMutations = mutations.map(
|
|
861
1663
|
(mutation) => toPersistedMutationEnvelope(
|
|
@@ -866,7 +1668,19 @@ class PersistedCollectionRuntime {
|
|
|
866
1668
|
this.collectionId,
|
|
867
1669
|
envelopeMutations
|
|
868
1670
|
);
|
|
1671
|
+
this.throwIfLifecycleReplaced(lifecycleGeneration);
|
|
869
1672
|
if (!response.ok) {
|
|
1673
|
+
if (response.code === `PERSISTENCE_ERROR`) {
|
|
1674
|
+
const error = new PersistedCollectionDurabilityError(
|
|
1675
|
+
`Failed to durably persist collection "${this.collectionId}": ${response.error}`,
|
|
1676
|
+
{
|
|
1677
|
+
cause: response,
|
|
1678
|
+
code: response.sourceCode ?? response.code,
|
|
1679
|
+
path: response.path
|
|
1680
|
+
}
|
|
1681
|
+
);
|
|
1682
|
+
throw error;
|
|
1683
|
+
}
|
|
870
1684
|
throw new Error(
|
|
871
1685
|
`failed to apply local mutations through coordinator: ${response.error}`
|
|
872
1686
|
);
|
|
@@ -894,7 +1708,8 @@ class PersistedCollectionRuntime {
|
|
|
894
1708
|
}
|
|
895
1709
|
const streamPosition = this.nextLocalStreamPosition();
|
|
896
1710
|
const tx = this.createPersistedTxFromMutations(mutations, streamPosition);
|
|
897
|
-
await this.
|
|
1711
|
+
await this.applyCommittedTx(tx, lifecycleGeneration);
|
|
1712
|
+
this.throwIfLifecycleReplaced(lifecycleGeneration);
|
|
898
1713
|
this.publishTxCommittedEvent(
|
|
899
1714
|
this.createTxCommittedPayload({
|
|
900
1715
|
term: tx.term,
|
|
@@ -991,15 +1806,23 @@ class PersistedCollectionRuntime {
|
|
|
991
1806
|
}
|
|
992
1807
|
return id;
|
|
993
1808
|
}
|
|
1809
|
+
canRouteRemoteDemandThroughCoordinator() {
|
|
1810
|
+
if (this.getCurrentTerminalFailure() || this.mode !== `sync-present` || this.persistence.coordinator instanceof SingleProcessCoordinator) {
|
|
1811
|
+
return false;
|
|
1812
|
+
}
|
|
1813
|
+
return !this.persistence.coordinator.isLeader(this.collectionId) || this.remoteSubsetOwnerUnsubscribe !== null;
|
|
1814
|
+
}
|
|
994
1815
|
queueRemoteSubsetEnsure(options) {
|
|
995
|
-
if (
|
|
1816
|
+
if (options.signal?.aborted || !this.canRouteRemoteDemandThroughCoordinator() || this.activeSubsets.get(this.getSubsetKey(options)) !== options) {
|
|
996
1817
|
return;
|
|
997
1818
|
}
|
|
998
|
-
this.
|
|
1819
|
+
const subsetKey = this.getSubsetKey(options);
|
|
1820
|
+
if (this.activeSubsets.get(subsetKey) !== options) return;
|
|
1821
|
+
this.pendingRemoteSubsetEnsures.set(subsetKey, options);
|
|
999
1822
|
void this.flushPendingRemoteSubsetEnsures();
|
|
1000
1823
|
}
|
|
1001
1824
|
scheduleRemoteEnsureRetry() {
|
|
1002
|
-
if (this.mode !== `sync-present` ||
|
|
1825
|
+
if (this.getCurrentTerminalFailure() || this.mode !== `sync-present` || this.persistence.coordinator instanceof SingleProcessCoordinator) {
|
|
1003
1826
|
return;
|
|
1004
1827
|
}
|
|
1005
1828
|
if (this.pendingRemoteSubsetEnsures.size === 0 || this.remoteEnsureRetryTimer !== null) {
|
|
@@ -1011,7 +1834,7 @@ class PersistedCollectionRuntime {
|
|
|
1011
1834
|
}, REMOTE_ENSURE_RETRY_DELAY_MS);
|
|
1012
1835
|
}
|
|
1013
1836
|
async flushPendingRemoteSubsetEnsures() {
|
|
1014
|
-
if (this.mode !== `sync-present` ||
|
|
1837
|
+
if (this.getCurrentTerminalFailure() || this.mode !== `sync-present` || this.persistence.coordinator instanceof SingleProcessCoordinator) {
|
|
1015
1838
|
return;
|
|
1016
1839
|
}
|
|
1017
1840
|
if (this.remoteEnsureRetryTimer !== null) {
|
|
@@ -1019,6 +1842,10 @@ class PersistedCollectionRuntime {
|
|
|
1019
1842
|
this.remoteEnsureRetryTimer = null;
|
|
1020
1843
|
}
|
|
1021
1844
|
for (const [subsetKey, options] of this.pendingRemoteSubsetEnsures) {
|
|
1845
|
+
if (options.signal?.aborted || this.activeSubsets.get(subsetKey) !== options) {
|
|
1846
|
+
this.pendingRemoteSubsetEnsures.delete(subsetKey);
|
|
1847
|
+
continue;
|
|
1848
|
+
}
|
|
1022
1849
|
try {
|
|
1023
1850
|
await this.persistence.coordinator.requestEnsureRemoteSubset(
|
|
1024
1851
|
this.collectionId,
|
|
@@ -1026,7 +1853,11 @@ class PersistedCollectionRuntime {
|
|
|
1026
1853
|
);
|
|
1027
1854
|
this.pendingRemoteSubsetEnsures.delete(subsetKey);
|
|
1028
1855
|
} catch (error) {
|
|
1029
|
-
|
|
1856
|
+
if (options.signal?.aborted || this.activeSubsets.get(subsetKey) !== options) {
|
|
1857
|
+
this.pendingRemoteSubsetEnsures.delete(subsetKey);
|
|
1858
|
+
} else {
|
|
1859
|
+
console.warn(`Failed to ensure remote subset:`, error);
|
|
1860
|
+
}
|
|
1030
1861
|
}
|
|
1031
1862
|
}
|
|
1032
1863
|
this.scheduleRemoteEnsureRetry();
|
|
@@ -1035,6 +1866,10 @@ class PersistedCollectionRuntime {
|
|
|
1035
1866
|
if (this.coordinatorUnsubscribe) {
|
|
1036
1867
|
return;
|
|
1037
1868
|
}
|
|
1869
|
+
this.persistence.coordinator.setAdapterForCollection?.(
|
|
1870
|
+
this.collectionId,
|
|
1871
|
+
this.persistence.adapter
|
|
1872
|
+
);
|
|
1038
1873
|
this.coordinatorUnsubscribe = this.persistence.coordinator.subscribe(
|
|
1039
1874
|
this.collectionId,
|
|
1040
1875
|
(message) => {
|
|
@@ -1046,15 +1881,22 @@ class PersistedCollectionRuntime {
|
|
|
1046
1881
|
if (message.collectionId !== this.collectionId) {
|
|
1047
1882
|
return;
|
|
1048
1883
|
}
|
|
1884
|
+
if (this.getCurrentTerminalFailure()) {
|
|
1885
|
+
return;
|
|
1886
|
+
}
|
|
1049
1887
|
const { payload } = message;
|
|
1888
|
+
const lifecycleGeneration = this.lifecycleGeneration;
|
|
1050
1889
|
const isSelf = message.senderId === this.persistence.coordinator.getNodeId();
|
|
1051
1890
|
if (isTxCommittedPayload(payload)) {
|
|
1052
1891
|
if (this.isHydratingNow()) {
|
|
1053
|
-
this.queuedTxCommitted.push(
|
|
1892
|
+
this.queuedTxCommitted.push({
|
|
1893
|
+
txCommitted: payload,
|
|
1894
|
+
lifecycleGeneration
|
|
1895
|
+
});
|
|
1054
1896
|
return;
|
|
1055
1897
|
}
|
|
1056
|
-
void this.applyMutex.run(() => this.processCommittedTxUnsafe(payload)).catch((error) => {
|
|
1057
|
-
|
|
1898
|
+
void this.applyMutex.run(() => this.processCommittedTxUnsafe(payload, lifecycleGeneration)).catch((error) => {
|
|
1899
|
+
this.markTerminalFailure(error, lifecycleGeneration);
|
|
1058
1900
|
});
|
|
1059
1901
|
return;
|
|
1060
1902
|
}
|
|
@@ -1062,8 +1904,15 @@ class PersistedCollectionRuntime {
|
|
|
1062
1904
|
return;
|
|
1063
1905
|
}
|
|
1064
1906
|
if (isCollectionResetPayload(payload)) {
|
|
1065
|
-
void this.applyMutex.run(
|
|
1066
|
-
|
|
1907
|
+
void this.applyMutex.run(async () => {
|
|
1908
|
+
await this.runInHydrationScope(
|
|
1909
|
+
(adapter) => this.truncateAndReloadUnsafe(adapter, lifecycleGeneration)
|
|
1910
|
+
);
|
|
1911
|
+
if (lifecycleGeneration === this.lifecycleGeneration) {
|
|
1912
|
+
await this.flushQueuedTxCommittedUnsafe();
|
|
1913
|
+
}
|
|
1914
|
+
}).catch((error) => {
|
|
1915
|
+
this.markTerminalFailure(error, lifecycleGeneration);
|
|
1067
1916
|
});
|
|
1068
1917
|
}
|
|
1069
1918
|
}
|
|
@@ -1073,10 +1922,14 @@ class PersistedCollectionRuntime {
|
|
|
1073
1922
|
if (!queued) {
|
|
1074
1923
|
continue;
|
|
1075
1924
|
}
|
|
1076
|
-
await this.processCommittedTxUnsafe(
|
|
1925
|
+
await this.processCommittedTxUnsafe(
|
|
1926
|
+
queued.txCommitted,
|
|
1927
|
+
queued.lifecycleGeneration
|
|
1928
|
+
);
|
|
1077
1929
|
}
|
|
1078
1930
|
}
|
|
1079
|
-
async processCommittedTxUnsafe(txCommitted) {
|
|
1931
|
+
async processCommittedTxUnsafe(txCommitted, lifecycleGeneration = this.lifecycleGeneration) {
|
|
1932
|
+
if (lifecycleGeneration !== this.lifecycleGeneration) return;
|
|
1080
1933
|
if (txCommitted.term < this.latestTerm) {
|
|
1081
1934
|
return;
|
|
1082
1935
|
}
|
|
@@ -1087,7 +1940,8 @@ class PersistedCollectionRuntime {
|
|
|
1087
1940
|
const hasGapAcrossTerms = txCommitted.term > this.latestTerm && txCommitted.seq > 1;
|
|
1088
1941
|
const hasGap = hasGapInCurrentTerm || hasGapAcrossTerms;
|
|
1089
1942
|
if (hasGap) {
|
|
1090
|
-
await this.recoverFromSeqGapUnsafe();
|
|
1943
|
+
await this.recoverFromSeqGapUnsafe(lifecycleGeneration);
|
|
1944
|
+
if (lifecycleGeneration !== this.lifecycleGeneration) return;
|
|
1091
1945
|
if (txCommitted.term < this.latestTerm || txCommitted.term === this.latestTerm && txCommitted.seq <= this.latestSeq) {
|
|
1092
1946
|
return;
|
|
1093
1947
|
}
|
|
@@ -1097,70 +1951,109 @@ class PersistedCollectionRuntime {
|
|
|
1097
1951
|
txCommitted.seq,
|
|
1098
1952
|
txCommitted.latestRowVersion
|
|
1099
1953
|
);
|
|
1100
|
-
await this.invalidateFromCommittedTxUnsafe(
|
|
1954
|
+
await this.invalidateFromCommittedTxUnsafe(
|
|
1955
|
+
txCommitted,
|
|
1956
|
+
this.persistence.adapter
|
|
1957
|
+
);
|
|
1958
|
+
if (lifecycleGeneration === this.lifecycleGeneration) {
|
|
1959
|
+
await this.flushQueuedTxCommittedUnsafe();
|
|
1960
|
+
}
|
|
1101
1961
|
}
|
|
1102
|
-
async recoverFromSeqGapUnsafe() {
|
|
1962
|
+
async recoverFromSeqGapUnsafe(lifecycleGeneration) {
|
|
1963
|
+
if (lifecycleGeneration !== this.lifecycleGeneration) return;
|
|
1103
1964
|
if (this.persistence.coordinator.pullSince && this.latestRowVersion >= 0) {
|
|
1965
|
+
let pullResponse;
|
|
1104
1966
|
try {
|
|
1105
|
-
|
|
1967
|
+
pullResponse = await this.persistence.coordinator.pullSince(
|
|
1106
1968
|
this.collectionId,
|
|
1107
1969
|
this.latestRowVersion
|
|
1108
1970
|
);
|
|
1971
|
+
} catch (error) {
|
|
1972
|
+
console.warn(`Failed pullSince recovery attempt:`, error);
|
|
1973
|
+
}
|
|
1974
|
+
if (pullResponse) {
|
|
1975
|
+
if (lifecycleGeneration !== this.lifecycleGeneration) return;
|
|
1109
1976
|
if (pullResponse.ok) {
|
|
1110
1977
|
this.observeStreamPosition(
|
|
1111
1978
|
pullResponse.latestTerm,
|
|
1112
1979
|
pullResponse.latestSeq,
|
|
1113
1980
|
pullResponse.latestRowVersion
|
|
1114
1981
|
);
|
|
1115
|
-
if (pullResponse.requiresFullReload
|
|
1116
|
-
await this.
|
|
1982
|
+
if (pullResponse.requiresFullReload) {
|
|
1983
|
+
await this.runInHydrationScope(
|
|
1984
|
+
(adapter) => this.reloadActiveSubsetsUnsafe(adapter)
|
|
1985
|
+
);
|
|
1117
1986
|
return;
|
|
1118
1987
|
}
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
latestRowVersion: delta.latestRowVersion,
|
|
1126
|
-
requiresFullReload: false,
|
|
1127
|
-
changedRows: delta.changedRows,
|
|
1128
|
-
deletedKeys: delta.deletedKeys,
|
|
1129
|
-
rowMetadataMutations: delta.rowMetadataMutations,
|
|
1130
|
-
collectionMetadataMutations: delta.collectionMetadataMutations
|
|
1131
|
-
});
|
|
1988
|
+
const deltas = pullResponse.deltas;
|
|
1989
|
+
if (!deltas) {
|
|
1990
|
+
await this.runInHydrationScope(
|
|
1991
|
+
(adapter) => this.reloadActiveSubsetsUnsafe(adapter)
|
|
1992
|
+
);
|
|
1993
|
+
return;
|
|
1132
1994
|
}
|
|
1995
|
+
await this.runInHydrationScope(async (adapter) => {
|
|
1996
|
+
for (const delta of deltas) {
|
|
1997
|
+
if (lifecycleGeneration !== this.lifecycleGeneration) return;
|
|
1998
|
+
await this.invalidateFromCommittedTxUnsafe(
|
|
1999
|
+
{
|
|
2000
|
+
type: `tx:committed`,
|
|
2001
|
+
term: pullResponse.latestTerm,
|
|
2002
|
+
seq: pullResponse.latestSeq,
|
|
2003
|
+
txId: delta.txId,
|
|
2004
|
+
latestRowVersion: delta.latestRowVersion,
|
|
2005
|
+
requiresFullReload: false,
|
|
2006
|
+
changedRows: delta.changedRows,
|
|
2007
|
+
deletedKeys: delta.deletedKeys,
|
|
2008
|
+
rowMetadataMutations: delta.rowMetadataMutations,
|
|
2009
|
+
collectionMetadataMutations: delta.collectionMetadataMutations
|
|
2010
|
+
},
|
|
2011
|
+
adapter
|
|
2012
|
+
);
|
|
2013
|
+
if (lifecycleGeneration !== this.lifecycleGeneration) return;
|
|
2014
|
+
}
|
|
2015
|
+
});
|
|
1133
2016
|
return;
|
|
1134
2017
|
}
|
|
1135
|
-
} catch (error) {
|
|
1136
|
-
console.warn(`Failed pullSince recovery attempt:`, error);
|
|
1137
2018
|
}
|
|
1138
2019
|
}
|
|
1139
|
-
|
|
2020
|
+
if (lifecycleGeneration !== this.lifecycleGeneration) return;
|
|
2021
|
+
await this.runInHydrationScope(
|
|
2022
|
+
(adapter) => this.truncateAndReloadUnsafe(adapter, lifecycleGeneration)
|
|
2023
|
+
);
|
|
1140
2024
|
if (this.mode === `sync-present`) {
|
|
1141
2025
|
for (const options of this.activeSubsets.values()) {
|
|
1142
2026
|
this.queueRemoteSubsetEnsure(options);
|
|
1143
2027
|
}
|
|
1144
2028
|
}
|
|
1145
2029
|
}
|
|
1146
|
-
async truncateAndReloadUnsafe() {
|
|
2030
|
+
async truncateAndReloadUnsafe(adapter, lifecycleGeneration = this.lifecycleGeneration) {
|
|
2031
|
+
if (lifecycleGeneration !== this.lifecycleGeneration) return;
|
|
2032
|
+
this.hydratedDemands.clear();
|
|
2033
|
+
this.resetSequence++;
|
|
1147
2034
|
if (this.syncControls.begin && this.syncControls.commit) {
|
|
1148
|
-
this.withInternalApply(() => {
|
|
2035
|
+
const applied = this.withInternalApply(() => {
|
|
1149
2036
|
this.syncControls.begin?.({ immediate: true });
|
|
1150
2037
|
this.syncControls.truncate?.();
|
|
1151
|
-
this.syncControls.commit?.();
|
|
2038
|
+
return this.syncControls.commit?.() ?? true;
|
|
1152
2039
|
});
|
|
2040
|
+
if (applied !== true) await applied;
|
|
1153
2041
|
}
|
|
1154
|
-
|
|
2042
|
+
if (lifecycleGeneration !== this.lifecycleGeneration) return;
|
|
2043
|
+
await this.reloadActiveSubsetsUnsafe(adapter);
|
|
1155
2044
|
}
|
|
1156
|
-
async invalidateFromCommittedTxUnsafe(txCommitted) {
|
|
2045
|
+
async invalidateFromCommittedTxUnsafe(txCommitted, adapter) {
|
|
2046
|
+
const reloadActiveSubsets = () => this.runInHydrationScope(
|
|
2047
|
+
(scopedAdapter) => this.reloadActiveSubsetsUnsafe(scopedAdapter),
|
|
2048
|
+
adapter
|
|
2049
|
+
);
|
|
1157
2050
|
if (txCommitted.requiresFullReload) {
|
|
1158
|
-
await
|
|
2051
|
+
await reloadActiveSubsets();
|
|
1159
2052
|
return;
|
|
1160
2053
|
}
|
|
1161
2054
|
const changedKeyCount = txCommitted.changedRows.length + txCommitted.deletedKeys.length;
|
|
1162
2055
|
if (changedKeyCount > TARGETED_INVALIDATION_KEY_LIMIT) {
|
|
1163
|
-
await
|
|
2056
|
+
await reloadActiveSubsets();
|
|
1164
2057
|
return;
|
|
1165
2058
|
}
|
|
1166
2059
|
const hasPaginatedSubset = Array.from(this.activeSubsets.values()).some(
|
|
@@ -1170,13 +2063,13 @@ class PersistedCollectionRuntime {
|
|
|
1170
2063
|
await this.applyTargetedInvalidationUnsafe(txCommitted);
|
|
1171
2064
|
return;
|
|
1172
2065
|
}
|
|
1173
|
-
await
|
|
2066
|
+
await reloadActiveSubsets();
|
|
1174
2067
|
}
|
|
1175
2068
|
async applyTargetedInvalidationUnsafe(txCommitted) {
|
|
1176
2069
|
const subsetEvaluators = Array.from(this.activeSubsets.values()).map(
|
|
1177
2070
|
(opt) => opt.where ? compileSingleRowExpression(opt.where) : null
|
|
1178
2071
|
);
|
|
1179
|
-
this.withInternalApply(() => {
|
|
2072
|
+
const applied = this.withInternalApply(() => {
|
|
1180
2073
|
this.syncControls.begin?.({ immediate: true });
|
|
1181
2074
|
for (const {
|
|
1182
2075
|
key: changedKey,
|
|
@@ -1215,19 +2108,25 @@ class PersistedCollectionRuntime {
|
|
|
1215
2108
|
);
|
|
1216
2109
|
}
|
|
1217
2110
|
});
|
|
1218
|
-
this.syncControls.commit?.();
|
|
2111
|
+
return this.syncControls.commit?.() ?? true;
|
|
1219
2112
|
});
|
|
2113
|
+
if (applied !== true) await applied;
|
|
1220
2114
|
}
|
|
1221
|
-
async reloadActiveSubsetsUnsafe() {
|
|
2115
|
+
async reloadActiveSubsetsUnsafe(adapter) {
|
|
1222
2116
|
const lifecycleGeneration = this.lifecycleGeneration;
|
|
2117
|
+
const truncateGeneration = this.sourceTruncateGeneration;
|
|
1223
2118
|
const activeSubsetOptions = this.activeSubsets.size > 0 ? Array.from(this.activeSubsets.values()) : [{}];
|
|
2119
|
+
this.hydratedDemands.clear();
|
|
2120
|
+
this.hydrationSequence++;
|
|
2121
|
+
const hydrationContext = { suppliedRowKeys: /* @__PURE__ */ new Set() };
|
|
2122
|
+
this.activeHydrationContext = hydrationContext;
|
|
1224
2123
|
this.hydratingGeneration = lifecycleGeneration;
|
|
1225
2124
|
try {
|
|
1226
2125
|
const mergedRows = /* @__PURE__ */ new Map();
|
|
1227
|
-
const collectionMetadata = await this.loadCollectionMetadataSnapshot();
|
|
2126
|
+
const collectionMetadata = await this.loadCollectionMetadataSnapshot(adapter);
|
|
1228
2127
|
if (lifecycleGeneration !== this.lifecycleGeneration) return;
|
|
1229
2128
|
for (const options of activeSubsetOptions) {
|
|
1230
|
-
const subsetRows = await this.loadSubsetRowsUnsafe(options);
|
|
2129
|
+
const subsetRows = await this.loadSubsetRowsUnsafe(options, adapter);
|
|
1231
2130
|
if (lifecycleGeneration !== this.lifecycleGeneration) return;
|
|
1232
2131
|
for (const row of subsetRows) {
|
|
1233
2132
|
mergedRows.set(row.key, {
|
|
@@ -1236,7 +2135,10 @@ class PersistedCollectionRuntime {
|
|
|
1236
2135
|
});
|
|
1237
2136
|
}
|
|
1238
2137
|
}
|
|
1239
|
-
|
|
2138
|
+
for (const key of mergedRows.keys()) {
|
|
2139
|
+
hydrationContext.suppliedRowKeys.add(key);
|
|
2140
|
+
}
|
|
2141
|
+
const applied = this.replaceCollectionSnapshot(
|
|
1240
2142
|
Array.from(mergedRows.entries()).map(([key, row]) => ({
|
|
1241
2143
|
key,
|
|
1242
2144
|
value: row.value,
|
|
@@ -1244,13 +2146,25 @@ class PersistedCollectionRuntime {
|
|
|
1244
2146
|
})),
|
|
1245
2147
|
collectionMetadata
|
|
1246
2148
|
);
|
|
2149
|
+
if (applied !== true) await applied;
|
|
1247
2150
|
} finally {
|
|
1248
|
-
if (this.hydratingGeneration === lifecycleGeneration) {
|
|
2151
|
+
if (this.activeHydrationContext === hydrationContext && this.hydratingGeneration === lifecycleGeneration) {
|
|
1249
2152
|
this.hydratingGeneration = null;
|
|
1250
2153
|
}
|
|
2154
|
+
if (this.activeHydrationContext === hydrationContext) {
|
|
2155
|
+
this.activeHydrationContext = void 0;
|
|
2156
|
+
}
|
|
2157
|
+
}
|
|
2158
|
+
if (lifecycleGeneration === this.lifecycleGeneration) {
|
|
2159
|
+
await this.flushQueuedHydrationTransactionsUnsafe(adapter);
|
|
2160
|
+
if (truncateGeneration === this.sourceTruncateGeneration && this.queuedTxCommitted.length === 0) {
|
|
2161
|
+
for (const options of activeSubsetOptions) {
|
|
2162
|
+
if (this.activeSubsets.get(this.getSubsetKey(options)) === options) {
|
|
2163
|
+
this.hydratedDemands.add(getLoadSubsetDemandKey(options));
|
|
2164
|
+
}
|
|
2165
|
+
}
|
|
2166
|
+
}
|
|
1251
2167
|
}
|
|
1252
|
-
await this.flushQueuedHydrationTransactionsUnsafe();
|
|
1253
|
-
await this.flushQueuedTxCommittedUnsafe();
|
|
1254
2168
|
}
|
|
1255
2169
|
attachIndexLifecycleListeners() {
|
|
1256
2170
|
if (!this.collection || this.indexAddedUnsubscribe || this.indexRemovedUnsubscribe) {
|
|
@@ -1266,14 +2180,26 @@ class PersistedCollectionRuntime {
|
|
|
1266
2180
|
}
|
|
1267
2181
|
);
|
|
1268
2182
|
}
|
|
1269
|
-
async bootstrapPersistedIndexes(indexMetadataSnapshot) {
|
|
2183
|
+
async bootstrapPersistedIndexes(indexMetadataSnapshot, adapter = this.persistence.adapter) {
|
|
1270
2184
|
const collection = this.collection;
|
|
1271
2185
|
if (!collection && !indexMetadataSnapshot) {
|
|
1272
|
-
return;
|
|
2186
|
+
return /* @__PURE__ */ new Set();
|
|
1273
2187
|
}
|
|
1274
2188
|
const indexMetadata = indexMetadataSnapshot ?? collection?.getIndexMetadata() ?? [];
|
|
2189
|
+
const completedLocalIndexSignatures = /* @__PURE__ */ new Set();
|
|
2190
|
+
for (const metadata of indexMetadata) {
|
|
2191
|
+
if (await this.ensureLocalPersistedIndex(metadata, adapter)) {
|
|
2192
|
+
completedLocalIndexSignatures.add(metadata.signature);
|
|
2193
|
+
}
|
|
2194
|
+
}
|
|
2195
|
+
return completedLocalIndexSignatures;
|
|
2196
|
+
}
|
|
2197
|
+
async requestCoordinatorPersistedIndexes(indexMetadata, completedLocalIndexSignatures) {
|
|
1275
2198
|
for (const metadata of indexMetadata) {
|
|
1276
|
-
await this.
|
|
2199
|
+
await this.requestCoordinatorPersistedIndex(
|
|
2200
|
+
metadata,
|
|
2201
|
+
completedLocalIndexSignatures.has(metadata.signature)
|
|
2202
|
+
);
|
|
1277
2203
|
}
|
|
1278
2204
|
}
|
|
1279
2205
|
buildPersistedIndexSpec(index) {
|
|
@@ -1286,22 +2212,36 @@ class PersistedCollectionRuntime {
|
|
|
1286
2212
|
}
|
|
1287
2213
|
};
|
|
1288
2214
|
}
|
|
1289
|
-
async ensurePersistedIndex(indexMetadata) {
|
|
2215
|
+
async ensurePersistedIndex(indexMetadata, adapter = this.persistence.adapter) {
|
|
2216
|
+
const completedLocally = await this.ensureLocalPersistedIndex(
|
|
2217
|
+
indexMetadata,
|
|
2218
|
+
adapter
|
|
2219
|
+
);
|
|
2220
|
+
await this.requestCoordinatorPersistedIndex(indexMetadata, completedLocally);
|
|
2221
|
+
}
|
|
2222
|
+
async ensureLocalPersistedIndex(indexMetadata, adapter) {
|
|
1290
2223
|
const spec = this.buildPersistedIndexSpec(indexMetadata);
|
|
1291
2224
|
try {
|
|
1292
|
-
await
|
|
2225
|
+
await adapter.ensureIndex(
|
|
1293
2226
|
this.collectionId,
|
|
1294
2227
|
indexMetadata.signature,
|
|
1295
2228
|
spec
|
|
1296
2229
|
);
|
|
2230
|
+
return true;
|
|
1297
2231
|
} catch (error) {
|
|
1298
2232
|
console.warn(`Failed to ensure persisted index in adapter:`, error);
|
|
2233
|
+
return false;
|
|
1299
2234
|
}
|
|
2235
|
+
}
|
|
2236
|
+
async requestCoordinatorPersistedIndex(indexMetadata, completedLocally) {
|
|
2237
|
+
const spec = this.buildPersistedIndexSpec(indexMetadata);
|
|
1300
2238
|
try {
|
|
1301
2239
|
await this.persistence.coordinator.requestEnsurePersistedIndex(
|
|
1302
2240
|
this.collectionId,
|
|
1303
2241
|
indexMetadata.signature,
|
|
1304
|
-
spec
|
|
2242
|
+
spec,
|
|
2243
|
+
completedLocally ? this.persistence.adapter : void 0,
|
|
2244
|
+
completedLocally
|
|
1305
2245
|
);
|
|
1306
2246
|
} catch (error) {
|
|
1307
2247
|
console.warn(
|
|
@@ -1324,71 +2264,327 @@ class PersistedCollectionRuntime {
|
|
|
1324
2264
|
}
|
|
1325
2265
|
}
|
|
1326
2266
|
}
|
|
1327
|
-
function createWrappedSyncConfig(sourceSyncConfig, runtime) {
|
|
2267
|
+
function createWrappedSyncConfig(sourceSyncConfig, runtime, persistedReadiness) {
|
|
1328
2268
|
return {
|
|
1329
2269
|
...sourceSyncConfig,
|
|
1330
2270
|
sync: (params) => {
|
|
1331
2271
|
const transactionStack = [];
|
|
2272
|
+
const pendingPublicationTransactions = [];
|
|
1332
2273
|
const getOpenTransaction = () => transactionStack[transactionStack.length - 1];
|
|
2274
|
+
const settlePublicationAdmissionWaiters = (transaction, error) => {
|
|
2275
|
+
const waiters = transaction.publicationAdmissionWaiters;
|
|
2276
|
+
transaction.publicationAdmissionWaiters = void 0;
|
|
2277
|
+
if (!waiters) return;
|
|
2278
|
+
for (const waiter of waiters) {
|
|
2279
|
+
if (error === void 0) waiter.resolve();
|
|
2280
|
+
else waiter.reject(error);
|
|
2281
|
+
}
|
|
2282
|
+
};
|
|
2283
|
+
const bindToCurrentHydration = (transaction) => {
|
|
2284
|
+
if (transaction.internal || !runtime.isHydratingNow()) return;
|
|
2285
|
+
const hydrationSequence = runtime.getHydrationSequence();
|
|
2286
|
+
if (transaction.hydrationSequence !== void 0 && transaction.hydrationSequence !== hydrationSequence) {
|
|
2287
|
+
throw new InvalidPersistedCollectionConfigError(
|
|
2288
|
+
`a persisted sync transaction cannot cross a hydration cycle`
|
|
2289
|
+
);
|
|
2290
|
+
}
|
|
2291
|
+
transaction.queuedBecauseHydrating = true;
|
|
2292
|
+
transaction.hydrationSequence = hydrationSequence;
|
|
2293
|
+
transaction.hydrationContext = runtime.getActiveHydrationContext();
|
|
2294
|
+
};
|
|
2295
|
+
const assertHydrationSequenceCurrent = (transaction) => {
|
|
2296
|
+
if (transaction.hydrationSequence !== void 0 && transaction.hydrationSequence !== runtime.getHydrationSequence()) {
|
|
2297
|
+
throw new InvalidPersistedCollectionConfigError(
|
|
2298
|
+
`a persisted sync transaction cannot cross a hydration cycle`
|
|
2299
|
+
);
|
|
2300
|
+
}
|
|
2301
|
+
};
|
|
2302
|
+
const markPendingMetadataDependency = (transaction) => {
|
|
2303
|
+
const openTransaction = getOpenTransaction();
|
|
2304
|
+
if (openTransaction && !openTransaction.internal) {
|
|
2305
|
+
transaction.hasDependentSuccessor = true;
|
|
2306
|
+
}
|
|
2307
|
+
};
|
|
2308
|
+
const removePendingPublicationTransaction = (transaction) => {
|
|
2309
|
+
const index = pendingPublicationTransactions.indexOf(transaction);
|
|
2310
|
+
if (index !== -1) pendingPublicationTransactions.splice(index, 1);
|
|
2311
|
+
};
|
|
2312
|
+
const settlePendingTransaction = (transaction) => {
|
|
2313
|
+
removePendingPublicationTransaction(transaction);
|
|
2314
|
+
};
|
|
2315
|
+
const settleRuntimeTransaction = (transaction, applied) => {
|
|
2316
|
+
void applied.then(
|
|
2317
|
+
() => settlePendingTransaction(transaction),
|
|
2318
|
+
() => {
|
|
2319
|
+
settlePendingTransaction(transaction);
|
|
2320
|
+
}
|
|
2321
|
+
);
|
|
2322
|
+
};
|
|
2323
|
+
const getPendingRowMetadataWrite = (key) => {
|
|
2324
|
+
for (let index = pendingPublicationTransactions.length - 1; index >= 0; index--) {
|
|
2325
|
+
const transaction = pendingPublicationTransactions[index];
|
|
2326
|
+
const write = transaction.rowMetadataWrites.get(key);
|
|
2327
|
+
if (write) {
|
|
2328
|
+
markPendingMetadataDependency(transaction);
|
|
2329
|
+
return { found: true, write };
|
|
2330
|
+
}
|
|
2331
|
+
if (transaction.truncate) {
|
|
2332
|
+
markPendingMetadataDependency(transaction);
|
|
2333
|
+
return {
|
|
2334
|
+
found: true,
|
|
2335
|
+
write: { type: `delete` }
|
|
2336
|
+
};
|
|
2337
|
+
}
|
|
2338
|
+
}
|
|
2339
|
+
return { found: false };
|
|
2340
|
+
};
|
|
2341
|
+
const getPendingCollectionMetadataWrite = (key) => {
|
|
2342
|
+
for (let index = pendingPublicationTransactions.length - 1; index >= 0; index--) {
|
|
2343
|
+
const transaction = pendingPublicationTransactions[index];
|
|
2344
|
+
const write = transaction.collectionMetadataWrites.get(key);
|
|
2345
|
+
if (write) {
|
|
2346
|
+
markPendingMetadataDependency(transaction);
|
|
2347
|
+
return { found: true, write };
|
|
2348
|
+
}
|
|
2349
|
+
}
|
|
2350
|
+
return { found: false };
|
|
2351
|
+
};
|
|
1333
2352
|
let fullStartPromise = null;
|
|
2353
|
+
let sourceResultPromise = null;
|
|
2354
|
+
let resolveSourceResultAssigned;
|
|
2355
|
+
const sourceResultAssigned = new Promise((resolve) => {
|
|
2356
|
+
resolveSourceResultAssigned = resolve;
|
|
2357
|
+
});
|
|
1334
2358
|
const startupState = { cleanedUp: false };
|
|
2359
|
+
const isCleanedUp = () => startupState.cleanedUp;
|
|
1335
2360
|
const acquisitions = /* @__PURE__ */ new Map();
|
|
2361
|
+
const waitForPublicationAdmission = (transaction, options, acquisition) => {
|
|
2362
|
+
if (options.signal?.aborted) {
|
|
2363
|
+
return Promise.reject(
|
|
2364
|
+
options.signal.reason ?? new SyncTransactionAbortedError()
|
|
2365
|
+
);
|
|
2366
|
+
}
|
|
2367
|
+
return new Promise((resolve, reject) => {
|
|
2368
|
+
const waiters = transaction.publicationAdmissionWaiters ?? (transaction.publicationAdmissionWaiters = /* @__PURE__ */ new Set());
|
|
2369
|
+
let settled = false;
|
|
2370
|
+
const settle = (action) => {
|
|
2371
|
+
if (settled) return;
|
|
2372
|
+
settled = true;
|
|
2373
|
+
waiters.delete(waiter);
|
|
2374
|
+
options.signal?.removeEventListener(`abort`, abort);
|
|
2375
|
+
acquisition.cancelAdmissionWait = void 0;
|
|
2376
|
+
action();
|
|
2377
|
+
};
|
|
2378
|
+
const waiter = {
|
|
2379
|
+
resolve: () => settle(resolve),
|
|
2380
|
+
reject: (error) => settle(() => reject(error))
|
|
2381
|
+
};
|
|
2382
|
+
const abort = () => waiter.reject(
|
|
2383
|
+
options.signal?.reason ?? new SyncTransactionAbortedError()
|
|
2384
|
+
);
|
|
2385
|
+
waiters.add(waiter);
|
|
2386
|
+
options.signal?.addEventListener(`abort`, abort, { once: true });
|
|
2387
|
+
acquisition.cancelAdmissionWait = waiter.resolve;
|
|
2388
|
+
});
|
|
2389
|
+
};
|
|
2390
|
+
const getTerminalFailure = () => runtime.getCurrentTerminalFailure();
|
|
2391
|
+
const reportUpstreamError = (error) => {
|
|
2392
|
+
if (persistedReadiness?.getSnapshot().status !== `loading`) {
|
|
2393
|
+
runtime.reportSyncError(error);
|
|
2394
|
+
return;
|
|
2395
|
+
}
|
|
2396
|
+
void (fullStartPromise ?? runtime.ensureStarted()).then(
|
|
2397
|
+
() => {
|
|
2398
|
+
if (!isCleanedUp() && !getTerminalFailure()) {
|
|
2399
|
+
runtime.reportSyncError(error);
|
|
2400
|
+
}
|
|
2401
|
+
},
|
|
2402
|
+
() => void 0
|
|
2403
|
+
);
|
|
2404
|
+
};
|
|
2405
|
+
const createHandledRejection = (error) => {
|
|
2406
|
+
const rejected = Promise.reject(error);
|
|
2407
|
+
void rejected.catch(() => void 0);
|
|
2408
|
+
return rejected;
|
|
2409
|
+
};
|
|
2410
|
+
const applyTransactionToCollection = (transaction, signal) => {
|
|
2411
|
+
if (transaction.applicationReceipt !== void 0) {
|
|
2412
|
+
return transaction.applicationReceipt;
|
|
2413
|
+
}
|
|
2414
|
+
try {
|
|
2415
|
+
assertHydrationSequenceCurrent(transaction);
|
|
2416
|
+
for (const key of transaction.deferredHydrationMetadataDeleteKeys) {
|
|
2417
|
+
if (!transaction.hydrationContext?.suppliedRowKeys.has(key) && !transaction.rowMetadataWrites.has(key)) {
|
|
2418
|
+
transaction.rowMetadataWrites.set(key, { type: `delete` });
|
|
2419
|
+
}
|
|
2420
|
+
}
|
|
2421
|
+
transaction.deferredHydrationMetadataDeleteKeys.clear();
|
|
2422
|
+
params.begin(
|
|
2423
|
+
transaction.queuedBecauseHydrating ? { immediate: true } : transaction.beginOptions
|
|
2424
|
+
);
|
|
2425
|
+
if (transaction.truncate) params.truncate();
|
|
2426
|
+
for (const operation of transaction.operations) {
|
|
2427
|
+
if (operation.type === `delete`) {
|
|
2428
|
+
params.write({ type: `delete`, key: operation.key });
|
|
2429
|
+
} else {
|
|
2430
|
+
params.write({
|
|
2431
|
+
type: `update`,
|
|
2432
|
+
value: operation.value,
|
|
2433
|
+
metadata: operation.metadata
|
|
2434
|
+
});
|
|
2435
|
+
}
|
|
2436
|
+
}
|
|
2437
|
+
if (params.metadata) {
|
|
2438
|
+
for (const [key, write] of transaction.rowMetadataWrites) {
|
|
2439
|
+
if (write.type === `delete`) params.metadata.row.delete(key);
|
|
2440
|
+
else params.metadata.row.set(key, write.value);
|
|
2441
|
+
}
|
|
2442
|
+
for (const [key, write] of transaction.collectionMetadataWrites) {
|
|
2443
|
+
if (write.type === `delete`)
|
|
2444
|
+
params.metadata.collection.delete(key);
|
|
2445
|
+
else params.metadata.collection.set(key, write.value);
|
|
2446
|
+
}
|
|
2447
|
+
}
|
|
2448
|
+
const applied = params.commit(signal);
|
|
2449
|
+
transaction.applicationReceipt = applied;
|
|
2450
|
+
if (applied === true) {
|
|
2451
|
+
removePendingPublicationTransaction(transaction);
|
|
2452
|
+
} else {
|
|
2453
|
+
void applied.then(
|
|
2454
|
+
() => removePendingPublicationTransaction(transaction),
|
|
2455
|
+
() => removePendingPublicationTransaction(transaction)
|
|
2456
|
+
);
|
|
2457
|
+
}
|
|
2458
|
+
return applied;
|
|
2459
|
+
} catch (error) {
|
|
2460
|
+
removePendingPublicationTransaction(transaction);
|
|
2461
|
+
throw error;
|
|
2462
|
+
}
|
|
2463
|
+
};
|
|
1336
2464
|
runtime.setSyncControls({
|
|
1337
2465
|
begin: params.begin,
|
|
1338
2466
|
write: params.write,
|
|
1339
2467
|
commit: params.commit,
|
|
1340
2468
|
truncate: params.truncate,
|
|
1341
|
-
metadata: params.metadata ?? null
|
|
2469
|
+
metadata: params.metadata ?? null,
|
|
2470
|
+
markError: params.markError
|
|
1342
2471
|
});
|
|
1343
2472
|
runtime.setCollection(
|
|
1344
2473
|
params.collection
|
|
1345
2474
|
);
|
|
1346
|
-
const
|
|
1347
|
-
|
|
1348
|
-
|
|
2475
|
+
const persistenceCapability = {
|
|
2476
|
+
protocol: SYNC_PERSISTENCE_PROTOCOL,
|
|
2477
|
+
version: SYNC_PERSISTENCE_VERSION,
|
|
2478
|
+
hydrateBaseline: async () => {
|
|
1349
2479
|
if (startupState.cleanedUp) return;
|
|
1350
|
-
|
|
2480
|
+
try {
|
|
2481
|
+
await runtime.ensureResumeBaselineHydrated();
|
|
2482
|
+
} catch (error) {
|
|
2483
|
+
throw runtime.reportSyncError(error);
|
|
2484
|
+
}
|
|
2485
|
+
},
|
|
2486
|
+
scanPersistedRows: (options) => startupState.cleanedUp ? Promise.resolve([]) : runtime.scanPersistedRows(options),
|
|
2487
|
+
resumeSnapshot: {
|
|
2488
|
+
certify: async () => {
|
|
1351
2489
|
if (startupState.cleanedUp) return;
|
|
1352
|
-
|
|
1353
|
-
|
|
2490
|
+
try {
|
|
2491
|
+
await runtime.ensureResumeBaselineCertified();
|
|
2492
|
+
} catch (error) {
|
|
2493
|
+
throw runtime.reportSyncError(error);
|
|
2494
|
+
}
|
|
2495
|
+
},
|
|
2496
|
+
getKeySetEvidence: () => startupState.cleanedUp ? void 0 : runtime.getKeySetEvidence(),
|
|
2497
|
+
expectCurrentCommit: () => {
|
|
1354
2498
|
if (startupState.cleanedUp) return;
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
2499
|
+
const openTransaction = getOpenTransaction();
|
|
2500
|
+
if (!openTransaction) {
|
|
2501
|
+
throw new InvalidPersistedCollectionConfigError(
|
|
2502
|
+
`resumeSnapshot.expectCurrentCommit must be called within an open sync transaction`
|
|
2503
|
+
);
|
|
2504
|
+
}
|
|
2505
|
+
openTransaction.expectedResumeGenerationOwner = runtime.getResumeGenerationOwner();
|
|
2506
|
+
}
|
|
2507
|
+
}
|
|
2508
|
+
};
|
|
2509
|
+
const wrappedParams = {
|
|
2510
|
+
...params,
|
|
2511
|
+
markError: reportUpstreamError,
|
|
2512
|
+
markReady: () => {
|
|
2513
|
+
if (startupState.cleanedUp || getTerminalFailure()) return;
|
|
2514
|
+
void (fullStartPromise ?? runtime.ensureStarted()).then(async () => {
|
|
2515
|
+
if (isCleanedUp() || getTerminalFailure()) return;
|
|
2516
|
+
await sourceResultAssigned;
|
|
2517
|
+
try {
|
|
2518
|
+
await sourceResultPromise;
|
|
2519
|
+
} catch (error) {
|
|
2520
|
+
reportUpstreamError(error);
|
|
2521
|
+
return;
|
|
2522
|
+
}
|
|
2523
|
+
if (isCleanedUp() || getTerminalFailure()) return;
|
|
1359
2524
|
params.markReady();
|
|
1360
|
-
});
|
|
2525
|
+
}).catch(() => void 0);
|
|
1361
2526
|
},
|
|
1362
2527
|
begin: (options) => {
|
|
1363
|
-
if (startupState.cleanedUp) return;
|
|
2528
|
+
if (startupState.cleanedUp) return void 0;
|
|
2529
|
+
const terminalFailure = getTerminalFailure();
|
|
2530
|
+
const internal = runtime.isApplyingInternally();
|
|
1364
2531
|
const transaction = {
|
|
1365
2532
|
operations: [],
|
|
2533
|
+
partialUpdateOperationIndexes: /* @__PURE__ */ new Set(),
|
|
1366
2534
|
rowMetadataWrites: /* @__PURE__ */ new Map(),
|
|
1367
2535
|
collectionMetadataWrites: /* @__PURE__ */ new Map(),
|
|
2536
|
+
deferredHydrationMetadataDeleteKeys: /* @__PURE__ */ new Set(),
|
|
1368
2537
|
truncate: false,
|
|
1369
|
-
internal
|
|
1370
|
-
|
|
2538
|
+
internal,
|
|
2539
|
+
lifecycleGeneration: runtime.getLifecycleGeneration(),
|
|
2540
|
+
beginOptions: options,
|
|
2541
|
+
operationKeys: /* @__PURE__ */ new Set(),
|
|
2542
|
+
hasDependentSuccessor: false,
|
|
2543
|
+
queuedBecauseHydrating: terminalFailure === void 0 && !internal && runtime.isHydratingNow(),
|
|
2544
|
+
hydrationSequence: terminalFailure === void 0 && !internal ? runtime.getHydrationSequence() : void 0,
|
|
2545
|
+
hydrationContext: terminalFailure === void 0 && !internal && runtime.isHydratingNow() ? runtime.getActiveHydrationContext() : void 0,
|
|
2546
|
+
...terminalFailure === void 0 ? {} : { terminalFailure }
|
|
1371
2547
|
};
|
|
1372
2548
|
transactionStack.push(transaction);
|
|
1373
|
-
if (!transaction.queuedBecauseHydrating) {
|
|
1374
|
-
params.begin(options);
|
|
1375
|
-
}
|
|
1376
2549
|
},
|
|
1377
2550
|
write: (message) => {
|
|
1378
2551
|
if (startupState.cleanedUp) return;
|
|
1379
|
-
const normalization = runtime.normalizeSyncWriteMessage(message);
|
|
1380
2552
|
const openTransaction = getOpenTransaction();
|
|
1381
|
-
|
|
1382
|
-
|
|
2553
|
+
const terminalFailure = openTransaction?.terminalFailure ?? getTerminalFailure();
|
|
2554
|
+
if (terminalFailure) {
|
|
2555
|
+
if (openTransaction)
|
|
2556
|
+
openTransaction.terminalFailure = terminalFailure;
|
|
1383
2557
|
return;
|
|
1384
2558
|
}
|
|
2559
|
+
if (!openTransaction) {
|
|
2560
|
+
throw new NoPendingSyncTransactionWriteError();
|
|
2561
|
+
}
|
|
2562
|
+
bindToCurrentHydration(openTransaction);
|
|
2563
|
+
const normalization = runtime.normalizeSyncWriteMessage(message);
|
|
2564
|
+
if (message.type === `update` && sourceSyncConfig.rowUpdateMode !== `full`) {
|
|
2565
|
+
if (!openTransaction.internal) {
|
|
2566
|
+
openTransaction.partialUpdateOperationIndexes.add(
|
|
2567
|
+
openTransaction.operations.length
|
|
2568
|
+
);
|
|
2569
|
+
}
|
|
2570
|
+
for (const pending of pendingPublicationTransactions) {
|
|
2571
|
+
if (pending.truncate || pending.operationKeys.has(normalization.operation.key)) {
|
|
2572
|
+
markPendingMetadataDependency(pending);
|
|
2573
|
+
}
|
|
2574
|
+
}
|
|
2575
|
+
}
|
|
1385
2576
|
openTransaction.operations.push(normalization.operation);
|
|
2577
|
+
openTransaction.operationKeys.add(normalization.operation.key);
|
|
1386
2578
|
if (normalization.operation.type === `delete`) {
|
|
1387
2579
|
openTransaction.rowMetadataWrites.set(normalization.operation.key, {
|
|
1388
2580
|
type: `delete`
|
|
1389
2581
|
});
|
|
1390
2582
|
} else if (message.type === `insert` && normalization.operation.metadata === void 0) {
|
|
1391
|
-
if (
|
|
2583
|
+
if (openTransaction.queuedBecauseHydrating) {
|
|
2584
|
+
openTransaction.deferredHydrationMetadataDeleteKeys.add(
|
|
2585
|
+
normalization.operation.key
|
|
2586
|
+
);
|
|
2587
|
+
} else if (!openTransaction.rowMetadataWrites.has(
|
|
1392
2588
|
normalization.operation.key
|
|
1393
2589
|
)) {
|
|
1394
2590
|
openTransaction.rowMetadataWrites.set(
|
|
@@ -1404,13 +2600,10 @@ function createWrappedSyncConfig(sourceSyncConfig, runtime) {
|
|
|
1404
2600
|
value: normalization.operation.metadata
|
|
1405
2601
|
});
|
|
1406
2602
|
}
|
|
1407
|
-
if (!openTransaction.queuedBecauseHydrating) {
|
|
1408
|
-
params.write(normalization.forwardMessage);
|
|
1409
|
-
}
|
|
1410
2603
|
},
|
|
1411
2604
|
metadata: params.metadata ? {
|
|
2605
|
+
persistence: persistenceCapability,
|
|
1412
2606
|
row: {
|
|
1413
|
-
whenHydrated: () => startupState.cleanedUp ? Promise.resolve() : runtime.ensureResumeBaselineHydrated(),
|
|
1414
2607
|
get: (key) => {
|
|
1415
2608
|
if (startupState.cleanedUp) return void 0;
|
|
1416
2609
|
const openTransaction = getOpenTransaction();
|
|
@@ -1421,12 +2614,17 @@ function createWrappedSyncConfig(sourceSyncConfig, runtime) {
|
|
|
1421
2614
|
if (openTransaction?.truncate) {
|
|
1422
2615
|
return void 0;
|
|
1423
2616
|
}
|
|
2617
|
+
const pending = getPendingRowMetadataWrite(key);
|
|
2618
|
+
if (pending.found) {
|
|
2619
|
+
return pending.write.type === `delete` ? void 0 : pending.write.value;
|
|
2620
|
+
}
|
|
1424
2621
|
return params.metadata.row.get(key);
|
|
1425
2622
|
},
|
|
1426
|
-
scanPersisted: (options) => startupState.cleanedUp ? Promise.resolve([]) : runtime.scanPersistedRows(options),
|
|
1427
2623
|
set: (key, value) => {
|
|
1428
2624
|
if (startupState.cleanedUp) return;
|
|
1429
2625
|
const openTransaction = getOpenTransaction();
|
|
2626
|
+
if (openTransaction?.terminalFailure ?? getTerminalFailure())
|
|
2627
|
+
return;
|
|
1430
2628
|
if (!openTransaction) {
|
|
1431
2629
|
throw new InvalidPersistedCollectionConfigError(
|
|
1432
2630
|
`metadata.row.set must be called within an open sync transaction`
|
|
@@ -1436,13 +2634,12 @@ function createWrappedSyncConfig(sourceSyncConfig, runtime) {
|
|
|
1436
2634
|
type: `set`,
|
|
1437
2635
|
value
|
|
1438
2636
|
});
|
|
1439
|
-
if (!openTransaction.queuedBecauseHydrating) {
|
|
1440
|
-
params.metadata.row.set(key, value);
|
|
1441
|
-
}
|
|
1442
2637
|
},
|
|
1443
2638
|
delete: (key) => {
|
|
1444
2639
|
if (startupState.cleanedUp) return;
|
|
1445
2640
|
const openTransaction = getOpenTransaction();
|
|
2641
|
+
if (openTransaction?.terminalFailure ?? getTerminalFailure())
|
|
2642
|
+
return;
|
|
1446
2643
|
if (!openTransaction) {
|
|
1447
2644
|
throw new InvalidPersistedCollectionConfigError(
|
|
1448
2645
|
`metadata.row.delete must be called within an open sync transaction`
|
|
@@ -1451,9 +2648,6 @@ function createWrappedSyncConfig(sourceSyncConfig, runtime) {
|
|
|
1451
2648
|
openTransaction.rowMetadataWrites.set(key, {
|
|
1452
2649
|
type: `delete`
|
|
1453
2650
|
});
|
|
1454
|
-
if (!openTransaction.queuedBecauseHydrating) {
|
|
1455
|
-
params.metadata.row.delete(key);
|
|
1456
|
-
}
|
|
1457
2651
|
}
|
|
1458
2652
|
},
|
|
1459
2653
|
collection: {
|
|
@@ -1464,11 +2658,17 @@ function createWrappedSyncConfig(sourceSyncConfig, runtime) {
|
|
|
1464
2658
|
if (pendingWrite) {
|
|
1465
2659
|
return pendingWrite.type === `delete` ? void 0 : pendingWrite.value;
|
|
1466
2660
|
}
|
|
2661
|
+
const pending = getPendingCollectionMetadataWrite(key);
|
|
2662
|
+
if (pending.found) {
|
|
2663
|
+
return pending.write.type === `delete` ? void 0 : pending.write.value;
|
|
2664
|
+
}
|
|
1467
2665
|
return params.metadata.collection.get(key);
|
|
1468
2666
|
},
|
|
1469
2667
|
set: (key, value) => {
|
|
1470
2668
|
if (startupState.cleanedUp) return;
|
|
1471
2669
|
const openTransaction = getOpenTransaction();
|
|
2670
|
+
if (openTransaction?.terminalFailure ?? getTerminalFailure())
|
|
2671
|
+
return;
|
|
1472
2672
|
if (!openTransaction) {
|
|
1473
2673
|
throw new InvalidPersistedCollectionConfigError(
|
|
1474
2674
|
`metadata.collection.set must be called within an open sync transaction`
|
|
@@ -1478,13 +2678,12 @@ function createWrappedSyncConfig(sourceSyncConfig, runtime) {
|
|
|
1478
2678
|
type: `set`,
|
|
1479
2679
|
value
|
|
1480
2680
|
});
|
|
1481
|
-
if (!openTransaction.queuedBecauseHydrating) {
|
|
1482
|
-
params.metadata.collection.set(key, value);
|
|
1483
|
-
}
|
|
1484
2681
|
},
|
|
1485
2682
|
delete: (key) => {
|
|
1486
2683
|
if (startupState.cleanedUp) return;
|
|
1487
2684
|
const openTransaction = getOpenTransaction();
|
|
2685
|
+
if (openTransaction?.terminalFailure ?? getTerminalFailure())
|
|
2686
|
+
return;
|
|
1488
2687
|
if (!openTransaction) {
|
|
1489
2688
|
throw new InvalidPersistedCollectionConfigError(
|
|
1490
2689
|
`metadata.collection.delete must be called within an open sync transaction`
|
|
@@ -1493,15 +2692,31 @@ function createWrappedSyncConfig(sourceSyncConfig, runtime) {
|
|
|
1493
2692
|
openTransaction.collectionMetadataWrites.set(key, {
|
|
1494
2693
|
type: `delete`
|
|
1495
2694
|
});
|
|
1496
|
-
if (!openTransaction.queuedBecauseHydrating) {
|
|
1497
|
-
params.metadata.collection.delete(key);
|
|
1498
|
-
}
|
|
1499
2695
|
},
|
|
1500
2696
|
list: (prefix) => {
|
|
1501
2697
|
if (startupState.cleanedUp) return [];
|
|
1502
2698
|
const merged = new Map(
|
|
1503
2699
|
params.metadata.collection.list().map(({ key, value }) => [key, value])
|
|
1504
2700
|
);
|
|
2701
|
+
const pendingOwners = /* @__PURE__ */ new Map();
|
|
2702
|
+
for (const transaction of pendingPublicationTransactions) {
|
|
2703
|
+
for (const [
|
|
2704
|
+
key,
|
|
2705
|
+
metadataWrite
|
|
2706
|
+
] of transaction.collectionMetadataWrites) {
|
|
2707
|
+
if (!prefix || key.startsWith(prefix)) {
|
|
2708
|
+
pendingOwners.set(key, transaction);
|
|
2709
|
+
}
|
|
2710
|
+
if (metadataWrite.type === `delete`) {
|
|
2711
|
+
merged.delete(key);
|
|
2712
|
+
} else {
|
|
2713
|
+
merged.set(key, metadataWrite.value);
|
|
2714
|
+
}
|
|
2715
|
+
}
|
|
2716
|
+
}
|
|
2717
|
+
for (const owner of new Set(pendingOwners.values())) {
|
|
2718
|
+
markPendingMetadataDependency(owner);
|
|
2719
|
+
}
|
|
1505
2720
|
const openTransaction = getOpenTransaction();
|
|
1506
2721
|
if (openTransaction) {
|
|
1507
2722
|
for (const [
|
|
@@ -1524,30 +2739,91 @@ function createWrappedSyncConfig(sourceSyncConfig, runtime) {
|
|
|
1524
2739
|
} : void 0,
|
|
1525
2740
|
truncate: () => {
|
|
1526
2741
|
if (startupState.cleanedUp) return;
|
|
2742
|
+
runtime.noteSourceTruncate();
|
|
1527
2743
|
const openTransaction = getOpenTransaction();
|
|
2744
|
+
if (openTransaction?.terminalFailure ?? getTerminalFailure()) return;
|
|
1528
2745
|
if (!openTransaction) {
|
|
1529
|
-
|
|
1530
|
-
return;
|
|
2746
|
+
throw new NoPendingSyncTransactionWriteError();
|
|
1531
2747
|
}
|
|
1532
2748
|
openTransaction.operations = [];
|
|
2749
|
+
openTransaction.operationKeys.clear();
|
|
2750
|
+
openTransaction.partialUpdateOperationIndexes.clear();
|
|
1533
2751
|
openTransaction.rowMetadataWrites.clear();
|
|
2752
|
+
openTransaction.deferredHydrationMetadataDeleteKeys.clear();
|
|
1534
2753
|
openTransaction.truncate = true;
|
|
1535
|
-
if (!openTransaction.queuedBecauseHydrating) {
|
|
1536
|
-
params.truncate();
|
|
1537
|
-
}
|
|
1538
2754
|
},
|
|
1539
2755
|
commit: (signal) => {
|
|
1540
2756
|
if (startupState.cleanedUp) return true;
|
|
1541
2757
|
const openTransaction = transactionStack.pop();
|
|
2758
|
+
const terminalFailure = openTransaction?.terminalFailure ?? getTerminalFailure();
|
|
2759
|
+
if (terminalFailure) {
|
|
2760
|
+
if (openTransaction) {
|
|
2761
|
+
settlePendingTransaction(openTransaction);
|
|
2762
|
+
settlePublicationAdmissionWaiters(
|
|
2763
|
+
openTransaction,
|
|
2764
|
+
terminalFailure.error
|
|
2765
|
+
);
|
|
2766
|
+
}
|
|
2767
|
+
return createHandledRejection(terminalFailure.error);
|
|
2768
|
+
}
|
|
1542
2769
|
if (!openTransaction) {
|
|
1543
|
-
|
|
2770
|
+
throw new NoPendingSyncTransactionCommitError();
|
|
1544
2771
|
}
|
|
1545
|
-
if (openTransaction.
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
2772
|
+
if (openTransaction.internal) {
|
|
2773
|
+
return applyTransactionToCollection(openTransaction, signal);
|
|
2774
|
+
}
|
|
2775
|
+
if (signal?.aborted) {
|
|
2776
|
+
settlePendingTransaction(openTransaction);
|
|
2777
|
+
const error = new SyncTransactionAbortedError();
|
|
2778
|
+
settlePublicationAdmissionWaiters(openTransaction, error);
|
|
2779
|
+
return createHandledRejection(error);
|
|
2780
|
+
}
|
|
2781
|
+
try {
|
|
2782
|
+
bindToCurrentHydration(openTransaction);
|
|
2783
|
+
assertHydrationSequenceCurrent(openTransaction);
|
|
2784
|
+
} catch (error) {
|
|
2785
|
+
settlePublicationAdmissionWaiters(openTransaction, error);
|
|
2786
|
+
throw error;
|
|
2787
|
+
}
|
|
2788
|
+
const transaction = {
|
|
2789
|
+
operations: openTransaction.operations,
|
|
2790
|
+
partialUpdateOperationIndexes: openTransaction.partialUpdateOperationIndexes,
|
|
2791
|
+
rowMetadataWrites: openTransaction.rowMetadataWrites,
|
|
2792
|
+
collectionMetadataWrites: openTransaction.collectionMetadataWrites,
|
|
2793
|
+
deferredHydrationMetadataDeleteKeys: openTransaction.deferredHydrationMetadataDeleteKeys,
|
|
2794
|
+
hydrationContext: openTransaction.hydrationContext,
|
|
2795
|
+
hydrationSequence: openTransaction.hydrationSequence,
|
|
2796
|
+
truncate: openTransaction.truncate,
|
|
2797
|
+
internal: false,
|
|
2798
|
+
lifecycleGeneration: openTransaction.lifecycleGeneration,
|
|
2799
|
+
beginOptions: openTransaction.beginOptions,
|
|
2800
|
+
expectedResumeGenerationOwner: openTransaction.expectedResumeGenerationOwner,
|
|
2801
|
+
signal,
|
|
2802
|
+
prependHydrationRows: (rows) => {
|
|
2803
|
+
const baselineOperations = rows.map(({ key, value, metadata }) => ({
|
|
2804
|
+
type: `update`,
|
|
2805
|
+
key,
|
|
2806
|
+
value,
|
|
2807
|
+
metadata
|
|
2808
|
+
}));
|
|
2809
|
+
openTransaction.operations = baselineOperations.concat(
|
|
2810
|
+
openTransaction.operations
|
|
2811
|
+
);
|
|
2812
|
+
for (const { key, metadata } of rows) {
|
|
2813
|
+
if (metadata !== void 0 && !openTransaction.rowMetadataWrites.has(key)) {
|
|
2814
|
+
openTransaction.rowMetadataWrites.set(key, {
|
|
2815
|
+
type: `set`,
|
|
2816
|
+
value: metadata
|
|
2817
|
+
});
|
|
2818
|
+
}
|
|
2819
|
+
}
|
|
2820
|
+
},
|
|
2821
|
+
applyToCollection: () => applyTransactionToCollection(openTransaction, signal),
|
|
2822
|
+
shouldFailStopOnAbort: () => openTransaction.hasDependentSuccessor
|
|
2823
|
+
};
|
|
2824
|
+
openTransaction.signal = signal;
|
|
2825
|
+
pendingPublicationTransactions.push(openTransaction);
|
|
2826
|
+
if (openTransaction.queuedBecauseHydrating && runtime.isHydratingNow()) {
|
|
1551
2827
|
let resolveApplied;
|
|
1552
2828
|
let rejectApplied;
|
|
1553
2829
|
const applied2 = new Promise((resolve, reject) => {
|
|
@@ -1556,65 +2832,118 @@ function createWrappedSyncConfig(sourceSyncConfig, runtime) {
|
|
|
1556
2832
|
});
|
|
1557
2833
|
void applied2.catch(() => void 0);
|
|
1558
2834
|
runtime.queueHydrationBufferedTransaction({
|
|
1559
|
-
|
|
1560
|
-
rowMetadataWrites: openTransaction.rowMetadataWrites,
|
|
1561
|
-
collectionMetadataWrites: openTransaction.collectionMetadataWrites,
|
|
1562
|
-
truncate: openTransaction.truncate,
|
|
1563
|
-
internal: openTransaction.internal,
|
|
1564
|
-
signal,
|
|
2835
|
+
...transaction,
|
|
1565
2836
|
resolveApplied,
|
|
1566
2837
|
rejectApplied
|
|
1567
2838
|
});
|
|
2839
|
+
settlePublicationAdmissionWaiters(openTransaction);
|
|
2840
|
+
settleRuntimeTransaction(openTransaction, applied2);
|
|
1568
2841
|
return applied2;
|
|
1569
2842
|
}
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
rowMetadataWrites: openTransaction.rowMetadataWrites,
|
|
1577
|
-
collectionMetadataWrites: openTransaction.collectionMetadataWrites,
|
|
1578
|
-
truncate: openTransaction.truncate,
|
|
1579
|
-
internal: false
|
|
1580
|
-
});
|
|
1581
|
-
};
|
|
1582
|
-
const persisted = persistAfterApplication();
|
|
1583
|
-
void persisted.catch(() => void 0);
|
|
1584
|
-
return persisted;
|
|
2843
|
+
let applied;
|
|
2844
|
+
try {
|
|
2845
|
+
applied = runtime.applyHydrationBufferedTransaction(transaction);
|
|
2846
|
+
} catch (error) {
|
|
2847
|
+
settlePublicationAdmissionWaiters(openTransaction, error);
|
|
2848
|
+
throw error;
|
|
1585
2849
|
}
|
|
2850
|
+
settlePublicationAdmissionWaiters(openTransaction);
|
|
2851
|
+
settleRuntimeTransaction(openTransaction, applied);
|
|
1586
2852
|
return applied;
|
|
1587
2853
|
}
|
|
1588
2854
|
};
|
|
1589
2855
|
let sourceResult = {};
|
|
2856
|
+
let sourceResultSettled = false;
|
|
2857
|
+
let activeSourceSyncEntry;
|
|
1590
2858
|
fullStartPromise = runtime.ensureStarted();
|
|
1591
|
-
|
|
2859
|
+
void fullStartPromise.catch(() => void 0);
|
|
2860
|
+
sourceResultPromise = (async () => {
|
|
1592
2861
|
await runtime.ensureStartupMetadataLoaded();
|
|
1593
2862
|
if (startupState.cleanedUp) {
|
|
2863
|
+
sourceResultSettled = true;
|
|
1594
2864
|
return sourceResult;
|
|
1595
2865
|
}
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
2866
|
+
let settleSourceSyncEntry;
|
|
2867
|
+
activeSourceSyncEntry = new Promise((resolve) => {
|
|
2868
|
+
settleSourceSyncEntry = resolve;
|
|
2869
|
+
});
|
|
2870
|
+
try {
|
|
2871
|
+
sourceResult = normalizeSyncFnResult(
|
|
2872
|
+
sourceSyncConfig.sync(wrappedParams)
|
|
2873
|
+
);
|
|
2874
|
+
} finally {
|
|
2875
|
+
activeSourceSyncEntry = void 0;
|
|
2876
|
+
settleSourceSyncEntry();
|
|
2877
|
+
}
|
|
2878
|
+
if (!startupState.cleanedUp && sourceResult.loadSubset) {
|
|
2879
|
+
const loadSubset = async (options) => {
|
|
2880
|
+
if (startupState.cleanedUp) {
|
|
2881
|
+
throw new Error(`persisted sync source is no longer active`);
|
|
2882
|
+
}
|
|
2883
|
+
await sourceResult.loadSubset?.(
|
|
2884
|
+
options
|
|
2885
|
+
);
|
|
2886
|
+
};
|
|
2887
|
+
runtime.registerRemoteSubsetOwner(
|
|
2888
|
+
Object.assign(loadSubset, {
|
|
2889
|
+
unloadSubset: (options) => sourceResult.unloadSubset?.(
|
|
2890
|
+
options
|
|
2891
|
+
),
|
|
2892
|
+
onError: reportUpstreamError
|
|
2893
|
+
})
|
|
2894
|
+
);
|
|
2895
|
+
}
|
|
2896
|
+
sourceResultSettled = true;
|
|
1599
2897
|
return sourceResult;
|
|
1600
2898
|
})();
|
|
2899
|
+
resolveSourceResultAssigned();
|
|
2900
|
+
void sourceResultPromise.catch((error) => {
|
|
2901
|
+
reportUpstreamError(error);
|
|
2902
|
+
});
|
|
1601
2903
|
return {
|
|
1602
|
-
cleanup: () => {
|
|
2904
|
+
cleanup: async () => {
|
|
1603
2905
|
startupState.cleanedUp = true;
|
|
2906
|
+
const sourceSyncEntry = activeSourceSyncEntry;
|
|
2907
|
+
const cleanupError = new SyncTransactionAbortedError();
|
|
2908
|
+
for (const acquisition of acquisitions.values()) {
|
|
2909
|
+
acquisition.cancelAdmissionWait?.();
|
|
2910
|
+
}
|
|
1604
2911
|
acquisitions.clear();
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
2912
|
+
pendingPublicationTransactions.length = 0;
|
|
2913
|
+
for (const transaction of transactionStack) {
|
|
2914
|
+
settlePublicationAdmissionWaiters(transaction, cleanupError);
|
|
2915
|
+
}
|
|
2916
|
+
transactionStack.length = 0;
|
|
2917
|
+
let sourceCleanup;
|
|
2918
|
+
try {
|
|
2919
|
+
sourceCleanup = sourceSyncEntry ? sourceSyncEntry.then(() => sourceResult.cleanup?.()) : Promise.resolve(sourceResult.cleanup?.());
|
|
2920
|
+
} catch (error) {
|
|
2921
|
+
sourceCleanup = Promise.reject(error);
|
|
2922
|
+
}
|
|
2923
|
+
let runtimeCleanup;
|
|
2924
|
+
try {
|
|
2925
|
+
runtimeCleanup = runtime.cleanup();
|
|
2926
|
+
} catch (error) {
|
|
2927
|
+
runtimeCleanup = Promise.reject(error);
|
|
2928
|
+
}
|
|
2929
|
+
const [sourceOutcome, runtimeOutcome] = await Promise.allSettled([
|
|
2930
|
+
sourceCleanup,
|
|
2931
|
+
runtimeCleanup
|
|
2932
|
+
]);
|
|
2933
|
+
if (sourceOutcome.status === `rejected` && runtimeOutcome.status === `rejected`) {
|
|
2934
|
+
throw new AggregateError(
|
|
2935
|
+
[sourceOutcome.reason, runtimeOutcome.reason],
|
|
2936
|
+
`Source cleanup and persistence runtime teardown both failed`,
|
|
2937
|
+
{ cause: sourceOutcome.reason }
|
|
2938
|
+
);
|
|
2939
|
+
}
|
|
2940
|
+
if (sourceOutcome.status === `rejected`) throw sourceOutcome.reason;
|
|
2941
|
+
if (runtimeOutcome.status === `rejected`) throw runtimeOutcome.reason;
|
|
1608
2942
|
},
|
|
1609
|
-
loadSubset:
|
|
2943
|
+
loadSubset: (options) => {
|
|
1610
2944
|
const acquisition = { forwarded: false };
|
|
1611
2945
|
acquisitions.set(options, acquisition);
|
|
1612
|
-
|
|
1613
|
-
const resolvedSourceResult = await sourceResultPromise;
|
|
1614
|
-
if (startupState.cleanedUp || acquisitions.get(options) !== acquisition) {
|
|
1615
|
-
return;
|
|
1616
|
-
}
|
|
1617
|
-
return runtime.loadSubset(options, (loadOptions) => {
|
|
2946
|
+
const forwardUpstream = (resolvedSourceResult, loadOptions) => {
|
|
1618
2947
|
if (startupState.cleanedUp || acquisitions.get(options) !== acquisition) {
|
|
1619
2948
|
return true;
|
|
1620
2949
|
}
|
|
@@ -1626,10 +2955,42 @@ function createWrappedSyncConfig(sourceSyncConfig, runtime) {
|
|
|
1626
2955
|
acquisition.forwarded = false;
|
|
1627
2956
|
throw error;
|
|
1628
2957
|
}
|
|
1629
|
-
}
|
|
2958
|
+
};
|
|
2959
|
+
const openTransaction = getOpenTransaction();
|
|
2960
|
+
const needsPublicationAdmission = openTransaction && !openTransaction.internal && openTransaction.beginOptions?.immediate;
|
|
2961
|
+
const hydrated = sourceResultSettled && !needsPublicationAdmission ? runtime.loadHydratedSubset(
|
|
2962
|
+
options,
|
|
2963
|
+
(loadOptions) => forwardUpstream(sourceResult, loadOptions)
|
|
2964
|
+
) : void 0;
|
|
2965
|
+
if (hydrated !== void 0) {
|
|
2966
|
+
return hydrated;
|
|
2967
|
+
}
|
|
2968
|
+
return (async () => {
|
|
2969
|
+
await fullStartPromise;
|
|
2970
|
+
const resolvedSourceResult = await sourceResultPromise;
|
|
2971
|
+
if (startupState.cleanedUp || acquisitions.get(options) !== acquisition) {
|
|
2972
|
+
return;
|
|
2973
|
+
}
|
|
2974
|
+
const currentTransaction = getOpenTransaction();
|
|
2975
|
+
if (currentTransaction && !currentTransaction.internal && currentTransaction.beginOptions?.immediate) {
|
|
2976
|
+
await waitForPublicationAdmission(
|
|
2977
|
+
currentTransaction,
|
|
2978
|
+
options,
|
|
2979
|
+
acquisition
|
|
2980
|
+
);
|
|
2981
|
+
}
|
|
2982
|
+
if (options.signal?.aborted || acquisitions.get(options) !== acquisition) {
|
|
2983
|
+
return;
|
|
2984
|
+
}
|
|
2985
|
+
return runtime.loadSubset(
|
|
2986
|
+
options,
|
|
2987
|
+
(loadOptions) => forwardUpstream(resolvedSourceResult, loadOptions)
|
|
2988
|
+
);
|
|
2989
|
+
})();
|
|
1630
2990
|
},
|
|
1631
2991
|
unloadSubset: (options) => {
|
|
1632
2992
|
const acquisition = acquisitions.get(options);
|
|
2993
|
+
acquisition?.cancelAdmissionWait?.();
|
|
1633
2994
|
acquisitions.delete(options);
|
|
1634
2995
|
runtime.unloadSubset(
|
|
1635
2996
|
options,
|
|
@@ -1648,23 +3009,19 @@ function createLoopbackSyncConfig(runtime) {
|
|
|
1648
3009
|
write: params.write,
|
|
1649
3010
|
commit: params.commit,
|
|
1650
3011
|
truncate: params.truncate,
|
|
1651
|
-
metadata: params.metadata ?? null
|
|
3012
|
+
metadata: params.metadata ?? null,
|
|
3013
|
+
markError: params.markError
|
|
1652
3014
|
});
|
|
1653
3015
|
runtime.setCollection(
|
|
1654
3016
|
params.collection
|
|
1655
3017
|
);
|
|
1656
3018
|
void runtime.ensureStarted().then(() => {
|
|
3019
|
+
if (runtime.getCurrentTerminalFailure()) return;
|
|
1657
3020
|
params.markReady();
|
|
1658
|
-
}).catch((
|
|
1659
|
-
console.warn(`Failed persisted loopback startup:`, error);
|
|
1660
|
-
params.markReady();
|
|
1661
|
-
});
|
|
3021
|
+
}).catch(() => void 0);
|
|
1662
3022
|
return {
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
runtime.clearSyncControls();
|
|
1666
|
-
},
|
|
1667
|
-
loadSubset: (options) => runtime.loadSubset(options),
|
|
3023
|
+
loadSubset: (options) => runtime.loadHydratedSubset(options) ?? runtime.loadSubset(options),
|
|
3024
|
+
cleanup: () => runtime.cleanup(),
|
|
1668
3025
|
unloadSubset: (options) => runtime.unloadSubset(options)
|
|
1669
3026
|
};
|
|
1670
3027
|
},
|
|
@@ -1679,13 +3036,24 @@ function persistedCollectionOptions(options) {
|
|
|
1679
3036
|
`persistedCollectionOptions requires a persistence adapter`
|
|
1680
3037
|
);
|
|
1681
3038
|
}
|
|
3039
|
+
const networkTimeoutMs = getNetworkTimeoutMs(options.initialRender);
|
|
3040
|
+
if (networkTimeoutMs !== void 0 && options.syncMode === `on-demand`) {
|
|
3041
|
+
throw new InvalidPersistedCollectionConfigError(
|
|
3042
|
+
`network-first initial rendering requires eager persistence syncMode`
|
|
3043
|
+
);
|
|
3044
|
+
}
|
|
3045
|
+
const persistedReadinessTracker = networkTimeoutMs !== void 0 ? new PersistedReadinessTracker(networkTimeoutMs) : void 0;
|
|
1682
3046
|
if (hasOwnSyncKey(options)) {
|
|
1683
3047
|
if (!isValidSyncConfig(options.sync)) {
|
|
1684
3048
|
throw new InvalidSyncConfigError(
|
|
1685
3049
|
`when the "sync" key is present it must provide a callable sync function`
|
|
1686
3050
|
);
|
|
1687
3051
|
}
|
|
1688
|
-
const {
|
|
3052
|
+
const {
|
|
3053
|
+
schemaVersion: schemaVersion2,
|
|
3054
|
+
initialRender: _initialRender2,
|
|
3055
|
+
...syncOptions
|
|
3056
|
+
} = options;
|
|
1689
3057
|
const collectionId2 = syncOptions.id ?? `persisted-collection:${safeRandomUUID()}`;
|
|
1690
3058
|
const persistence2 = resolvePersistenceForCollection(
|
|
1691
3059
|
syncOptions.persistence,
|
|
@@ -1700,13 +3068,19 @@ function persistedCollectionOptions(options) {
|
|
|
1700
3068
|
collectionId2,
|
|
1701
3069
|
persistence2,
|
|
1702
3070
|
syncOptions.syncMode ?? `eager`,
|
|
1703
|
-
collectionId2
|
|
3071
|
+
collectionId2,
|
|
3072
|
+
persistedReadinessTracker
|
|
1704
3073
|
);
|
|
1705
3074
|
const result2 = {
|
|
1706
3075
|
...syncOptions,
|
|
1707
3076
|
id: collectionId2,
|
|
1708
|
-
sync: createWrappedSyncConfig(
|
|
1709
|
-
|
|
3077
|
+
sync: createWrappedSyncConfig(
|
|
3078
|
+
syncOptions.sync,
|
|
3079
|
+
runtime2,
|
|
3080
|
+
persistedReadinessTracker
|
|
3081
|
+
),
|
|
3082
|
+
persistence: persistence2,
|
|
3083
|
+
...persistedReadinessTracker ? { [PERSISTED_READINESS]: persistedReadinessTracker } : {}
|
|
1710
3084
|
};
|
|
1711
3085
|
return withCollectionConfigFactory(
|
|
1712
3086
|
result2,
|
|
@@ -1716,7 +3090,11 @@ function persistedCollectionOptions(options) {
|
|
|
1716
3090
|
})
|
|
1717
3091
|
);
|
|
1718
3092
|
}
|
|
1719
|
-
const {
|
|
3093
|
+
const {
|
|
3094
|
+
schemaVersion,
|
|
3095
|
+
initialRender: _initialRender,
|
|
3096
|
+
...localOnlyOptions
|
|
3097
|
+
} = options;
|
|
1720
3098
|
const collectionId = localOnlyOptions.id ?? `persisted-collection:${safeRandomUUID()}`;
|
|
1721
3099
|
const persistence = resolvePersistenceForCollection(
|
|
1722
3100
|
localOnlyOptions.persistence,
|
|
@@ -1731,32 +3109,39 @@ function persistedCollectionOptions(options) {
|
|
|
1731
3109
|
collectionId,
|
|
1732
3110
|
persistence,
|
|
1733
3111
|
localOnlyOptions.syncMode ?? `eager`,
|
|
1734
|
-
localOnlyOptions.id ?? DEFAULT_DB_NAME
|
|
3112
|
+
localOnlyOptions.id ?? DEFAULT_DB_NAME,
|
|
3113
|
+
persistedReadinessTracker
|
|
1735
3114
|
);
|
|
1736
3115
|
const wrappedOnInsert = async (params) => {
|
|
3116
|
+
const lifecycleGeneration = runtime.getLifecycleGeneration();
|
|
1737
3117
|
const handlerResult = localOnlyOptions.onInsert ? await localOnlyOptions.onInsert(
|
|
1738
3118
|
params
|
|
1739
3119
|
) : void 0;
|
|
1740
3120
|
await runtime.persistAndConfirmCollectionMutations(
|
|
1741
|
-
params.transaction.mutations
|
|
3121
|
+
params.transaction.mutations,
|
|
3122
|
+
lifecycleGeneration
|
|
1742
3123
|
);
|
|
1743
3124
|
return handlerResult ?? {};
|
|
1744
3125
|
};
|
|
1745
3126
|
const wrappedOnUpdate = async (params) => {
|
|
3127
|
+
const lifecycleGeneration = runtime.getLifecycleGeneration();
|
|
1746
3128
|
const handlerResult = localOnlyOptions.onUpdate ? await localOnlyOptions.onUpdate(
|
|
1747
3129
|
params
|
|
1748
3130
|
) : void 0;
|
|
1749
3131
|
await runtime.persistAndConfirmCollectionMutations(
|
|
1750
|
-
params.transaction.mutations
|
|
3132
|
+
params.transaction.mutations,
|
|
3133
|
+
lifecycleGeneration
|
|
1751
3134
|
);
|
|
1752
3135
|
return handlerResult ?? {};
|
|
1753
3136
|
};
|
|
1754
3137
|
const wrappedOnDelete = async (params) => {
|
|
3138
|
+
const lifecycleGeneration = runtime.getLifecycleGeneration();
|
|
1755
3139
|
const handlerResult = localOnlyOptions.onDelete ? await localOnlyOptions.onDelete(
|
|
1756
3140
|
params
|
|
1757
3141
|
) : void 0;
|
|
1758
3142
|
await runtime.persistAndConfirmCollectionMutations(
|
|
1759
|
-
params.transaction.mutations
|
|
3143
|
+
params.transaction.mutations,
|
|
3144
|
+
lifecycleGeneration
|
|
1760
3145
|
);
|
|
1761
3146
|
return handlerResult ?? {};
|
|
1762
3147
|
};
|
|
@@ -1776,6 +3161,7 @@ function persistedCollectionOptions(options) {
|
|
|
1776
3161
|
...localOnlyOptions,
|
|
1777
3162
|
id: collectionId,
|
|
1778
3163
|
persistence,
|
|
3164
|
+
...persistedReadinessTracker ? { [PERSISTED_READINESS]: persistedReadinessTracker } : {},
|
|
1779
3165
|
sync: createLoopbackSyncConfig(runtime),
|
|
1780
3166
|
onInsert: wrappedOnInsert,
|
|
1781
3167
|
onUpdate: wrappedOnUpdate,
|
|
@@ -1848,10 +3234,12 @@ function createPersistedTableName(collectionId, prefix = `c`) {
|
|
|
1848
3234
|
return `${prefix}_${hashPart}_${lengthPart}`;
|
|
1849
3235
|
}
|
|
1850
3236
|
export {
|
|
3237
|
+
SQLITE_DRIVER_SHARED_LOGICAL_SCHEDULING_KEY,
|
|
1851
3238
|
SingleProcessCoordinator,
|
|
1852
3239
|
createPersistedTableName,
|
|
1853
3240
|
decodePersistedStorageKey,
|
|
1854
3241
|
encodePersistedStorageKey,
|
|
3242
|
+
forwardSQLiteDriverSharedLogicalScheduling,
|
|
1855
3243
|
persistedCollectionOptions,
|
|
1856
3244
|
validatePersistedCollectionCoordinator
|
|
1857
3245
|
};
|