@tanstack/db-sqlite-persistence-core 0.2.21 → 0.4.0
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 +1840 -399
- package/dist/cjs/persisted.cjs.map +1 -1
- package/dist/cjs/persisted.d.cts +177 -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 +177 -11
- package/dist/esm/persisted.js +1842 -401
- 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 +2690 -444
- 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`:
|
|
@@ -165,15 +374,6 @@ function toStableSerializable(value) {
|
|
|
165
374
|
function stableSerialize(value) {
|
|
166
375
|
return JSON.stringify(toStableSerializable(value) ?? null);
|
|
167
376
|
}
|
|
168
|
-
function normalizeSubsetOptionsForKey(options) {
|
|
169
|
-
return {
|
|
170
|
-
where: toStableSerializable(options.where),
|
|
171
|
-
orderBy: toStableSerializable(options.orderBy),
|
|
172
|
-
limit: options.limit,
|
|
173
|
-
cursor: toStableSerializable(options.cursor),
|
|
174
|
-
offset: options.offset
|
|
175
|
-
};
|
|
176
|
-
}
|
|
177
377
|
function normalizeSyncFnResult(result) {
|
|
178
378
|
if (typeof result === `function`) {
|
|
179
379
|
return { cleanup: result };
|
|
@@ -209,38 +409,52 @@ function toPersistedMutationEnvelope(mutation) {
|
|
|
209
409
|
};
|
|
210
410
|
}
|
|
211
411
|
class PersistedCollectionRuntime {
|
|
212
|
-
constructor(mode, collectionId, persistence, syncMode, dbName) {
|
|
412
|
+
constructor(mode, collectionId, persistence, syncMode, dbName, persistedReadiness) {
|
|
213
413
|
this.mode = mode;
|
|
214
414
|
this.collectionId = collectionId;
|
|
215
415
|
this.persistence = persistence;
|
|
216
416
|
this.syncMode = syncMode;
|
|
217
417
|
this.dbName = dbName;
|
|
418
|
+
this.persistedReadiness = persistedReadiness;
|
|
218
419
|
this.applyMutex = new ApplyMutex();
|
|
219
420
|
this.activeSubsets = /* @__PURE__ */ new Map();
|
|
421
|
+
this.hydrationSequence = 0;
|
|
422
|
+
this.resetSequence = 0;
|
|
220
423
|
this.pendingRemoteSubsetEnsures = /* @__PURE__ */ new Map();
|
|
221
424
|
this.queuedHydrationTransactions = [];
|
|
222
425
|
this.queuedTxCommitted = [];
|
|
223
|
-
this.
|
|
426
|
+
this.requestIds = /* @__PURE__ */ new WeakMap();
|
|
427
|
+
this.hydratedDemands = /* @__PURE__ */ new Set();
|
|
224
428
|
this.collection = null;
|
|
225
429
|
this.syncControls = {
|
|
226
430
|
begin: null,
|
|
227
431
|
write: null,
|
|
228
432
|
commit: null,
|
|
229
433
|
truncate: null,
|
|
230
|
-
metadata: null
|
|
434
|
+
metadata: null,
|
|
435
|
+
markError: null
|
|
231
436
|
};
|
|
232
|
-
this.started = false;
|
|
233
437
|
this.startupMetadataPromise = null;
|
|
234
438
|
this.startPromise = null;
|
|
439
|
+
this.hasAttemptedStartup = false;
|
|
440
|
+
this.resumeBaselinePromise = null;
|
|
441
|
+
this.resumeCertificationPromise = null;
|
|
442
|
+
this.resumeGenerationOwner = /* @__PURE__ */ Symbol(`persisted resume generation owner`);
|
|
443
|
+
this.lifecycleGeneration = 0;
|
|
235
444
|
this.internalApplyDepth = 0;
|
|
445
|
+
this.sourcePublicationWaitDepth = 0;
|
|
236
446
|
this.appliedReceiptSequence = 0;
|
|
447
|
+
this.syncErrorReported = false;
|
|
237
448
|
this.pendingAppliedReceipts = /* @__PURE__ */ new Map();
|
|
238
|
-
this.
|
|
449
|
+
this.hydratingGeneration = null;
|
|
239
450
|
this.coordinatorUnsubscribe = null;
|
|
451
|
+
this.remoteSubsetOwnerUnsubscribe = null;
|
|
240
452
|
this.indexAddedUnsubscribe = null;
|
|
241
453
|
this.indexRemovedUnsubscribe = null;
|
|
242
454
|
this.remoteEnsureRetryTimer = null;
|
|
243
|
-
this.
|
|
455
|
+
this.nextRequestId = 0;
|
|
456
|
+
this.startupSettled = false;
|
|
457
|
+
this.sourceTruncateGeneration = 0;
|
|
244
458
|
this.latestTerm = 0;
|
|
245
459
|
this.latestSeq = 0;
|
|
246
460
|
this.latestRowVersion = 0;
|
|
@@ -249,12 +463,34 @@ class PersistedCollectionRuntime {
|
|
|
249
463
|
this.localRowVersion = 0;
|
|
250
464
|
}
|
|
251
465
|
setSyncControls(syncControls) {
|
|
466
|
+
this.advanceLifecycle();
|
|
467
|
+
this.syncErrorReported = false;
|
|
468
|
+
this.reportedSyncError = void 0;
|
|
252
469
|
const commit = syncControls.commit;
|
|
253
470
|
this.syncControls = {
|
|
254
471
|
...syncControls,
|
|
255
472
|
commit: commit ? (signal) => this.trackAppliedReceipt(commit(signal)) : null
|
|
256
473
|
};
|
|
257
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
|
+
}
|
|
258
494
|
trackAppliedReceipt(receipt) {
|
|
259
495
|
const sequence = ++this.appliedReceiptSequence;
|
|
260
496
|
if (receipt === true) {
|
|
@@ -279,11 +515,54 @@ class PersistedCollectionRuntime {
|
|
|
279
515
|
write: null,
|
|
280
516
|
commit: null,
|
|
281
517
|
truncate: null,
|
|
282
|
-
metadata: null
|
|
518
|
+
metadata: null,
|
|
519
|
+
markError: null
|
|
283
520
|
};
|
|
284
521
|
}
|
|
285
522
|
isHydratingNow() {
|
|
286
|
-
return this.
|
|
523
|
+
return this.hydratingGeneration === this.lifecycleGeneration;
|
|
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;
|
|
287
566
|
}
|
|
288
567
|
isApplyingInternally() {
|
|
289
568
|
return this.internalApplyDepth > 0;
|
|
@@ -301,70 +580,233 @@ class PersistedCollectionRuntime {
|
|
|
301
580
|
isLeader: this.persistence.coordinator.isLeader(this.collectionId)
|
|
302
581
|
};
|
|
303
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
|
+
}
|
|
304
589
|
async ensureStarted() {
|
|
590
|
+
this.throwIfTerminal();
|
|
305
591
|
if (this.startPromise) {
|
|
306
592
|
return this.startPromise;
|
|
307
593
|
}
|
|
308
|
-
|
|
594
|
+
const lifecycleGeneration = this.lifecycleGeneration;
|
|
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);
|
|
309
682
|
return this.startPromise;
|
|
310
683
|
}
|
|
684
|
+
ensureResumeBaselineHydrated() {
|
|
685
|
+
this.throwIfTerminal();
|
|
686
|
+
if (this.resumeBaselinePromise) {
|
|
687
|
+
return this.resumeBaselinePromise;
|
|
688
|
+
}
|
|
689
|
+
const lifecycleGeneration = this.lifecycleGeneration;
|
|
690
|
+
this.resumeBaselinePromise = (async () => {
|
|
691
|
+
await this.ensureStarted();
|
|
692
|
+
if (lifecycleGeneration !== this.lifecycleGeneration) return;
|
|
693
|
+
if (this.syncMode !== `on-demand`) return;
|
|
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
|
+
});
|
|
709
|
+
return this.resumeBaselinePromise;
|
|
710
|
+
}
|
|
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;
|
|
739
|
+
const baseline = {};
|
|
740
|
+
this.activeSubsets.set(this.getSubsetKey(baseline), baseline);
|
|
741
|
+
const appliedCursor = this.appliedReceiptSequence;
|
|
742
|
+
await this.hydrateSubsetUnsafe(
|
|
743
|
+
baseline,
|
|
744
|
+
{
|
|
745
|
+
requestRemoteEnsure: false,
|
|
746
|
+
lifecycleGeneration,
|
|
747
|
+
bindKeySetEvidence: true
|
|
748
|
+
},
|
|
749
|
+
adapter
|
|
750
|
+
);
|
|
751
|
+
return lifecycleGeneration === this.lifecycleGeneration ? appliedCursor : void 0;
|
|
752
|
+
}
|
|
311
753
|
async ensureStartupMetadataLoaded() {
|
|
754
|
+
this.throwIfTerminal();
|
|
312
755
|
if (this.startupMetadataPromise) {
|
|
313
756
|
return this.startupMetadataPromise;
|
|
314
757
|
}
|
|
315
|
-
|
|
758
|
+
void this.ensureStarted();
|
|
316
759
|
return this.startupMetadataPromise;
|
|
317
760
|
}
|
|
318
|
-
async startInternal() {
|
|
319
|
-
if (this.
|
|
320
|
-
return;
|
|
321
|
-
}
|
|
322
|
-
this.started = true;
|
|
323
|
-
await this.ensureStartupMetadataLoaded();
|
|
761
|
+
async startInternal(lifecycleGeneration, adapter, onStartupMetadataLoaded) {
|
|
762
|
+
if (lifecycleGeneration !== this.lifecycleGeneration) return void 0;
|
|
324
763
|
const indexBootstrapSnapshot = this.collection?.getIndexMetadata() ?? [];
|
|
325
764
|
this.attachIndexLifecycleListeners();
|
|
326
|
-
await this.bootstrapPersistedIndexes(
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
this.replaceCollectionMetadataSnapshot(
|
|
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
|
+
});
|
|
782
|
+
if (lifecycleGeneration !== this.lifecycleGeneration) return;
|
|
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;
|
|
352
794
|
}
|
|
353
|
-
async loadCollectionMetadataSnapshot() {
|
|
354
|
-
if (!
|
|
795
|
+
async loadCollectionMetadataSnapshot(adapter) {
|
|
796
|
+
if (!adapter.loadCollectionMetadata) {
|
|
355
797
|
return [];
|
|
356
798
|
}
|
|
357
|
-
return
|
|
799
|
+
return adapter.loadCollectionMetadata(this.collectionId);
|
|
358
800
|
}
|
|
359
801
|
replaceCollectionMetadataSnapshot(collectionMetadata) {
|
|
360
802
|
if (!this.syncControls.begin || !this.syncControls.commit || !this.syncControls.metadata) {
|
|
361
|
-
return;
|
|
803
|
+
return true;
|
|
362
804
|
}
|
|
363
805
|
const nextMetadata = new Map(
|
|
364
806
|
collectionMetadata.map(({ key, value }) => [key, value])
|
|
365
807
|
);
|
|
366
808
|
const currentKeys = this.syncControls.metadata.collection.list().map(({ key }) => key);
|
|
367
|
-
this.withInternalApply(() => {
|
|
809
|
+
return this.withInternalApply(() => {
|
|
368
810
|
this.syncControls.begin?.({ immediate: true });
|
|
369
811
|
currentKeys.forEach((key) => {
|
|
370
812
|
if (!nextMetadata.has(key)) {
|
|
@@ -374,49 +816,182 @@ class PersistedCollectionRuntime {
|
|
|
374
816
|
nextMetadata.forEach((value, key) => {
|
|
375
817
|
this.syncControls.metadata?.collection.set(key, value);
|
|
376
818
|
});
|
|
377
|
-
this.syncControls.commit?.();
|
|
819
|
+
return this.syncControls.commit?.() ?? true;
|
|
378
820
|
});
|
|
379
821
|
}
|
|
380
822
|
async loadSubset(options, upstreamLoadSubset) {
|
|
381
|
-
this.
|
|
823
|
+
this.throwIfTerminal();
|
|
824
|
+
const lifecycleGeneration = this.lifecycleGeneration;
|
|
825
|
+
const subsetKey = this.getSubsetKey(options);
|
|
826
|
+
const truncateGeneration = this.sourceTruncateGeneration;
|
|
827
|
+
const routeRemoteDemandDuringHydration = this.canRouteRemoteDemandThroughCoordinator();
|
|
828
|
+
this.activeSubsets.set(subsetKey, options);
|
|
382
829
|
const appliedCursor = this.appliedReceiptSequence;
|
|
383
|
-
|
|
384
|
-
() =>
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
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()) {
|
|
390
863
|
try {
|
|
391
|
-
await
|
|
864
|
+
await this.persistence.coordinator.requestEnsureRemoteSubset(
|
|
865
|
+
this.collectionId,
|
|
866
|
+
options
|
|
867
|
+
);
|
|
392
868
|
} catch (error) {
|
|
393
869
|
if (options.signal?.aborted || typeof error === `object` && error !== null && `name` in error && error.name === `AbortError`) {
|
|
394
870
|
this.pendingRemoteSubsetEnsures.delete(this.getSubsetKey(options));
|
|
395
871
|
throw error;
|
|
396
872
|
}
|
|
397
|
-
console.warn(`Failed to trigger remote subset load:`, error);
|
|
398
873
|
this.queueRemoteSubsetEnsure(options);
|
|
874
|
+
throw error;
|
|
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;
|
|
399
887
|
}
|
|
400
888
|
}
|
|
401
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
|
+
}
|
|
402
929
|
unloadSubset(options, upstreamUnloadSubset) {
|
|
403
|
-
this.
|
|
404
|
-
this.
|
|
405
|
-
|
|
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
|
+
}
|
|
406
953
|
}
|
|
407
954
|
async forceReloadSubset(options) {
|
|
408
|
-
this.
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
955
|
+
this.throwIfTerminal();
|
|
956
|
+
const lifecycleGeneration = this.lifecycleGeneration;
|
|
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
|
+
});
|
|
412
973
|
}
|
|
413
974
|
queueHydrationBufferedTransaction(transaction) {
|
|
975
|
+
const failure = this.getCurrentTerminalFailure();
|
|
976
|
+
if (failure) {
|
|
977
|
+
transaction.rejectApplied?.(failure.error);
|
|
978
|
+
return;
|
|
979
|
+
}
|
|
414
980
|
this.queuedHydrationTransactions.push(transaction);
|
|
415
981
|
}
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
)
|
|
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
|
+
});
|
|
420
995
|
}
|
|
421
996
|
normalizeSyncWriteMessage(message) {
|
|
422
997
|
if (!this.collection) {
|
|
@@ -428,10 +1003,6 @@ class PersistedCollectionRuntime {
|
|
|
428
1003
|
const key2 = message.key;
|
|
429
1004
|
const previousValue = this.collection.get(key2) ?? {};
|
|
430
1005
|
return {
|
|
431
|
-
forwardMessage: {
|
|
432
|
-
type: `delete`,
|
|
433
|
-
key: key2
|
|
434
|
-
},
|
|
435
1006
|
operation: {
|
|
436
1007
|
type: `delete`,
|
|
437
1008
|
key: key2,
|
|
@@ -443,10 +1014,6 @@ class PersistedCollectionRuntime {
|
|
|
443
1014
|
const key2 = this.collection.getKeyFromItem(message.value);
|
|
444
1015
|
const previousValue = this.collection.get(key2) ?? message.value;
|
|
445
1016
|
return {
|
|
446
|
-
forwardMessage: {
|
|
447
|
-
type: `delete`,
|
|
448
|
-
key: key2
|
|
449
|
-
},
|
|
450
1017
|
operation: {
|
|
451
1018
|
type: `delete`,
|
|
452
1019
|
key: key2,
|
|
@@ -456,11 +1023,6 @@ class PersistedCollectionRuntime {
|
|
|
456
1023
|
}
|
|
457
1024
|
const key = this.collection.getKeyFromItem(message.value);
|
|
458
1025
|
return {
|
|
459
|
-
forwardMessage: {
|
|
460
|
-
type: `update`,
|
|
461
|
-
value: message.value,
|
|
462
|
-
metadata: message.metadata
|
|
463
|
-
},
|
|
464
1026
|
operation: {
|
|
465
1027
|
type: `update`,
|
|
466
1028
|
key,
|
|
@@ -469,25 +1031,45 @@ class PersistedCollectionRuntime {
|
|
|
469
1031
|
}
|
|
470
1032
|
};
|
|
471
1033
|
}
|
|
472
|
-
async persistAndConfirmCollectionMutations(mutations) {
|
|
1034
|
+
async persistAndConfirmCollectionMutations(mutations, lifecycleGeneration = this.lifecycleGeneration) {
|
|
1035
|
+
this.throwIfTerminal();
|
|
1036
|
+
this.throwIfLifecycleReplaced(lifecycleGeneration);
|
|
473
1037
|
if (mutations.length === 0) {
|
|
474
1038
|
return;
|
|
475
1039
|
}
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
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)
|
|
485
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);
|
|
486
1067
|
}
|
|
487
|
-
|
|
488
|
-
}
|
|
1068
|
+
throw error;
|
|
1069
|
+
}
|
|
489
1070
|
}
|
|
490
1071
|
async acceptTransactionMutations(transaction) {
|
|
1072
|
+
this.throwIfTerminal();
|
|
491
1073
|
const collectionMutations = this.filterMutationsForCollection(
|
|
492
1074
|
transaction.mutations
|
|
493
1075
|
);
|
|
@@ -497,11 +1079,34 @@ class PersistedCollectionRuntime {
|
|
|
497
1079
|
await this.persistAndConfirmCollectionMutations(collectionMutations);
|
|
498
1080
|
}
|
|
499
1081
|
cleanup() {
|
|
500
|
-
this.
|
|
1082
|
+
this.advanceLifecycle();
|
|
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?.());
|
|
501
1104
|
this.coordinatorUnsubscribe = null;
|
|
502
|
-
this.
|
|
1105
|
+
attempt(() => this.remoteSubsetOwnerUnsubscribe?.());
|
|
1106
|
+
this.remoteSubsetOwnerUnsubscribe = null;
|
|
1107
|
+
attempt(() => this.indexAddedUnsubscribe?.());
|
|
503
1108
|
this.indexAddedUnsubscribe = null;
|
|
504
|
-
this.indexRemovedUnsubscribe?.();
|
|
1109
|
+
attempt(() => this.indexRemovedUnsubscribe?.());
|
|
505
1110
|
this.indexRemovedUnsubscribe = null;
|
|
506
1111
|
if (this.remoteEnsureRetryTimer !== null) {
|
|
507
1112
|
clearTimeout(this.remoteEnsureRetryTimer);
|
|
@@ -509,12 +1114,36 @@ class PersistedCollectionRuntime {
|
|
|
509
1114
|
}
|
|
510
1115
|
this.pendingRemoteSubsetEnsures.clear();
|
|
511
1116
|
this.activeSubsets.clear();
|
|
1117
|
+
this.hydratedDemands.clear();
|
|
512
1118
|
for (const transaction of this.queuedHydrationTransactions) {
|
|
513
1119
|
transaction.rejectApplied?.(new db.SyncTransactionAbortedError());
|
|
514
1120
|
}
|
|
515
1121
|
this.queuedHydrationTransactions.length = 0;
|
|
516
1122
|
this.queuedTxCommitted.length = 0;
|
|
517
1123
|
this.clearSyncControls();
|
|
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
|
+
});
|
|
1133
|
+
}
|
|
1134
|
+
advanceLifecycle() {
|
|
1135
|
+
this.lifecycleGeneration++;
|
|
1136
|
+
this.persistedReadiness?.set({ status: `loading` });
|
|
1137
|
+
this.startupSettled = false;
|
|
1138
|
+
this.hydratedDemands.clear();
|
|
1139
|
+
this.startupMetadataPromise = null;
|
|
1140
|
+
this.startPromise = null;
|
|
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`);
|
|
518
1147
|
}
|
|
519
1148
|
withInternalApply(task) {
|
|
520
1149
|
this.internalApplyDepth++;
|
|
@@ -530,8 +1159,8 @@ class PersistedCollectionRuntime {
|
|
|
530
1159
|
}
|
|
531
1160
|
return this.collection.getIndexMetadata().map((metadata) => metadata.signature);
|
|
532
1161
|
}
|
|
533
|
-
loadSubsetRowsUnsafe(options) {
|
|
534
|
-
return
|
|
1162
|
+
loadSubsetRowsUnsafe(options, adapter) {
|
|
1163
|
+
return adapter.loadSubset(this.collectionId, options, {
|
|
535
1164
|
requiredIndexSignatures: this.getRequiredIndexSignatures()
|
|
536
1165
|
});
|
|
537
1166
|
}
|
|
@@ -545,27 +1174,184 @@ class PersistedCollectionRuntime {
|
|
|
545
1174
|
);
|
|
546
1175
|
}
|
|
547
1176
|
async scanPersistedRows(options) {
|
|
1177
|
+
this.throwIfTerminal();
|
|
548
1178
|
return this.applyMutex.run(() => this.scanPersistedRowsUnsafe(options));
|
|
549
1179
|
}
|
|
550
|
-
async hydrateSubsetUnsafe(options, config) {
|
|
551
|
-
|
|
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;
|
|
552
1187
|
try {
|
|
553
|
-
|
|
554
|
-
this.
|
|
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);
|
|
1221
|
+
if (config.lifecycleGeneration !== this.lifecycleGeneration) return;
|
|
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);
|
|
555
1247
|
} finally {
|
|
556
|
-
this.
|
|
1248
|
+
if (this.activeHydrationContext === hydrationContext) {
|
|
1249
|
+
this.activeHydrationContext = void 0;
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
if (replayFailure && config.rejectBufferedReplayFailure) {
|
|
1253
|
+
throw replayFailure.reason;
|
|
557
1254
|
}
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
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);
|
|
562
1347
|
}
|
|
1348
|
+
this.queuedHydrationTransactions.length = 0;
|
|
563
1349
|
}
|
|
564
1350
|
applyRowsToCollection(rows) {
|
|
565
1351
|
if (!this.syncControls.begin || !this.syncControls.write || !this.syncControls.commit) {
|
|
566
|
-
return;
|
|
1352
|
+
return true;
|
|
567
1353
|
}
|
|
568
|
-
this.withInternalApply(() => {
|
|
1354
|
+
return this.withInternalApply(() => {
|
|
569
1355
|
this.syncControls.begin?.({ immediate: true });
|
|
570
1356
|
for (const row of rows) {
|
|
571
1357
|
if (this.collection?._hasHydratedKey(row.key)) {
|
|
@@ -577,18 +1363,41 @@ class PersistedCollectionRuntime {
|
|
|
577
1363
|
metadata: row.metadata
|
|
578
1364
|
});
|
|
579
1365
|
}
|
|
580
|
-
this.syncControls.commit?.();
|
|
1366
|
+
return this.syncControls.commit?.() ?? true;
|
|
581
1367
|
});
|
|
582
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
|
+
}
|
|
583
1392
|
replaceCollectionSnapshot(rows, collectionMetadata) {
|
|
584
1393
|
if (!this.syncControls.begin || !this.syncControls.write || !this.syncControls.commit || !this.syncControls.metadata) {
|
|
585
|
-
return;
|
|
1394
|
+
return true;
|
|
586
1395
|
}
|
|
587
1396
|
const nextMetadata = new Map(
|
|
588
1397
|
collectionMetadata.map(({ key, value }) => [key, value])
|
|
589
1398
|
);
|
|
590
1399
|
const currentKeys = this.syncControls.metadata.collection.list().map(({ key }) => key);
|
|
591
|
-
this.withInternalApply(() => {
|
|
1400
|
+
return this.withInternalApply(() => {
|
|
592
1401
|
this.syncControls.begin?.({ immediate: true });
|
|
593
1402
|
this.syncControls.truncate?.();
|
|
594
1403
|
for (const row of rows) {
|
|
@@ -606,17 +1415,22 @@ class PersistedCollectionRuntime {
|
|
|
606
1415
|
nextMetadata.forEach((value, key) => {
|
|
607
1416
|
this.syncControls.metadata?.collection.set(key, value);
|
|
608
1417
|
});
|
|
609
|
-
this.syncControls.commit?.();
|
|
1418
|
+
return this.syncControls.commit?.() ?? true;
|
|
610
1419
|
});
|
|
611
1420
|
}
|
|
612
|
-
async flushQueuedHydrationTransactionsUnsafe() {
|
|
1421
|
+
async flushQueuedHydrationTransactionsUnsafe(adapter) {
|
|
1422
|
+
let operationFailure;
|
|
613
1423
|
while (this.queuedHydrationTransactions.length > 0) {
|
|
614
1424
|
const transaction = this.queuedHydrationTransactions.shift();
|
|
615
1425
|
if (!transaction) {
|
|
616
1426
|
continue;
|
|
617
1427
|
}
|
|
618
1428
|
try {
|
|
619
|
-
await this.applyBufferedSyncTransactionUnsafe(
|
|
1429
|
+
const outcome = await this.applyBufferedSyncTransactionUnsafe(
|
|
1430
|
+
transaction,
|
|
1431
|
+
adapter
|
|
1432
|
+
);
|
|
1433
|
+
operationFailure ??= outcome.hydrationFailure;
|
|
620
1434
|
} catch (error) {
|
|
621
1435
|
transaction.rejectApplied?.(error);
|
|
622
1436
|
for (const abandoned of this.queuedHydrationTransactions) {
|
|
@@ -626,103 +1440,126 @@ class PersistedCollectionRuntime {
|
|
|
626
1440
|
throw error;
|
|
627
1441
|
}
|
|
628
1442
|
}
|
|
1443
|
+
return operationFailure;
|
|
629
1444
|
}
|
|
630
|
-
async applyBufferedSyncTransactionUnsafe(transaction) {
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
if (!begin || !write || !commit) {
|
|
637
|
-
transaction.rejectApplied?.(new db.SyncTransactionAbortedError());
|
|
638
|
-
return;
|
|
639
|
-
}
|
|
640
|
-
const applyToCollection = () => {
|
|
641
|
-
begin();
|
|
642
|
-
if (transaction.truncate) {
|
|
643
|
-
truncate?.();
|
|
644
|
-
}
|
|
645
|
-
for (const operation of transaction.operations) {
|
|
646
|
-
if (operation.type === `delete`) {
|
|
647
|
-
write({
|
|
648
|
-
type: `delete`,
|
|
649
|
-
key: operation.key
|
|
650
|
-
});
|
|
651
|
-
} else {
|
|
652
|
-
write({
|
|
653
|
-
type: `update`,
|
|
654
|
-
value: operation.value,
|
|
655
|
-
metadata: operation.metadata
|
|
656
|
-
});
|
|
657
|
-
}
|
|
658
|
-
}
|
|
659
|
-
for (const [key, metadataWrite] of transaction.rowMetadataWrites) {
|
|
660
|
-
if (metadataWrite.type === `delete`) {
|
|
661
|
-
metadata?.row.delete(key);
|
|
662
|
-
} else {
|
|
663
|
-
metadata?.row.set(key, metadataWrite.value);
|
|
664
|
-
}
|
|
665
|
-
}
|
|
666
|
-
for (const [key, metadataWrite] of transaction.collectionMetadataWrites) {
|
|
667
|
-
if (metadataWrite.type === `delete`) {
|
|
668
|
-
metadata?.collection.delete(key);
|
|
669
|
-
} else {
|
|
670
|
-
metadata?.collection.set(key, metadataWrite.value);
|
|
671
|
-
}
|
|
672
|
-
}
|
|
673
|
-
return commit(transaction.signal);
|
|
674
|
-
};
|
|
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;
|
|
675
1451
|
try {
|
|
676
|
-
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;
|
|
677
1455
|
if (applied !== true) {
|
|
678
|
-
|
|
1456
|
+
this.sourcePublicationWaitDepth++;
|
|
1457
|
+
try {
|
|
1458
|
+
await applied;
|
|
1459
|
+
} finally {
|
|
1460
|
+
this.sourcePublicationWaitDepth--;
|
|
1461
|
+
}
|
|
679
1462
|
}
|
|
1463
|
+
this.throwIfLifecycleReplaced(transaction.lifecycleGeneration);
|
|
680
1464
|
if (!transaction.internal) {
|
|
681
|
-
await this.persistAndBroadcastExternalSyncTransactionUnsafe(
|
|
1465
|
+
await this.persistAndBroadcastExternalSyncTransactionUnsafe(
|
|
1466
|
+
transaction,
|
|
1467
|
+
scopedAdapter
|
|
1468
|
+
);
|
|
682
1469
|
}
|
|
683
1470
|
transaction.resolveApplied?.();
|
|
1471
|
+
return { applied: true };
|
|
684
1472
|
} catch (error) {
|
|
685
|
-
|
|
686
|
-
|
|
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;
|
|
687
1499
|
}
|
|
1500
|
+
return this.markTerminalFailure(error, transaction.lifecycleGeneration);
|
|
688
1501
|
}
|
|
689
|
-
async persistAndBroadcastExternalSyncTransactionUnsafe(transaction) {
|
|
1502
|
+
async persistAndBroadcastExternalSyncTransactionUnsafe(transaction, scopedAdapter) {
|
|
690
1503
|
if (transaction.internal) {
|
|
691
1504
|
return;
|
|
692
1505
|
}
|
|
693
|
-
|
|
1506
|
+
this.throwIfLifecycleReplaced(transaction.lifecycleGeneration);
|
|
694
1507
|
if (!transaction.truncate && transaction.operations.length === 0 && transaction.rowMetadataWrites.size === 0 && transaction.collectionMetadataWrites.size === 0) {
|
|
695
|
-
this.publishTxCommittedEvent(
|
|
696
|
-
this.createTxCommittedPayload({
|
|
697
|
-
term: streamPosition.term,
|
|
698
|
-
seq: streamPosition.seq,
|
|
699
|
-
txId: db.safeRandomUUID(),
|
|
700
|
-
latestRowVersion: streamPosition.rowVersion,
|
|
701
|
-
changedRows: [],
|
|
702
|
-
deletedKeys: [],
|
|
703
|
-
requiresFullReload: true
|
|
704
|
-
})
|
|
705
|
-
);
|
|
706
1508
|
return;
|
|
707
1509
|
}
|
|
1510
|
+
const streamPosition = this.nextLocalStreamPosition();
|
|
708
1511
|
const tx = this.createPersistedTxFromOperations(transaction, streamPosition);
|
|
709
|
-
await this.persistence.
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
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
|
|
725
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
|
|
1560
|
+
);
|
|
1561
|
+
}
|
|
1562
|
+
this.throwIfLifecycleReplaced(lifecycleGeneration);
|
|
726
1563
|
}
|
|
727
1564
|
createPersistedTxFromOperations(transaction, streamPosition) {
|
|
728
1565
|
return {
|
|
@@ -787,11 +1624,11 @@ class PersistedCollectionRuntime {
|
|
|
787
1624
|
})
|
|
788
1625
|
};
|
|
789
1626
|
}
|
|
790
|
-
confirmMutationsSyncUnsafe(mutations) {
|
|
1627
|
+
async confirmMutationsSyncUnsafe(mutations) {
|
|
791
1628
|
if (!this.syncControls.begin || !this.syncControls.write || !this.syncControls.commit) {
|
|
792
1629
|
return;
|
|
793
1630
|
}
|
|
794
|
-
this.withInternalApply(() => {
|
|
1631
|
+
const applied = this.withInternalApply(() => {
|
|
795
1632
|
this.syncControls.begin?.({ immediate: true });
|
|
796
1633
|
for (const mutation of mutations) {
|
|
797
1634
|
if (mutation.type === `delete`) {
|
|
@@ -806,8 +1643,11 @@ class PersistedCollectionRuntime {
|
|
|
806
1643
|
});
|
|
807
1644
|
}
|
|
808
1645
|
}
|
|
809
|
-
this.syncControls.commit?.();
|
|
1646
|
+
return this.syncControls.commit?.() ?? true;
|
|
810
1647
|
});
|
|
1648
|
+
if (applied !== true) {
|
|
1649
|
+
await applied;
|
|
1650
|
+
}
|
|
811
1651
|
}
|
|
812
1652
|
filterMutationsForCollection(mutations) {
|
|
813
1653
|
const collection = this.collection;
|
|
@@ -818,7 +1658,8 @@ class PersistedCollectionRuntime {
|
|
|
818
1658
|
return mutation.collection.id === this.collectionId;
|
|
819
1659
|
});
|
|
820
1660
|
}
|
|
821
|
-
async persistCollectionMutationsUnsafe(mutations) {
|
|
1661
|
+
async persistCollectionMutationsUnsafe(mutations, lifecycleGeneration) {
|
|
1662
|
+
this.throwIfLifecycleReplaced(lifecycleGeneration);
|
|
822
1663
|
if (this.persistence.coordinator.requestApplyLocalMutations) {
|
|
823
1664
|
const envelopeMutations = mutations.map(
|
|
824
1665
|
(mutation) => toPersistedMutationEnvelope(
|
|
@@ -829,7 +1670,19 @@ class PersistedCollectionRuntime {
|
|
|
829
1670
|
this.collectionId,
|
|
830
1671
|
envelopeMutations
|
|
831
1672
|
);
|
|
1673
|
+
this.throwIfLifecycleReplaced(lifecycleGeneration);
|
|
832
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
|
+
}
|
|
833
1686
|
throw new Error(
|
|
834
1687
|
`failed to apply local mutations through coordinator: ${response.error}`
|
|
835
1688
|
);
|
|
@@ -857,7 +1710,8 @@ class PersistedCollectionRuntime {
|
|
|
857
1710
|
}
|
|
858
1711
|
const streamPosition = this.nextLocalStreamPosition();
|
|
859
1712
|
const tx = this.createPersistedTxFromMutations(mutations, streamPosition);
|
|
860
|
-
await this.
|
|
1713
|
+
await this.applyCommittedTx(tx, lifecycleGeneration);
|
|
1714
|
+
this.throwIfLifecycleReplaced(lifecycleGeneration);
|
|
861
1715
|
this.publishTxCommittedEvent(
|
|
862
1716
|
this.createTxCommittedPayload({
|
|
863
1717
|
term: tx.term,
|
|
@@ -947,28 +1801,30 @@ class PersistedCollectionRuntime {
|
|
|
947
1801
|
};
|
|
948
1802
|
}
|
|
949
1803
|
getSubsetKey(options) {
|
|
950
|
-
|
|
951
|
-
if (
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
return
|
|
1804
|
+
let id = this.requestIds.get(options);
|
|
1805
|
+
if (id === void 0) {
|
|
1806
|
+
id = `request:${++this.nextRequestId}`;
|
|
1807
|
+
this.requestIds.set(options, id);
|
|
1808
|
+
}
|
|
1809
|
+
return id;
|
|
1810
|
+
}
|
|
1811
|
+
canRouteRemoteDemandThroughCoordinator() {
|
|
1812
|
+
if (this.getCurrentTerminalFailure() || this.mode !== `sync-present` || this.persistence.coordinator instanceof SingleProcessCoordinator) {
|
|
1813
|
+
return false;
|
|
960
1814
|
}
|
|
961
|
-
return
|
|
1815
|
+
return !this.persistence.coordinator.isLeader(this.collectionId) || this.remoteSubsetOwnerUnsubscribe !== null;
|
|
962
1816
|
}
|
|
963
1817
|
queueRemoteSubsetEnsure(options) {
|
|
964
|
-
if (
|
|
1818
|
+
if (options.signal?.aborted || !this.canRouteRemoteDemandThroughCoordinator() || this.activeSubsets.get(this.getSubsetKey(options)) !== options) {
|
|
965
1819
|
return;
|
|
966
1820
|
}
|
|
967
|
-
this.
|
|
1821
|
+
const subsetKey = this.getSubsetKey(options);
|
|
1822
|
+
if (this.activeSubsets.get(subsetKey) !== options) return;
|
|
1823
|
+
this.pendingRemoteSubsetEnsures.set(subsetKey, options);
|
|
968
1824
|
void this.flushPendingRemoteSubsetEnsures();
|
|
969
1825
|
}
|
|
970
1826
|
scheduleRemoteEnsureRetry() {
|
|
971
|
-
if (this.mode !== `sync-present` ||
|
|
1827
|
+
if (this.getCurrentTerminalFailure() || this.mode !== `sync-present` || this.persistence.coordinator instanceof SingleProcessCoordinator) {
|
|
972
1828
|
return;
|
|
973
1829
|
}
|
|
974
1830
|
if (this.pendingRemoteSubsetEnsures.size === 0 || this.remoteEnsureRetryTimer !== null) {
|
|
@@ -980,7 +1836,7 @@ class PersistedCollectionRuntime {
|
|
|
980
1836
|
}, REMOTE_ENSURE_RETRY_DELAY_MS);
|
|
981
1837
|
}
|
|
982
1838
|
async flushPendingRemoteSubsetEnsures() {
|
|
983
|
-
if (this.mode !== `sync-present` ||
|
|
1839
|
+
if (this.getCurrentTerminalFailure() || this.mode !== `sync-present` || this.persistence.coordinator instanceof SingleProcessCoordinator) {
|
|
984
1840
|
return;
|
|
985
1841
|
}
|
|
986
1842
|
if (this.remoteEnsureRetryTimer !== null) {
|
|
@@ -988,6 +1844,10 @@ class PersistedCollectionRuntime {
|
|
|
988
1844
|
this.remoteEnsureRetryTimer = null;
|
|
989
1845
|
}
|
|
990
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
|
+
}
|
|
991
1851
|
try {
|
|
992
1852
|
await this.persistence.coordinator.requestEnsureRemoteSubset(
|
|
993
1853
|
this.collectionId,
|
|
@@ -995,7 +1855,11 @@ class PersistedCollectionRuntime {
|
|
|
995
1855
|
);
|
|
996
1856
|
this.pendingRemoteSubsetEnsures.delete(subsetKey);
|
|
997
1857
|
} catch (error) {
|
|
998
|
-
|
|
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
|
+
}
|
|
999
1863
|
}
|
|
1000
1864
|
}
|
|
1001
1865
|
this.scheduleRemoteEnsureRetry();
|
|
@@ -1004,6 +1868,10 @@ class PersistedCollectionRuntime {
|
|
|
1004
1868
|
if (this.coordinatorUnsubscribe) {
|
|
1005
1869
|
return;
|
|
1006
1870
|
}
|
|
1871
|
+
this.persistence.coordinator.setAdapterForCollection?.(
|
|
1872
|
+
this.collectionId,
|
|
1873
|
+
this.persistence.adapter
|
|
1874
|
+
);
|
|
1007
1875
|
this.coordinatorUnsubscribe = this.persistence.coordinator.subscribe(
|
|
1008
1876
|
this.collectionId,
|
|
1009
1877
|
(message) => {
|
|
@@ -1015,15 +1883,22 @@ class PersistedCollectionRuntime {
|
|
|
1015
1883
|
if (message.collectionId !== this.collectionId) {
|
|
1016
1884
|
return;
|
|
1017
1885
|
}
|
|
1886
|
+
if (this.getCurrentTerminalFailure()) {
|
|
1887
|
+
return;
|
|
1888
|
+
}
|
|
1018
1889
|
const { payload } = message;
|
|
1890
|
+
const lifecycleGeneration = this.lifecycleGeneration;
|
|
1019
1891
|
const isSelf = message.senderId === this.persistence.coordinator.getNodeId();
|
|
1020
1892
|
if (isTxCommittedPayload(payload)) {
|
|
1021
|
-
if (this.
|
|
1022
|
-
this.queuedTxCommitted.push(
|
|
1893
|
+
if (this.isHydratingNow()) {
|
|
1894
|
+
this.queuedTxCommitted.push({
|
|
1895
|
+
txCommitted: payload,
|
|
1896
|
+
lifecycleGeneration
|
|
1897
|
+
});
|
|
1023
1898
|
return;
|
|
1024
1899
|
}
|
|
1025
|
-
void this.applyMutex.run(() => this.processCommittedTxUnsafe(payload)).catch((error) => {
|
|
1026
|
-
|
|
1900
|
+
void this.applyMutex.run(() => this.processCommittedTxUnsafe(payload, lifecycleGeneration)).catch((error) => {
|
|
1901
|
+
this.markTerminalFailure(error, lifecycleGeneration);
|
|
1027
1902
|
});
|
|
1028
1903
|
return;
|
|
1029
1904
|
}
|
|
@@ -1031,8 +1906,15 @@ class PersistedCollectionRuntime {
|
|
|
1031
1906
|
return;
|
|
1032
1907
|
}
|
|
1033
1908
|
if (isCollectionResetPayload(payload)) {
|
|
1034
|
-
void this.applyMutex.run(
|
|
1035
|
-
|
|
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);
|
|
1036
1918
|
});
|
|
1037
1919
|
}
|
|
1038
1920
|
}
|
|
@@ -1042,10 +1924,14 @@ class PersistedCollectionRuntime {
|
|
|
1042
1924
|
if (!queued) {
|
|
1043
1925
|
continue;
|
|
1044
1926
|
}
|
|
1045
|
-
await this.processCommittedTxUnsafe(
|
|
1927
|
+
await this.processCommittedTxUnsafe(
|
|
1928
|
+
queued.txCommitted,
|
|
1929
|
+
queued.lifecycleGeneration
|
|
1930
|
+
);
|
|
1046
1931
|
}
|
|
1047
1932
|
}
|
|
1048
|
-
async processCommittedTxUnsafe(txCommitted) {
|
|
1933
|
+
async processCommittedTxUnsafe(txCommitted, lifecycleGeneration = this.lifecycleGeneration) {
|
|
1934
|
+
if (lifecycleGeneration !== this.lifecycleGeneration) return;
|
|
1049
1935
|
if (txCommitted.term < this.latestTerm) {
|
|
1050
1936
|
return;
|
|
1051
1937
|
}
|
|
@@ -1056,7 +1942,8 @@ class PersistedCollectionRuntime {
|
|
|
1056
1942
|
const hasGapAcrossTerms = txCommitted.term > this.latestTerm && txCommitted.seq > 1;
|
|
1057
1943
|
const hasGap = hasGapInCurrentTerm || hasGapAcrossTerms;
|
|
1058
1944
|
if (hasGap) {
|
|
1059
|
-
await this.recoverFromSeqGapUnsafe();
|
|
1945
|
+
await this.recoverFromSeqGapUnsafe(lifecycleGeneration);
|
|
1946
|
+
if (lifecycleGeneration !== this.lifecycleGeneration) return;
|
|
1060
1947
|
if (txCommitted.term < this.latestTerm || txCommitted.term === this.latestTerm && txCommitted.seq <= this.latestSeq) {
|
|
1061
1948
|
return;
|
|
1062
1949
|
}
|
|
@@ -1066,70 +1953,109 @@ class PersistedCollectionRuntime {
|
|
|
1066
1953
|
txCommitted.seq,
|
|
1067
1954
|
txCommitted.latestRowVersion
|
|
1068
1955
|
);
|
|
1069
|
-
await this.invalidateFromCommittedTxUnsafe(
|
|
1956
|
+
await this.invalidateFromCommittedTxUnsafe(
|
|
1957
|
+
txCommitted,
|
|
1958
|
+
this.persistence.adapter
|
|
1959
|
+
);
|
|
1960
|
+
if (lifecycleGeneration === this.lifecycleGeneration) {
|
|
1961
|
+
await this.flushQueuedTxCommittedUnsafe();
|
|
1962
|
+
}
|
|
1070
1963
|
}
|
|
1071
|
-
async recoverFromSeqGapUnsafe() {
|
|
1964
|
+
async recoverFromSeqGapUnsafe(lifecycleGeneration) {
|
|
1965
|
+
if (lifecycleGeneration !== this.lifecycleGeneration) return;
|
|
1072
1966
|
if (this.persistence.coordinator.pullSince && this.latestRowVersion >= 0) {
|
|
1967
|
+
let pullResponse;
|
|
1073
1968
|
try {
|
|
1074
|
-
|
|
1969
|
+
pullResponse = await this.persistence.coordinator.pullSince(
|
|
1075
1970
|
this.collectionId,
|
|
1076
1971
|
this.latestRowVersion
|
|
1077
1972
|
);
|
|
1973
|
+
} catch (error) {
|
|
1974
|
+
console.warn(`Failed pullSince recovery attempt:`, error);
|
|
1975
|
+
}
|
|
1976
|
+
if (pullResponse) {
|
|
1977
|
+
if (lifecycleGeneration !== this.lifecycleGeneration) return;
|
|
1078
1978
|
if (pullResponse.ok) {
|
|
1079
1979
|
this.observeStreamPosition(
|
|
1080
1980
|
pullResponse.latestTerm,
|
|
1081
1981
|
pullResponse.latestSeq,
|
|
1082
1982
|
pullResponse.latestRowVersion
|
|
1083
1983
|
);
|
|
1084
|
-
if (pullResponse.requiresFullReload
|
|
1085
|
-
await this.
|
|
1984
|
+
if (pullResponse.requiresFullReload) {
|
|
1985
|
+
await this.runInHydrationScope(
|
|
1986
|
+
(adapter) => this.reloadActiveSubsetsUnsafe(adapter)
|
|
1987
|
+
);
|
|
1086
1988
|
return;
|
|
1087
1989
|
}
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
latestRowVersion: delta.latestRowVersion,
|
|
1095
|
-
requiresFullReload: false,
|
|
1096
|
-
changedRows: delta.changedRows,
|
|
1097
|
-
deletedKeys: delta.deletedKeys,
|
|
1098
|
-
rowMetadataMutations: delta.rowMetadataMutations,
|
|
1099
|
-
collectionMetadataMutations: delta.collectionMetadataMutations
|
|
1100
|
-
});
|
|
1990
|
+
const deltas = pullResponse.deltas;
|
|
1991
|
+
if (!deltas) {
|
|
1992
|
+
await this.runInHydrationScope(
|
|
1993
|
+
(adapter) => this.reloadActiveSubsetsUnsafe(adapter)
|
|
1994
|
+
);
|
|
1995
|
+
return;
|
|
1101
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
|
+
});
|
|
1102
2018
|
return;
|
|
1103
2019
|
}
|
|
1104
|
-
} catch (error) {
|
|
1105
|
-
console.warn(`Failed pullSince recovery attempt:`, error);
|
|
1106
2020
|
}
|
|
1107
2021
|
}
|
|
1108
|
-
|
|
2022
|
+
if (lifecycleGeneration !== this.lifecycleGeneration) return;
|
|
2023
|
+
await this.runInHydrationScope(
|
|
2024
|
+
(adapter) => this.truncateAndReloadUnsafe(adapter, lifecycleGeneration)
|
|
2025
|
+
);
|
|
1109
2026
|
if (this.mode === `sync-present`) {
|
|
1110
2027
|
for (const options of this.activeSubsets.values()) {
|
|
1111
2028
|
this.queueRemoteSubsetEnsure(options);
|
|
1112
2029
|
}
|
|
1113
2030
|
}
|
|
1114
2031
|
}
|
|
1115
|
-
async truncateAndReloadUnsafe() {
|
|
2032
|
+
async truncateAndReloadUnsafe(adapter, lifecycleGeneration = this.lifecycleGeneration) {
|
|
2033
|
+
if (lifecycleGeneration !== this.lifecycleGeneration) return;
|
|
2034
|
+
this.hydratedDemands.clear();
|
|
2035
|
+
this.resetSequence++;
|
|
1116
2036
|
if (this.syncControls.begin && this.syncControls.commit) {
|
|
1117
|
-
this.withInternalApply(() => {
|
|
2037
|
+
const applied = this.withInternalApply(() => {
|
|
1118
2038
|
this.syncControls.begin?.({ immediate: true });
|
|
1119
2039
|
this.syncControls.truncate?.();
|
|
1120
|
-
this.syncControls.commit?.();
|
|
2040
|
+
return this.syncControls.commit?.() ?? true;
|
|
1121
2041
|
});
|
|
2042
|
+
if (applied !== true) await applied;
|
|
1122
2043
|
}
|
|
1123
|
-
|
|
2044
|
+
if (lifecycleGeneration !== this.lifecycleGeneration) return;
|
|
2045
|
+
await this.reloadActiveSubsetsUnsafe(adapter);
|
|
1124
2046
|
}
|
|
1125
|
-
async invalidateFromCommittedTxUnsafe(txCommitted) {
|
|
2047
|
+
async invalidateFromCommittedTxUnsafe(txCommitted, adapter) {
|
|
2048
|
+
const reloadActiveSubsets = () => this.runInHydrationScope(
|
|
2049
|
+
(scopedAdapter) => this.reloadActiveSubsetsUnsafe(scopedAdapter),
|
|
2050
|
+
adapter
|
|
2051
|
+
);
|
|
1126
2052
|
if (txCommitted.requiresFullReload) {
|
|
1127
|
-
await
|
|
2053
|
+
await reloadActiveSubsets();
|
|
1128
2054
|
return;
|
|
1129
2055
|
}
|
|
1130
2056
|
const changedKeyCount = txCommitted.changedRows.length + txCommitted.deletedKeys.length;
|
|
1131
2057
|
if (changedKeyCount > TARGETED_INVALIDATION_KEY_LIMIT) {
|
|
1132
|
-
await
|
|
2058
|
+
await reloadActiveSubsets();
|
|
1133
2059
|
return;
|
|
1134
2060
|
}
|
|
1135
2061
|
const hasPaginatedSubset = Array.from(this.activeSubsets.values()).some(
|
|
@@ -1139,13 +2065,13 @@ class PersistedCollectionRuntime {
|
|
|
1139
2065
|
await this.applyTargetedInvalidationUnsafe(txCommitted);
|
|
1140
2066
|
return;
|
|
1141
2067
|
}
|
|
1142
|
-
await
|
|
2068
|
+
await reloadActiveSubsets();
|
|
1143
2069
|
}
|
|
1144
2070
|
async applyTargetedInvalidationUnsafe(txCommitted) {
|
|
1145
2071
|
const subsetEvaluators = Array.from(this.activeSubsets.values()).map(
|
|
1146
2072
|
(opt) => opt.where ? db.compileSingleRowExpression(opt.where) : null
|
|
1147
2073
|
);
|
|
1148
|
-
this.withInternalApply(() => {
|
|
2074
|
+
const applied = this.withInternalApply(() => {
|
|
1149
2075
|
this.syncControls.begin?.({ immediate: true });
|
|
1150
2076
|
for (const {
|
|
1151
2077
|
key: changedKey,
|
|
@@ -1184,17 +2110,26 @@ class PersistedCollectionRuntime {
|
|
|
1184
2110
|
);
|
|
1185
2111
|
}
|
|
1186
2112
|
});
|
|
1187
|
-
this.syncControls.commit?.();
|
|
2113
|
+
return this.syncControls.commit?.() ?? true;
|
|
1188
2114
|
});
|
|
2115
|
+
if (applied !== true) await applied;
|
|
1189
2116
|
}
|
|
1190
|
-
async reloadActiveSubsetsUnsafe() {
|
|
2117
|
+
async reloadActiveSubsetsUnsafe(adapter) {
|
|
2118
|
+
const lifecycleGeneration = this.lifecycleGeneration;
|
|
2119
|
+
const truncateGeneration = this.sourceTruncateGeneration;
|
|
1191
2120
|
const activeSubsetOptions = this.activeSubsets.size > 0 ? Array.from(this.activeSubsets.values()) : [{}];
|
|
1192
|
-
this.
|
|
2121
|
+
this.hydratedDemands.clear();
|
|
2122
|
+
this.hydrationSequence++;
|
|
2123
|
+
const hydrationContext = { suppliedRowKeys: /* @__PURE__ */ new Set() };
|
|
2124
|
+
this.activeHydrationContext = hydrationContext;
|
|
2125
|
+
this.hydratingGeneration = lifecycleGeneration;
|
|
1193
2126
|
try {
|
|
1194
2127
|
const mergedRows = /* @__PURE__ */ new Map();
|
|
1195
|
-
const collectionMetadata = await this.loadCollectionMetadataSnapshot();
|
|
2128
|
+
const collectionMetadata = await this.loadCollectionMetadataSnapshot(adapter);
|
|
2129
|
+
if (lifecycleGeneration !== this.lifecycleGeneration) return;
|
|
1196
2130
|
for (const options of activeSubsetOptions) {
|
|
1197
|
-
const subsetRows = await this.loadSubsetRowsUnsafe(options);
|
|
2131
|
+
const subsetRows = await this.loadSubsetRowsUnsafe(options, adapter);
|
|
2132
|
+
if (lifecycleGeneration !== this.lifecycleGeneration) return;
|
|
1198
2133
|
for (const row of subsetRows) {
|
|
1199
2134
|
mergedRows.set(row.key, {
|
|
1200
2135
|
value: row.value,
|
|
@@ -1202,7 +2137,10 @@ class PersistedCollectionRuntime {
|
|
|
1202
2137
|
});
|
|
1203
2138
|
}
|
|
1204
2139
|
}
|
|
1205
|
-
|
|
2140
|
+
for (const key of mergedRows.keys()) {
|
|
2141
|
+
hydrationContext.suppliedRowKeys.add(key);
|
|
2142
|
+
}
|
|
2143
|
+
const applied = this.replaceCollectionSnapshot(
|
|
1206
2144
|
Array.from(mergedRows.entries()).map(([key, row]) => ({
|
|
1207
2145
|
key,
|
|
1208
2146
|
value: row.value,
|
|
@@ -1210,11 +2148,25 @@ class PersistedCollectionRuntime {
|
|
|
1210
2148
|
})),
|
|
1211
2149
|
collectionMetadata
|
|
1212
2150
|
);
|
|
2151
|
+
if (applied !== true) await applied;
|
|
1213
2152
|
} finally {
|
|
1214
|
-
this.
|
|
2153
|
+
if (this.activeHydrationContext === hydrationContext && this.hydratingGeneration === lifecycleGeneration) {
|
|
2154
|
+
this.hydratingGeneration = null;
|
|
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
|
+
}
|
|
1215
2169
|
}
|
|
1216
|
-
await this.flushQueuedHydrationTransactionsUnsafe();
|
|
1217
|
-
await this.flushQueuedTxCommittedUnsafe();
|
|
1218
2170
|
}
|
|
1219
2171
|
attachIndexLifecycleListeners() {
|
|
1220
2172
|
if (!this.collection || this.indexAddedUnsubscribe || this.indexRemovedUnsubscribe) {
|
|
@@ -1230,14 +2182,26 @@ class PersistedCollectionRuntime {
|
|
|
1230
2182
|
}
|
|
1231
2183
|
);
|
|
1232
2184
|
}
|
|
1233
|
-
async bootstrapPersistedIndexes(indexMetadataSnapshot) {
|
|
2185
|
+
async bootstrapPersistedIndexes(indexMetadataSnapshot, adapter = this.persistence.adapter) {
|
|
1234
2186
|
const collection = this.collection;
|
|
1235
2187
|
if (!collection && !indexMetadataSnapshot) {
|
|
1236
|
-
return;
|
|
2188
|
+
return /* @__PURE__ */ new Set();
|
|
1237
2189
|
}
|
|
1238
2190
|
const indexMetadata = indexMetadataSnapshot ?? collection?.getIndexMetadata() ?? [];
|
|
2191
|
+
const completedLocalIndexSignatures = /* @__PURE__ */ new Set();
|
|
1239
2192
|
for (const metadata of indexMetadata) {
|
|
1240
|
-
await this.
|
|
2193
|
+
if (await this.ensureLocalPersistedIndex(metadata, adapter)) {
|
|
2194
|
+
completedLocalIndexSignatures.add(metadata.signature);
|
|
2195
|
+
}
|
|
2196
|
+
}
|
|
2197
|
+
return completedLocalIndexSignatures;
|
|
2198
|
+
}
|
|
2199
|
+
async requestCoordinatorPersistedIndexes(indexMetadata, completedLocalIndexSignatures) {
|
|
2200
|
+
for (const metadata of indexMetadata) {
|
|
2201
|
+
await this.requestCoordinatorPersistedIndex(
|
|
2202
|
+
metadata,
|
|
2203
|
+
completedLocalIndexSignatures.has(metadata.signature)
|
|
2204
|
+
);
|
|
1241
2205
|
}
|
|
1242
2206
|
}
|
|
1243
2207
|
buildPersistedIndexSpec(index) {
|
|
@@ -1250,22 +2214,36 @@ class PersistedCollectionRuntime {
|
|
|
1250
2214
|
}
|
|
1251
2215
|
};
|
|
1252
2216
|
}
|
|
1253
|
-
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) {
|
|
1254
2225
|
const spec = this.buildPersistedIndexSpec(indexMetadata);
|
|
1255
2226
|
try {
|
|
1256
|
-
await
|
|
2227
|
+
await adapter.ensureIndex(
|
|
1257
2228
|
this.collectionId,
|
|
1258
2229
|
indexMetadata.signature,
|
|
1259
2230
|
spec
|
|
1260
2231
|
);
|
|
2232
|
+
return true;
|
|
1261
2233
|
} catch (error) {
|
|
1262
2234
|
console.warn(`Failed to ensure persisted index in adapter:`, error);
|
|
2235
|
+
return false;
|
|
1263
2236
|
}
|
|
2237
|
+
}
|
|
2238
|
+
async requestCoordinatorPersistedIndex(indexMetadata, completedLocally) {
|
|
2239
|
+
const spec = this.buildPersistedIndexSpec(indexMetadata);
|
|
1264
2240
|
try {
|
|
1265
2241
|
await this.persistence.coordinator.requestEnsurePersistedIndex(
|
|
1266
2242
|
this.collectionId,
|
|
1267
2243
|
indexMetadata.signature,
|
|
1268
|
-
spec
|
|
2244
|
+
spec,
|
|
2245
|
+
completedLocally ? this.persistence.adapter : void 0,
|
|
2246
|
+
completedLocally
|
|
1269
2247
|
);
|
|
1270
2248
|
} catch (error) {
|
|
1271
2249
|
console.warn(
|
|
@@ -1288,65 +2266,327 @@ class PersistedCollectionRuntime {
|
|
|
1288
2266
|
}
|
|
1289
2267
|
}
|
|
1290
2268
|
}
|
|
1291
|
-
function createWrappedSyncConfig(sourceSyncConfig, runtime) {
|
|
2269
|
+
function createWrappedSyncConfig(sourceSyncConfig, runtime, persistedReadiness) {
|
|
1292
2270
|
return {
|
|
1293
2271
|
...sourceSyncConfig,
|
|
1294
2272
|
sync: (params) => {
|
|
1295
2273
|
const transactionStack = [];
|
|
2274
|
+
const pendingPublicationTransactions = [];
|
|
1296
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
|
+
};
|
|
1297
2354
|
let fullStartPromise = null;
|
|
2355
|
+
let sourceResultPromise = null;
|
|
2356
|
+
let resolveSourceResultAssigned;
|
|
2357
|
+
const sourceResultAssigned = new Promise((resolve) => {
|
|
2358
|
+
resolveSourceResultAssigned = resolve;
|
|
2359
|
+
});
|
|
2360
|
+
const startupState = { cleanedUp: false };
|
|
2361
|
+
const isCleanedUp = () => startupState.cleanedUp;
|
|
1298
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
|
+
};
|
|
1299
2466
|
runtime.setSyncControls({
|
|
1300
2467
|
begin: params.begin,
|
|
1301
2468
|
write: params.write,
|
|
1302
2469
|
commit: params.commit,
|
|
1303
2470
|
truncate: params.truncate,
|
|
1304
|
-
metadata: params.metadata ?? null
|
|
2471
|
+
metadata: params.metadata ?? null,
|
|
2472
|
+
markError: params.markError
|
|
1305
2473
|
});
|
|
1306
2474
|
runtime.setCollection(
|
|
1307
2475
|
params.collection
|
|
1308
2476
|
);
|
|
2477
|
+
const persistenceCapability = {
|
|
2478
|
+
protocol: db.SYNC_PERSISTENCE_PROTOCOL,
|
|
2479
|
+
version: db.SYNC_PERSISTENCE_VERSION,
|
|
2480
|
+
hydrateBaseline: async () => {
|
|
2481
|
+
if (startupState.cleanedUp) return;
|
|
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 () => {
|
|
2491
|
+
if (startupState.cleanedUp) return;
|
|
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: () => {
|
|
2500
|
+
if (startupState.cleanedUp) return;
|
|
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
|
+
};
|
|
1309
2511
|
const wrappedParams = {
|
|
1310
2512
|
...params,
|
|
2513
|
+
markError: reportUpstreamError,
|
|
1311
2514
|
markReady: () => {
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
)
|
|
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;
|
|
1319
2526
|
params.markReady();
|
|
1320
|
-
});
|
|
2527
|
+
}).catch(() => void 0);
|
|
1321
2528
|
},
|
|
1322
2529
|
begin: (options) => {
|
|
2530
|
+
if (startupState.cleanedUp) return void 0;
|
|
2531
|
+
const terminalFailure = getTerminalFailure();
|
|
2532
|
+
const internal = runtime.isApplyingInternally();
|
|
1323
2533
|
const transaction = {
|
|
1324
2534
|
operations: [],
|
|
2535
|
+
partialUpdateOperationIndexes: /* @__PURE__ */ new Set(),
|
|
1325
2536
|
rowMetadataWrites: /* @__PURE__ */ new Map(),
|
|
1326
2537
|
collectionMetadataWrites: /* @__PURE__ */ new Map(),
|
|
2538
|
+
deferredHydrationMetadataDeleteKeys: /* @__PURE__ */ new Set(),
|
|
1327
2539
|
truncate: false,
|
|
1328
|
-
internal
|
|
1329
|
-
|
|
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 }
|
|
1330
2549
|
};
|
|
1331
2550
|
transactionStack.push(transaction);
|
|
1332
|
-
if (!transaction.queuedBecauseHydrating) {
|
|
1333
|
-
params.begin(options);
|
|
1334
|
-
}
|
|
1335
2551
|
},
|
|
1336
2552
|
write: (message) => {
|
|
1337
|
-
|
|
2553
|
+
if (startupState.cleanedUp) return;
|
|
1338
2554
|
const openTransaction = getOpenTransaction();
|
|
1339
|
-
|
|
1340
|
-
|
|
2555
|
+
const terminalFailure = openTransaction?.terminalFailure ?? getTerminalFailure();
|
|
2556
|
+
if (terminalFailure) {
|
|
2557
|
+
if (openTransaction)
|
|
2558
|
+
openTransaction.terminalFailure = terminalFailure;
|
|
1341
2559
|
return;
|
|
1342
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
|
+
}
|
|
1343
2578
|
openTransaction.operations.push(normalization.operation);
|
|
2579
|
+
openTransaction.operationKeys.add(normalization.operation.key);
|
|
1344
2580
|
if (normalization.operation.type === `delete`) {
|
|
1345
2581
|
openTransaction.rowMetadataWrites.set(normalization.operation.key, {
|
|
1346
2582
|
type: `delete`
|
|
1347
2583
|
});
|
|
1348
2584
|
} else if (message.type === `insert` && normalization.operation.metadata === void 0) {
|
|
1349
|
-
if (
|
|
2585
|
+
if (openTransaction.queuedBecauseHydrating) {
|
|
2586
|
+
openTransaction.deferredHydrationMetadataDeleteKeys.add(
|
|
2587
|
+
normalization.operation.key
|
|
2588
|
+
);
|
|
2589
|
+
} else if (!openTransaction.rowMetadataWrites.has(
|
|
1350
2590
|
normalization.operation.key
|
|
1351
2591
|
)) {
|
|
1352
2592
|
openTransaction.rowMetadataWrites.set(
|
|
@@ -1362,13 +2602,12 @@ function createWrappedSyncConfig(sourceSyncConfig, runtime) {
|
|
|
1362
2602
|
value: normalization.operation.metadata
|
|
1363
2603
|
});
|
|
1364
2604
|
}
|
|
1365
|
-
if (!openTransaction.queuedBecauseHydrating) {
|
|
1366
|
-
params.write(normalization.forwardMessage);
|
|
1367
|
-
}
|
|
1368
2605
|
},
|
|
1369
2606
|
metadata: params.metadata ? {
|
|
2607
|
+
persistence: persistenceCapability,
|
|
1370
2608
|
row: {
|
|
1371
2609
|
get: (key) => {
|
|
2610
|
+
if (startupState.cleanedUp) return void 0;
|
|
1372
2611
|
const openTransaction = getOpenTransaction();
|
|
1373
2612
|
const pendingWrite = openTransaction?.rowMetadataWrites.get(key);
|
|
1374
2613
|
if (pendingWrite) {
|
|
@@ -1377,11 +2616,17 @@ function createWrappedSyncConfig(sourceSyncConfig, runtime) {
|
|
|
1377
2616
|
if (openTransaction?.truncate) {
|
|
1378
2617
|
return void 0;
|
|
1379
2618
|
}
|
|
2619
|
+
const pending = getPendingRowMetadataWrite(key);
|
|
2620
|
+
if (pending.found) {
|
|
2621
|
+
return pending.write.type === `delete` ? void 0 : pending.write.value;
|
|
2622
|
+
}
|
|
1380
2623
|
return params.metadata.row.get(key);
|
|
1381
2624
|
},
|
|
1382
|
-
scanPersisted: (options) => runtime.scanPersistedRows(options),
|
|
1383
2625
|
set: (key, value) => {
|
|
2626
|
+
if (startupState.cleanedUp) return;
|
|
1384
2627
|
const openTransaction = getOpenTransaction();
|
|
2628
|
+
if (openTransaction?.terminalFailure ?? getTerminalFailure())
|
|
2629
|
+
return;
|
|
1385
2630
|
if (!openTransaction) {
|
|
1386
2631
|
throw new errors.InvalidPersistedCollectionConfigError(
|
|
1387
2632
|
`metadata.row.set must be called within an open sync transaction`
|
|
@@ -1391,12 +2636,12 @@ function createWrappedSyncConfig(sourceSyncConfig, runtime) {
|
|
|
1391
2636
|
type: `set`,
|
|
1392
2637
|
value
|
|
1393
2638
|
});
|
|
1394
|
-
if (!openTransaction.queuedBecauseHydrating) {
|
|
1395
|
-
params.metadata.row.set(key, value);
|
|
1396
|
-
}
|
|
1397
2639
|
},
|
|
1398
2640
|
delete: (key) => {
|
|
2641
|
+
if (startupState.cleanedUp) return;
|
|
1399
2642
|
const openTransaction = getOpenTransaction();
|
|
2643
|
+
if (openTransaction?.terminalFailure ?? getTerminalFailure())
|
|
2644
|
+
return;
|
|
1400
2645
|
if (!openTransaction) {
|
|
1401
2646
|
throw new errors.InvalidPersistedCollectionConfigError(
|
|
1402
2647
|
`metadata.row.delete must be called within an open sync transaction`
|
|
@@ -1405,22 +2650,27 @@ function createWrappedSyncConfig(sourceSyncConfig, runtime) {
|
|
|
1405
2650
|
openTransaction.rowMetadataWrites.set(key, {
|
|
1406
2651
|
type: `delete`
|
|
1407
2652
|
});
|
|
1408
|
-
if (!openTransaction.queuedBecauseHydrating) {
|
|
1409
|
-
params.metadata.row.delete(key);
|
|
1410
|
-
}
|
|
1411
2653
|
}
|
|
1412
2654
|
},
|
|
1413
2655
|
collection: {
|
|
1414
2656
|
get: (key) => {
|
|
2657
|
+
if (startupState.cleanedUp) return void 0;
|
|
1415
2658
|
const openTransaction = getOpenTransaction();
|
|
1416
2659
|
const pendingWrite = openTransaction?.collectionMetadataWrites.get(key);
|
|
1417
2660
|
if (pendingWrite) {
|
|
1418
2661
|
return pendingWrite.type === `delete` ? void 0 : pendingWrite.value;
|
|
1419
2662
|
}
|
|
2663
|
+
const pending = getPendingCollectionMetadataWrite(key);
|
|
2664
|
+
if (pending.found) {
|
|
2665
|
+
return pending.write.type === `delete` ? void 0 : pending.write.value;
|
|
2666
|
+
}
|
|
1420
2667
|
return params.metadata.collection.get(key);
|
|
1421
2668
|
},
|
|
1422
2669
|
set: (key, value) => {
|
|
2670
|
+
if (startupState.cleanedUp) return;
|
|
1423
2671
|
const openTransaction = getOpenTransaction();
|
|
2672
|
+
if (openTransaction?.terminalFailure ?? getTerminalFailure())
|
|
2673
|
+
return;
|
|
1424
2674
|
if (!openTransaction) {
|
|
1425
2675
|
throw new errors.InvalidPersistedCollectionConfigError(
|
|
1426
2676
|
`metadata.collection.set must be called within an open sync transaction`
|
|
@@ -1430,12 +2680,12 @@ function createWrappedSyncConfig(sourceSyncConfig, runtime) {
|
|
|
1430
2680
|
type: `set`,
|
|
1431
2681
|
value
|
|
1432
2682
|
});
|
|
1433
|
-
if (!openTransaction.queuedBecauseHydrating) {
|
|
1434
|
-
params.metadata.collection.set(key, value);
|
|
1435
|
-
}
|
|
1436
2683
|
},
|
|
1437
2684
|
delete: (key) => {
|
|
2685
|
+
if (startupState.cleanedUp) return;
|
|
1438
2686
|
const openTransaction = getOpenTransaction();
|
|
2687
|
+
if (openTransaction?.terminalFailure ?? getTerminalFailure())
|
|
2688
|
+
return;
|
|
1439
2689
|
if (!openTransaction) {
|
|
1440
2690
|
throw new errors.InvalidPersistedCollectionConfigError(
|
|
1441
2691
|
`metadata.collection.delete must be called within an open sync transaction`
|
|
@@ -1444,14 +2694,31 @@ function createWrappedSyncConfig(sourceSyncConfig, runtime) {
|
|
|
1444
2694
|
openTransaction.collectionMetadataWrites.set(key, {
|
|
1445
2695
|
type: `delete`
|
|
1446
2696
|
});
|
|
1447
|
-
if (!openTransaction.queuedBecauseHydrating) {
|
|
1448
|
-
params.metadata.collection.delete(key);
|
|
1449
|
-
}
|
|
1450
2697
|
},
|
|
1451
2698
|
list: (prefix) => {
|
|
2699
|
+
if (startupState.cleanedUp) return [];
|
|
1452
2700
|
const merged = new Map(
|
|
1453
2701
|
params.metadata.collection.list().map(({ key, value }) => [key, value])
|
|
1454
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
|
+
}
|
|
1455
2722
|
const openTransaction = getOpenTransaction();
|
|
1456
2723
|
if (openTransaction) {
|
|
1457
2724
|
for (const [
|
|
@@ -1473,29 +2740,92 @@ function createWrappedSyncConfig(sourceSyncConfig, runtime) {
|
|
|
1473
2740
|
}
|
|
1474
2741
|
} : void 0,
|
|
1475
2742
|
truncate: () => {
|
|
2743
|
+
if (startupState.cleanedUp) return;
|
|
2744
|
+
runtime.noteSourceTruncate();
|
|
1476
2745
|
const openTransaction = getOpenTransaction();
|
|
2746
|
+
if (openTransaction?.terminalFailure ?? getTerminalFailure()) return;
|
|
1477
2747
|
if (!openTransaction) {
|
|
1478
|
-
|
|
1479
|
-
return;
|
|
2748
|
+
throw new db.NoPendingSyncTransactionWriteError();
|
|
1480
2749
|
}
|
|
1481
2750
|
openTransaction.operations = [];
|
|
2751
|
+
openTransaction.operationKeys.clear();
|
|
2752
|
+
openTransaction.partialUpdateOperationIndexes.clear();
|
|
1482
2753
|
openTransaction.rowMetadataWrites.clear();
|
|
2754
|
+
openTransaction.deferredHydrationMetadataDeleteKeys.clear();
|
|
1483
2755
|
openTransaction.truncate = true;
|
|
1484
|
-
if (!openTransaction.queuedBecauseHydrating) {
|
|
1485
|
-
params.truncate();
|
|
1486
|
-
}
|
|
1487
2756
|
},
|
|
1488
2757
|
commit: (signal) => {
|
|
2758
|
+
if (startupState.cleanedUp) return true;
|
|
1489
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
|
+
}
|
|
1490
2771
|
if (!openTransaction) {
|
|
1491
|
-
|
|
2772
|
+
throw new db.NoPendingSyncTransactionCommitError();
|
|
1492
2773
|
}
|
|
1493
|
-
if (openTransaction.
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
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()) {
|
|
1499
2829
|
let resolveApplied;
|
|
1500
2830
|
let rejectApplied;
|
|
1501
2831
|
const applied2 = new Promise((resolve, reject) => {
|
|
@@ -1504,66 +2834,118 @@ function createWrappedSyncConfig(sourceSyncConfig, runtime) {
|
|
|
1504
2834
|
});
|
|
1505
2835
|
void applied2.catch(() => void 0);
|
|
1506
2836
|
runtime.queueHydrationBufferedTransaction({
|
|
1507
|
-
|
|
1508
|
-
rowMetadataWrites: openTransaction.rowMetadataWrites,
|
|
1509
|
-
collectionMetadataWrites: openTransaction.collectionMetadataWrites,
|
|
1510
|
-
truncate: openTransaction.truncate,
|
|
1511
|
-
internal: openTransaction.internal,
|
|
1512
|
-
signal,
|
|
2837
|
+
...transaction,
|
|
1513
2838
|
resolveApplied,
|
|
1514
2839
|
rejectApplied
|
|
1515
2840
|
});
|
|
2841
|
+
settlePublicationAdmissionWaiters(openTransaction);
|
|
2842
|
+
settleRuntimeTransaction(openTransaction, applied2);
|
|
1516
2843
|
return applied2;
|
|
1517
2844
|
}
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
rowMetadataWrites: openTransaction.rowMetadataWrites,
|
|
1525
|
-
collectionMetadataWrites: openTransaction.collectionMetadataWrites,
|
|
1526
|
-
truncate: openTransaction.truncate,
|
|
1527
|
-
internal: false
|
|
1528
|
-
});
|
|
1529
|
-
};
|
|
1530
|
-
const persisted = persistAfterApplication();
|
|
1531
|
-
void persisted.catch(() => void 0);
|
|
1532
|
-
return persisted;
|
|
2845
|
+
let applied;
|
|
2846
|
+
try {
|
|
2847
|
+
applied = runtime.applyHydrationBufferedTransaction(transaction);
|
|
2848
|
+
} catch (error) {
|
|
2849
|
+
settlePublicationAdmissionWaiters(openTransaction, error);
|
|
2850
|
+
throw error;
|
|
1533
2851
|
}
|
|
2852
|
+
settlePublicationAdmissionWaiters(openTransaction);
|
|
2853
|
+
settleRuntimeTransaction(openTransaction, applied);
|
|
1534
2854
|
return applied;
|
|
1535
2855
|
}
|
|
1536
2856
|
};
|
|
1537
2857
|
let sourceResult = {};
|
|
1538
|
-
|
|
2858
|
+
let sourceResultSettled = false;
|
|
2859
|
+
let activeSourceSyncEntry;
|
|
1539
2860
|
fullStartPromise = runtime.ensureStarted();
|
|
1540
|
-
|
|
2861
|
+
void fullStartPromise.catch(() => void 0);
|
|
2862
|
+
sourceResultPromise = (async () => {
|
|
1541
2863
|
await runtime.ensureStartupMetadataLoaded();
|
|
1542
2864
|
if (startupState.cleanedUp) {
|
|
2865
|
+
sourceResultSettled = true;
|
|
1543
2866
|
return sourceResult;
|
|
1544
2867
|
}
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
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;
|
|
1548
2899
|
return sourceResult;
|
|
1549
2900
|
})();
|
|
2901
|
+
resolveSourceResultAssigned();
|
|
2902
|
+
void sourceResultPromise.catch((error) => {
|
|
2903
|
+
reportUpstreamError(error);
|
|
2904
|
+
});
|
|
1550
2905
|
return {
|
|
1551
|
-
cleanup: () => {
|
|
2906
|
+
cleanup: async () => {
|
|
1552
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
|
+
}
|
|
1553
2913
|
acquisitions.clear();
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
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;
|
|
1557
2944
|
},
|
|
1558
|
-
loadSubset:
|
|
2945
|
+
loadSubset: (options) => {
|
|
1559
2946
|
const acquisition = { forwarded: false };
|
|
1560
2947
|
acquisitions.set(options, acquisition);
|
|
1561
|
-
|
|
1562
|
-
const resolvedSourceResult = await sourceResultPromise;
|
|
1563
|
-
if (startupState.cleanedUp || acquisitions.get(options) !== acquisition) {
|
|
1564
|
-
return;
|
|
1565
|
-
}
|
|
1566
|
-
return runtime.loadSubset(options, (loadOptions) => {
|
|
2948
|
+
const forwardUpstream = (resolvedSourceResult, loadOptions) => {
|
|
1567
2949
|
if (startupState.cleanedUp || acquisitions.get(options) !== acquisition) {
|
|
1568
2950
|
return true;
|
|
1569
2951
|
}
|
|
@@ -1575,10 +2957,42 @@ function createWrappedSyncConfig(sourceSyncConfig, runtime) {
|
|
|
1575
2957
|
acquisition.forwarded = false;
|
|
1576
2958
|
throw error;
|
|
1577
2959
|
}
|
|
1578
|
-
}
|
|
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
|
+
})();
|
|
1579
2992
|
},
|
|
1580
2993
|
unloadSubset: (options) => {
|
|
1581
2994
|
const acquisition = acquisitions.get(options);
|
|
2995
|
+
acquisition?.cancelAdmissionWait?.();
|
|
1582
2996
|
acquisitions.delete(options);
|
|
1583
2997
|
runtime.unloadSubset(
|
|
1584
2998
|
options,
|
|
@@ -1597,23 +3011,19 @@ function createLoopbackSyncConfig(runtime) {
|
|
|
1597
3011
|
write: params.write,
|
|
1598
3012
|
commit: params.commit,
|
|
1599
3013
|
truncate: params.truncate,
|
|
1600
|
-
metadata: params.metadata ?? null
|
|
3014
|
+
metadata: params.metadata ?? null,
|
|
3015
|
+
markError: params.markError
|
|
1601
3016
|
});
|
|
1602
3017
|
runtime.setCollection(
|
|
1603
3018
|
params.collection
|
|
1604
3019
|
);
|
|
1605
3020
|
void runtime.ensureStarted().then(() => {
|
|
3021
|
+
if (runtime.getCurrentTerminalFailure()) return;
|
|
1606
3022
|
params.markReady();
|
|
1607
|
-
}).catch((
|
|
1608
|
-
console.warn(`Failed persisted loopback startup:`, error);
|
|
1609
|
-
params.markReady();
|
|
1610
|
-
});
|
|
3023
|
+
}).catch(() => void 0);
|
|
1611
3024
|
return {
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
runtime.clearSyncControls();
|
|
1615
|
-
},
|
|
1616
|
-
loadSubset: (options) => runtime.loadSubset(options),
|
|
3025
|
+
loadSubset: (options) => runtime.loadHydratedSubset(options) ?? runtime.loadSubset(options),
|
|
3026
|
+
cleanup: () => runtime.cleanup(),
|
|
1617
3027
|
unloadSubset: (options) => runtime.unloadSubset(options)
|
|
1618
3028
|
};
|
|
1619
3029
|
},
|
|
@@ -1628,13 +3038,24 @@ function persistedCollectionOptions(options) {
|
|
|
1628
3038
|
`persistedCollectionOptions requires a persistence adapter`
|
|
1629
3039
|
);
|
|
1630
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;
|
|
1631
3048
|
if (hasOwnSyncKey(options)) {
|
|
1632
3049
|
if (!isValidSyncConfig(options.sync)) {
|
|
1633
3050
|
throw new errors.InvalidSyncConfigError(
|
|
1634
3051
|
`when the "sync" key is present it must provide a callable sync function`
|
|
1635
3052
|
);
|
|
1636
3053
|
}
|
|
1637
|
-
const {
|
|
3054
|
+
const {
|
|
3055
|
+
schemaVersion: schemaVersion2,
|
|
3056
|
+
initialRender: _initialRender2,
|
|
3057
|
+
...syncOptions
|
|
3058
|
+
} = options;
|
|
1638
3059
|
const collectionId2 = syncOptions.id ?? `persisted-collection:${db.safeRandomUUID()}`;
|
|
1639
3060
|
const persistence2 = resolvePersistenceForCollection(
|
|
1640
3061
|
syncOptions.persistence,
|
|
@@ -1649,13 +3070,19 @@ function persistedCollectionOptions(options) {
|
|
|
1649
3070
|
collectionId2,
|
|
1650
3071
|
persistence2,
|
|
1651
3072
|
syncOptions.syncMode ?? `eager`,
|
|
1652
|
-
collectionId2
|
|
3073
|
+
collectionId2,
|
|
3074
|
+
persistedReadinessTracker
|
|
1653
3075
|
);
|
|
1654
3076
|
const result2 = {
|
|
1655
3077
|
...syncOptions,
|
|
1656
3078
|
id: collectionId2,
|
|
1657
|
-
sync: createWrappedSyncConfig(
|
|
1658
|
-
|
|
3079
|
+
sync: createWrappedSyncConfig(
|
|
3080
|
+
syncOptions.sync,
|
|
3081
|
+
runtime2,
|
|
3082
|
+
persistedReadinessTracker
|
|
3083
|
+
),
|
|
3084
|
+
persistence: persistence2,
|
|
3085
|
+
...persistedReadinessTracker ? { [db.PERSISTED_READINESS]: persistedReadinessTracker } : {}
|
|
1659
3086
|
};
|
|
1660
3087
|
return db.withCollectionConfigFactory(
|
|
1661
3088
|
result2,
|
|
@@ -1665,7 +3092,11 @@ function persistedCollectionOptions(options) {
|
|
|
1665
3092
|
})
|
|
1666
3093
|
);
|
|
1667
3094
|
}
|
|
1668
|
-
const {
|
|
3095
|
+
const {
|
|
3096
|
+
schemaVersion,
|
|
3097
|
+
initialRender: _initialRender,
|
|
3098
|
+
...localOnlyOptions
|
|
3099
|
+
} = options;
|
|
1669
3100
|
const collectionId = localOnlyOptions.id ?? `persisted-collection:${db.safeRandomUUID()}`;
|
|
1670
3101
|
const persistence = resolvePersistenceForCollection(
|
|
1671
3102
|
localOnlyOptions.persistence,
|
|
@@ -1680,32 +3111,39 @@ function persistedCollectionOptions(options) {
|
|
|
1680
3111
|
collectionId,
|
|
1681
3112
|
persistence,
|
|
1682
3113
|
localOnlyOptions.syncMode ?? `eager`,
|
|
1683
|
-
localOnlyOptions.id ?? DEFAULT_DB_NAME
|
|
3114
|
+
localOnlyOptions.id ?? DEFAULT_DB_NAME,
|
|
3115
|
+
persistedReadinessTracker
|
|
1684
3116
|
);
|
|
1685
3117
|
const wrappedOnInsert = async (params) => {
|
|
3118
|
+
const lifecycleGeneration = runtime.getLifecycleGeneration();
|
|
1686
3119
|
const handlerResult = localOnlyOptions.onInsert ? await localOnlyOptions.onInsert(
|
|
1687
3120
|
params
|
|
1688
3121
|
) : void 0;
|
|
1689
3122
|
await runtime.persistAndConfirmCollectionMutations(
|
|
1690
|
-
params.transaction.mutations
|
|
3123
|
+
params.transaction.mutations,
|
|
3124
|
+
lifecycleGeneration
|
|
1691
3125
|
);
|
|
1692
3126
|
return handlerResult ?? {};
|
|
1693
3127
|
};
|
|
1694
3128
|
const wrappedOnUpdate = async (params) => {
|
|
3129
|
+
const lifecycleGeneration = runtime.getLifecycleGeneration();
|
|
1695
3130
|
const handlerResult = localOnlyOptions.onUpdate ? await localOnlyOptions.onUpdate(
|
|
1696
3131
|
params
|
|
1697
3132
|
) : void 0;
|
|
1698
3133
|
await runtime.persistAndConfirmCollectionMutations(
|
|
1699
|
-
params.transaction.mutations
|
|
3134
|
+
params.transaction.mutations,
|
|
3135
|
+
lifecycleGeneration
|
|
1700
3136
|
);
|
|
1701
3137
|
return handlerResult ?? {};
|
|
1702
3138
|
};
|
|
1703
3139
|
const wrappedOnDelete = async (params) => {
|
|
3140
|
+
const lifecycleGeneration = runtime.getLifecycleGeneration();
|
|
1704
3141
|
const handlerResult = localOnlyOptions.onDelete ? await localOnlyOptions.onDelete(
|
|
1705
3142
|
params
|
|
1706
3143
|
) : void 0;
|
|
1707
3144
|
await runtime.persistAndConfirmCollectionMutations(
|
|
1708
|
-
params.transaction.mutations
|
|
3145
|
+
params.transaction.mutations,
|
|
3146
|
+
lifecycleGeneration
|
|
1709
3147
|
);
|
|
1710
3148
|
return handlerResult ?? {};
|
|
1711
3149
|
};
|
|
@@ -1725,6 +3163,7 @@ function persistedCollectionOptions(options) {
|
|
|
1725
3163
|
...localOnlyOptions,
|
|
1726
3164
|
id: collectionId,
|
|
1727
3165
|
persistence,
|
|
3166
|
+
...persistedReadinessTracker ? { [db.PERSISTED_READINESS]: persistedReadinessTracker } : {},
|
|
1728
3167
|
sync: createLoopbackSyncConfig(runtime),
|
|
1729
3168
|
onInsert: wrappedOnInsert,
|
|
1730
3169
|
onUpdate: wrappedOnUpdate,
|
|
@@ -1796,10 +3235,12 @@ function createPersistedTableName(collectionId, prefix = `c`) {
|
|
|
1796
3235
|
const lengthPart = collectionId.length.toString(36);
|
|
1797
3236
|
return `${prefix}_${hashPart}_${lengthPart}`;
|
|
1798
3237
|
}
|
|
3238
|
+
exports.SQLITE_DRIVER_SHARED_LOGICAL_SCHEDULING_KEY = SQLITE_DRIVER_SHARED_LOGICAL_SCHEDULING_KEY;
|
|
1799
3239
|
exports.SingleProcessCoordinator = SingleProcessCoordinator;
|
|
1800
3240
|
exports.createPersistedTableName = createPersistedTableName;
|
|
1801
3241
|
exports.decodePersistedStorageKey = decodePersistedStorageKey;
|
|
1802
3242
|
exports.encodePersistedStorageKey = encodePersistedStorageKey;
|
|
3243
|
+
exports.forwardSQLiteDriverSharedLogicalScheduling = forwardSQLiteDriverSharedLogicalScheduling;
|
|
1803
3244
|
exports.persistedCollectionOptions = persistedCollectionOptions;
|
|
1804
3245
|
exports.validatePersistedCollectionCoordinator = validatePersistedCollectionCoordinator;
|
|
1805
3246
|
//# sourceMappingURL=persisted.cjs.map
|