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