@yorkie-js/react 0.7.6 → 0.7.8

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.
@@ -362,7 +362,7 @@
362
362
  }
363
363
  }
364
364
  const IMPLICIT$3 = 2;
365
- const unsafeLocal = Symbol.for("reflect unsafe local");
365
+ const unsafeLocal = /* @__PURE__ */ Symbol.for("reflect unsafe local");
366
366
  function unsafeOneofCase(target, oneof) {
367
367
  const c = target[oneof.localName].case;
368
368
  if (c === void 0) {
@@ -588,7 +588,7 @@
588
588
  }
589
589
  return ret;
590
590
  }
591
- const tokenZeroMessageField = Symbol();
591
+ const tokenZeroMessageField = /* @__PURE__ */ Symbol();
592
592
  const messagePrototypes = /* @__PURE__ */ new WeakMap();
593
593
  function createZeroMessage(desc) {
594
594
  let msg;
@@ -690,7 +690,7 @@
690
690
  function isFieldError(arg) {
691
691
  return arg instanceof Error && errorNames.includes(arg.name) && "field" in arg && typeof arg.field == "function";
692
692
  }
693
- const symbol = Symbol.for("@bufbuild/protobuf/text-encoding");
693
+ const symbol = /* @__PURE__ */ Symbol.for("@bufbuild/protobuf/text-encoding");
694
694
  function getTextEncoding() {
695
695
  if (globalThis[symbol] == void 0) {
696
696
  const te = new globalThis.TextEncoder();
@@ -4058,7 +4058,7 @@
4058
4058
  msg.set(field, scalarValue);
4059
4059
  }
4060
4060
  }
4061
- const tokenIgnoredUnknownEnum = Symbol();
4061
+ const tokenIgnoredUnknownEnum = /* @__PURE__ */ Symbol();
4062
4062
  function readEnum(desc, json, ignoreUnknownFields, nullAsZeroValue) {
4063
4063
  if (json === null) {
4064
4064
  if (desc.typeName == "google.protobuf.NullValue") {
@@ -4084,7 +4084,7 @@
4084
4084
  }
4085
4085
  throw new Error(`cannot decode ${desc} from JSON: ${formatVal(json)}`);
4086
4086
  }
4087
- const tokenNull = Symbol();
4087
+ const tokenNull = /* @__PURE__ */ Symbol();
4088
4088
  function scalarFromJson(field, json, nullAsZeroValue) {
4089
4089
  if (json === null) {
4090
4090
  if (nullAsZeroValue) {
@@ -5905,6 +5905,8 @@
5905
5905
  this.message = message;
5906
5906
  this.toString = () => `[code=${this.code}]: ${this.message}`;
5907
5907
  }
5908
+ code;
5909
+ message;
5908
5910
  name = "YorkieError";
5909
5911
  stack;
5910
5912
  }
@@ -11134,6 +11136,32 @@
11134
11136
  }
11135
11137
  actualRight.push(child);
11136
11138
  }
11139
+ if (versionVector) {
11140
+ const movedToLeft = [];
11141
+ const remaining = [];
11142
+ let boundaryReached = false;
11143
+ for (const child of actualRight) {
11144
+ if (!boundaryReached) {
11145
+ if (child.insPrevID !== void 0 && !child.isText) {
11146
+ remaining.push(child);
11147
+ continue;
11148
+ }
11149
+ const actorID = child.id.getCreatedAt().getActorID();
11150
+ const knownLamport = versionVector.get(actorID);
11151
+ if (knownLamport === void 0 || knownLamport < child.id.getCreatedAt().getLamport()) {
11152
+ movedToLeft.push(child);
11153
+ continue;
11154
+ }
11155
+ }
11156
+ boundaryReached = true;
11157
+ remaining.push(child);
11158
+ }
11159
+ if (movedToLeft.length > 0) {
11160
+ left.push(...movedToLeft);
11161
+ actualRight.length = 0;
11162
+ actualRight.push(...remaining);
11163
+ }
11164
+ }
11137
11165
  this._children = left;
11138
11166
  clone._children = actualRight;
11139
11167
  this.visibleSize = this._children.reduce(
@@ -11910,8 +11938,14 @@
11910
11938
  split.insPrevID = this.id;
11911
11939
  if (this.insNextID) {
11912
11940
  const insNext = tree.findFloorNode(this.insNextID);
11913
- insNext.insPrevID = split.id;
11914
11941
  split.insNextID = this.insNextID;
11942
+ if (insNext) {
11943
+ insNext.insPrevID = split.id;
11944
+ if (!this.isText && insNext.parent && !insNext.isRemoved && insNext.parent !== split.parent && split.allChildren.length === 0) {
11945
+ split.parent.detachChild(split);
11946
+ insNext.parent.insertBefore(split, insNext);
11947
+ }
11948
+ }
11915
11949
  }
11916
11950
  this.insNextID = split.id;
11917
11951
  tree.registerNode(split);
@@ -12135,7 +12169,7 @@
12135
12169
  * given node, advancing past element-type split siblings that the editing
12136
12170
  * client did not know about (not in versionVector).
12137
12171
  */
12138
- advancePastUnknownSplitSiblings(node, versionVector) {
12172
+ advancePastUnknownSplitSiblings(node, versionVector, relaxParentCheck = false, skipActorID) {
12139
12173
  if (!versionVector || !node) {
12140
12174
  return node;
12141
12175
  }
@@ -12145,10 +12179,13 @@
12145
12179
  if (!next || next.isText) {
12146
12180
  break;
12147
12181
  }
12148
- if (next.parent !== current.parent) {
12182
+ if (!relaxParentCheck && next.parent !== current.parent) {
12149
12183
  break;
12150
12184
  }
12151
12185
  const actorID = next.id.getCreatedAt().getActorID();
12186
+ if (skipActorID !== void 0 && actorID === skipActorID) {
12187
+ break;
12188
+ }
12152
12189
  const knownLamport = versionVector.get(actorID);
12153
12190
  if (knownLamport !== void 0 && knownLamport >= next.id.getCreatedAt().getLamport()) {
12154
12191
  break;
@@ -12489,6 +12526,25 @@
12489
12526
  addDataSizes(diff, diffTo, diffFrom);
12490
12527
  const fromLeft = fromLeftRaw !== fromParent ? this.advancePastUnknownSplitSiblings(fromLeftRaw, versionVector) : fromLeftRaw;
12491
12528
  const toLeft = toLeftRaw !== toParent ? this.advancePastUnknownSplitSiblings(toLeftRaw, versionVector) : toLeftRaw;
12529
+ let collectFromParent = fromParent;
12530
+ let collectFromLeft = fromLeft;
12531
+ if (fromLeft !== fromParent && fromParent !== toParent) {
12532
+ let current = fromLeft;
12533
+ while (current.insNextID) {
12534
+ const next = this.findFloorNode(current.insNextID);
12535
+ if (!next || next.isText) {
12536
+ break;
12537
+ }
12538
+ if (next.parent && next.parent === toParent) {
12539
+ if (toLeft !== toParent) {
12540
+ collectFromLeft = next;
12541
+ collectFromParent = toParent;
12542
+ }
12543
+ break;
12544
+ }
12545
+ current = next;
12546
+ }
12547
+ }
12492
12548
  const fromIdx = this.toIndex(fromParent, fromLeft);
12493
12549
  const fromPath = this.toPath(fromParent, fromLeft);
12494
12550
  const nodesToBeRemoved = [];
@@ -12497,8 +12553,8 @@
12497
12553
  const toBeMergedNodes = [];
12498
12554
  const preTombstoned = /* @__PURE__ */ new Set();
12499
12555
  this.traverseInPosRange(
12500
- fromParent,
12501
- fromLeft,
12556
+ collectFromParent,
12557
+ collectFromLeft,
12502
12558
  toParent,
12503
12559
  toLeft,
12504
12560
  ([node, tokenType], ended) => {
@@ -12546,6 +12602,7 @@
12546
12602
  tokensToBeRemoved,
12547
12603
  editedAt
12548
12604
  );
12605
+ const mergeLevel = toBeMergedNodes.length;
12549
12606
  const pairs = [];
12550
12607
  for (const node of nodesToBeRemoved) {
12551
12608
  if (node.remove(editedAt)) {
@@ -12602,9 +12659,20 @@
12602
12659
  let parent = fromParent;
12603
12660
  let left = fromLeft;
12604
12661
  while (splitCount < splitLevel) {
12662
+ if (left !== parent) {
12663
+ left = this.advancePastUnknownSplitSiblings(
12664
+ left,
12665
+ versionVector,
12666
+ true,
12667
+ editedAt.getActorID()
12668
+ );
12669
+ if (left.parent && left.parent !== parent) {
12670
+ parent = left.parent;
12671
+ }
12672
+ }
12605
12673
  parent.split(
12606
12674
  this,
12607
- parent.findOffset(left, true) + 1,
12675
+ left !== parent ? parent.findOffset(left, true) + 1 : 0,
12608
12676
  issueTimeTicket,
12609
12677
  versionVector
12610
12678
  );
@@ -12661,7 +12729,15 @@
12661
12729
  }
12662
12730
  }
12663
12731
  }
12664
- return [changes, pairs, diff, nodesToBeRemoved, fromIdx];
12732
+ return [
12733
+ changes,
12734
+ pairs,
12735
+ diff,
12736
+ nodesToBeRemoved,
12737
+ fromIdx,
12738
+ mergeLevel,
12739
+ preTombstoned
12740
+ ];
12665
12741
  }
12666
12742
  /**
12667
12743
  * `editT` edits the given range with the given value.
@@ -13102,11 +13178,35 @@
13102
13178
  return [prev, prev.isText ? TokenType.Text : TokenType.End];
13103
13179
  }
13104
13180
  }
13105
- function clearRemovedAt(node) {
13106
- traverseAll(node, (n) => {
13181
+ function cloneAndDropPreTombstoned(node, preTombstoned) {
13182
+ const clone = node.deepcopy();
13183
+ filterChildren(clone, preTombstoned);
13184
+ traverseAll(clone, (n) => {
13107
13185
  n.removedAt = void 0;
13108
- n.visibleSize = n.totalSize;
13186
+ if (n.isText) {
13187
+ n.visibleSize = n.value.length;
13188
+ n.totalSize = n.value.length;
13189
+ return;
13190
+ }
13191
+ let size = 0;
13192
+ for (const child of n._children) size += child.paddedSize();
13193
+ n.visibleSize = size;
13194
+ n.totalSize = size;
13109
13195
  });
13196
+ return clone;
13197
+ }
13198
+ function filterChildren(node, preTombstoned) {
13199
+ const all = node._children;
13200
+ if (!all) return;
13201
+ const kept = [];
13202
+ for (const child of all) {
13203
+ if (preTombstoned.has(child.id.toIDString())) {
13204
+ continue;
13205
+ }
13206
+ filterChildren(child, preTombstoned);
13207
+ kept.push(child);
13208
+ }
13209
+ node._children = kept;
13110
13210
  }
13111
13211
  class TreeEditOperation extends Operation {
13112
13212
  fromPos;
@@ -13178,7 +13278,15 @@
13178
13278
  this.toPos = tree.findPos(this.toIdx);
13179
13279
  }
13180
13280
  }
13181
- const [changes, pairs, diff, removedNodes, preEditFromIdx] = tree.edit(
13281
+ const [
13282
+ changes,
13283
+ pairs,
13284
+ diff,
13285
+ removedNodes,
13286
+ preEditFromIdx,
13287
+ mergeLevel,
13288
+ preTombstoned
13289
+ ] = tree.edit(
13182
13290
  [this.fromPos, this.toPos],
13183
13291
  this.contents?.map((content) => content.deepcopy()),
13184
13292
  this.splitLevel,
@@ -13211,10 +13319,16 @@
13211
13319
  );
13212
13320
  this.lastToIdx = preEditFromIdx + removedSize;
13213
13321
  let reverseOp;
13214
- const isPureL1Split = this.splitLevel === 1 && !this.contents?.length && removedNodes.length === 0;
13322
+ const isPureSplit = this.splitLevel > 0 && !this.contents?.length && removedNodes.length === 0;
13215
13323
  if (this.splitLevel === 0) {
13216
- reverseOp = this.toReverseOperation(tree, removedNodes, preEditFromIdx);
13217
- } else if (isPureL1Split) {
13324
+ reverseOp = this.toReverseOperation(
13325
+ tree,
13326
+ removedNodes,
13327
+ preEditFromIdx,
13328
+ preTombstoned,
13329
+ mergeLevel
13330
+ );
13331
+ } else if (isPureSplit) {
13218
13332
  reverseOp = this.toSplitReverseOperation(tree, preEditFromIdx);
13219
13333
  }
13220
13334
  root.acc(diff);
@@ -13251,7 +13365,7 @@
13251
13365
  * @param removedNodes - Nodes that were removed by this edit
13252
13366
  * @param preEditFromIdx - The from index captured BEFORE the edit
13253
13367
  */
13254
- toReverseOperation(tree, removedNodes, preEditFromIdx) {
13368
+ toReverseOperation(tree, removedNodes, preEditFromIdx, preTombstoned, mergeLevel) {
13255
13369
  if (this.redoSplitLevel !== void 0 && this.redoSplitLevel > 0) {
13256
13370
  const splitRedoFromPos = tree.findPos(preEditFromIdx);
13257
13371
  const splitRedoOp = TreeEditOperation.create(
@@ -13270,19 +13384,36 @@
13270
13384
  );
13271
13385
  return splitRedoOp;
13272
13386
  }
13387
+ if (mergeLevel && mergeLevel > 0) {
13388
+ const splitFromPos = tree.findPos(preEditFromIdx);
13389
+ const splitUndoOp = TreeEditOperation.create(
13390
+ this.getParentCreatedAt(),
13391
+ splitFromPos,
13392
+ splitFromPos,
13393
+ void 0,
13394
+ // no inserted content — split creates boundaries
13395
+ mergeLevel,
13396
+ // splitLevel = number of merged boundaries
13397
+ void 0,
13398
+ // executedAt assigned at undo time
13399
+ true,
13400
+ // isUndoOp
13401
+ preEditFromIdx,
13402
+ preEditFromIdx
13403
+ );
13404
+ return splitUndoOp;
13405
+ }
13273
13406
  const insertedContentSize = this.contents ? this.contents.reduce((sum, node) => sum + node.paddedSize(), 0) : 0;
13274
13407
  const maxNeededIdx = preEditFromIdx + insertedContentSize;
13275
13408
  if (maxNeededIdx > tree.getSize()) {
13276
13409
  return void 0;
13277
13410
  }
13278
13411
  const topLevelRemoved = removedNodes.filter(
13279
- (node) => !node.parent || !removedNodes.includes(node.parent)
13412
+ (node) => !preTombstoned.has(node.id.toIDString()) && (!node.parent || !removedNodes.includes(node.parent))
13280
13413
  );
13281
- const reverseContents = topLevelRemoved.length > 0 ? topLevelRemoved.map((n) => {
13282
- const clone = n.deepcopy();
13283
- clearRemovedAt(clone);
13284
- return clone;
13285
- }) : void 0;
13414
+ const reverseContents = topLevelRemoved.length > 0 ? topLevelRemoved.map(
13415
+ (n) => cloneAndDropPreTombstoned(n, preTombstoned)
13416
+ ) : void 0;
13286
13417
  const reverseFromPos = tree.findPos(preEditFromIdx);
13287
13418
  let reverseToPos;
13288
13419
  if (insertedContentSize > 0) {
@@ -20096,6 +20227,11 @@
20096
20227
  }
20097
20228
  ]);
20098
20229
  }
20230
+ /**
20231
+ * `clearHistory` flushes both undo and redo stacks. This is used
20232
+ * after applying a snapshot or initialRoot so that setup operations
20233
+ * are not reachable via undo.
20234
+ */
20099
20235
  clearHistory() {
20100
20236
  this.internalHistory.clearRedo();
20101
20237
  this.internalHistory.clearUndo();
@@ -20555,10 +20691,7 @@
20555
20691
  }
20556
20692
  const ops = isUndo ? this.internalHistory.popUndo() : this.internalHistory.popRedo();
20557
20693
  if (!ops) {
20558
- throw new YorkieError(
20559
- Code.ErrRefused,
20560
- `There is no operation to be ${isUndo ? "undone" : "redone"}`
20561
- );
20694
+ return;
20562
20695
  }
20563
20696
  this.ensureClone();
20564
20697
  const ctx = ChangeContext.create(
@@ -20651,6 +20784,8 @@
20651
20784
  syncMode;
20652
20785
  changeEventReceived;
20653
20786
  lastHeartbeatTime;
20787
+ pollInterval;
20788
+ pollIntervalPinned;
20654
20789
  reconnectStreamDelay;
20655
20790
  cancelled;
20656
20791
  watchStream;
@@ -20658,13 +20793,15 @@
20658
20793
  watchAbortController;
20659
20794
  syncPromise;
20660
20795
  _detaching = false;
20661
- constructor(reconnectStreamDelay, resource, resourceID, syncMode) {
20796
+ constructor(reconnectStreamDelay, resource, resourceID, syncMode, pollInterval = 0, pollIntervalPinned = false) {
20662
20797
  this.reconnectStreamDelay = reconnectStreamDelay;
20663
20798
  this.resource = resource;
20664
20799
  this.resourceID = resourceID;
20665
20800
  this.syncMode = syncMode;
20666
20801
  this.changeEventReceived = syncMode !== void 0 ? false : void 0;
20667
20802
  this.lastHeartbeatTime = Date.now();
20803
+ this.pollInterval = pollInterval;
20804
+ this.pollIntervalPinned = pollIntervalPinned;
20668
20805
  this.cancelled = false;
20669
20806
  }
20670
20807
  /**
@@ -20684,6 +20821,9 @@
20684
20821
  if (this.syncMode === SyncMode.RealtimePushOnly) {
20685
20822
  return this.resource.hasLocalChanges();
20686
20823
  }
20824
+ if (this.syncMode === SyncMode.Polling) {
20825
+ return Date.now() - this.lastHeartbeatTime >= this.pollInterval;
20826
+ }
20687
20827
  return this.syncMode !== SyncMode.Manual && (this.resource.hasLocalChanges() || (this.changeEventReceived ?? false));
20688
20828
  }
20689
20829
  /**
@@ -20697,7 +20837,8 @@
20697
20837
  if (this.syncMode === SyncMode.Manual) {
20698
20838
  return false;
20699
20839
  }
20700
- return Date.now() - this.lastHeartbeatTime >= heartbeatInterval;
20840
+ const interval = this.pollInterval > 0 ? this.pollInterval : heartbeatInterval;
20841
+ return Date.now() - this.lastHeartbeatTime >= interval;
20701
20842
  }
20702
20843
  /**
20703
20844
  * `updateHeartbeatTime` updates the last heartbeat time.
@@ -20776,6 +20917,16 @@
20776
20917
  }
20777
20918
  }
20778
20919
  }
20920
+ /**
20921
+ * `resetCancelled` clears the cancelled flag so the watch loop can run again
20922
+ * after a previous cancellation (e.g., after changeSyncMode back to Realtime).
20923
+ * Caller must invoke `runWatchLoop` immediately after to claim the stream slot;
20924
+ * `doLoop`'s `if (this.watchStream)` guard prevents double-stream creation if a
20925
+ * delayed `onDisconnect` callback from the previously-cancelled stream races.
20926
+ */
20927
+ resetCancelled() {
20928
+ this.cancelled = false;
20929
+ }
20779
20930
  /**
20780
20931
  * `cancelWatchStream` cancels the watch stream.
20781
20932
  */
@@ -20810,7 +20961,7 @@
20810
20961
  };
20811
20962
  }
20812
20963
  const name$1 = "@yorkie-js/sdk";
20813
- const version$1 = "0.7.6";
20964
+ const version$1 = "0.7.8";
20814
20965
  const pkg$1 = {
20815
20966
  name: name$1,
20816
20967
  version: version$1
@@ -21093,8 +21244,10 @@
21093
21244
  SyncMode2["Realtime"] = "realtime";
21094
21245
  SyncMode2["RealtimePushOnly"] = "realtime-pushonly";
21095
21246
  SyncMode2["RealtimeSyncOff"] = "realtime-syncoff";
21247
+ SyncMode2["Polling"] = "polling";
21096
21248
  return SyncMode2;
21097
21249
  })(SyncMode || {});
21250
+ const DefaultPollingIntervalMs = 3e3;
21098
21251
  const DefaultClientOptions = {
21099
21252
  rpcAddr: "https://api.yorkie.dev",
21100
21253
  syncLoopDuration: 50,
@@ -21290,6 +21443,14 @@
21290
21443
  doc.setActor(this.id);
21291
21444
  doc.update((_, p) => p.set(opts.initialPresence || {}));
21292
21445
  const syncMode = opts.syncMode ?? "realtime";
21446
+ if (opts.documentPollInterval !== void 0 && opts.documentPollInterval <= 0) {
21447
+ throw new YorkieError(
21448
+ Code.ErrInvalidArgument,
21449
+ "documentPollInterval must be greater than 0"
21450
+ );
21451
+ }
21452
+ const pollIntervalPinned = opts.documentPollInterval !== void 0;
21453
+ const pollInterval = pollIntervalPinned ? opts.documentPollInterval : syncMode === "polling" ? DefaultPollingIntervalMs : 0;
21293
21454
  return this.enqueueTask(async () => {
21294
21455
  try {
21295
21456
  const res = await this.rpcClient.attachDocument(
@@ -21319,10 +21480,12 @@
21319
21480
  this.reconnectStreamDelay,
21320
21481
  doc,
21321
21482
  res.documentId,
21322
- syncMode
21483
+ syncMode,
21484
+ pollInterval,
21485
+ pollIntervalPinned
21323
21486
  )
21324
21487
  );
21325
- if (syncMode !== "manual") {
21488
+ if (syncMode !== "manual" && syncMode !== "polling") {
21326
21489
  await this.runWatchLoop(doc.getKey());
21327
21490
  }
21328
21491
  logger.info(`[AD] c:"${this.getKey()}" attaches d:"${doc.getKey()}"`);
@@ -21338,6 +21501,7 @@
21338
21501
  }
21339
21502
  });
21340
21503
  }
21504
+ doc.clearHistory();
21341
21505
  return doc;
21342
21506
  } catch (err) {
21343
21507
  logger.error(`[AD] c:"${this.getKey()}" err :`, err);
@@ -21448,12 +21612,23 @@
21448
21612
  channel.setSessionID(res.sessionId);
21449
21613
  channel.updateSessionCount(Number(res.sessionCount), 0);
21450
21614
  channel.applyStatus(ChannelStatus.Attached);
21451
- const syncMode = opts.isRealtime !== false ? "realtime" : "manual";
21615
+ const syncMode = opts.syncMode ?? "realtime";
21616
+ this.assertValidChannelSyncMode(syncMode);
21617
+ if (opts.channelHeartbeatInterval !== void 0 && opts.channelHeartbeatInterval <= 0) {
21618
+ throw new YorkieError(
21619
+ Code.ErrInvalidArgument,
21620
+ "channelHeartbeatInterval must be greater than 0"
21621
+ );
21622
+ }
21623
+ const pollIntervalPinned = opts.channelHeartbeatInterval !== void 0;
21624
+ const pollInterval = pollIntervalPinned ? opts.channelHeartbeatInterval : syncMode === "polling" ? DefaultPollingIntervalMs : this.channelHeartbeatInterval;
21452
21625
  const attachment = new Attachment(
21453
21626
  this.reconnectStreamDelay,
21454
21627
  channel,
21455
21628
  res.sessionId,
21456
- syncMode
21629
+ syncMode,
21630
+ pollInterval,
21631
+ pollIntervalPinned
21457
21632
  );
21458
21633
  channel.subscribe("local-broadcast", (event) => {
21459
21634
  const { topic, payload, options } = event;
@@ -21529,9 +21704,17 @@
21529
21704
  return this.enqueueTask(task);
21530
21705
  }
21531
21706
  /**
21532
- * `changeSyncMode` changes the synchronization mode of the given document.
21707
+ * `changeSyncMode` changes the synchronization mode of the given resource.
21533
21708
  */
21534
- async changeSyncMode(doc, syncMode) {
21709
+ async changeSyncMode(resource, syncMode) {
21710
+ return this.enqueueTask(async () => {
21711
+ if (resource instanceof Channel) {
21712
+ return this.changeChannelSyncMode(resource, syncMode);
21713
+ }
21714
+ return this.changeDocumentSyncMode(resource, syncMode);
21715
+ });
21716
+ }
21717
+ async changeDocumentSyncMode(doc, syncMode) {
21535
21718
  if (!this.isActive()) {
21536
21719
  throw new YorkieError(
21537
21720
  Code.ErrClientNotActivated,
@@ -21549,19 +21732,66 @@
21549
21732
  if (prevSyncMode === syncMode) {
21550
21733
  return doc;
21551
21734
  }
21552
- attachment.changeSyncMode(syncMode);
21553
- if (syncMode === "manual") {
21735
+ if (syncMode === "manual" || syncMode === "polling") {
21554
21736
  attachment.cancelWatchStream();
21555
- return doc;
21556
21737
  }
21738
+ attachment.changeSyncMode(syncMode);
21557
21739
  if (syncMode === "realtime") {
21558
21740
  attachment.changeEventReceived = true;
21559
21741
  }
21560
- if (prevSyncMode === "manual") {
21742
+ if (!attachment.pollIntervalPinned) {
21743
+ attachment.pollInterval = syncMode === "polling" ? DefaultPollingIntervalMs : 0;
21744
+ }
21745
+ if ((prevSyncMode === "manual" || prevSyncMode === "polling") && syncMode !== "manual" && syncMode !== "polling") {
21746
+ attachment.resetCancelled();
21561
21747
  await this.runWatchLoop(doc.getKey());
21562
21748
  }
21563
21749
  return doc;
21564
21750
  }
21751
+ /**
21752
+ * `assertValidChannelSyncMode` rejects sync modes that are not valid for
21753
+ * channels. `RealtimePushOnly` and `RealtimeSyncOff` are document-only.
21754
+ */
21755
+ assertValidChannelSyncMode(syncMode) {
21756
+ if (syncMode !== "manual" && syncMode !== "realtime" && syncMode !== "polling") {
21757
+ throw new YorkieError(
21758
+ Code.ErrInvalidArgument,
21759
+ `invalid channel sync mode: ${syncMode}`
21760
+ );
21761
+ }
21762
+ }
21763
+ async changeChannelSyncMode(channel, syncMode) {
21764
+ if (!this.isActive()) {
21765
+ throw new YorkieError(
21766
+ Code.ErrClientNotActivated,
21767
+ `${this.key} is not active`
21768
+ );
21769
+ }
21770
+ const attachment = this.attachmentMap.get(channel.getKey());
21771
+ if (!attachment) {
21772
+ throw new YorkieError(
21773
+ Code.ErrNotAttached,
21774
+ `${channel.getKey()} is not attached`
21775
+ );
21776
+ }
21777
+ const prevSyncMode = attachment.syncMode;
21778
+ if (prevSyncMode === syncMode) {
21779
+ return channel;
21780
+ }
21781
+ this.assertValidChannelSyncMode(syncMode);
21782
+ if (prevSyncMode === "realtime") {
21783
+ attachment.cancelWatchStream();
21784
+ }
21785
+ attachment.changeSyncMode(syncMode);
21786
+ if (!attachment.pollIntervalPinned) {
21787
+ attachment.pollInterval = syncMode === "polling" ? DefaultPollingIntervalMs : syncMode === "realtime" ? this.channelHeartbeatInterval : 0;
21788
+ }
21789
+ if (syncMode === "realtime") {
21790
+ attachment.resetCancelled();
21791
+ await this.runWatchLoop(channel.getKey());
21792
+ }
21793
+ return channel;
21794
+ }
21565
21795
  /**
21566
21796
  * `sync` implementation that handles both Document and Channel.
21567
21797
  */
@@ -22406,6 +22636,7 @@
22406
22636
  return doc;
22407
22637
  }
22408
22638
  doc.applyChangePack(respPack);
22639
+ attachment.updateHeartbeatTime();
22409
22640
  attachment.resource.publish([
22410
22641
  {
22411
22642
  type: DocEventType.SyncStatusChanged,
@@ -22516,8 +22747,11 @@
22516
22747
  function isCounter(value) {
22517
22748
  return typeof value === "object" && value !== null && value.type === "Counter" && typeof value.value === "object";
22518
22749
  }
22750
+ function isDedupCounter(value) {
22751
+ return typeof value === "object" && value !== null && value.type === "DedupCounter" && typeof value.value === "object" && typeof value.registers === "string";
22752
+ }
22519
22753
  function isObject(value) {
22520
- return typeof value === "object" && value !== null && !Array.isArray(value) && !isText(value) && !isTree(value) && !isInt(value) && !isLong(value) && !isDate(value) && !isBinData(value) && !isCounter(value);
22754
+ return typeof value === "object" && value !== null && !Array.isArray(value) && !isText(value) && !isTree(value) && !isInt(value) && !isLong(value) && !isDate(value) && !isBinData(value) && !isCounter(value) && !isDedupCounter(value);
22521
22755
  }
22522
22756
  function parse(yson) {
22523
22757
  try {
@@ -22533,6 +22767,12 @@
22533
22767
  }
22534
22768
  function preprocessYSON(yson) {
22535
22769
  let result = yson;
22770
+ result = result.replace(
22771
+ /DedupCounter\(Int\((-?\d+)\),"([^"]+)"\)/g,
22772
+ (_, value, registers) => {
22773
+ return `{"__yson_type":"DedupCounter","__yson_data":{"__yson_type":"Int","__yson_data":${value}},"__yson_registers":"${registers}"}`;
22774
+ }
22775
+ );
22536
22776
  result = result.replace(
22537
22777
  /Counter\((Int|Long)\((-?\d+)\)\)/g,
22538
22778
  (_, type, value) => {
@@ -22593,6 +22833,20 @@
22593
22833
  value: value.__yson_data
22594
22834
  };
22595
22835
  }
22836
+ if (value.__yson_type === "DedupCounter" && typeof value.__yson_data === "object" && typeof value.__yson_registers === "string") {
22837
+ const counterValue = postprocessValue(value.__yson_data);
22838
+ if (typeof counterValue === "object" && counterValue !== null && "type" in counterValue && counterValue.type === "Int") {
22839
+ return {
22840
+ type: "DedupCounter",
22841
+ value: counterValue,
22842
+ registers: value.__yson_registers
22843
+ };
22844
+ }
22845
+ throw new YorkieError(
22846
+ Code.ErrInvalidArgument,
22847
+ "DedupCounter must contain Int"
22848
+ );
22849
+ }
22596
22850
  if (value.__yson_type === "Counter" && typeof value.__yson_data === "object") {
22597
22851
  const counterValue = postprocessValue(value.__yson_data);
22598
22852
  if (typeof counterValue === "object" && counterValue !== null && "type" in counterValue && (counterValue.type === "Int" || counterValue.type === "Long")) {
@@ -22681,6 +22935,7 @@
22681
22935
  isBinData,
22682
22936
  isCounter,
22683
22937
  isDate,
22938
+ isDedupCounter,
22684
22939
  isInt,
22685
22940
  isLong,
22686
22941
  isObject,
@@ -22707,7 +22962,7 @@
22707
22962
  };
22708
22963
  }
22709
22964
  const name = "@yorkie-js/react";
22710
- const version = "0.7.6";
22965
+ const version = "0.7.8";
22711
22966
  const pkg = {
22712
22967
  name,
22713
22968
  version
@@ -23163,7 +23418,7 @@
23163
23418
  const ChannelContext = react.createContext(
23164
23419
  void 0
23165
23420
  );
23166
- function useYorkieChannel(client, clientLoading, clientError, channelKey, isRealtime, channelStore) {
23421
+ function useYorkieChannel(client, clientLoading, clientError, channelKey, syncMode, channelStore) {
23167
23422
  const channelRef = react.useRef(void 0);
23168
23423
  const [didMount, setDidMount] = react.useState(false);
23169
23424
  react.useEffect(() => {
@@ -23192,7 +23447,7 @@
23192
23447
  }));
23193
23448
  try {
23194
23449
  const newChannel = new Channel(channelKey);
23195
- await client.attach(newChannel, { isRealtime });
23450
+ await client.attach(newChannel, { syncMode });
23196
23451
  channelRef.current = newChannel;
23197
23452
  unsubscribe = newChannel.subscribe(() => {
23198
23453
  channelStore.setState((state) => ({
@@ -23230,12 +23485,13 @@
23230
23485
  }
23231
23486
  detachChannel();
23232
23487
  };
23233
- }, [client, clientLoading, clientError, channelKey, isRealtime, didMount]);
23488
+ }, [client, clientLoading, clientError, channelKey, syncMode, didMount]);
23234
23489
  }
23235
23490
  const ChannelProvider = ({
23236
23491
  children,
23237
23492
  channelKey,
23238
- isRealtime = true
23493
+ syncMode,
23494
+ isRealtime
23239
23495
  }) => {
23240
23496
  const { client, loading: clientLoading, error: clientError } = useYorkie();
23241
23497
  const channelStoreRef = react.useRef(
@@ -23250,12 +23506,13 @@
23250
23506
  });
23251
23507
  }
23252
23508
  const channelStore = channelStoreRef.current;
23509
+ const resolvedSyncMode = syncMode ?? (isRealtime === false ? SyncMode.Manual : SyncMode.Realtime);
23253
23510
  useYorkieChannel(
23254
23511
  client,
23255
23512
  clientLoading,
23256
23513
  clientError,
23257
23514
  channelKey,
23258
- isRealtime,
23515
+ resolvedSyncMode,
23259
23516
  channelStore
23260
23517
  );
23261
23518
  return /* @__PURE__ */ jsxRuntime.jsx(ChannelContext.Provider, { value: channelStore, children });
@@ -23281,6 +23538,7 @@
23281
23538
  exports2.ChannelProvider = ChannelProvider;
23282
23539
  exports2.Counter = Counter;
23283
23540
  exports2.DocumentProvider = DocumentProvider;
23541
+ exports2.SyncMode = SyncMode;
23284
23542
  exports2.Text = Text;
23285
23543
  exports2.Tree = Tree;
23286
23544
  exports2.YorkieProvider = YorkieProvider;