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