@tanstack/electric-db-collection 0.4.10 → 0.5.2
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 +146 -103
- package/dist/cjs/electric.cjs.map +1 -1
- package/dist/cjs/electric.d.cts +94 -25
- package/dist/cjs/sql-compiler.cjs +43 -92
- package/dist/cjs/sql-compiler.cjs.map +1 -1
- package/dist/esm/electric.d.ts +94 -25
- package/dist/esm/electric.js +147 -104
- package/dist/esm/electric.js.map +1 -1
- package/dist/esm/sql-compiler.js +43 -92
- package/dist/esm/sql-compiler.js.map +1 -1
- package/package.json +3 -3
- package/src/electric.ts +295 -166
- package/src/sql-compiler.ts +57 -134
package/dist/cjs/electric.cjs
CHANGED
|
@@ -7,7 +7,6 @@ const errors = require("./errors.cjs");
|
|
|
7
7
|
const sqlCompiler = require("./sql-compiler.cjs");
|
|
8
8
|
const tagIndex = require("./tag-index.cjs");
|
|
9
9
|
const debug = DebugModule.debug(`ts/db:electric`);
|
|
10
|
-
const FORCE_DISCONNECT_AND_REFRESH_TIMEOUT_MS = 250;
|
|
11
10
|
const ELECTRIC_TEST_HOOKS = /* @__PURE__ */ Symbol(`electricTestHooks`);
|
|
12
11
|
function exportElectricSyncMeta(evidence) {
|
|
13
12
|
const resume = evidence.hydratedResumeState;
|
|
@@ -159,6 +158,7 @@ function createLoadSubsetDedupe({
|
|
|
159
158
|
commit,
|
|
160
159
|
getCommitCursor,
|
|
161
160
|
waitForCommitsAfter,
|
|
161
|
+
recordSnapshotRow,
|
|
162
162
|
collectionId,
|
|
163
163
|
encodeColumnName,
|
|
164
164
|
signal
|
|
@@ -201,7 +201,9 @@ function createLoadSubsetDedupe({
|
|
|
201
201
|
metadata: { ...row.headers }
|
|
202
202
|
});
|
|
203
203
|
}
|
|
204
|
-
|
|
204
|
+
const applied = commit(opts.signal);
|
|
205
|
+
for (const row of rows) recordSnapshotRow?.(row.value, row);
|
|
206
|
+
if (applied !== true) await applied;
|
|
205
207
|
debug(`${logPrefix}Applied snapshot with ${rows.length} rows`);
|
|
206
208
|
}
|
|
207
209
|
} catch (error) {
|
|
@@ -217,47 +219,6 @@ function createLoadSubsetDedupe({
|
|
|
217
219
|
return;
|
|
218
220
|
}
|
|
219
221
|
const { cursor, where, orderBy, limit } = opts;
|
|
220
|
-
if (stream.isUpToDate) {
|
|
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
|
-
}
|
|
234
|
-
try {
|
|
235
|
-
await Promise.race([
|
|
236
|
-
stream.forceDisconnectAndRefresh(),
|
|
237
|
-
new Promise((resolve) => {
|
|
238
|
-
timeoutId = setTimeout(
|
|
239
|
-
resolve,
|
|
240
|
-
FORCE_DISCONNECT_AND_REFRESH_TIMEOUT_MS
|
|
241
|
-
);
|
|
242
|
-
}),
|
|
243
|
-
aborted
|
|
244
|
-
]);
|
|
245
|
-
} catch (error) {
|
|
246
|
-
if (signal.aborted || opts.signal?.aborted) throw error;
|
|
247
|
-
if (handleSnapshotError(error, `forceDisconnectAndRefresh`)) {
|
|
248
|
-
return;
|
|
249
|
-
}
|
|
250
|
-
debug(
|
|
251
|
-
`${logPrefix}forceDisconnectAndRefresh failed, proceeding to requestSnapshot: %o`,
|
|
252
|
-
error
|
|
253
|
-
);
|
|
254
|
-
} finally {
|
|
255
|
-
clearTimeout(timeoutId);
|
|
256
|
-
for (const abortSignal of abortSignals) {
|
|
257
|
-
abortSignal.removeEventListener(`abort`, abort);
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
222
|
throwIfAborted();
|
|
262
223
|
try {
|
|
263
224
|
if (cursor) {
|
|
@@ -377,7 +338,7 @@ class ElectricLifecycle {
|
|
|
377
338
|
waiter.resolve(true);
|
|
378
339
|
}
|
|
379
340
|
}
|
|
380
|
-
async awaitTxId(txId, timeout =
|
|
341
|
+
async awaitTxId(txId, timeout = 15e3) {
|
|
381
342
|
debug(
|
|
382
343
|
`${this.collectionId ? `[${this.collectionId}] ` : ``}awaitTxId called with txid %d`,
|
|
383
344
|
txId
|
|
@@ -400,7 +361,7 @@ class ElectricLifecycle {
|
|
|
400
361
|
});
|
|
401
362
|
});
|
|
402
363
|
}
|
|
403
|
-
async awaitMatch(matchFn, timeout =
|
|
364
|
+
async awaitMatch(matchFn, timeout = 15e3) {
|
|
404
365
|
debug(
|
|
405
366
|
`${this.collectionId ? `[${this.collectionId}] ` : ``}awaitMatch called with custom function`
|
|
406
367
|
);
|
|
@@ -496,12 +457,20 @@ function electricCollectionOptions(config) {
|
|
|
496
457
|
});
|
|
497
458
|
const sync = createSync();
|
|
498
459
|
const processMatchingStrategy = async (result, waitForTxId) => {
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
460
|
+
const canHaveProperties = typeof result === `object` && result !== null || typeof result === `function`;
|
|
461
|
+
if (canHaveProperties && `txid` in result) {
|
|
462
|
+
const strategy = result;
|
|
463
|
+
db.warnOnce(
|
|
464
|
+
"electric-collection-txid-return",
|
|
465
|
+
"[TanStack DB] DEPRECATED: Returning { txid } from mutation handlers is deprecated and will be removed in v1.0. Use `await collection.utils.awaitTxId(txid)` instead of returning { txid }. See migration guide: https://tanstack.com/db/latest/docs/collections/electric-collection#persistence-handlers--synchronization"
|
|
466
|
+
);
|
|
467
|
+
const timeout = strategy.timeout;
|
|
468
|
+
if (Array.isArray(strategy.txid)) {
|
|
469
|
+
await Promise.all(
|
|
470
|
+
strategy.txid.map((txid) => waitForTxId(txid, timeout))
|
|
471
|
+
);
|
|
503
472
|
} else {
|
|
504
|
-
await waitForTxId(
|
|
473
|
+
await waitForTxId(strategy.txid, timeout);
|
|
505
474
|
}
|
|
506
475
|
}
|
|
507
476
|
};
|
|
@@ -515,7 +484,6 @@ function electricCollectionOptions(config) {
|
|
|
515
484
|
handlerResult,
|
|
516
485
|
getMutationAwaitTxId(params)
|
|
517
486
|
);
|
|
518
|
-
return handlerResult;
|
|
519
487
|
} : void 0;
|
|
520
488
|
const wrappedOnUpdate = config.onUpdate ? async (params) => {
|
|
521
489
|
const handlerResult = await config.onUpdate(params);
|
|
@@ -523,7 +491,6 @@ function electricCollectionOptions(config) {
|
|
|
523
491
|
handlerResult,
|
|
524
492
|
getMutationAwaitTxId(params)
|
|
525
493
|
);
|
|
526
|
-
return handlerResult;
|
|
527
494
|
} : void 0;
|
|
528
495
|
const wrappedOnDelete = config.onDelete ? async (params) => {
|
|
529
496
|
const handlerResult = await config.onDelete(params);
|
|
@@ -531,7 +498,6 @@ function electricCollectionOptions(config) {
|
|
|
531
498
|
handlerResult,
|
|
532
499
|
getMutationAwaitTxId(params)
|
|
533
500
|
);
|
|
534
|
-
return handlerResult;
|
|
535
501
|
} : void 0;
|
|
536
502
|
const {
|
|
537
503
|
shapeOptions: _shapeOptions,
|
|
@@ -602,7 +568,6 @@ function electricCollectionOptions(config) {
|
|
|
602
568
|
function createElectricSync(shapeOptions, options) {
|
|
603
569
|
const { getLifecycle, syncMode, collectionId, testHooks } = options;
|
|
604
570
|
let relationSchema;
|
|
605
|
-
let warnedUnverifiableResume = false;
|
|
606
571
|
const createTagState = () => {
|
|
607
572
|
const tagCache = /* @__PURE__ */ new Map();
|
|
608
573
|
const parseTag = (tag) => {
|
|
@@ -844,9 +809,13 @@ function createElectricSync(shapeOptions, options) {
|
|
|
844
809
|
const persistedResumeState2 = metadata?.collection.get(`electric:resume`);
|
|
845
810
|
return parseElectricResumeState(persistedResumeState2);
|
|
846
811
|
};
|
|
847
|
-
const
|
|
848
|
-
const
|
|
849
|
-
const
|
|
812
|
+
const persistence = metadata === void 0 ? null : db.validateSyncPersistenceCapability(metadata.persistence);
|
|
813
|
+
const hydrateBaseline = persistence?.hydrateBaseline;
|
|
814
|
+
const resumeSnapshot = persistence?.resumeSnapshot;
|
|
815
|
+
const certifyResumeSnapshot = resumeSnapshot?.certify;
|
|
816
|
+
const getKeySetEvidence = resumeSnapshot?.getKeySetEvidence;
|
|
817
|
+
const expectCurrentCommit = resumeSnapshot?.expectCurrentCommit;
|
|
818
|
+
const persistedKeySetEvidence = getKeySetEvidence?.();
|
|
850
819
|
const persistedResumeState = getNewestElectricResumeState(
|
|
851
820
|
readPersistedResumeState(),
|
|
852
821
|
lifecycle.resumeState
|
|
@@ -856,24 +825,19 @@ function createElectricSync(shapeOptions, options) {
|
|
|
856
825
|
params: shapeOptions.params
|
|
857
826
|
});
|
|
858
827
|
const hasIncompatiblePersistedResume = persistedResumeState?.kind === `resume` && persistedResumeState.shapeId !== shapeIdentity;
|
|
859
|
-
const
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
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.
|
|
828
|
+
const lacksCompletePersistedKeySet = persistedResumeState?.kind === `resume` && getKeySetEvidence !== void 0 && persistedKeySetEvidence?.status !== `consistent`;
|
|
829
|
+
const needsFullSnapshot = shapeOptions.offset === void 0 && shapeOptions.handle === void 0 && persistedResumeState !== void 0 && (persistedResumeState.kind === `reset` || lacksCompletePersistedKeySet || !retainsTagState && persistedResumeState.requiresTagState !== false);
|
|
830
|
+
const canUsePersistedResume = shapeOptions.offset === void 0 && shapeOptions.handle === void 0 && persistedResumeState?.kind === `resume` && !hasIncompatiblePersistedResume && // Cached rows do not contain authoritative tag/active-condition state.
|
|
831
|
+
// Only a complete adapter ledger can justify a persisted cursor.
|
|
869
832
|
!needsFullSnapshot;
|
|
870
833
|
const hasExplicitResumeOffset = shapeOptions.offset !== void 0 && shapeOptions.offset !== `-1`;
|
|
871
834
|
if (!canUsePersistedResume && !hasExplicitResumeOffset) {
|
|
872
835
|
clearTagTrackingState();
|
|
873
836
|
}
|
|
874
837
|
const receivesCompleteRows = shapeOptions.params?.replica === `full`;
|
|
838
|
+
const requiresKeySetCertification = canUsePersistedResume && certifyResumeSnapshot !== void 0;
|
|
875
839
|
const requiresCompleteResume = syncMode !== `on-demand` && (canUsePersistedResume || hasExplicitResumeOffset && !receivesCompleteRows);
|
|
876
|
-
let freshSnapshotPending = (syncMode === `eager` || needsFullSnapshot) && !canUsePersistedResume && !hasExplicitResumeOffset &&
|
|
840
|
+
let freshSnapshotPending = (syncMode === `eager` || needsFullSnapshot) && !canUsePersistedResume && !hasExplicitResumeOffset && hydrateBaseline !== void 0;
|
|
877
841
|
let progressiveReadyGate = null;
|
|
878
842
|
let streamErrorVersion = 0;
|
|
879
843
|
const wrappedMarkReady = (isBuffering, expectedErrorVersion = streamErrorVersion) => {
|
|
@@ -940,6 +904,39 @@ function createElectricSync(shapeOptions, options) {
|
|
|
940
904
|
let resetGeneration = 0;
|
|
941
905
|
const isBufferingInitialSync = () => syncMode === `progressive` && !hasReceivedUpToDate && !isResettingSnapshot;
|
|
942
906
|
const bufferedMessages = [];
|
|
907
|
+
const progressiveSnapshotRows = /* @__PURE__ */ new Map();
|
|
908
|
+
const pendingPresence = /* @__PURE__ */ new Map();
|
|
909
|
+
let usesBaseline = true;
|
|
910
|
+
let pendingPresenceRevision = 0;
|
|
911
|
+
const recordPendingPresence = (rowId, present) => {
|
|
912
|
+
pendingPresenceRevision++;
|
|
913
|
+
pendingPresence.set(rowId, present);
|
|
914
|
+
};
|
|
915
|
+
const recordPendingTruncate = () => {
|
|
916
|
+
pendingPresenceRevision++;
|
|
917
|
+
pendingPresence.clear();
|
|
918
|
+
progressiveSnapshotRows.clear();
|
|
919
|
+
usesBaseline = false;
|
|
920
|
+
};
|
|
921
|
+
const commitSourceTransaction = () => {
|
|
922
|
+
const committedPresenceRevision = pendingPresenceRevision;
|
|
923
|
+
const applied = commit();
|
|
924
|
+
if (metadata?.persistence) {
|
|
925
|
+
const retireAppliedPresence = () => {
|
|
926
|
+
if (pendingPresenceRevision === committedPresenceRevision) {
|
|
927
|
+
pendingPresence.clear();
|
|
928
|
+
usesBaseline = true;
|
|
929
|
+
}
|
|
930
|
+
};
|
|
931
|
+
if (applied === true) retireAppliedPresence();
|
|
932
|
+
else void applied.then(retireAppliedPresence, () => void 0);
|
|
933
|
+
}
|
|
934
|
+
return applied;
|
|
935
|
+
};
|
|
936
|
+
const beginSourceTransaction = () => {
|
|
937
|
+
if (metadata?.persistence) begin({ immediate: true });
|
|
938
|
+
else begin();
|
|
939
|
+
};
|
|
943
940
|
const syncedKeys = /* @__PURE__ */ new Set();
|
|
944
941
|
let resumeInvalid = false;
|
|
945
942
|
const stageResumeMetadata = () => {
|
|
@@ -962,7 +959,7 @@ function createElectricSync(shapeOptions, options) {
|
|
|
962
959
|
lifecycle.resumeState = resumeState;
|
|
963
960
|
metadata?.collection.set(`electric:resume`, resumeState);
|
|
964
961
|
};
|
|
965
|
-
const commitResetResumeMetadataImmediately = () => {
|
|
962
|
+
const commitResetResumeMetadataImmediately = (expectInResumeSnapshot = false) => {
|
|
966
963
|
const resetState = {
|
|
967
964
|
kind: `reset`,
|
|
968
965
|
updatedAt: Date.now()
|
|
@@ -971,11 +968,14 @@ function createElectricSync(shapeOptions, options) {
|
|
|
971
968
|
if (metadata) {
|
|
972
969
|
begin({ immediate: true });
|
|
973
970
|
metadata.collection.set(`electric:resume`, resetState);
|
|
971
|
+
if (expectInResumeSnapshot) {
|
|
972
|
+
expectCurrentCommit?.();
|
|
973
|
+
}
|
|
974
974
|
commit();
|
|
975
975
|
}
|
|
976
976
|
};
|
|
977
|
-
if (hasIncompatiblePersistedResume ||
|
|
978
|
-
commitResetResumeMetadataImmediately();
|
|
977
|
+
if (hasIncompatiblePersistedResume || needsFullSnapshot && persistedResumeState.kind === `resume`) {
|
|
978
|
+
commitResetResumeMetadataImmediately(true);
|
|
979
979
|
}
|
|
980
980
|
const processChangeMessage = (changeMessage) => {
|
|
981
981
|
if (!client.isChangeMessage(changeMessage)) {
|
|
@@ -1022,6 +1022,10 @@ function createElectricSync(shapeOptions, options) {
|
|
|
1022
1022
|
commit,
|
|
1023
1023
|
getCommitCursor: () => commitSequence,
|
|
1024
1024
|
waitForCommitsAfter,
|
|
1025
|
+
recordSnapshotRow: (row, message) => {
|
|
1026
|
+
recordPendingPresence(collection.getKeyFromItem(row), true);
|
|
1027
|
+
progressiveSnapshotRows.set(collection.getKeyFromItem(row), message);
|
|
1028
|
+
},
|
|
1025
1029
|
collectionId,
|
|
1026
1030
|
// Pass the columnMapper's encode function to transform column names
|
|
1027
1031
|
// (e.g., camelCase to snake_case) when compiling SQL for subset queries
|
|
@@ -1029,7 +1033,23 @@ function createElectricSync(shapeOptions, options) {
|
|
|
1029
1033
|
// Pass abort signal so requestSnapshot errors can be ignored during cleanup
|
|
1030
1034
|
signal: abortController.signal
|
|
1031
1035
|
});
|
|
1032
|
-
const resumeKeysPromise = requiresCompleteResume || freshSnapshotPending ?
|
|
1036
|
+
const resumeKeysPromise = requiresCompleteResume || freshSnapshotPending ? hydrateBaseline ? (async () => {
|
|
1037
|
+
await hydrateBaseline();
|
|
1038
|
+
const currentKeySetEvidence = getKeySetEvidence?.();
|
|
1039
|
+
if (canUsePersistedResume && currentKeySetEvidence?.status !== `consistent`) {
|
|
1040
|
+
throw new Error(
|
|
1041
|
+
`Electric persisted resume baseline could not be certified during hydration`
|
|
1042
|
+
);
|
|
1043
|
+
}
|
|
1044
|
+
})() : void 0 : requiresKeySetCertification ? (async () => {
|
|
1045
|
+
await certifyResumeSnapshot();
|
|
1046
|
+
const currentKeySetEvidence = getKeySetEvidence?.();
|
|
1047
|
+
if (currentKeySetEvidence?.status !== `consistent`) {
|
|
1048
|
+
throw new Error(
|
|
1049
|
+
`Electric persisted resume baseline could not be certified`
|
|
1050
|
+
);
|
|
1051
|
+
}
|
|
1052
|
+
})() : void 0;
|
|
1033
1053
|
let areResumeKeysReady = !resumeKeysPromise;
|
|
1034
1054
|
const pendingResumeBatches = [];
|
|
1035
1055
|
let unsubscribeStream = () => {
|
|
@@ -1061,31 +1081,34 @@ function createElectricSync(shapeOptions, options) {
|
|
|
1061
1081
|
}
|
|
1062
1082
|
if (freshSnapshotPending) {
|
|
1063
1083
|
freshSnapshotPending = false;
|
|
1064
|
-
|
|
1084
|
+
beginSourceTransaction();
|
|
1065
1085
|
transactionStarted = true;
|
|
1066
1086
|
truncate();
|
|
1087
|
+
recordPendingTruncate();
|
|
1067
1088
|
syncedKeys.clear();
|
|
1068
1089
|
clearTagTrackingState();
|
|
1069
1090
|
isResettingSnapshot = true;
|
|
1070
1091
|
resetGeneration++;
|
|
1071
1092
|
}
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1093
|
+
if (!metadata?.persistence && !transactionStarted) {
|
|
1094
|
+
pendingPresence.clear();
|
|
1095
|
+
usesBaseline = true;
|
|
1096
|
+
for (const pending of collection._state.pendingSyncedTransactions) {
|
|
1097
|
+
if (pending.truncate) {
|
|
1098
|
+
pendingPresence.clear();
|
|
1099
|
+
usesBaseline = false;
|
|
1100
|
+
}
|
|
1101
|
+
for (const operation of pending.operations) {
|
|
1102
|
+
pendingPresence.set(operation.key, operation.type !== `delete`);
|
|
1103
|
+
}
|
|
1081
1104
|
}
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1105
|
+
for (const message of bufferedMessages) {
|
|
1106
|
+
if (client.isChangeMessage(message)) {
|
|
1107
|
+
pendingPresence.set(
|
|
1108
|
+
collection.getKeyFromItem(message.value),
|
|
1109
|
+
message.headers.operation !== `delete`
|
|
1110
|
+
);
|
|
1111
|
+
}
|
|
1089
1112
|
}
|
|
1090
1113
|
}
|
|
1091
1114
|
let commitPoint = null;
|
|
@@ -1107,7 +1130,10 @@ function createElectricSync(shapeOptions, options) {
|
|
|
1107
1130
|
}
|
|
1108
1131
|
if (!receivesCompleteRows) continue;
|
|
1109
1132
|
}
|
|
1110
|
-
|
|
1133
|
+
recordPendingPresence(rowId, operation !== `delete`);
|
|
1134
|
+
if (operation !== `update`) {
|
|
1135
|
+
progressiveSnapshotRows.delete(rowId);
|
|
1136
|
+
}
|
|
1111
1137
|
}
|
|
1112
1138
|
if (client.isChangeMessage(message)) {
|
|
1113
1139
|
const schema = message.headers.schema;
|
|
@@ -1115,10 +1141,18 @@ function createElectricSync(shapeOptions, options) {
|
|
|
1115
1141
|
relationSchema = schema;
|
|
1116
1142
|
}
|
|
1117
1143
|
if (isBufferingInitialSync() && !transactionStarted) {
|
|
1144
|
+
if (message.headers.operation === `update`) {
|
|
1145
|
+
const rowId = collection.getKeyFromItem(message.value);
|
|
1146
|
+
const snapshotRow = progressiveSnapshotRows.get(rowId);
|
|
1147
|
+
if (snapshotRow) {
|
|
1148
|
+
bufferedMessages.push(snapshotRow);
|
|
1149
|
+
progressiveSnapshotRows.delete(rowId);
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1118
1152
|
bufferedMessages.push(message);
|
|
1119
1153
|
} else {
|
|
1120
1154
|
if (!transactionStarted) {
|
|
1121
|
-
|
|
1155
|
+
beginSourceTransaction();
|
|
1122
1156
|
transactionStarted = true;
|
|
1123
1157
|
}
|
|
1124
1158
|
processChangeMessage(message);
|
|
@@ -1139,11 +1173,11 @@ function createElectricSync(shapeOptions, options) {
|
|
|
1139
1173
|
} else {
|
|
1140
1174
|
transactionStarted = processMoveOutEvent(
|
|
1141
1175
|
message.headers.patterns,
|
|
1142
|
-
|
|
1176
|
+
beginSourceTransaction,
|
|
1143
1177
|
write,
|
|
1144
1178
|
transactionStarted,
|
|
1145
1179
|
(rowId) => {
|
|
1146
|
-
|
|
1180
|
+
recordPendingPresence(rowId, false);
|
|
1147
1181
|
syncedKeys.delete(rowId);
|
|
1148
1182
|
}
|
|
1149
1183
|
);
|
|
@@ -1160,20 +1194,20 @@ function createElectricSync(shapeOptions, options) {
|
|
|
1160
1194
|
);
|
|
1161
1195
|
commitResetResumeMetadataImmediately();
|
|
1162
1196
|
if (!transactionStarted) {
|
|
1163
|
-
|
|
1197
|
+
beginSourceTransaction();
|
|
1164
1198
|
transactionStarted = true;
|
|
1165
1199
|
}
|
|
1166
1200
|
truncate();
|
|
1167
1201
|
clearTagTrackingState();
|
|
1168
1202
|
syncedKeys.clear();
|
|
1169
|
-
|
|
1170
|
-
usesBaseline = false;
|
|
1203
|
+
recordPendingTruncate();
|
|
1171
1204
|
isResettingSnapshot = true;
|
|
1172
1205
|
resetGeneration++;
|
|
1173
1206
|
loadSubsetDedupe?.reset();
|
|
1174
1207
|
commitPoint = null;
|
|
1175
1208
|
hasReceivedUpToDate = false;
|
|
1176
1209
|
bufferedMessages.length = 0;
|
|
1210
|
+
progressiveSnapshotRows.clear();
|
|
1177
1211
|
}
|
|
1178
1212
|
}
|
|
1179
1213
|
if (needsFullSnapshot && isResettingSnapshot && commitPoint === `subset-end`)
|
|
@@ -1187,7 +1221,7 @@ function createElectricSync(shapeOptions, options) {
|
|
|
1187
1221
|
debug(
|
|
1188
1222
|
`${collectionId ? `[${collectionId}] ` : ``}Progressive mode: Performing atomic swap with ${bufferedMessages.length} buffered messages`
|
|
1189
1223
|
);
|
|
1190
|
-
|
|
1224
|
+
beginSourceTransaction();
|
|
1191
1225
|
truncate();
|
|
1192
1226
|
clearTagTrackingState();
|
|
1193
1227
|
syncedKeys.clear();
|
|
@@ -1210,7 +1244,7 @@ function createElectricSync(shapeOptions, options) {
|
|
|
1210
1244
|
// normal-stream transactionStarted flag is still false.
|
|
1211
1245
|
true,
|
|
1212
1246
|
(rowId) => {
|
|
1213
|
-
|
|
1247
|
+
recordPendingPresence(rowId, false);
|
|
1214
1248
|
syncedKeys.delete(rowId);
|
|
1215
1249
|
}
|
|
1216
1250
|
);
|
|
@@ -1219,8 +1253,9 @@ function createElectricSync(shapeOptions, options) {
|
|
|
1219
1253
|
}
|
|
1220
1254
|
}
|
|
1221
1255
|
stageResumeMetadata();
|
|
1222
|
-
applied =
|
|
1256
|
+
applied = commitSourceTransaction();
|
|
1223
1257
|
bufferedMessages.length = 0;
|
|
1258
|
+
progressiveSnapshotRows.clear();
|
|
1224
1259
|
debug(
|
|
1225
1260
|
`${collectionId ? `[${collectionId}] ` : ``}Progressive mode: Atomic swap complete, now in normal sync mode`
|
|
1226
1261
|
);
|
|
@@ -1229,12 +1264,12 @@ function createElectricSync(shapeOptions, options) {
|
|
|
1229
1264
|
if (!isResettingSnapshot || finishesReset) {
|
|
1230
1265
|
stageResumeMetadata();
|
|
1231
1266
|
}
|
|
1232
|
-
applied =
|
|
1267
|
+
applied = commitSourceTransaction();
|
|
1233
1268
|
transactionStarted = false;
|
|
1234
1269
|
} else if (commitPoint === `up-to-date` && metadata) {
|
|
1235
|
-
|
|
1270
|
+
beginSourceTransaction();
|
|
1236
1271
|
stageResumeMetadata();
|
|
1237
|
-
applied =
|
|
1272
|
+
applied = commitSourceTransaction();
|
|
1238
1273
|
}
|
|
1239
1274
|
}
|
|
1240
1275
|
const readyErrorVersion = streamErrorVersion;
|
|
@@ -1243,7 +1278,15 @@ function createElectricSync(shapeOptions, options) {
|
|
|
1243
1278
|
} else {
|
|
1244
1279
|
void applied.then(
|
|
1245
1280
|
() => wrappedMarkReady(wasBufferingInitialSync, readyErrorVersion),
|
|
1246
|
-
() =>
|
|
1281
|
+
(error) => {
|
|
1282
|
+
if (!isActiveLifecycle() || abortController.signal.aborted) {
|
|
1283
|
+
return;
|
|
1284
|
+
}
|
|
1285
|
+
streamErrorVersion++;
|
|
1286
|
+
unsubscribeStream();
|
|
1287
|
+
abortController.abort();
|
|
1288
|
+
if (collection.status !== `error`) markError(error);
|
|
1289
|
+
}
|
|
1247
1290
|
);
|
|
1248
1291
|
}
|
|
1249
1292
|
if (finishesReset) {
|