@tanstack/electric-db-collection 0.4.7 → 0.4.10
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/dist/cjs/electric.cjs +681 -447
- package/dist/cjs/electric.cjs.map +1 -1
- package/dist/cjs/sql-compiler.cjs +5 -4
- package/dist/cjs/sql-compiler.cjs.map +1 -1
- package/dist/esm/electric.js +683 -449
- package/dist/esm/electric.js.map +1 -1
- package/dist/esm/sql-compiler.js +5 -4
- package/dist/esm/sql-compiler.js.map +1 -1
- package/package.json +4 -3
- package/src/electric.ts +977 -658
- package/src/sql-compiler.ts +18 -4
package/dist/cjs/electric.cjs
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
3
|
const client = require("@electric-sql/client");
|
|
4
|
-
const store = require("@tanstack/store");
|
|
5
4
|
const DebugModule = require("debug");
|
|
6
5
|
const db = require("@tanstack/db");
|
|
7
6
|
const errors = require("./errors.cjs");
|
|
@@ -10,6 +9,20 @@ const tagIndex = require("./tag-index.cjs");
|
|
|
10
9
|
const debug = DebugModule.debug(`ts/db:electric`);
|
|
11
10
|
const FORCE_DISCONNECT_AND_REFRESH_TIMEOUT_MS = 250;
|
|
12
11
|
const ELECTRIC_TEST_HOOKS = /* @__PURE__ */ Symbol(`electricTestHooks`);
|
|
12
|
+
function exportElectricSyncMeta(evidence) {
|
|
13
|
+
const resume = evidence.hydratedResumeState;
|
|
14
|
+
return {
|
|
15
|
+
version: 1,
|
|
16
|
+
...resume ? { resume } : {},
|
|
17
|
+
seenTxids: Array.from(evidence.seenTxids).sort((a, b) => a - b)
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
function importElectricSyncMeta(evidence, meta) {
|
|
21
|
+
const parsed = parseElectricSyncMeta(meta);
|
|
22
|
+
if (!parsed) return;
|
|
23
|
+
evidence.hydratedResumeState = parsed.resume;
|
|
24
|
+
evidence.seenTxids = new Set(parsed.seenTxids);
|
|
25
|
+
}
|
|
13
26
|
function parseElectricResumeState(value) {
|
|
14
27
|
if (!value || typeof value !== `object`) {
|
|
15
28
|
return void 0;
|
|
@@ -21,7 +34,10 @@ function parseElectricResumeState(value) {
|
|
|
21
34
|
offset: record.offset,
|
|
22
35
|
handle: record.handle,
|
|
23
36
|
shapeId: record.shapeId,
|
|
24
|
-
updatedAt: record.updatedAt
|
|
37
|
+
updatedAt: record.updatedAt,
|
|
38
|
+
...typeof record.requiresTagState === `boolean` && {
|
|
39
|
+
requiresTagState: record.requiresTagState
|
|
40
|
+
}
|
|
25
41
|
};
|
|
26
42
|
}
|
|
27
43
|
if (record.kind === `reset` && typeof record.updatedAt === `number` && Number.isFinite(record.updatedAt)) {
|
|
@@ -76,7 +92,10 @@ function mergeElectricSyncMeta(current, incoming) {
|
|
|
76
92
|
function getNewestElectricResumeState(current, incoming) {
|
|
77
93
|
if (!current) return incoming;
|
|
78
94
|
if (!incoming) return current;
|
|
79
|
-
|
|
95
|
+
if (incoming.updatedAt > current.updatedAt) return incoming;
|
|
96
|
+
if (incoming.updatedAt < current.updatedAt) return current;
|
|
97
|
+
const isSameState = current.kind === incoming.kind && (current.kind === `reset` || incoming.kind === `resume` && current.offset === incoming.offset && current.handle === incoming.handle && current.shapeId === incoming.shapeId);
|
|
98
|
+
return isSameState ? current : { kind: `reset`, updatedAt: current.updatedAt };
|
|
80
99
|
}
|
|
81
100
|
function isUpToDateMessage(message) {
|
|
82
101
|
return client.isControlMessage(message) && message.headers.control === `up-to-date`;
|
|
@@ -149,6 +168,7 @@ function createLoadSubsetDedupe({
|
|
|
149
168
|
}
|
|
150
169
|
const compileOptions = encodeColumnName ? { encodeColumnName } : void 0;
|
|
151
170
|
const logPrefix = collectionId ? `[${collectionId}] ` : ``;
|
|
171
|
+
const abortReason = (abortedSignal) => abortedSignal.reason ?? new db.LoadSubsetOperationAbortedError();
|
|
152
172
|
function handleSnapshotError(error, operation) {
|
|
153
173
|
if (signal.aborted) {
|
|
154
174
|
debug(`${logPrefix}Ignoring ${operation} error during cleanup: %o`, error);
|
|
@@ -159,7 +179,11 @@ function createLoadSubsetDedupe({
|
|
|
159
179
|
}
|
|
160
180
|
const loadSubset = async (opts) => {
|
|
161
181
|
const commitCursor = getCommitCursor();
|
|
162
|
-
|
|
182
|
+
const throwIfAborted = () => {
|
|
183
|
+
if (signal.aborted) throw abortReason(signal);
|
|
184
|
+
if (opts.signal?.aborted) throw abortReason(opts.signal);
|
|
185
|
+
};
|
|
186
|
+
throwIfAborted();
|
|
163
187
|
if (isBufferingInitialSync()) {
|
|
164
188
|
const snapshotParams = sqlCompiler.compileSQL(opts, compileOptions);
|
|
165
189
|
try {
|
|
@@ -195,6 +219,18 @@ function createLoadSubsetDedupe({
|
|
|
195
219
|
const { cursor, where, orderBy, limit } = opts;
|
|
196
220
|
if (stream.isUpToDate) {
|
|
197
221
|
let timeoutId;
|
|
222
|
+
const abortSignals = [signal, opts.signal].filter(
|
|
223
|
+
(candidate) => candidate !== void 0
|
|
224
|
+
);
|
|
225
|
+
let rejectAbort = () => {
|
|
226
|
+
};
|
|
227
|
+
const aborted = new Promise((_resolve, reject) => {
|
|
228
|
+
rejectAbort = reject;
|
|
229
|
+
});
|
|
230
|
+
const abort = (event) => rejectAbort(abortReason(event.currentTarget));
|
|
231
|
+
for (const abortSignal of abortSignals) {
|
|
232
|
+
abortSignal.addEventListener(`abort`, abort, { once: true });
|
|
233
|
+
}
|
|
198
234
|
try {
|
|
199
235
|
await Promise.race([
|
|
200
236
|
stream.forceDisconnectAndRefresh(),
|
|
@@ -203,9 +239,11 @@ function createLoadSubsetDedupe({
|
|
|
203
239
|
resolve,
|
|
204
240
|
FORCE_DISCONNECT_AND_REFRESH_TIMEOUT_MS
|
|
205
241
|
);
|
|
206
|
-
})
|
|
242
|
+
}),
|
|
243
|
+
aborted
|
|
207
244
|
]);
|
|
208
245
|
} catch (error) {
|
|
246
|
+
if (signal.aborted || opts.signal?.aborted) throw error;
|
|
209
247
|
if (handleSnapshotError(error, `forceDisconnectAndRefresh`)) {
|
|
210
248
|
return;
|
|
211
249
|
}
|
|
@@ -215,9 +253,12 @@ function createLoadSubsetDedupe({
|
|
|
215
253
|
);
|
|
216
254
|
} finally {
|
|
217
255
|
clearTimeout(timeoutId);
|
|
256
|
+
for (const abortSignal of abortSignals) {
|
|
257
|
+
abortSignal.removeEventListener(`abort`, abort);
|
|
258
|
+
}
|
|
218
259
|
}
|
|
219
260
|
}
|
|
220
|
-
|
|
261
|
+
throwIfAborted();
|
|
221
262
|
try {
|
|
222
263
|
if (cursor) {
|
|
223
264
|
const whereCurrentOpts = {
|
|
@@ -257,203 +298,239 @@ function createLoadSubsetDedupe({
|
|
|
257
298
|
};
|
|
258
299
|
return new db.DeduplicatedLoadSubset({ loadSubset });
|
|
259
300
|
}
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
301
|
+
class ElectricLifecycle {
|
|
302
|
+
constructor(collectionId) {
|
|
303
|
+
this.collectionId = collectionId;
|
|
304
|
+
this.evidence = {
|
|
305
|
+
seenTxids: /* @__PURE__ */ new Set(),
|
|
306
|
+
seenSnapshots: []
|
|
307
|
+
};
|
|
308
|
+
this.pendingMatches = /* @__PURE__ */ new Map();
|
|
309
|
+
this.pendingTxidWaits = /* @__PURE__ */ new Map();
|
|
310
|
+
this.matchBuffer = {
|
|
311
|
+
committedMessages: [],
|
|
312
|
+
pendingMessages: []
|
|
313
|
+
};
|
|
314
|
+
this.nextWaiterId = 0;
|
|
315
|
+
this.epoch = 0;
|
|
316
|
+
this.active = false;
|
|
317
|
+
this.utils = {
|
|
318
|
+
awaitTxId: (txId, timeout) => this.awaitTxId(txId, timeout),
|
|
319
|
+
awaitMatch: (matchFn, timeout) => this.awaitMatch(matchFn, timeout)
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
start() {
|
|
323
|
+
if (this.active) this.retire();
|
|
324
|
+
this.active = true;
|
|
325
|
+
this.epoch++;
|
|
326
|
+
this.matchBuffer = { committedMessages: [], pendingMessages: [] };
|
|
327
|
+
return this.epoch;
|
|
328
|
+
}
|
|
329
|
+
isActive(epoch) {
|
|
330
|
+
return this.active && this.epoch === epoch;
|
|
331
|
+
}
|
|
332
|
+
retire(epoch) {
|
|
333
|
+
if (epoch !== void 0 && !this.isActive(epoch)) return;
|
|
334
|
+
this.active = false;
|
|
335
|
+
this.epoch++;
|
|
336
|
+
for (const match of this.pendingMatches.values()) {
|
|
337
|
+
clearTimeout(match.timeoutId);
|
|
338
|
+
match.reject(new errors.StreamAbortedError(this.collectionId));
|
|
278
339
|
}
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
340
|
+
this.pendingMatches.clear();
|
|
341
|
+
for (const waiter of this.pendingTxidWaits.values()) {
|
|
342
|
+
clearTimeout(waiter.timeoutId);
|
|
343
|
+
waiter.reject(new errors.StreamAbortedError(this.collectionId));
|
|
344
|
+
}
|
|
345
|
+
this.pendingTxidWaits.clear();
|
|
346
|
+
this.matchBuffer = { committedMessages: [], pendingMessages: [] };
|
|
347
|
+
this.evidence.hydratedResumeState = void 0;
|
|
348
|
+
}
|
|
349
|
+
exportMeta() {
|
|
350
|
+
return exportElectricSyncMeta(this.evidence);
|
|
351
|
+
}
|
|
352
|
+
importMeta(meta) {
|
|
353
|
+
importElectricSyncMeta(this.evidence, meta);
|
|
354
|
+
this.resolveTxidWaiters();
|
|
355
|
+
}
|
|
356
|
+
get resumeState() {
|
|
357
|
+
return this.evidence.hydratedResumeState;
|
|
358
|
+
}
|
|
359
|
+
set resumeState(value) {
|
|
360
|
+
this.evidence.hydratedResumeState = value;
|
|
361
|
+
}
|
|
362
|
+
publishEvidence(txids, snapshots) {
|
|
363
|
+
txids.forEach((txid) => this.evidence.seenTxids.add(txid));
|
|
364
|
+
this.evidence.seenSnapshots.push(...snapshots);
|
|
365
|
+
this.resolveTxidWaiters();
|
|
366
|
+
}
|
|
367
|
+
hasTxid(txId) {
|
|
368
|
+
return this.evidence.seenTxids.has(txId) || this.evidence.seenSnapshots.some(
|
|
369
|
+
(snapshot) => client.isVisibleInSnapshot(txId, snapshot)
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
resolveTxidWaiters() {
|
|
373
|
+
for (const [waitId, waiter] of this.pendingTxidWaits) {
|
|
374
|
+
if (!this.hasTxid(waiter.txId)) continue;
|
|
375
|
+
clearTimeout(waiter.timeoutId);
|
|
376
|
+
this.pendingTxidWaits.delete(waitId);
|
|
377
|
+
waiter.resolve(true);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
async awaitTxId(txId, timeout = 5e3) {
|
|
309
381
|
debug(
|
|
310
|
-
`${
|
|
382
|
+
`${this.collectionId ? `[${this.collectionId}] ` : ``}awaitTxId called with txid %d`,
|
|
311
383
|
txId
|
|
312
384
|
);
|
|
313
385
|
if (typeof txId !== `number`) {
|
|
314
|
-
throw new errors.ExpectedNumberInAwaitTxIdError(typeof txId,
|
|
386
|
+
throw new errors.ExpectedNumberInAwaitTxIdError(typeof txId, this.collectionId);
|
|
315
387
|
}
|
|
316
|
-
|
|
317
|
-
if (hasTxid) return true;
|
|
318
|
-
const hasSnapshot = seenSnapshots.state.some(
|
|
319
|
-
(snapshot) => client.isVisibleInSnapshot(txId, snapshot)
|
|
320
|
-
);
|
|
321
|
-
if (hasSnapshot) return true;
|
|
388
|
+
if (this.hasTxid(txId)) return true;
|
|
322
389
|
return new Promise((resolve, reject) => {
|
|
323
|
-
const
|
|
324
|
-
clearTimeout(timeoutId);
|
|
325
|
-
subSeenTxids.unsubscribe();
|
|
326
|
-
subSeenSnapshots.unsubscribe();
|
|
327
|
-
};
|
|
390
|
+
const waitId = this.nextWaiterId++;
|
|
328
391
|
const timeoutId = setTimeout(() => {
|
|
329
|
-
|
|
330
|
-
reject(new errors.TimeoutWaitingForTxIdError(txId,
|
|
392
|
+
this.pendingTxidWaits.delete(waitId);
|
|
393
|
+
reject(new errors.TimeoutWaitingForTxIdError(txId, this.collectionId));
|
|
331
394
|
}, timeout);
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
);
|
|
338
|
-
cleanup();
|
|
339
|
-
resolve(true);
|
|
340
|
-
}
|
|
341
|
-
});
|
|
342
|
-
const subSeenSnapshots = seenSnapshots.subscribe(() => {
|
|
343
|
-
const visibleSnapshot = seenSnapshots.state.find(
|
|
344
|
-
(snapshot) => client.isVisibleInSnapshot(txId, snapshot)
|
|
345
|
-
);
|
|
346
|
-
if (visibleSnapshot) {
|
|
347
|
-
debug(
|
|
348
|
-
`${config.id ? `[${config.id}] ` : ``}awaitTxId found match for txid %o in snapshot %o`,
|
|
349
|
-
txId,
|
|
350
|
-
visibleSnapshot
|
|
351
|
-
);
|
|
352
|
-
cleanup();
|
|
353
|
-
resolve(true);
|
|
354
|
-
}
|
|
395
|
+
this.pendingTxidWaits.set(waitId, {
|
|
396
|
+
txId,
|
|
397
|
+
resolve,
|
|
398
|
+
reject,
|
|
399
|
+
timeoutId
|
|
355
400
|
});
|
|
356
401
|
});
|
|
357
|
-
}
|
|
358
|
-
|
|
402
|
+
}
|
|
403
|
+
async awaitMatch(matchFn, timeout = 3e3) {
|
|
359
404
|
debug(
|
|
360
|
-
`${
|
|
405
|
+
`${this.collectionId ? `[${this.collectionId}] ` : ``}awaitMatch called with custom function`
|
|
361
406
|
);
|
|
407
|
+
for (const message of this.matchBuffer.committedMessages) {
|
|
408
|
+
if (!matchFn(message)) continue;
|
|
409
|
+
return true;
|
|
410
|
+
}
|
|
411
|
+
for (const message of this.matchBuffer.pendingMessages) {
|
|
412
|
+
if (matchFn(message)) return this.registerMatch(matchFn, timeout, true);
|
|
413
|
+
}
|
|
414
|
+
return this.registerMatch(matchFn, timeout, false);
|
|
415
|
+
}
|
|
416
|
+
registerMatch(matchFn, timeout, matched) {
|
|
362
417
|
return new Promise((resolve, reject) => {
|
|
363
|
-
const matchId =
|
|
364
|
-
const
|
|
365
|
-
pendingMatches.
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
};
|
|
375
|
-
const timeoutId = setTimeout(onTimeout, timeout);
|
|
376
|
-
const checkMatch = (message) => {
|
|
377
|
-
if (matchFn(message)) {
|
|
378
|
-
debug(
|
|
379
|
-
`${config.id ? `[${config.id}] ` : ``}awaitMatch found matching message, waiting for up-to-date`
|
|
380
|
-
);
|
|
381
|
-
pendingMatches.setState((current) => {
|
|
382
|
-
const newMatches = new Map(current);
|
|
383
|
-
const existing = newMatches.get(matchId);
|
|
384
|
-
if (existing) {
|
|
385
|
-
newMatches.set(matchId, { ...existing, matched: true });
|
|
386
|
-
}
|
|
387
|
-
return newMatches;
|
|
388
|
-
});
|
|
389
|
-
return true;
|
|
390
|
-
}
|
|
391
|
-
return false;
|
|
392
|
-
};
|
|
393
|
-
for (const message of currentBatchMessages.state) {
|
|
394
|
-
if (matchFn(message)) {
|
|
395
|
-
if (batchCommitted.state) {
|
|
396
|
-
debug(
|
|
397
|
-
`${config.id ? `[${config.id}] ` : ``}awaitMatch found immediate match in committed batch, resolving immediately`
|
|
398
|
-
);
|
|
399
|
-
clearTimeout(timeoutId);
|
|
400
|
-
resolve(true);
|
|
401
|
-
return;
|
|
402
|
-
}
|
|
403
|
-
debug(
|
|
404
|
-
`${config.id ? `[${config.id}] ` : ``}awaitMatch found immediate match in current batch, waiting for up-to-date`
|
|
405
|
-
);
|
|
406
|
-
pendingMatches.setState((current) => {
|
|
407
|
-
const newMatches = new Map(current);
|
|
408
|
-
newMatches.set(matchId, {
|
|
409
|
-
matchFn: checkMatch,
|
|
410
|
-
resolve,
|
|
411
|
-
reject,
|
|
412
|
-
timeoutId,
|
|
413
|
-
matched: true
|
|
414
|
-
// Already matched, will resolve on up-to-date
|
|
415
|
-
});
|
|
416
|
-
return newMatches;
|
|
417
|
-
});
|
|
418
|
-
return;
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
pendingMatches.setState((current) => {
|
|
422
|
-
const newMatches = new Map(current);
|
|
423
|
-
newMatches.set(matchId, {
|
|
424
|
-
matchFn: checkMatch,
|
|
425
|
-
resolve,
|
|
426
|
-
reject,
|
|
427
|
-
timeoutId,
|
|
428
|
-
matched: false
|
|
429
|
-
});
|
|
430
|
-
return newMatches;
|
|
418
|
+
const matchId = this.nextWaiterId++;
|
|
419
|
+
const timeoutId = setTimeout(() => {
|
|
420
|
+
this.pendingMatches.delete(matchId);
|
|
421
|
+
reject(new errors.TimeoutWaitingForMatchError(this.collectionId));
|
|
422
|
+
}, timeout);
|
|
423
|
+
this.pendingMatches.set(matchId, {
|
|
424
|
+
matchFn,
|
|
425
|
+
resolve,
|
|
426
|
+
reject,
|
|
427
|
+
timeoutId,
|
|
428
|
+
matched
|
|
431
429
|
});
|
|
432
430
|
});
|
|
433
|
-
}
|
|
434
|
-
|
|
431
|
+
}
|
|
432
|
+
beginMatchGeneration(messages) {
|
|
433
|
+
if (!messages.some(isMustRefetchMessage)) return;
|
|
434
|
+
this.matchBuffer = { committedMessages: [], pendingMessages: [] };
|
|
435
|
+
for (const match of this.pendingMatches.values()) match.matched = false;
|
|
436
|
+
}
|
|
437
|
+
observeMatchMessage(message) {
|
|
438
|
+
if (client.isChangeMessage(message) || tagIndex.isMoveOutMessage(message) || tagIndex.isMoveInMessage(message)) {
|
|
439
|
+
this.matchBuffer.pendingMessages.push(message);
|
|
440
|
+
let overflow = this.matchBuffer.committedMessages.length + this.matchBuffer.pendingMessages.length - 1e3;
|
|
441
|
+
if (overflow > 0) {
|
|
442
|
+
const committedOverflow = Math.min(
|
|
443
|
+
overflow,
|
|
444
|
+
this.matchBuffer.committedMessages.length
|
|
445
|
+
);
|
|
446
|
+
this.matchBuffer.committedMessages.splice(0, committedOverflow);
|
|
447
|
+
overflow -= committedOverflow;
|
|
448
|
+
if (overflow > 0) {
|
|
449
|
+
this.matchBuffer.pendingMessages.splice(0, overflow);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
const epoch = this.epoch;
|
|
454
|
+
for (const [matchId, match] of this.pendingMatches) {
|
|
455
|
+
if (match.matched) continue;
|
|
456
|
+
try {
|
|
457
|
+
match.matched = match.matchFn(message);
|
|
458
|
+
} catch (error) {
|
|
459
|
+
clearTimeout(match.timeoutId);
|
|
460
|
+
this.pendingMatches.delete(matchId);
|
|
461
|
+
match.reject(error instanceof Error ? error : new Error(String(error)));
|
|
462
|
+
}
|
|
463
|
+
if (!this.isActive(epoch)) return;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
commitMatches() {
|
|
467
|
+
this.matchBuffer.committedMessages.push(...this.matchBuffer.pendingMessages);
|
|
468
|
+
this.matchBuffer.pendingMessages = [];
|
|
469
|
+
if (this.matchBuffer.committedMessages.length > 1e3) {
|
|
470
|
+
this.matchBuffer.committedMessages.splice(
|
|
471
|
+
0,
|
|
472
|
+
this.matchBuffer.committedMessages.length - 1e3
|
|
473
|
+
);
|
|
474
|
+
}
|
|
475
|
+
for (const [matchId, match] of this.pendingMatches) {
|
|
476
|
+
if (!match.matched) continue;
|
|
477
|
+
clearTimeout(match.timeoutId);
|
|
478
|
+
this.pendingMatches.delete(matchId);
|
|
479
|
+
match.resolve(true);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
function electricCollectionOptions(config) {
|
|
484
|
+
let descriptorLifecycle = new ElectricLifecycle(config.id);
|
|
485
|
+
let utilityLifecycle = descriptorLifecycle;
|
|
486
|
+
const internalSyncMode = config.syncMode ?? `eager`;
|
|
487
|
+
const finalSyncMode = internalSyncMode === `progressive` ? `on-demand` : internalSyncMode;
|
|
488
|
+
const boundLifecycles = /* @__PURE__ */ new WeakMap();
|
|
489
|
+
const awaitTxId = (txId, timeout) => utilityLifecycle.utils.awaitTxId(txId, timeout);
|
|
490
|
+
const awaitMatch = (matchFn, timeout) => utilityLifecycle.utils.awaitMatch(matchFn, timeout);
|
|
491
|
+
const createSync = () => createElectricSync(config.shapeOptions, {
|
|
492
|
+
getLifecycle: (collection) => boundLifecycles.get(collection) ?? descriptorLifecycle,
|
|
493
|
+
syncMode: internalSyncMode,
|
|
494
|
+
collectionId: config.id,
|
|
495
|
+
testHooks: config[ELECTRIC_TEST_HOOKS]
|
|
496
|
+
});
|
|
497
|
+
const sync = createSync();
|
|
498
|
+
const processMatchingStrategy = async (result, waitForTxId) => {
|
|
435
499
|
if (result && `txid` in result) {
|
|
436
500
|
const timeout = result.timeout;
|
|
437
501
|
if (Array.isArray(result.txid)) {
|
|
438
|
-
await Promise.all(result.txid.map((txid) =>
|
|
502
|
+
await Promise.all(result.txid.map((txid) => waitForTxId(txid, timeout)));
|
|
439
503
|
} else {
|
|
440
|
-
await
|
|
504
|
+
await waitForTxId(result.txid, timeout);
|
|
441
505
|
}
|
|
442
506
|
}
|
|
443
507
|
};
|
|
508
|
+
const getMutationAwaitTxId = (params) => {
|
|
509
|
+
const collection = params.collection;
|
|
510
|
+
return collection?.utils?.awaitTxId ?? awaitTxId;
|
|
511
|
+
};
|
|
444
512
|
const wrappedOnInsert = config.onInsert ? async (params) => {
|
|
445
513
|
const handlerResult = await config.onInsert(params);
|
|
446
|
-
await processMatchingStrategy(
|
|
514
|
+
await processMatchingStrategy(
|
|
515
|
+
handlerResult,
|
|
516
|
+
getMutationAwaitTxId(params)
|
|
517
|
+
);
|
|
447
518
|
return handlerResult;
|
|
448
519
|
} : void 0;
|
|
449
520
|
const wrappedOnUpdate = config.onUpdate ? async (params) => {
|
|
450
521
|
const handlerResult = await config.onUpdate(params);
|
|
451
|
-
await processMatchingStrategy(
|
|
522
|
+
await processMatchingStrategy(
|
|
523
|
+
handlerResult,
|
|
524
|
+
getMutationAwaitTxId(params)
|
|
525
|
+
);
|
|
452
526
|
return handlerResult;
|
|
453
527
|
} : void 0;
|
|
454
528
|
const wrappedOnDelete = config.onDelete ? async (params) => {
|
|
455
529
|
const handlerResult = await config.onDelete(params);
|
|
456
|
-
await processMatchingStrategy(
|
|
530
|
+
await processMatchingStrategy(
|
|
531
|
+
handlerResult,
|
|
532
|
+
getMutationAwaitTxId(params)
|
|
533
|
+
);
|
|
457
534
|
return handlerResult;
|
|
458
535
|
} : void 0;
|
|
459
536
|
const {
|
|
@@ -463,236 +540,275 @@ function electricCollectionOptions(config) {
|
|
|
463
540
|
onDelete: _onDelete,
|
|
464
541
|
...restConfig
|
|
465
542
|
} = config;
|
|
466
|
-
const
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
543
|
+
const utilityTemplate = {
|
|
544
|
+
awaitTxId,
|
|
545
|
+
awaitMatch
|
|
546
|
+
};
|
|
547
|
+
const consumeDescriptorLifecycle = () => {
|
|
548
|
+
const lifecycle = descriptorLifecycle;
|
|
549
|
+
descriptorLifecycle = new ElectricLifecycle(config.id);
|
|
550
|
+
utilityLifecycle = lifecycle;
|
|
551
|
+
return lifecycle;
|
|
552
|
+
};
|
|
553
|
+
const createBoundSync = (source, utilities) => {
|
|
554
|
+
const lifecycle = consumeDescriptorLifecycle();
|
|
555
|
+
let collectionKey;
|
|
556
|
+
Object.assign(utilities, lifecycle.utils);
|
|
557
|
+
const boundSync = {
|
|
558
|
+
...source,
|
|
559
|
+
sync: (params) => {
|
|
560
|
+
if (!boundLifecycles.has(params.collection)) {
|
|
561
|
+
collectionKey = params.collection;
|
|
562
|
+
boundLifecycles.set(params.collection, lifecycle);
|
|
480
563
|
}
|
|
481
|
-
|
|
482
|
-
seenTxids.setState(() => new Set(parsed.seenTxids));
|
|
564
|
+
return source.sync(params);
|
|
483
565
|
},
|
|
566
|
+
exportSyncMeta: () => lifecycle.exportMeta(),
|
|
567
|
+
importSyncMeta: (meta) => lifecycle.importMeta(meta),
|
|
568
|
+
mergeSyncMeta: mergeElectricSyncMeta
|
|
569
|
+
};
|
|
570
|
+
return db.withCollectionSyncConfigCleanup(boundSync, () => {
|
|
571
|
+
lifecycle.retire();
|
|
572
|
+
if (collectionKey) boundLifecycles.delete(collectionKey);
|
|
573
|
+
});
|
|
574
|
+
};
|
|
575
|
+
const syncTemplate = db.withCollectionSyncConfigFactory(
|
|
576
|
+
{
|
|
577
|
+
...sync,
|
|
578
|
+
exportSyncMeta: () => descriptorLifecycle.exportMeta(),
|
|
579
|
+
importSyncMeta: (meta) => descriptorLifecycle.importMeta(meta),
|
|
484
580
|
mergeSyncMeta: mergeElectricSyncMeta
|
|
485
581
|
},
|
|
582
|
+
createBoundSync
|
|
583
|
+
);
|
|
584
|
+
const options = {
|
|
585
|
+
...restConfig,
|
|
586
|
+
syncMode: finalSyncMode,
|
|
587
|
+
sync: syncTemplate,
|
|
486
588
|
onInsert: wrappedOnInsert,
|
|
487
589
|
onUpdate: wrappedOnUpdate,
|
|
488
590
|
onDelete: wrappedOnDelete,
|
|
489
|
-
utils:
|
|
490
|
-
awaitTxId,
|
|
491
|
-
awaitMatch
|
|
492
|
-
}
|
|
591
|
+
utils: utilityTemplate
|
|
493
592
|
};
|
|
593
|
+
Object.defineProperty(options, `utils`, {
|
|
594
|
+
enumerable: true,
|
|
595
|
+
get: () => ({ ...utilityTemplate })
|
|
596
|
+
});
|
|
494
597
|
return db.withCollectionConfigFactory(
|
|
495
598
|
options,
|
|
496
599
|
() => electricCollectionOptions(config)
|
|
497
600
|
);
|
|
498
601
|
}
|
|
499
602
|
function createElectricSync(shapeOptions, options) {
|
|
500
|
-
const {
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
resolveMatchedPendingMatches,
|
|
510
|
-
collectionId,
|
|
511
|
-
testHooks
|
|
512
|
-
} = options;
|
|
513
|
-
const MAX_BATCH_MESSAGES = 1e3;
|
|
514
|
-
const relationSchema = new store.Store(void 0);
|
|
515
|
-
const tagCache = /* @__PURE__ */ new Map();
|
|
516
|
-
const parseTag = (tag) => {
|
|
517
|
-
const cachedTag = tagCache.get(tag);
|
|
518
|
-
if (cachedTag) {
|
|
519
|
-
return cachedTag;
|
|
520
|
-
}
|
|
521
|
-
const parsedTag = tagIndex.parseTag(tag);
|
|
522
|
-
tagCache.set(tag, parsedTag);
|
|
523
|
-
return parsedTag;
|
|
524
|
-
};
|
|
525
|
-
const rowTagSets = /* @__PURE__ */ new Map();
|
|
526
|
-
const tagIndex$1 = [];
|
|
527
|
-
let tagLength = void 0;
|
|
528
|
-
const rowActiveConditions = /* @__PURE__ */ new Map();
|
|
529
|
-
let disjunctPositions = void 0;
|
|
530
|
-
const initializeTagIndex = (length) => {
|
|
531
|
-
if (tagIndex$1.length < length) {
|
|
532
|
-
for (let i = tagIndex$1.length; i < length; i++) {
|
|
533
|
-
tagIndex$1[i] = /* @__PURE__ */ new Map();
|
|
603
|
+
const { getLifecycle, syncMode, collectionId, testHooks } = options;
|
|
604
|
+
let relationSchema;
|
|
605
|
+
let warnedUnverifiableResume = false;
|
|
606
|
+
const createTagState = () => {
|
|
607
|
+
const tagCache = /* @__PURE__ */ new Map();
|
|
608
|
+
const parseTag = (tag) => {
|
|
609
|
+
const cachedTag = tagCache.get(tag);
|
|
610
|
+
if (cachedTag) {
|
|
611
|
+
return cachedTag;
|
|
534
612
|
}
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
613
|
+
const parsedTag = tagIndex.parseTag(tag);
|
|
614
|
+
tagCache.set(tag, parsedTag);
|
|
615
|
+
return parsedTag;
|
|
616
|
+
};
|
|
617
|
+
const rowTagSets = /* @__PURE__ */ new Map();
|
|
618
|
+
const tagIndex$1 = [];
|
|
619
|
+
let tagLength = void 0;
|
|
620
|
+
const rowActiveConditions = /* @__PURE__ */ new Map();
|
|
621
|
+
let disjunctPositions = void 0;
|
|
622
|
+
const initializeTagIndex = (length) => {
|
|
623
|
+
if (tagIndex$1.length < length) {
|
|
624
|
+
for (let i = tagIndex$1.length; i < length; i++) {
|
|
625
|
+
tagIndex$1[i] = /* @__PURE__ */ new Map();
|
|
626
|
+
}
|
|
543
627
|
}
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
)
|
|
549
|
-
|
|
628
|
+
};
|
|
629
|
+
const addTagsToRow = (tags, rowId, rowTagSet) => {
|
|
630
|
+
for (const tag of tags) {
|
|
631
|
+
const parsedTag = parseTag(tag);
|
|
632
|
+
if (tagLength === void 0) {
|
|
633
|
+
tagLength = tagIndex.getTagLength(parsedTag);
|
|
634
|
+
initializeTagIndex(tagLength);
|
|
635
|
+
}
|
|
636
|
+
const currentTagLength = tagIndex.getTagLength(parsedTag);
|
|
637
|
+
if (currentTagLength !== tagLength) {
|
|
638
|
+
debug(
|
|
639
|
+
`${collectionId ? `[${collectionId}] ` : ``}Tag length mismatch: expected ${tagLength}, got ${currentTagLength}`
|
|
640
|
+
);
|
|
641
|
+
continue;
|
|
642
|
+
}
|
|
643
|
+
rowTagSet.add(tag);
|
|
644
|
+
tagIndex.addTagToIndex(parsedTag, rowId, tagIndex$1, tagLength);
|
|
550
645
|
}
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
const removeTagsFromRow = (removedTags, rowId, rowTagSet) => {
|
|
556
|
-
if (tagLength === void 0) {
|
|
557
|
-
return;
|
|
558
|
-
}
|
|
559
|
-
for (const tag of removedTags) {
|
|
560
|
-
const parsedTag = parseTag(tag);
|
|
561
|
-
rowTagSet.delete(tag);
|
|
562
|
-
tagIndex.removeTagFromIndex(parsedTag, rowId, tagIndex$1, tagLength);
|
|
563
|
-
tagCache.delete(tag);
|
|
564
|
-
}
|
|
565
|
-
};
|
|
566
|
-
const processTagsForChangeMessage = (tags, removedTags, rowId, activeConditions) => {
|
|
567
|
-
if (!rowTagSets.has(rowId)) {
|
|
568
|
-
rowTagSets.set(rowId, /* @__PURE__ */ new Set());
|
|
569
|
-
}
|
|
570
|
-
const rowTagSet = rowTagSets.get(rowId);
|
|
571
|
-
if (tags) {
|
|
572
|
-
addTagsToRow(tags, rowId, rowTagSet);
|
|
573
|
-
if (disjunctPositions === void 0) {
|
|
574
|
-
const parsedTags = tags.map(parseTag);
|
|
575
|
-
disjunctPositions = tagIndex.deriveDisjunctPositions(parsedTags);
|
|
646
|
+
};
|
|
647
|
+
const removeTagsFromRow = (removedTags, rowId, rowTagSet) => {
|
|
648
|
+
if (tagLength === void 0) {
|
|
649
|
+
return;
|
|
576
650
|
}
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
}
|
|
581
|
-
if (activeConditions && activeConditions.length > 0) {
|
|
582
|
-
rowActiveConditions.set(rowId, [...activeConditions]);
|
|
583
|
-
}
|
|
584
|
-
return rowTagSet;
|
|
585
|
-
};
|
|
586
|
-
const clearTagTrackingState = () => {
|
|
587
|
-
rowTagSets.clear();
|
|
588
|
-
tagIndex$1.length = 0;
|
|
589
|
-
tagLength = void 0;
|
|
590
|
-
rowActiveConditions.clear();
|
|
591
|
-
disjunctPositions = void 0;
|
|
592
|
-
};
|
|
593
|
-
const clearTagsForRow = (rowId) => {
|
|
594
|
-
if (tagLength === void 0) {
|
|
595
|
-
return;
|
|
596
|
-
}
|
|
597
|
-
const rowTagSet = rowTagSets.get(rowId);
|
|
598
|
-
if (!rowTagSet) {
|
|
599
|
-
return;
|
|
600
|
-
}
|
|
601
|
-
for (const tag of rowTagSet) {
|
|
602
|
-
const parsedTag = parseTag(tag);
|
|
603
|
-
const currentTagLength = tagIndex.getTagLength(parsedTag);
|
|
604
|
-
if (currentTagLength === tagLength) {
|
|
651
|
+
for (const tag of removedTags) {
|
|
652
|
+
const parsedTag = parseTag(tag);
|
|
653
|
+
rowTagSet.delete(tag);
|
|
605
654
|
tagIndex.removeTagFromIndex(parsedTag, rowId, tagIndex$1, tagLength);
|
|
655
|
+
tagCache.delete(tag);
|
|
606
656
|
}
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
if (
|
|
621
|
-
|
|
622
|
-
|
|
657
|
+
};
|
|
658
|
+
const processTagsForChangeMessage = (tags, removedTags, rowId, activeConditions) => {
|
|
659
|
+
if (!rowTagSets.has(rowId)) {
|
|
660
|
+
rowTagSets.set(rowId, /* @__PURE__ */ new Set());
|
|
661
|
+
}
|
|
662
|
+
const rowTagSet = rowTagSets.get(rowId);
|
|
663
|
+
if (tags) {
|
|
664
|
+
addTagsToRow(tags, rowId, rowTagSet);
|
|
665
|
+
if (disjunctPositions === void 0) {
|
|
666
|
+
const parsedTags = tags.map(parseTag);
|
|
667
|
+
disjunctPositions = tagIndex.deriveDisjunctPositions(parsedTags);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
if (removedTags) {
|
|
671
|
+
removeTagsFromRow(removedTags, rowId, rowTagSet);
|
|
672
|
+
}
|
|
673
|
+
if (activeConditions && activeConditions.length > 0) {
|
|
674
|
+
rowActiveConditions.set(rowId, [...activeConditions]);
|
|
675
|
+
}
|
|
676
|
+
return rowTagSet;
|
|
677
|
+
};
|
|
678
|
+
const clearTagTrackingState = () => {
|
|
679
|
+
rowTagSets.clear();
|
|
680
|
+
tagIndex$1.length = 0;
|
|
681
|
+
tagLength = void 0;
|
|
682
|
+
rowActiveConditions.clear();
|
|
683
|
+
disjunctPositions = void 0;
|
|
684
|
+
};
|
|
685
|
+
const clearTagsForRow = (rowId) => {
|
|
686
|
+
if (tagLength === void 0) {
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
const rowTagSet = rowTagSets.get(rowId);
|
|
690
|
+
if (!rowTagSet) {
|
|
691
|
+
return;
|
|
692
|
+
}
|
|
693
|
+
for (const tag of rowTagSet) {
|
|
694
|
+
const parsedTag = parseTag(tag);
|
|
695
|
+
const currentTagLength = tagIndex.getTagLength(parsedTag);
|
|
696
|
+
if (currentTagLength === tagLength) {
|
|
623
697
|
tagIndex.removeTagFromIndex(parsedTag, rowId, tagIndex$1, tagLength);
|
|
624
|
-
tagCache.delete(tag);
|
|
625
698
|
}
|
|
699
|
+
tagCache.delete(tag);
|
|
700
|
+
}
|
|
701
|
+
rowTagSets.delete(rowId);
|
|
702
|
+
rowActiveConditions.delete(rowId);
|
|
703
|
+
};
|
|
704
|
+
const removeMatchingTagsFromRow = (rowId, pattern) => {
|
|
705
|
+
const rowTagSet = rowTagSets.get(rowId);
|
|
706
|
+
if (!rowTagSet) {
|
|
707
|
+
return false;
|
|
708
|
+
}
|
|
709
|
+
const activeConditions = rowActiveConditions.get(rowId);
|
|
710
|
+
if (activeConditions && disjunctPositions) {
|
|
711
|
+
activeConditions[pattern.pos] = false;
|
|
712
|
+
if (!tagIndex.rowVisible(activeConditions, disjunctPositions)) {
|
|
713
|
+
for (const tag of rowTagSet) {
|
|
714
|
+
const parsedTag = parseTag(tag);
|
|
715
|
+
tagIndex.removeTagFromIndex(parsedTag, rowId, tagIndex$1, tagLength);
|
|
716
|
+
tagCache.delete(tag);
|
|
717
|
+
}
|
|
718
|
+
rowTagSets.delete(rowId);
|
|
719
|
+
rowActiveConditions.delete(rowId);
|
|
720
|
+
return true;
|
|
721
|
+
}
|
|
722
|
+
return false;
|
|
723
|
+
}
|
|
724
|
+
for (const tag of rowTagSet) {
|
|
725
|
+
const parsedTag = parseTag(tag);
|
|
726
|
+
if (tagIndex.tagMatchesPattern(parsedTag, pattern)) {
|
|
727
|
+
rowTagSet.delete(tag);
|
|
728
|
+
tagIndex.removeTagFromIndex(parsedTag, rowId, tagIndex$1, tagLength);
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
if (rowTagSet.size === 0) {
|
|
626
732
|
rowTagSets.delete(rowId);
|
|
627
|
-
rowActiveConditions.delete(rowId);
|
|
628
733
|
return true;
|
|
629
734
|
}
|
|
630
735
|
return false;
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
736
|
+
};
|
|
737
|
+
const processMoveOutEvent = (patterns, begin, write, transactionStarted, onDelete) => {
|
|
738
|
+
if (tagLength === void 0) {
|
|
739
|
+
debug(
|
|
740
|
+
`${collectionId ? `[${collectionId}] ` : ``}Received move-out message but no tag length set yet, ignoring`
|
|
741
|
+
);
|
|
742
|
+
return transactionStarted;
|
|
637
743
|
}
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
let txStarted = transactionStarted;
|
|
653
|
-
for (const pattern of patterns) {
|
|
654
|
-
const affectedRowIds = tagIndex.findRowsMatchingPattern(pattern, tagIndex$1);
|
|
655
|
-
for (const rowId of affectedRowIds) {
|
|
656
|
-
if (removeMatchingTagsFromRow(rowId, pattern)) {
|
|
657
|
-
if (!txStarted) {
|
|
658
|
-
begin();
|
|
659
|
-
txStarted = true;
|
|
744
|
+
let txStarted = transactionStarted;
|
|
745
|
+
for (const pattern of patterns) {
|
|
746
|
+
const affectedRowIds = tagIndex.findRowsMatchingPattern(pattern, tagIndex$1);
|
|
747
|
+
for (const rowId of affectedRowIds) {
|
|
748
|
+
if (removeMatchingTagsFromRow(rowId, pattern)) {
|
|
749
|
+
if (!txStarted) {
|
|
750
|
+
begin();
|
|
751
|
+
txStarted = true;
|
|
752
|
+
}
|
|
753
|
+
write({
|
|
754
|
+
type: `delete`,
|
|
755
|
+
key: rowId
|
|
756
|
+
});
|
|
757
|
+
onDelete(rowId);
|
|
660
758
|
}
|
|
661
|
-
write({
|
|
662
|
-
type: `delete`,
|
|
663
|
-
key: rowId
|
|
664
|
-
});
|
|
665
759
|
}
|
|
666
760
|
}
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
761
|
+
return txStarted;
|
|
762
|
+
};
|
|
763
|
+
const processMoveInEvent = (patterns) => {
|
|
764
|
+
if (tagLength === void 0) {
|
|
765
|
+
debug(
|
|
766
|
+
`${collectionId ? `[${collectionId}] ` : ``}Received move-in message but no tag length set yet, ignoring`
|
|
767
|
+
);
|
|
768
|
+
return;
|
|
769
|
+
}
|
|
770
|
+
for (const pattern of patterns) {
|
|
771
|
+
const affectedRowIds = tagIndex.findRowsMatchingPattern(pattern, tagIndex$1);
|
|
772
|
+
for (const rowId of affectedRowIds) {
|
|
773
|
+
const activeConditions = rowActiveConditions.get(rowId);
|
|
774
|
+
if (activeConditions) {
|
|
775
|
+
activeConditions[pattern.pos] = true;
|
|
776
|
+
}
|
|
683
777
|
}
|
|
684
778
|
}
|
|
685
|
-
}
|
|
686
|
-
};
|
|
687
|
-
const getSyncMetadata = () => {
|
|
688
|
-
const schema = relationSchema.state || `public`;
|
|
779
|
+
};
|
|
689
780
|
return {
|
|
690
|
-
|
|
781
|
+
hasTags: () => tagLength !== void 0,
|
|
782
|
+
processTagsForChangeMessage,
|
|
783
|
+
clearTagTrackingState,
|
|
784
|
+
clearTagsForRow,
|
|
785
|
+
processMoveOutEvent,
|
|
786
|
+
processMoveInEvent
|
|
691
787
|
};
|
|
692
788
|
};
|
|
693
|
-
|
|
789
|
+
const collectionTags = /* @__PURE__ */ new WeakMap();
|
|
694
790
|
return {
|
|
791
|
+
getSyncMetadata: () => ({
|
|
792
|
+
relation: shapeOptions.params?.table ? [relationSchema || `public`, shapeOptions.params.table] : void 0
|
|
793
|
+
}),
|
|
695
794
|
sync: (params) => {
|
|
795
|
+
const retainsTagState = collectionTags.has(params.collection);
|
|
796
|
+
let tagState = collectionTags.get(params.collection);
|
|
797
|
+
if (!tagState) {
|
|
798
|
+
tagState = createTagState();
|
|
799
|
+
collectionTags.set(params.collection, tagState);
|
|
800
|
+
}
|
|
801
|
+
const {
|
|
802
|
+
processTagsForChangeMessage,
|
|
803
|
+
clearTagTrackingState,
|
|
804
|
+
clearTagsForRow,
|
|
805
|
+
processMoveOutEvent,
|
|
806
|
+
processMoveInEvent
|
|
807
|
+
} = tagState;
|
|
808
|
+
const lifecycle = getLifecycle(params.collection);
|
|
809
|
+
const lifecycleEpoch = lifecycle.start();
|
|
810
|
+
const isActiveLifecycle = () => lifecycle.isActive(lifecycleEpoch);
|
|
811
|
+
Object.assign(params.collection.utils, lifecycle.utils);
|
|
696
812
|
const {
|
|
697
813
|
begin,
|
|
698
814
|
write,
|
|
@@ -728,16 +844,36 @@ function createElectricSync(shapeOptions, options) {
|
|
|
728
844
|
const persistedResumeState2 = metadata?.collection.get(`electric:resume`);
|
|
729
845
|
return parseElectricResumeState(persistedResumeState2);
|
|
730
846
|
};
|
|
847
|
+
const persistedMetadata = metadata;
|
|
848
|
+
const scanPersisted = persistedMetadata?.row.scanPersisted;
|
|
849
|
+
const whenHydrated = persistedMetadata?.row.whenHydrated;
|
|
731
850
|
const persistedResumeState = getNewestElectricResumeState(
|
|
732
851
|
readPersistedResumeState(),
|
|
733
|
-
|
|
852
|
+
lifecycle.resumeState
|
|
734
853
|
);
|
|
735
854
|
const shapeIdentity = getStableShapeIdentity({
|
|
736
855
|
url: shapeOptions.url,
|
|
737
856
|
params: shapeOptions.params
|
|
738
857
|
});
|
|
739
858
|
const hasIncompatiblePersistedResume = persistedResumeState?.kind === `resume` && persistedResumeState.shapeId !== shapeIdentity;
|
|
740
|
-
const
|
|
859
|
+
const hasUnverifiablePersistedResume = shapeOptions.offset === void 0 && shapeOptions.handle === void 0 && persistedResumeState?.kind === `resume` && scanPersisted !== void 0 && whenHydrated === void 0;
|
|
860
|
+
if (hasUnverifiablePersistedResume && !warnedUnverifiableResume) {
|
|
861
|
+
warnedUnverifiableResume = true;
|
|
862
|
+
console.warn(
|
|
863
|
+
`Electric persistence cannot verify hydration for saved resume state. Update the persistence adapter alongside Electric to enable safe resume.`
|
|
864
|
+
);
|
|
865
|
+
}
|
|
866
|
+
const needsFullSnapshot = shapeOptions.offset === void 0 && shapeOptions.handle === void 0 && persistedResumeState !== void 0 && (persistedResumeState.kind === `reset` || !retainsTagState && persistedResumeState.requiresTagState !== false);
|
|
867
|
+
const canUsePersistedResume = shapeOptions.offset === void 0 && shapeOptions.handle === void 0 && persistedResumeState?.kind === `resume` && !hasIncompatiblePersistedResume && !hasUnverifiablePersistedResume && // Cached rows do not contain authoritative tag/active-condition state.
|
|
868
|
+
// Unknown (older) metadata is conservative; untagged shapes still resume.
|
|
869
|
+
!needsFullSnapshot;
|
|
870
|
+
const hasExplicitResumeOffset = shapeOptions.offset !== void 0 && shapeOptions.offset !== `-1`;
|
|
871
|
+
if (!canUsePersistedResume && !hasExplicitResumeOffset) {
|
|
872
|
+
clearTagTrackingState();
|
|
873
|
+
}
|
|
874
|
+
const receivesCompleteRows = shapeOptions.params?.replica === `full`;
|
|
875
|
+
const requiresCompleteResume = syncMode !== `on-demand` && (canUsePersistedResume || hasExplicitResumeOffset && !receivesCompleteRows);
|
|
876
|
+
let freshSnapshotPending = (syncMode === `eager` || needsFullSnapshot) && !canUsePersistedResume && !hasExplicitResumeOffset && whenHydrated !== void 0;
|
|
741
877
|
let progressiveReadyGate = null;
|
|
742
878
|
let streamErrorVersion = 0;
|
|
743
879
|
const wrappedMarkReady = (isBuffering, expectedErrorVersion = streamErrorVersion) => {
|
|
@@ -754,36 +890,26 @@ function createElectricSync(shapeOptions, options) {
|
|
|
754
890
|
}
|
|
755
891
|
};
|
|
756
892
|
const abortController = new AbortController();
|
|
893
|
+
const forwardExternalAbort = () => abortController.abort();
|
|
757
894
|
if (shapeOptions.signal) {
|
|
758
|
-
shapeOptions.signal.addEventListener(
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
abortController.abort();
|
|
762
|
-
},
|
|
763
|
-
{
|
|
764
|
-
once: true
|
|
765
|
-
}
|
|
766
|
-
);
|
|
895
|
+
shapeOptions.signal.addEventListener(`abort`, forwardExternalAbort, {
|
|
896
|
+
once: true
|
|
897
|
+
});
|
|
767
898
|
if (shapeOptions.signal.aborted) {
|
|
768
899
|
abortController.abort();
|
|
769
900
|
}
|
|
770
901
|
}
|
|
771
902
|
abortController.signal.addEventListener(`abort`, () => {
|
|
772
|
-
|
|
773
|
-
current.forEach((match) => {
|
|
774
|
-
clearTimeout(match.timeoutId);
|
|
775
|
-
match.reject(new errors.StreamAbortedError());
|
|
776
|
-
});
|
|
777
|
-
return /* @__PURE__ */ new Map();
|
|
778
|
-
});
|
|
903
|
+
lifecycle.retire(lifecycleEpoch);
|
|
779
904
|
});
|
|
780
905
|
const stream = new client.ShapeStream({
|
|
781
906
|
...shapeOptions,
|
|
782
|
-
//
|
|
783
|
-
|
|
907
|
+
// Recovery needs a complete snapshot even for on-demand shapes.
|
|
908
|
+
// Normal on-demand startup still subscribes to changes only.
|
|
909
|
+
log: syncMode === `on-demand` && !needsFullSnapshot ? `changes_only` : void 0,
|
|
784
910
|
// In on-demand mode, we only need the changes from the point of time the collection was created
|
|
785
911
|
// so we default to `now` when there is no saved offset.
|
|
786
|
-
offset: shapeOptions.offset ?? (canUsePersistedResume ? persistedResumeState.offset : syncMode === `on-demand` ? `now` : void 0),
|
|
912
|
+
offset: shapeOptions.offset ?? (canUsePersistedResume ? persistedResumeState.offset : syncMode === `on-demand` && !needsFullSnapshot ? `now` : void 0),
|
|
787
913
|
handle: shapeOptions.handle ?? (canUsePersistedResume ? persistedResumeState.handle : void 0),
|
|
788
914
|
signal: abortController.signal,
|
|
789
915
|
onError: (errorParams) => {
|
|
@@ -809,11 +935,17 @@ function createElectricSync(shapeOptions, options) {
|
|
|
809
935
|
let transactionStarted = false;
|
|
810
936
|
const newTxids = /* @__PURE__ */ new Set();
|
|
811
937
|
const newSnapshots = [];
|
|
812
|
-
let hasReceivedUpToDate = syncMode === `progressive` &&
|
|
813
|
-
|
|
938
|
+
let hasReceivedUpToDate = syncMode === `progressive` && requiresCompleteResume;
|
|
939
|
+
let isResettingSnapshot = false;
|
|
940
|
+
let resetGeneration = 0;
|
|
941
|
+
const isBufferingInitialSync = () => syncMode === `progressive` && !hasReceivedUpToDate && !isResettingSnapshot;
|
|
814
942
|
const bufferedMessages = [];
|
|
815
943
|
const syncedKeys = /* @__PURE__ */ new Set();
|
|
944
|
+
let resumeInvalid = false;
|
|
816
945
|
const stageResumeMetadata = () => {
|
|
946
|
+
if (!isActiveLifecycle() || resumeInvalid) {
|
|
947
|
+
return;
|
|
948
|
+
}
|
|
817
949
|
const shapeHandle = stream.shapeHandle;
|
|
818
950
|
const lastOffset = stream.lastOffset;
|
|
819
951
|
if (!shapeHandle || lastOffset === `-1`) {
|
|
@@ -824,9 +956,10 @@ function createElectricSync(shapeOptions, options) {
|
|
|
824
956
|
offset: lastOffset,
|
|
825
957
|
handle: shapeHandle,
|
|
826
958
|
shapeId: shapeIdentity,
|
|
827
|
-
updatedAt: Date.now()
|
|
959
|
+
updatedAt: Date.now(),
|
|
960
|
+
requiresTagState: tagState.hasTags()
|
|
828
961
|
};
|
|
829
|
-
|
|
962
|
+
lifecycle.resumeState = resumeState;
|
|
830
963
|
metadata?.collection.set(`electric:resume`, resumeState);
|
|
831
964
|
};
|
|
832
965
|
const commitResetResumeMetadataImmediately = () => {
|
|
@@ -834,14 +967,14 @@ function createElectricSync(shapeOptions, options) {
|
|
|
834
967
|
kind: `reset`,
|
|
835
968
|
updatedAt: Date.now()
|
|
836
969
|
};
|
|
837
|
-
|
|
970
|
+
lifecycle.resumeState = resetState;
|
|
838
971
|
if (metadata) {
|
|
839
972
|
begin({ immediate: true });
|
|
840
973
|
metadata.collection.set(`electric:resume`, resetState);
|
|
841
974
|
commit();
|
|
842
975
|
}
|
|
843
976
|
};
|
|
844
|
-
if (hasIncompatiblePersistedResume) {
|
|
977
|
+
if (hasIncompatiblePersistedResume || hasUnverifiablePersistedResume || needsFullSnapshot && persistedResumeState.kind === `resume`) {
|
|
845
978
|
commitResetResumeMetadataImmediately();
|
|
846
979
|
}
|
|
847
980
|
const processChangeMessage = (changeMessage) => {
|
|
@@ -896,42 +1029,90 @@ function createElectricSync(shapeOptions, options) {
|
|
|
896
1029
|
// Pass abort signal so requestSnapshot errors can be ignored during cleanup
|
|
897
1030
|
signal: abortController.signal
|
|
898
1031
|
});
|
|
899
|
-
|
|
1032
|
+
const resumeKeysPromise = requiresCompleteResume || freshSnapshotPending ? whenHydrated?.() : void 0;
|
|
1033
|
+
let areResumeKeysReady = !resumeKeysPromise;
|
|
1034
|
+
const pendingResumeBatches = [];
|
|
1035
|
+
let unsubscribeStream = () => {
|
|
1036
|
+
};
|
|
1037
|
+
const invalidateResume = () => {
|
|
1038
|
+
resumeInvalid = true;
|
|
1039
|
+
if (transactionStarted) {
|
|
1040
|
+
const cancellation = new AbortController();
|
|
1041
|
+
cancellation.abort();
|
|
1042
|
+
commit(cancellation.signal);
|
|
1043
|
+
transactionStarted = false;
|
|
1044
|
+
}
|
|
1045
|
+
syncedKeys.clear();
|
|
1046
|
+
newTxids.clear();
|
|
1047
|
+
newSnapshots.length = 0;
|
|
1048
|
+
commitResetResumeMetadataImmediately();
|
|
1049
|
+
streamErrorVersion++;
|
|
1050
|
+
unsubscribeStream();
|
|
1051
|
+
abortController.abort();
|
|
1052
|
+
markError(
|
|
1053
|
+
new Error(
|
|
1054
|
+
`Electric resume state referenced an unseen row; a full snapshot is required`
|
|
1055
|
+
)
|
|
1056
|
+
);
|
|
1057
|
+
};
|
|
1058
|
+
const processMessages = (messages) => {
|
|
1059
|
+
if (!isActiveLifecycle() || resumeInvalid) {
|
|
1060
|
+
return;
|
|
1061
|
+
}
|
|
1062
|
+
if (freshSnapshotPending) {
|
|
1063
|
+
freshSnapshotPending = false;
|
|
1064
|
+
begin();
|
|
1065
|
+
transactionStarted = true;
|
|
1066
|
+
truncate();
|
|
1067
|
+
syncedKeys.clear();
|
|
1068
|
+
clearTagTrackingState();
|
|
1069
|
+
isResettingSnapshot = true;
|
|
1070
|
+
resetGeneration++;
|
|
1071
|
+
}
|
|
1072
|
+
const pendingPresence = /* @__PURE__ */ new Map();
|
|
1073
|
+
let usesBaseline = true;
|
|
1074
|
+
for (const pending of collection._state.pendingSyncedTransactions) {
|
|
1075
|
+
if (pending.truncate) {
|
|
1076
|
+
pendingPresence.clear();
|
|
1077
|
+
usesBaseline = false;
|
|
1078
|
+
}
|
|
1079
|
+
for (const operation of pending.operations) {
|
|
1080
|
+
pendingPresence.set(operation.key, operation.type !== `delete`);
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
for (const message of bufferedMessages) {
|
|
1084
|
+
if (client.isChangeMessage(message)) {
|
|
1085
|
+
pendingPresence.set(
|
|
1086
|
+
collection.getKeyFromItem(message.value),
|
|
1087
|
+
message.headers.operation !== `delete`
|
|
1088
|
+
);
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
900
1091
|
let commitPoint = null;
|
|
901
|
-
|
|
1092
|
+
lifecycle.beginMatchGeneration(messages);
|
|
902
1093
|
for (const message of messages) {
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
const newBuffer = [...currentBuffer, message];
|
|
906
|
-
if (newBuffer.length > MAX_BATCH_MESSAGES) {
|
|
907
|
-
newBuffer.splice(0, newBuffer.length - MAX_BATCH_MESSAGES);
|
|
908
|
-
}
|
|
909
|
-
return newBuffer;
|
|
910
|
-
});
|
|
911
|
-
}
|
|
1094
|
+
lifecycle.observeMatchMessage(message);
|
|
1095
|
+
if (!isActiveLifecycle()) return;
|
|
912
1096
|
if (hasTxids(message) && (!isBufferingInitialSync() || transactionStarted)) {
|
|
913
1097
|
message.headers.txids?.forEach((txid) => newTxids.add(txid));
|
|
914
1098
|
}
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
err instanceof Error ? err : new Error(String(err))
|
|
924
|
-
);
|
|
925
|
-
matchesToRemove.push(matchId);
|
|
926
|
-
debug(`matchFn error: %o`, err);
|
|
1099
|
+
if (client.isChangeMessage(message)) {
|
|
1100
|
+
const rowId = collection.getKeyFromItem(message.value);
|
|
1101
|
+
const operation = message.headers.operation;
|
|
1102
|
+
const hasKnownRow = pendingPresence.get(rowId) ?? (usesBaseline && collection._state.syncedData.has(rowId));
|
|
1103
|
+
if (operation === `update` && !hasKnownRow) {
|
|
1104
|
+
if (requiresCompleteResume && !isResettingSnapshot) {
|
|
1105
|
+
invalidateResume();
|
|
1106
|
+
return;
|
|
927
1107
|
}
|
|
1108
|
+
if (!receivesCompleteRows) continue;
|
|
928
1109
|
}
|
|
929
|
-
|
|
930
|
-
|
|
1110
|
+
pendingPresence.set(rowId, operation !== `delete`);
|
|
1111
|
+
}
|
|
931
1112
|
if (client.isChangeMessage(message)) {
|
|
932
1113
|
const schema = message.headers.schema;
|
|
933
1114
|
if (schema && typeof schema === `string`) {
|
|
934
|
-
relationSchema
|
|
1115
|
+
relationSchema = schema;
|
|
935
1116
|
}
|
|
936
1117
|
if (isBufferingInitialSync() && !transactionStarted) {
|
|
937
1118
|
bufferedMessages.push(message);
|
|
@@ -960,7 +1141,11 @@ function createElectricSync(shapeOptions, options) {
|
|
|
960
1141
|
message.headers.patterns,
|
|
961
1142
|
begin,
|
|
962
1143
|
write,
|
|
963
|
-
transactionStarted
|
|
1144
|
+
transactionStarted,
|
|
1145
|
+
(rowId) => {
|
|
1146
|
+
pendingPresence.set(rowId, false);
|
|
1147
|
+
syncedKeys.delete(rowId);
|
|
1148
|
+
}
|
|
964
1149
|
);
|
|
965
1150
|
}
|
|
966
1151
|
} else if (tagIndex.isMoveInMessage(message)) {
|
|
@@ -981,15 +1166,23 @@ function createElectricSync(shapeOptions, options) {
|
|
|
981
1166
|
truncate();
|
|
982
1167
|
clearTagTrackingState();
|
|
983
1168
|
syncedKeys.clear();
|
|
1169
|
+
pendingPresence.clear();
|
|
1170
|
+
usesBaseline = false;
|
|
1171
|
+
isResettingSnapshot = true;
|
|
1172
|
+
resetGeneration++;
|
|
984
1173
|
loadSubsetDedupe?.reset();
|
|
985
1174
|
commitPoint = null;
|
|
986
1175
|
hasReceivedUpToDate = false;
|
|
987
1176
|
bufferedMessages.length = 0;
|
|
988
1177
|
}
|
|
989
1178
|
}
|
|
1179
|
+
if (needsFullSnapshot && isResettingSnapshot && commitPoint === `subset-end`)
|
|
1180
|
+
return;
|
|
990
1181
|
if (commitPoint !== null) {
|
|
991
1182
|
let applied = true;
|
|
992
1183
|
const wasBufferingInitialSync = isBufferingInitialSync();
|
|
1184
|
+
const finishesReset = isResettingSnapshot && commitPoint === `up-to-date`;
|
|
1185
|
+
const finishingResetGeneration = resetGeneration;
|
|
993
1186
|
if (isBufferingInitialSync() && commitPoint === `up-to-date` && !transactionStarted) {
|
|
994
1187
|
debug(
|
|
995
1188
|
`${collectionId ? `[${collectionId}] ` : ``}Progressive mode: Performing atomic swap with ${bufferedMessages.length} buffered messages`
|
|
@@ -1013,7 +1206,13 @@ function createElectricSync(shapeOptions, options) {
|
|
|
1013
1206
|
bufferedMsg.headers.patterns,
|
|
1014
1207
|
begin,
|
|
1015
1208
|
write,
|
|
1016
|
-
|
|
1209
|
+
// The swap already opened a transaction, even though the
|
|
1210
|
+
// normal-stream transactionStarted flag is still false.
|
|
1211
|
+
true,
|
|
1212
|
+
(rowId) => {
|
|
1213
|
+
pendingPresence.set(rowId, false);
|
|
1214
|
+
syncedKeys.delete(rowId);
|
|
1215
|
+
}
|
|
1017
1216
|
);
|
|
1018
1217
|
} else if (tagIndex.isMoveInMessage(bufferedMsg)) {
|
|
1019
1218
|
processMoveInEvent(bufferedMsg.headers.patterns);
|
|
@@ -1027,7 +1226,9 @@ function createElectricSync(shapeOptions, options) {
|
|
|
1027
1226
|
);
|
|
1028
1227
|
} else {
|
|
1029
1228
|
if (transactionStarted) {
|
|
1030
|
-
|
|
1229
|
+
if (!isResettingSnapshot || finishesReset) {
|
|
1230
|
+
stageResumeMetadata();
|
|
1231
|
+
}
|
|
1031
1232
|
applied = commit();
|
|
1032
1233
|
transactionStarted = false;
|
|
1033
1234
|
} else if (commitPoint === `up-to-date` && metadata) {
|
|
@@ -1045,48 +1246,81 @@ function createElectricSync(shapeOptions, options) {
|
|
|
1045
1246
|
() => void 0
|
|
1046
1247
|
);
|
|
1047
1248
|
}
|
|
1249
|
+
if (finishesReset) {
|
|
1250
|
+
const finishReset = () => {
|
|
1251
|
+
if (resetGeneration === finishingResetGeneration) {
|
|
1252
|
+
isResettingSnapshot = false;
|
|
1253
|
+
}
|
|
1254
|
+
};
|
|
1255
|
+
if (applied === true) {
|
|
1256
|
+
finishReset();
|
|
1257
|
+
} else {
|
|
1258
|
+
void applied.then(finishReset, () => void 0);
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1048
1261
|
if (commitPoint === `up-to-date`) {
|
|
1049
1262
|
hasReceivedUpToDate = true;
|
|
1050
1263
|
}
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
`${collectionId ? `[${collectionId}] ` : ``}new txids synced from pg %O`,
|
|
1056
|
-
Array.from(newTxids)
|
|
1057
|
-
);
|
|
1058
|
-
}
|
|
1059
|
-
newTxids.forEach((txid) => clonedSeen.add(txid));
|
|
1060
|
-
newTxids.clear();
|
|
1061
|
-
return clonedSeen;
|
|
1062
|
-
});
|
|
1063
|
-
seenSnapshots.setState((currentSnapshots) => {
|
|
1064
|
-
const seen = [...currentSnapshots, ...newSnapshots];
|
|
1065
|
-
newSnapshots.forEach(
|
|
1066
|
-
(snapshot) => debug(
|
|
1067
|
-
`${collectionId ? `[${collectionId}] ` : ``}new snapshot synced from pg %o`,
|
|
1068
|
-
snapshot
|
|
1069
|
-
)
|
|
1264
|
+
if (newTxids.size > 0) {
|
|
1265
|
+
debug(
|
|
1266
|
+
`${collectionId ? `[${collectionId}] ` : ``}new txids synced from pg %O`,
|
|
1267
|
+
Array.from(newTxids)
|
|
1070
1268
|
);
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1269
|
+
}
|
|
1270
|
+
newSnapshots.forEach(
|
|
1271
|
+
(snapshot) => debug(
|
|
1272
|
+
`${collectionId ? `[${collectionId}] ` : ``}new snapshot synced from pg %o`,
|
|
1273
|
+
snapshot
|
|
1274
|
+
)
|
|
1275
|
+
);
|
|
1276
|
+
lifecycle.publishEvidence(newTxids, newSnapshots);
|
|
1277
|
+
newTxids.clear();
|
|
1278
|
+
newSnapshots.length = 0;
|
|
1279
|
+
lifecycle.commitMatches();
|
|
1076
1280
|
}
|
|
1281
|
+
};
|
|
1282
|
+
unsubscribeStream = stream.subscribe((messages) => {
|
|
1283
|
+
if (!areResumeKeysReady) {
|
|
1284
|
+
pendingResumeBatches.push([...messages]);
|
|
1285
|
+
return;
|
|
1286
|
+
}
|
|
1287
|
+
processMessages(messages);
|
|
1077
1288
|
});
|
|
1289
|
+
if (!areResumeKeysReady && resumeKeysPromise) {
|
|
1290
|
+
void resumeKeysPromise.then(
|
|
1291
|
+
() => {
|
|
1292
|
+
if (abortController.signal.aborted) return;
|
|
1293
|
+
areResumeKeysReady = true;
|
|
1294
|
+
const queuedBatches = pendingResumeBatches.splice(0);
|
|
1295
|
+
queuedBatches.forEach(processMessages);
|
|
1296
|
+
},
|
|
1297
|
+
(error) => {
|
|
1298
|
+
if (abortController.signal.aborted) return;
|
|
1299
|
+
pendingResumeBatches.length = 0;
|
|
1300
|
+
resumeInvalid = true;
|
|
1301
|
+
commitResetResumeMetadataImmediately();
|
|
1302
|
+
streamErrorVersion++;
|
|
1303
|
+
unsubscribeStream();
|
|
1304
|
+
abortController.abort();
|
|
1305
|
+
markError(error);
|
|
1306
|
+
}
|
|
1307
|
+
);
|
|
1308
|
+
}
|
|
1078
1309
|
return {
|
|
1079
1310
|
loadSubset: loadSubsetDedupe?.loadSubset,
|
|
1080
1311
|
cleanup: () => {
|
|
1312
|
+
shapeOptions.signal?.removeEventListener(
|
|
1313
|
+
`abort`,
|
|
1314
|
+
forwardExternalAbort
|
|
1315
|
+
);
|
|
1081
1316
|
unsubscribeStream();
|
|
1082
1317
|
abortController.abort();
|
|
1318
|
+
pendingResumeBatches.length = 0;
|
|
1083
1319
|
loadSubsetDedupe?.reset();
|
|
1084
|
-
|
|
1320
|
+
lifecycle.retire(lifecycleEpoch);
|
|
1085
1321
|
}
|
|
1086
1322
|
};
|
|
1087
|
-
}
|
|
1088
|
-
// Expose the getSyncMetadata function
|
|
1089
|
-
getSyncMetadata
|
|
1323
|
+
}
|
|
1090
1324
|
};
|
|
1091
1325
|
}
|
|
1092
1326
|
Object.defineProperty(exports, "isChangeMessage", {
|