@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.
@@ -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;
@@ -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 hasUpToDate = false;
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
- write({
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
- hasUpToDate = true;
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
- hasUpToDate = false;
496
- hasSnapshotEnd = false;
657
+ commitPoint = null;
497
658
  hasReceivedUpToDate = false;
498
659
  bufferedMessages.length = 0;
499
660
  }
500
661
  }
501
- if (hasUpToDate || hasSnapshotEnd) {
502
- if (isBufferingInitialSync() && hasUpToDate) {
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
- write({
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
- const shouldCommit = hasUpToDate || syncMode === `on-demand` || hasReceivedUpToDate;
533
- if (transactionStarted && shouldCommit) {
695
+ if (transactionStarted) {
534
696
  commit();
535
697
  transactionStarted = false;
536
698
  }
537
699
  }
538
- if (hasUpToDate || hasSnapshotEnd && syncMode === `on-demand`) {
539
- wrappedMarkReady(isBufferingInitialSync());
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
- if (hasUpToDate || hasSnapshotEnd && syncMode === `on-demand`) {
568
- batchCommitted.setState(() => true);
569
- }
727
+ batchCommitted.setState(() => true);
570
728
  resolveMatchedPendingMatches();
571
729
  }
572
730
  });