@tanstack/electric-db-collection 0.3.17 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/electric.cjs +113 -35
- package/dist/cjs/electric.cjs.map +1 -1
- package/dist/esm/electric.js +114 -36
- package/dist/esm/electric.js.map +1 -1
- package/package.json +2 -2
- package/src/electric.ts +186 -47
package/dist/cjs/electric.cjs
CHANGED
|
@@ -10,6 +10,74 @@ const tagIndex = require("./tag-index.cjs");
|
|
|
10
10
|
const debug = DebugModule.debug(`ts/db:electric`);
|
|
11
11
|
const FORCE_DISCONNECT_AND_REFRESH_TIMEOUT_MS = 250;
|
|
12
12
|
const ELECTRIC_TEST_HOOKS = /* @__PURE__ */ Symbol(`electricTestHooks`);
|
|
13
|
+
function parseElectricResumeState(value) {
|
|
14
|
+
if (!value || typeof value !== `object`) {
|
|
15
|
+
return void 0;
|
|
16
|
+
}
|
|
17
|
+
const record = value;
|
|
18
|
+
if (record.kind === `resume` && typeof record.offset === `string` && typeof record.handle === `string` && typeof record.shapeId === `string` && typeof record.updatedAt === `number` && Number.isFinite(record.updatedAt)) {
|
|
19
|
+
return {
|
|
20
|
+
kind: `resume`,
|
|
21
|
+
offset: record.offset,
|
|
22
|
+
handle: record.handle,
|
|
23
|
+
shapeId: record.shapeId,
|
|
24
|
+
updatedAt: record.updatedAt
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
if (record.kind === `reset` && typeof record.updatedAt === `number` && Number.isFinite(record.updatedAt)) {
|
|
28
|
+
return {
|
|
29
|
+
kind: `reset`,
|
|
30
|
+
updatedAt: record.updatedAt
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
return void 0;
|
|
34
|
+
}
|
|
35
|
+
function parseElectricSyncMeta(value) {
|
|
36
|
+
if (!value || typeof value !== `object`) {
|
|
37
|
+
return void 0;
|
|
38
|
+
}
|
|
39
|
+
const record = value;
|
|
40
|
+
if (record.version !== 1 || !Array.isArray(record.seenTxids) || !record.seenTxids.every(
|
|
41
|
+
(txid) => typeof txid === `number` && Number.isFinite(txid)
|
|
42
|
+
)) {
|
|
43
|
+
return void 0;
|
|
44
|
+
}
|
|
45
|
+
const resume = record.resume === void 0 ? void 0 : parseElectricResumeState(record.resume);
|
|
46
|
+
if (record.resume !== void 0 && resume === void 0) {
|
|
47
|
+
return void 0;
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
version: 1,
|
|
51
|
+
...resume ? { resume } : {},
|
|
52
|
+
seenTxids: Array.from(new Set(record.seenTxids)).sort((a, b) => a - b)
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function mergeElectricSyncMeta(current, incoming) {
|
|
56
|
+
const currentMeta = parseElectricSyncMeta(current);
|
|
57
|
+
const incomingMeta = parseElectricSyncMeta(incoming);
|
|
58
|
+
if (!incomingMeta) {
|
|
59
|
+
return current;
|
|
60
|
+
}
|
|
61
|
+
if (!currentMeta) {
|
|
62
|
+
return incomingMeta;
|
|
63
|
+
}
|
|
64
|
+
const resume = getNewestElectricResumeState(
|
|
65
|
+
currentMeta.resume,
|
|
66
|
+
incomingMeta.resume
|
|
67
|
+
);
|
|
68
|
+
return {
|
|
69
|
+
version: 1,
|
|
70
|
+
...resume ? { resume } : {},
|
|
71
|
+
seenTxids: Array.from(
|
|
72
|
+
/* @__PURE__ */ new Set([...currentMeta.seenTxids, ...incomingMeta.seenTxids])
|
|
73
|
+
).sort((a, b) => a - b)
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
function getNewestElectricResumeState(current, incoming) {
|
|
77
|
+
if (!current) return incoming;
|
|
78
|
+
if (!incoming) return current;
|
|
79
|
+
return incoming.updatedAt >= current.updatedAt ? incoming : current;
|
|
80
|
+
}
|
|
13
81
|
function isUpToDateMessage(message) {
|
|
14
82
|
return client.isControlMessage(message) && message.headers.control === `up-to-date`;
|
|
15
83
|
}
|
|
@@ -184,6 +252,9 @@ function createLoadSubsetDedupe({
|
|
|
184
252
|
function electricCollectionOptions(config) {
|
|
185
253
|
const seenTxids = new store.Store(/* @__PURE__ */ new Set([]));
|
|
186
254
|
const seenSnapshots = new store.Store([]);
|
|
255
|
+
const hydratedResumeState = new store.Store(
|
|
256
|
+
void 0
|
|
257
|
+
);
|
|
187
258
|
const internalSyncMode = config.syncMode ?? `eager`;
|
|
188
259
|
const finalSyncMode = internalSyncMode === `progressive` ? `on-demand` : internalSyncMode;
|
|
189
260
|
const pendingMatches = new store.Store(/* @__PURE__ */ new Map());
|
|
@@ -216,6 +287,7 @@ function electricCollectionOptions(config) {
|
|
|
216
287
|
const sync = createElectricSync(config.shapeOptions, {
|
|
217
288
|
seenTxids,
|
|
218
289
|
seenSnapshots,
|
|
290
|
+
hydratedResumeState,
|
|
219
291
|
syncMode: internalSyncMode,
|
|
220
292
|
pendingMatches,
|
|
221
293
|
currentBatchMessages,
|
|
@@ -383,10 +455,26 @@ function electricCollectionOptions(config) {
|
|
|
383
455
|
onDelete: _onDelete,
|
|
384
456
|
...restConfig
|
|
385
457
|
} = config;
|
|
386
|
-
|
|
458
|
+
const options = {
|
|
387
459
|
...restConfig,
|
|
388
460
|
syncMode: finalSyncMode,
|
|
389
|
-
sync
|
|
461
|
+
sync: {
|
|
462
|
+
...sync,
|
|
463
|
+
exportSyncMeta: () => ({
|
|
464
|
+
version: 1,
|
|
465
|
+
...hydratedResumeState.state ? { resume: hydratedResumeState.state } : {},
|
|
466
|
+
seenTxids: Array.from(seenTxids.state).sort((a, b) => a - b)
|
|
467
|
+
}),
|
|
468
|
+
importSyncMeta: (meta) => {
|
|
469
|
+
const parsed = parseElectricSyncMeta(meta);
|
|
470
|
+
if (!parsed) {
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
hydratedResumeState.setState(() => parsed.resume);
|
|
474
|
+
seenTxids.setState(() => new Set(parsed.seenTxids));
|
|
475
|
+
},
|
|
476
|
+
mergeSyncMeta: mergeElectricSyncMeta
|
|
477
|
+
},
|
|
390
478
|
onInsert: wrappedOnInsert,
|
|
391
479
|
onUpdate: wrappedOnUpdate,
|
|
392
480
|
onDelete: wrappedOnDelete,
|
|
@@ -395,11 +483,16 @@ function electricCollectionOptions(config) {
|
|
|
395
483
|
awaitMatch
|
|
396
484
|
}
|
|
397
485
|
};
|
|
486
|
+
return db.withCollectionConfigFactory(
|
|
487
|
+
options,
|
|
488
|
+
() => electricCollectionOptions(config)
|
|
489
|
+
);
|
|
398
490
|
}
|
|
399
491
|
function createElectricSync(shapeOptions, options) {
|
|
400
492
|
const {
|
|
401
493
|
seenTxids,
|
|
402
494
|
seenSnapshots,
|
|
495
|
+
hydratedResumeState,
|
|
403
496
|
syncMode,
|
|
404
497
|
pendingMatches,
|
|
405
498
|
currentBatchMessages,
|
|
@@ -603,28 +696,12 @@ function createElectricSync(shapeOptions, options) {
|
|
|
603
696
|
} = params;
|
|
604
697
|
const readPersistedResumeState = () => {
|
|
605
698
|
const persistedResumeState2 = metadata?.collection.get(`electric:resume`);
|
|
606
|
-
|
|
607
|
-
return void 0;
|
|
608
|
-
}
|
|
609
|
-
const record = persistedResumeState2;
|
|
610
|
-
if (record.kind === `resume` && typeof record.offset === `string` && typeof record.handle === `string` && typeof record.shapeId === `string` && typeof record.updatedAt === `number`) {
|
|
611
|
-
return {
|
|
612
|
-
kind: `resume`,
|
|
613
|
-
offset: record.offset,
|
|
614
|
-
handle: record.handle,
|
|
615
|
-
shapeId: record.shapeId,
|
|
616
|
-
updatedAt: record.updatedAt
|
|
617
|
-
};
|
|
618
|
-
}
|
|
619
|
-
if (record.kind === `reset` && typeof record.updatedAt === `number`) {
|
|
620
|
-
return {
|
|
621
|
-
kind: `reset`,
|
|
622
|
-
updatedAt: record.updatedAt
|
|
623
|
-
};
|
|
624
|
-
}
|
|
625
|
-
return void 0;
|
|
699
|
+
return parseElectricResumeState(persistedResumeState2);
|
|
626
700
|
};
|
|
627
|
-
const persistedResumeState =
|
|
701
|
+
const persistedResumeState = getNewestElectricResumeState(
|
|
702
|
+
readPersistedResumeState(),
|
|
703
|
+
hydratedResumeState.state
|
|
704
|
+
);
|
|
628
705
|
const shapeIdentity = getStableShapeIdentity({
|
|
629
706
|
url: shapeOptions.url,
|
|
630
707
|
params: shapeOptions.params
|
|
@@ -698,32 +775,32 @@ You can provide an 'onError' handler on the shapeOptions to handle this error, a
|
|
|
698
775
|
const bufferedMessages = [];
|
|
699
776
|
const syncedKeys = /* @__PURE__ */ new Set();
|
|
700
777
|
const stageResumeMetadata = () => {
|
|
701
|
-
if (!metadata) {
|
|
702
|
-
return;
|
|
703
|
-
}
|
|
704
778
|
const shapeHandle = stream.shapeHandle;
|
|
705
779
|
const lastOffset = stream.lastOffset;
|
|
706
780
|
if (!shapeHandle || lastOffset === `-1`) {
|
|
707
781
|
return;
|
|
708
782
|
}
|
|
709
|
-
|
|
783
|
+
const resumeState = {
|
|
710
784
|
kind: `resume`,
|
|
711
785
|
offset: lastOffset,
|
|
712
786
|
handle: shapeHandle,
|
|
713
787
|
shapeId: shapeIdentity,
|
|
714
788
|
updatedAt: Date.now()
|
|
715
|
-
}
|
|
789
|
+
};
|
|
790
|
+
hydratedResumeState.setState(() => resumeState);
|
|
791
|
+
metadata?.collection.set(`electric:resume`, resumeState);
|
|
716
792
|
};
|
|
717
793
|
const commitResetResumeMetadataImmediately = () => {
|
|
718
|
-
|
|
719
|
-
return;
|
|
720
|
-
}
|
|
721
|
-
begin({ immediate: true });
|
|
722
|
-
metadata.collection.set(`electric:resume`, {
|
|
794
|
+
const resetState = {
|
|
723
795
|
kind: `reset`,
|
|
724
796
|
updatedAt: Date.now()
|
|
725
|
-
}
|
|
726
|
-
|
|
797
|
+
};
|
|
798
|
+
hydratedResumeState.setState(() => resetState);
|
|
799
|
+
if (metadata) {
|
|
800
|
+
begin({ immediate: true });
|
|
801
|
+
metadata.collection.set(`electric:resume`, resetState);
|
|
802
|
+
commit();
|
|
803
|
+
}
|
|
727
804
|
};
|
|
728
805
|
if (hasIncompatiblePersistedResume) {
|
|
729
806
|
commitResetResumeMetadataImmediately();
|
|
@@ -953,6 +1030,7 @@ You can provide an 'onError' handler on the shapeOptions to handle this error, a
|
|
|
953
1030
|
unsubscribeStream();
|
|
954
1031
|
abortController.abort();
|
|
955
1032
|
loadSubsetDedupe?.reset();
|
|
1033
|
+
hydratedResumeState.setState(() => void 0);
|
|
956
1034
|
}
|
|
957
1035
|
};
|
|
958
1036
|
},
|