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