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