@tanstack/electric-db-collection 0.2.14 → 0.2.16
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 +191 -33
- package/dist/cjs/electric.cjs.map +1 -1
- package/dist/cjs/tag-index.cjs +67 -0
- package/dist/cjs/tag-index.cjs.map +1 -0
- package/dist/cjs/tag-index.d.cts +66 -0
- package/dist/esm/electric.js +191 -33
- package/dist/esm/electric.js.map +1 -1
- package/dist/esm/tag-index.d.ts +66 -0
- package/dist/esm/tag-index.js +67 -0
- package/dist/esm/tag-index.js.map +1 -0
- package/package.json +3 -3
- package/src/electric.ts +334 -43
- package/src/tag-index.ts +160 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const TAG_WILDCARD = `_`;
|
|
4
|
+
function getValue(tag, position) {
|
|
5
|
+
if (position >= tag.length) {
|
|
6
|
+
throw new Error(`Position out of bounds`);
|
|
7
|
+
}
|
|
8
|
+
return tag[position];
|
|
9
|
+
}
|
|
10
|
+
function getPositionalValue(pattern) {
|
|
11
|
+
return pattern;
|
|
12
|
+
}
|
|
13
|
+
function getTagLength(tag) {
|
|
14
|
+
return tag.length;
|
|
15
|
+
}
|
|
16
|
+
function tagMatchesPattern(tag, pattern) {
|
|
17
|
+
const { pos, value } = getPositionalValue(pattern);
|
|
18
|
+
const tagValue = getValue(tag, pos);
|
|
19
|
+
return tagValue === value || tagValue === TAG_WILDCARD;
|
|
20
|
+
}
|
|
21
|
+
function addTagToIndex(tag, rowId, index, tagLength) {
|
|
22
|
+
for (let i = 0; i < tagLength; i++) {
|
|
23
|
+
const value = getValue(tag, i);
|
|
24
|
+
if (value !== TAG_WILDCARD) {
|
|
25
|
+
const positionIndex = index[i];
|
|
26
|
+
if (!positionIndex.has(value)) {
|
|
27
|
+
positionIndex.set(value, /* @__PURE__ */ new Set());
|
|
28
|
+
}
|
|
29
|
+
const tags = positionIndex.get(value);
|
|
30
|
+
tags.add(rowId);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function removeTagFromIndex(tag, rowId, index, tagLength) {
|
|
35
|
+
for (let i = 0; i < tagLength; i++) {
|
|
36
|
+
const value = getValue(tag, i);
|
|
37
|
+
if (value !== TAG_WILDCARD) {
|
|
38
|
+
const positionIndex = index[i];
|
|
39
|
+
if (positionIndex) {
|
|
40
|
+
const rowSet = positionIndex.get(value);
|
|
41
|
+
if (rowSet) {
|
|
42
|
+
rowSet.delete(rowId);
|
|
43
|
+
if (rowSet.size === 0) {
|
|
44
|
+
positionIndex.delete(value);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function findRowsMatchingPattern(pattern, index) {
|
|
52
|
+
const { pos, value } = getPositionalValue(pattern);
|
|
53
|
+
const positionIndex = index[pos];
|
|
54
|
+
const rowSet = positionIndex?.get(value);
|
|
55
|
+
return rowSet ?? /* @__PURE__ */ new Set();
|
|
56
|
+
}
|
|
57
|
+
function isMoveOutMessage(message) {
|
|
58
|
+
return message.headers.event === `move-out`;
|
|
59
|
+
}
|
|
60
|
+
exports.addTagToIndex = addTagToIndex;
|
|
61
|
+
exports.findRowsMatchingPattern = findRowsMatchingPattern;
|
|
62
|
+
exports.getTagLength = getTagLength;
|
|
63
|
+
exports.getValue = getValue;
|
|
64
|
+
exports.isMoveOutMessage = isMoveOutMessage;
|
|
65
|
+
exports.removeTagFromIndex = removeTagFromIndex;
|
|
66
|
+
exports.tagMatchesPattern = tagMatchesPattern;
|
|
67
|
+
//# sourceMappingURL=tag-index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tag-index.cjs","sources":["../../src/tag-index.ts"],"sourcesContent":["// Import Row and Message types for the isEventMessage function\nimport type { Message, Row } from '@electric-sql/client'\n\nexport type RowId = string | number\nexport type MoveTag = string\nexport type ParsedMoveTag = Array<string>\nexport type Position = number\nexport type Value = string\nexport type MoveOutPattern = {\n pos: Position\n value: Value\n}\n\nconst TAG_WILDCARD = `_`\n\n/**\n * Event message type for move-out events\n */\nexport interface EventMessage {\n headers: {\n event: `move-out`\n patterns: Array<MoveOutPattern>\n }\n}\n\n/**\n * Tag index structure: array indexed by position, maps value to set of row IDs.\n * For example:\n * ```example\n * const tag1 = [a, b, c]\n * const tag2 = [a, b, d]\n * const tag3 = [a, d, e]\n *\n * // Index is:\n * [\n * new Map([a -> <rows with a on index 0>])\n * new Map([b -> <rows with b on index 1>, d -> <rows with d on index 1>])\n * new Map([c -> <rows with c on index 2>, d -> <rows with d on index 2>, e -> <rows with e on index 2>])\n * ]\n * ```\n */\nexport type TagIndex = Array<Map<Value, Set<RowId>>>\n\n/**\n * Abstraction to get the value at a specific position in a tag\n */\nexport function getValue(tag: ParsedMoveTag, position: Position): Value {\n if (position >= tag.length) {\n throw new Error(`Position out of bounds`)\n }\n return tag[position]!\n}\n\n/**\n * Abstraction to extract position and value from a pattern.\n */\nfunction getPositionalValue(pattern: MoveOutPattern): {\n pos: number\n value: string\n} {\n return pattern\n}\n\n/**\n * Abstraction to get the length of a tag\n */\nexport function getTagLength(tag: ParsedMoveTag): number {\n return tag.length\n}\n\n/**\n * Check if a tag matches a pattern.\n * A tag matches if the value at the pattern's position equals the pattern's value,\n * or if the value at that position is \"_\" (wildcard).\n */\nexport function tagMatchesPattern(\n tag: ParsedMoveTag,\n pattern: MoveOutPattern,\n): boolean {\n const { pos, value } = getPositionalValue(pattern)\n const tagValue = getValue(tag, pos)\n return tagValue === value || tagValue === TAG_WILDCARD\n}\n\n/**\n * Add a tag to the index for efficient pattern matching\n */\nexport function addTagToIndex(\n tag: ParsedMoveTag,\n rowId: RowId,\n index: TagIndex,\n tagLength: number,\n): void {\n for (let i = 0; i < tagLength; i++) {\n const value = getValue(tag, i)\n\n // Only index non-wildcard values\n if (value !== TAG_WILDCARD) {\n const positionIndex = index[i]!\n if (!positionIndex.has(value)) {\n positionIndex.set(value, new Set())\n }\n\n const tags = positionIndex.get(value)!\n tags.add(rowId)\n }\n }\n}\n\n/**\n * Remove a tag from the index\n */\nexport function removeTagFromIndex(\n tag: ParsedMoveTag,\n rowId: RowId,\n index: TagIndex,\n tagLength: number,\n): void {\n for (let i = 0; i < tagLength; i++) {\n const value = getValue(tag, i)\n\n // Only remove non-wildcard values\n if (value !== TAG_WILDCARD) {\n const positionIndex = index[i]\n if (positionIndex) {\n const rowSet = positionIndex.get(value)\n if (rowSet) {\n rowSet.delete(rowId)\n\n // Clean up empty sets\n if (rowSet.size === 0) {\n positionIndex.delete(value)\n }\n }\n }\n }\n }\n}\n\n/**\n * Find all rows that match a given pattern\n */\nexport function findRowsMatchingPattern(\n pattern: MoveOutPattern,\n index: TagIndex,\n): Set<RowId> {\n const { pos, value } = getPositionalValue(pattern)\n const positionIndex = index[pos]\n const rowSet = positionIndex?.get(value)\n return rowSet ?? new Set()\n}\n\n/**\n * Check if a message is an event message with move-out event\n */\nexport function isMoveOutMessage<T extends Row<unknown>>(\n message: Message<T>,\n): message is Message<T> & EventMessage {\n return message.headers.event === `move-out`\n}\n"],"names":[],"mappings":";;AAaA,MAAM,eAAe;AAiCd,SAAS,SAAS,KAAoB,UAA2B;AACtE,MAAI,YAAY,IAAI,QAAQ;AAC1B,UAAM,IAAI,MAAM,wBAAwB;AAAA,EAC1C;AACA,SAAO,IAAI,QAAQ;AACrB;AAKA,SAAS,mBAAmB,SAG1B;AACA,SAAO;AACT;AAKO,SAAS,aAAa,KAA4B;AACvD,SAAO,IAAI;AACb;AAOO,SAAS,kBACd,KACA,SACS;AACT,QAAM,EAAE,KAAK,UAAU,mBAAmB,OAAO;AACjD,QAAM,WAAW,SAAS,KAAK,GAAG;AAClC,SAAO,aAAa,SAAS,aAAa;AAC5C;AAKO,SAAS,cACd,KACA,OACA,OACA,WACM;AACN,WAAS,IAAI,GAAG,IAAI,WAAW,KAAK;AAClC,UAAM,QAAQ,SAAS,KAAK,CAAC;AAG7B,QAAI,UAAU,cAAc;AAC1B,YAAM,gBAAgB,MAAM,CAAC;AAC7B,UAAI,CAAC,cAAc,IAAI,KAAK,GAAG;AAC7B,sBAAc,IAAI,OAAO,oBAAI,IAAA,CAAK;AAAA,MACpC;AAEA,YAAM,OAAO,cAAc,IAAI,KAAK;AACpC,WAAK,IAAI,KAAK;AAAA,IAChB;AAAA,EACF;AACF;AAKO,SAAS,mBACd,KACA,OACA,OACA,WACM;AACN,WAAS,IAAI,GAAG,IAAI,WAAW,KAAK;AAClC,UAAM,QAAQ,SAAS,KAAK,CAAC;AAG7B,QAAI,UAAU,cAAc;AAC1B,YAAM,gBAAgB,MAAM,CAAC;AAC7B,UAAI,eAAe;AACjB,cAAM,SAAS,cAAc,IAAI,KAAK;AACtC,YAAI,QAAQ;AACV,iBAAO,OAAO,KAAK;AAGnB,cAAI,OAAO,SAAS,GAAG;AACrB,0BAAc,OAAO,KAAK;AAAA,UAC5B;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AAKO,SAAS,wBACd,SACA,OACY;AACZ,QAAM,EAAE,KAAK,UAAU,mBAAmB,OAAO;AACjD,QAAM,gBAAgB,MAAM,GAAG;AAC/B,QAAM,SAAS,eAAe,IAAI,KAAK;AACvC,SAAO,8BAAc,IAAA;AACvB;AAKO,SAAS,iBACd,SACsC;AACtC,SAAO,QAAQ,QAAQ,UAAU;AACnC;;;;;;;;"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { Message, Row } from '@electric-sql/client';
|
|
2
|
+
export type RowId = string | number;
|
|
3
|
+
export type MoveTag = string;
|
|
4
|
+
export type ParsedMoveTag = Array<string>;
|
|
5
|
+
export type Position = number;
|
|
6
|
+
export type Value = string;
|
|
7
|
+
export type MoveOutPattern = {
|
|
8
|
+
pos: Position;
|
|
9
|
+
value: Value;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Event message type for move-out events
|
|
13
|
+
*/
|
|
14
|
+
export interface EventMessage {
|
|
15
|
+
headers: {
|
|
16
|
+
event: `move-out`;
|
|
17
|
+
patterns: Array<MoveOutPattern>;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Tag index structure: array indexed by position, maps value to set of row IDs.
|
|
22
|
+
* For example:
|
|
23
|
+
* ```example
|
|
24
|
+
* const tag1 = [a, b, c]
|
|
25
|
+
* const tag2 = [a, b, d]
|
|
26
|
+
* const tag3 = [a, d, e]
|
|
27
|
+
*
|
|
28
|
+
* // Index is:
|
|
29
|
+
* [
|
|
30
|
+
* new Map([a -> <rows with a on index 0>])
|
|
31
|
+
* new Map([b -> <rows with b on index 1>, d -> <rows with d on index 1>])
|
|
32
|
+
* new Map([c -> <rows with c on index 2>, d -> <rows with d on index 2>, e -> <rows with e on index 2>])
|
|
33
|
+
* ]
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export type TagIndex = Array<Map<Value, Set<RowId>>>;
|
|
37
|
+
/**
|
|
38
|
+
* Abstraction to get the value at a specific position in a tag
|
|
39
|
+
*/
|
|
40
|
+
export declare function getValue(tag: ParsedMoveTag, position: Position): Value;
|
|
41
|
+
/**
|
|
42
|
+
* Abstraction to get the length of a tag
|
|
43
|
+
*/
|
|
44
|
+
export declare function getTagLength(tag: ParsedMoveTag): number;
|
|
45
|
+
/**
|
|
46
|
+
* Check if a tag matches a pattern.
|
|
47
|
+
* A tag matches if the value at the pattern's position equals the pattern's value,
|
|
48
|
+
* or if the value at that position is "_" (wildcard).
|
|
49
|
+
*/
|
|
50
|
+
export declare function tagMatchesPattern(tag: ParsedMoveTag, pattern: MoveOutPattern): boolean;
|
|
51
|
+
/**
|
|
52
|
+
* Add a tag to the index for efficient pattern matching
|
|
53
|
+
*/
|
|
54
|
+
export declare function addTagToIndex(tag: ParsedMoveTag, rowId: RowId, index: TagIndex, tagLength: number): void;
|
|
55
|
+
/**
|
|
56
|
+
* Remove a tag from the index
|
|
57
|
+
*/
|
|
58
|
+
export declare function removeTagFromIndex(tag: ParsedMoveTag, rowId: RowId, index: TagIndex, tagLength: number): void;
|
|
59
|
+
/**
|
|
60
|
+
* Find all rows that match a given pattern
|
|
61
|
+
*/
|
|
62
|
+
export declare function findRowsMatchingPattern(pattern: MoveOutPattern, index: TagIndex): Set<RowId>;
|
|
63
|
+
/**
|
|
64
|
+
* Check if a message is an event message with move-out event
|
|
65
|
+
*/
|
|
66
|
+
export declare function isMoveOutMessage<T extends Row<unknown>>(message: Message<T>): message is Message<T> & EventMessage;
|
package/dist/esm/electric.js
CHANGED
|
@@ -5,6 +5,7 @@ import DebugModule from "debug";
|
|
|
5
5
|
import { DeduplicatedLoadSubset, and } from "@tanstack/db";
|
|
6
6
|
import { StreamAbortedError, ExpectedNumberInAwaitTxIdError, TimeoutWaitingForTxIdError, TimeoutWaitingForMatchError } from "./errors.js";
|
|
7
7
|
import { compileSQL } from "./sql-compiler.js";
|
|
8
|
+
import { isMoveOutMessage, getTagLength, addTagToIndex, removeTagFromIndex, findRowsMatchingPattern, tagMatchesPattern } from "./tag-index.js";
|
|
8
9
|
const debug = DebugModule.debug(`ts/db:electric`);
|
|
9
10
|
const ELECTRIC_TEST_HOOKS = Symbol(`electricTestHooks`);
|
|
10
11
|
function isUpToDateMessage(message) {
|
|
@@ -16,6 +17,9 @@ function isMustRefetchMessage(message) {
|
|
|
16
17
|
function isSnapshotEndMessage(message) {
|
|
17
18
|
return isControlMessage(message) && message.headers.control === `snapshot-end`;
|
|
18
19
|
}
|
|
20
|
+
function isSubsetEndMessage(message) {
|
|
21
|
+
return isControlMessage(message) && message.headers.control === `subset-end`;
|
|
22
|
+
}
|
|
19
23
|
function parseSnapshotMessage(message) {
|
|
20
24
|
return {
|
|
21
25
|
xmin: message.headers.xmin,
|
|
@@ -337,6 +341,134 @@ function createElectricSync(shapeOptions, options) {
|
|
|
337
341
|
} = options;
|
|
338
342
|
const MAX_BATCH_MESSAGES = 1e3;
|
|
339
343
|
const relationSchema = new Store(void 0);
|
|
344
|
+
const tagCache = /* @__PURE__ */ new Map();
|
|
345
|
+
const parseTag = (tag) => {
|
|
346
|
+
const cachedTag = tagCache.get(tag);
|
|
347
|
+
if (cachedTag) {
|
|
348
|
+
return cachedTag;
|
|
349
|
+
}
|
|
350
|
+
const parsedTag = tag.split(`|`);
|
|
351
|
+
tagCache.set(tag, parsedTag);
|
|
352
|
+
return parsedTag;
|
|
353
|
+
};
|
|
354
|
+
const rowTagSets = /* @__PURE__ */ new Map();
|
|
355
|
+
const tagIndex = [];
|
|
356
|
+
let tagLength = void 0;
|
|
357
|
+
const initializeTagIndex = (length) => {
|
|
358
|
+
if (tagIndex.length < length) {
|
|
359
|
+
for (let i = tagIndex.length; i < length; i++) {
|
|
360
|
+
tagIndex[i] = /* @__PURE__ */ new Map();
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
};
|
|
364
|
+
const addTagsToRow = (tags, rowId, rowTagSet) => {
|
|
365
|
+
for (const tag of tags) {
|
|
366
|
+
const parsedTag = parseTag(tag);
|
|
367
|
+
if (tagLength === void 0) {
|
|
368
|
+
tagLength = getTagLength(parsedTag);
|
|
369
|
+
initializeTagIndex(tagLength);
|
|
370
|
+
}
|
|
371
|
+
const currentTagLength = getTagLength(parsedTag);
|
|
372
|
+
if (currentTagLength !== tagLength) {
|
|
373
|
+
debug(
|
|
374
|
+
`${collectionId ? `[${collectionId}] ` : ``}Tag length mismatch: expected ${tagLength}, got ${currentTagLength}`
|
|
375
|
+
);
|
|
376
|
+
continue;
|
|
377
|
+
}
|
|
378
|
+
rowTagSet.add(tag);
|
|
379
|
+
addTagToIndex(parsedTag, rowId, tagIndex, tagLength);
|
|
380
|
+
}
|
|
381
|
+
};
|
|
382
|
+
const removeTagsFromRow = (removedTags, rowId, rowTagSet) => {
|
|
383
|
+
if (tagLength === void 0) {
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
for (const tag of removedTags) {
|
|
387
|
+
const parsedTag = parseTag(tag);
|
|
388
|
+
rowTagSet.delete(tag);
|
|
389
|
+
removeTagFromIndex(parsedTag, rowId, tagIndex, tagLength);
|
|
390
|
+
tagCache.delete(tag);
|
|
391
|
+
}
|
|
392
|
+
};
|
|
393
|
+
const processTagsForChangeMessage = (tags, removedTags, rowId) => {
|
|
394
|
+
if (!rowTagSets.has(rowId)) {
|
|
395
|
+
rowTagSets.set(rowId, /* @__PURE__ */ new Set());
|
|
396
|
+
}
|
|
397
|
+
const rowTagSet = rowTagSets.get(rowId);
|
|
398
|
+
if (tags) {
|
|
399
|
+
addTagsToRow(tags, rowId, rowTagSet);
|
|
400
|
+
}
|
|
401
|
+
if (removedTags) {
|
|
402
|
+
removeTagsFromRow(removedTags, rowId, rowTagSet);
|
|
403
|
+
}
|
|
404
|
+
return rowTagSet;
|
|
405
|
+
};
|
|
406
|
+
const clearTagTrackingState = () => {
|
|
407
|
+
rowTagSets.clear();
|
|
408
|
+
tagIndex.length = 0;
|
|
409
|
+
tagLength = void 0;
|
|
410
|
+
};
|
|
411
|
+
const clearTagsForRow = (rowId) => {
|
|
412
|
+
if (tagLength === void 0) {
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
const rowTagSet = rowTagSets.get(rowId);
|
|
416
|
+
if (!rowTagSet) {
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
for (const tag of rowTagSet) {
|
|
420
|
+
const parsedTag = parseTag(tag);
|
|
421
|
+
const currentTagLength = getTagLength(parsedTag);
|
|
422
|
+
if (currentTagLength === tagLength) {
|
|
423
|
+
removeTagFromIndex(parsedTag, rowId, tagIndex, tagLength);
|
|
424
|
+
}
|
|
425
|
+
tagCache.delete(tag);
|
|
426
|
+
}
|
|
427
|
+
rowTagSets.delete(rowId);
|
|
428
|
+
};
|
|
429
|
+
const removeMatchingTagsFromRow = (rowId, pattern) => {
|
|
430
|
+
const rowTagSet = rowTagSets.get(rowId);
|
|
431
|
+
if (!rowTagSet) {
|
|
432
|
+
return false;
|
|
433
|
+
}
|
|
434
|
+
for (const tag of rowTagSet) {
|
|
435
|
+
const parsedTag = parseTag(tag);
|
|
436
|
+
if (tagMatchesPattern(parsedTag, pattern)) {
|
|
437
|
+
rowTagSet.delete(tag);
|
|
438
|
+
removeTagFromIndex(parsedTag, rowId, tagIndex, tagLength);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
if (rowTagSet.size === 0) {
|
|
442
|
+
rowTagSets.delete(rowId);
|
|
443
|
+
return true;
|
|
444
|
+
}
|
|
445
|
+
return false;
|
|
446
|
+
};
|
|
447
|
+
const processMoveOutEvent = (patterns, begin, write, transactionStarted) => {
|
|
448
|
+
if (tagLength === void 0) {
|
|
449
|
+
debug(
|
|
450
|
+
`${collectionId ? `[${collectionId}] ` : ``}Received move-out message but no tag length set yet, ignoring`
|
|
451
|
+
);
|
|
452
|
+
return transactionStarted;
|
|
453
|
+
}
|
|
454
|
+
let txStarted = transactionStarted;
|
|
455
|
+
for (const pattern of patterns) {
|
|
456
|
+
const affectedRowIds = findRowsMatchingPattern(pattern, tagIndex);
|
|
457
|
+
for (const rowId of affectedRowIds) {
|
|
458
|
+
if (removeMatchingTagsFromRow(rowId, pattern)) {
|
|
459
|
+
if (!txStarted) {
|
|
460
|
+
begin();
|
|
461
|
+
txStarted = true;
|
|
462
|
+
}
|
|
463
|
+
write({
|
|
464
|
+
type: `delete`,
|
|
465
|
+
key: rowId
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
return txStarted;
|
|
471
|
+
};
|
|
340
472
|
const getSyncMetadata = () => {
|
|
341
473
|
const schema = relationSchema.state || `public`;
|
|
342
474
|
return {
|
|
@@ -411,6 +543,29 @@ You can provide an 'onError' handler on the shapeOptions to handle this error, a
|
|
|
411
543
|
let hasReceivedUpToDate = false;
|
|
412
544
|
const isBufferingInitialSync = () => syncMode === `progressive` && !hasReceivedUpToDate;
|
|
413
545
|
const bufferedMessages = [];
|
|
546
|
+
const processChangeMessage = (changeMessage) => {
|
|
547
|
+
if (!isChangeMessage(changeMessage)) {
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
const tags = changeMessage.headers.tags;
|
|
551
|
+
const removedTags = changeMessage.headers.removed_tags;
|
|
552
|
+
const hasTags = tags || removedTags;
|
|
553
|
+
const rowId = collection.getKeyFromItem(changeMessage.value);
|
|
554
|
+
const operation = changeMessage.headers.operation;
|
|
555
|
+
if (operation === `delete`) {
|
|
556
|
+
clearTagsForRow(rowId);
|
|
557
|
+
} else if (hasTags) {
|
|
558
|
+
processTagsForChangeMessage(tags, removedTags, rowId);
|
|
559
|
+
}
|
|
560
|
+
write({
|
|
561
|
+
type: changeMessage.headers.operation,
|
|
562
|
+
value: changeMessage.value,
|
|
563
|
+
// Include the primary key and relation info in the metadata
|
|
564
|
+
metadata: {
|
|
565
|
+
...changeMessage.headers
|
|
566
|
+
}
|
|
567
|
+
});
|
|
568
|
+
};
|
|
414
569
|
const loadSubsetDedupe = createLoadSubsetDedupe({
|
|
415
570
|
stream,
|
|
416
571
|
syncMode,
|
|
@@ -421,12 +576,11 @@ You can provide an 'onError' handler on the shapeOptions to handle this error, a
|
|
|
421
576
|
collectionId
|
|
422
577
|
});
|
|
423
578
|
unsubscribeStream = stream.subscribe((messages) => {
|
|
424
|
-
let
|
|
425
|
-
let hasSnapshotEnd = false;
|
|
579
|
+
let commitPoint = null;
|
|
426
580
|
currentBatchMessages.setState(() => []);
|
|
427
581
|
batchCommitted.setState(() => false);
|
|
428
582
|
for (const message of messages) {
|
|
429
|
-
if (isChangeMessage(message)) {
|
|
583
|
+
if (isChangeMessage(message) || isMoveOutMessage(message)) {
|
|
430
584
|
currentBatchMessages.setState((currentBuffer) => {
|
|
431
585
|
const newBuffer = [...currentBuffer, message];
|
|
432
586
|
if (newBuffer.length > MAX_BATCH_MESSAGES) {
|
|
@@ -466,22 +620,29 @@ You can provide an 'onError' handler on the shapeOptions to handle this error, a
|
|
|
466
620
|
begin();
|
|
467
621
|
transactionStarted = true;
|
|
468
622
|
}
|
|
469
|
-
|
|
470
|
-
type: message.headers.operation,
|
|
471
|
-
value: message.value,
|
|
472
|
-
// Include the primary key and relation info in the metadata
|
|
473
|
-
metadata: {
|
|
474
|
-
...message.headers
|
|
475
|
-
}
|
|
476
|
-
});
|
|
623
|
+
processChangeMessage(message);
|
|
477
624
|
}
|
|
478
625
|
} else if (isSnapshotEndMessage(message)) {
|
|
479
626
|
if (!isBufferingInitialSync()) {
|
|
480
627
|
newSnapshots.push(parseSnapshotMessage(message));
|
|
481
628
|
}
|
|
482
|
-
hasSnapshotEnd = true;
|
|
483
629
|
} else if (isUpToDateMessage(message)) {
|
|
484
|
-
|
|
630
|
+
commitPoint = `up-to-date`;
|
|
631
|
+
} else if (isSubsetEndMessage(message)) {
|
|
632
|
+
if (commitPoint !== `up-to-date`) {
|
|
633
|
+
commitPoint = `subset-end`;
|
|
634
|
+
}
|
|
635
|
+
} else if (isMoveOutMessage(message)) {
|
|
636
|
+
if (isBufferingInitialSync()) {
|
|
637
|
+
bufferedMessages.push(message);
|
|
638
|
+
} else {
|
|
639
|
+
transactionStarted = processMoveOutEvent(
|
|
640
|
+
message.headers.patterns,
|
|
641
|
+
begin,
|
|
642
|
+
write,
|
|
643
|
+
transactionStarted
|
|
644
|
+
);
|
|
645
|
+
}
|
|
485
646
|
} else if (isMustRefetchMessage(message)) {
|
|
486
647
|
debug(
|
|
487
648
|
`${collectionId ? `[${collectionId}] ` : ``}Received must-refetch message, starting transaction with truncate`
|
|
@@ -491,29 +652,24 @@ You can provide an 'onError' handler on the shapeOptions to handle this error, a
|
|
|
491
652
|
transactionStarted = true;
|
|
492
653
|
}
|
|
493
654
|
truncate();
|
|
655
|
+
clearTagTrackingState();
|
|
494
656
|
loadSubsetDedupe?.reset();
|
|
495
|
-
|
|
496
|
-
hasSnapshotEnd = false;
|
|
657
|
+
commitPoint = null;
|
|
497
658
|
hasReceivedUpToDate = false;
|
|
498
659
|
bufferedMessages.length = 0;
|
|
499
660
|
}
|
|
500
661
|
}
|
|
501
|
-
if (
|
|
502
|
-
if (isBufferingInitialSync() &&
|
|
662
|
+
if (commitPoint !== null) {
|
|
663
|
+
if (isBufferingInitialSync() && commitPoint === `up-to-date`) {
|
|
503
664
|
debug(
|
|
504
665
|
`${collectionId ? `[${collectionId}] ` : ``}Progressive mode: Performing atomic swap with ${bufferedMessages.length} buffered messages`
|
|
505
666
|
);
|
|
506
667
|
begin();
|
|
507
668
|
truncate();
|
|
669
|
+
clearTagTrackingState();
|
|
508
670
|
for (const bufferedMsg of bufferedMessages) {
|
|
509
671
|
if (isChangeMessage(bufferedMsg)) {
|
|
510
|
-
|
|
511
|
-
type: bufferedMsg.headers.operation,
|
|
512
|
-
value: bufferedMsg.value,
|
|
513
|
-
metadata: {
|
|
514
|
-
...bufferedMsg.headers
|
|
515
|
-
}
|
|
516
|
-
});
|
|
672
|
+
processChangeMessage(bufferedMsg);
|
|
517
673
|
if (hasTxids(bufferedMsg)) {
|
|
518
674
|
bufferedMsg.headers.txids?.forEach(
|
|
519
675
|
(txid) => newTxids.add(txid)
|
|
@@ -521,6 +677,13 @@ You can provide an 'onError' handler on the shapeOptions to handle this error, a
|
|
|
521
677
|
}
|
|
522
678
|
} else if (isSnapshotEndMessage(bufferedMsg)) {
|
|
523
679
|
newSnapshots.push(parseSnapshotMessage(bufferedMsg));
|
|
680
|
+
} else if (isMoveOutMessage(bufferedMsg)) {
|
|
681
|
+
processMoveOutEvent(
|
|
682
|
+
bufferedMsg.headers.patterns,
|
|
683
|
+
begin,
|
|
684
|
+
write,
|
|
685
|
+
transactionStarted
|
|
686
|
+
);
|
|
524
687
|
}
|
|
525
688
|
}
|
|
526
689
|
commit();
|
|
@@ -529,16 +692,13 @@ You can provide an 'onError' handler on the shapeOptions to handle this error, a
|
|
|
529
692
|
`${collectionId ? `[${collectionId}] ` : ``}Progressive mode: Atomic swap complete, now in normal sync mode`
|
|
530
693
|
);
|
|
531
694
|
} else {
|
|
532
|
-
|
|
533
|
-
if (transactionStarted && shouldCommit) {
|
|
695
|
+
if (transactionStarted) {
|
|
534
696
|
commit();
|
|
535
697
|
transactionStarted = false;
|
|
536
698
|
}
|
|
537
699
|
}
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
}
|
|
541
|
-
if (hasUpToDate) {
|
|
700
|
+
wrappedMarkReady(isBufferingInitialSync());
|
|
701
|
+
if (commitPoint === `up-to-date`) {
|
|
542
702
|
hasReceivedUpToDate = true;
|
|
543
703
|
}
|
|
544
704
|
seenTxids.setState((currentTxids) => {
|
|
@@ -564,9 +724,7 @@ You can provide an 'onError' handler on the shapeOptions to handle this error, a
|
|
|
564
724
|
newSnapshots.length = 0;
|
|
565
725
|
return seen;
|
|
566
726
|
});
|
|
567
|
-
|
|
568
|
-
batchCommitted.setState(() => true);
|
|
569
|
-
}
|
|
727
|
+
batchCommitted.setState(() => true);
|
|
570
728
|
resolveMatchedPendingMatches();
|
|
571
729
|
}
|
|
572
730
|
});
|