@liveblocks/core 3.20.0-exp7 → 3.20.0-exp8

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/index.cjs CHANGED
@@ -6,7 +6,7 @@ var __export = (target, all) => {
6
6
 
7
7
  // src/version.ts
8
8
  var PKG_NAME = "@liveblocks/core";
9
- var PKG_VERSION = "3.20.0-exp7";
9
+ var PKG_VERSION = "3.20.0-exp8";
10
10
  var PKG_FORMAT = "cjs";
11
11
 
12
12
  // src/dupe-detection.ts
@@ -5519,7 +5519,7 @@ function isIgnoredOp(op) {
5519
5519
  return op.type === OpCode.DELETE_CRDT && op.id === "ACK";
5520
5520
  }
5521
5521
  function isCreateOp(op) {
5522
- return op.type === OpCode.CREATE_OBJECT || op.type === OpCode.CREATE_REGISTER || op.type === OpCode.CREATE_MAP || op.type === OpCode.CREATE_LIST;
5522
+ return op.type === OpCode.CREATE_OBJECT || op.type === OpCode.CREATE_REGISTER || op.type === OpCode.CREATE_MAP || op.type === OpCode.CREATE_LIST || op.type === OpCode.CREATE_TEXT;
5523
5523
  }
5524
5524
 
5525
5525
  // src/protocol/StorageNode.ts
@@ -5814,6 +5814,10 @@ ${parentKey}`;
5814
5814
  get size() {
5815
5815
  return this.#byOpId.size;
5816
5816
  }
5817
+ /** The still-unacknowledged op with the given opId, if any. */
5818
+ get(opId) {
5819
+ return this.#byOpId.get(opId);
5820
+ }
5817
5821
  /**
5818
5822
  * Mark the given Op as still unacknowledged.
5819
5823
  */
@@ -5914,8 +5918,8 @@ function createManagedPool(roomId, options) {
5914
5918
  deleteNode: (id) => void nodes.delete(id),
5915
5919
  generateId: () => `${getCurrentConnectionId()}:${clock++}`,
5916
5920
  generateOpId: () => `${getCurrentConnectionId()}:${opClock++}`,
5917
- dispatch(ops, reverse, storageUpdates) {
5918
- _optionalChain([onDispatch, 'optionalCall', _125 => _125(ops, reverse, storageUpdates)]);
5921
+ dispatch(ops, reverse, storageUpdates, options2) {
5922
+ _optionalChain([onDispatch, 'optionalCall', _125 => _125(ops, reverse, storageUpdates, options2)]);
5919
5923
  },
5920
5924
  assertStorageIsWritable: () => {
5921
5925
  if (!isStorageWritable()) {
@@ -8674,43 +8678,171 @@ function formatReverseOperations(segments, index, length, patch) {
8674
8678
  }
8675
8679
  return result;
8676
8680
  }
8677
- function mapIndexThroughOperation(index, op) {
8678
- if (op.type === "insert") {
8679
- return op.index <= index ? index + op.text.length : index;
8680
- } else if (op.type === "delete") {
8681
- if (op.index >= index) {
8682
- return index;
8683
- }
8684
- return Math.max(op.index, index - op.length);
8685
- } else {
8681
+ function oppositeOrder(order) {
8682
+ return order === "before" ? "after" : "before";
8683
+ }
8684
+ function mapIndexOverDelete(index, deleteIndex, deleteLength) {
8685
+ if (deleteIndex >= index) {
8686
8686
  return index;
8687
8687
  }
8688
+ return Math.max(deleteIndex, index - deleteLength);
8688
8689
  }
8689
- function mapTextIndexThroughOperations(index, ops) {
8690
- let mapped = index;
8691
- for (const op of ops) {
8692
- mapped = mapIndexThroughOperation(mapped, op);
8690
+ function transformInsert(op, over, order) {
8691
+ if (over.type === "insert") {
8692
+ const shifts = over.index < op.index || over.index === op.index && order === "after";
8693
+ return [shifts ? { ...op, index: op.index + over.text.length } : { ...op }];
8694
+ } else if (over.type === "delete") {
8695
+ return [
8696
+ { ...op, index: mapIndexOverDelete(op.index, over.index, over.length) }
8697
+ ];
8698
+ } else {
8699
+ return [{ ...op }];
8693
8700
  }
8694
- return mapped;
8695
8701
  }
8696
- function rebaseTextOperations(ops, acceptedOps) {
8697
- return ops.map((op) => {
8698
- if (op.type === "insert") {
8699
- return {
8700
- ...op,
8701
- index: mapTextIndexThroughOperations(op.index, acceptedOps)
8702
- };
8703
- } else if (op.type === "delete" || op.type === "format") {
8704
- const start = mapTextIndexThroughOperations(op.index, acceptedOps);
8705
- const end = mapTextIndexThroughOperations(
8706
- op.index + op.length,
8707
- acceptedOps
8708
- );
8709
- return { ...op, index: start, length: Math.max(0, end - start) };
8710
- } else {
8711
- return op;
8702
+ function transformDelete(op, over) {
8703
+ const start = op.index;
8704
+ const end = op.index + op.length;
8705
+ if (over.type === "insert") {
8706
+ const at = over.index;
8707
+ const len = over.text.length;
8708
+ if (at <= start) {
8709
+ return [{ ...op, index: start + len }];
8712
8710
  }
8713
- });
8711
+ if (at >= end) {
8712
+ return [{ ...op }];
8713
+ }
8714
+ return [
8715
+ { type: "delete", index: start, length: at - start },
8716
+ { type: "delete", index: start + len, length: end - at }
8717
+ ];
8718
+ } else if (over.type === "delete") {
8719
+ const newStart = mapIndexOverDelete(start, over.index, over.length);
8720
+ const newEnd = mapIndexOverDelete(end, over.index, over.length);
8721
+ return newEnd - newStart > 0 ? [{ type: "delete", index: newStart, length: newEnd - newStart }] : [];
8722
+ } else {
8723
+ return [{ ...op }];
8724
+ }
8725
+ }
8726
+ function transformFormat(op, over, order) {
8727
+ const start = op.index;
8728
+ const end = op.index + op.length;
8729
+ if (over.type === "insert") {
8730
+ const at = over.index;
8731
+ const len = over.text.length;
8732
+ if (at <= start) {
8733
+ return [{ ...op, index: start + len }];
8734
+ }
8735
+ if (at >= end) {
8736
+ return [{ ...op }];
8737
+ }
8738
+ return [
8739
+ {
8740
+ type: "format",
8741
+ index: start,
8742
+ length: at - start,
8743
+ attributes: op.attributes
8744
+ },
8745
+ {
8746
+ type: "format",
8747
+ index: at + len,
8748
+ length: end - at,
8749
+ attributes: op.attributes
8750
+ }
8751
+ ];
8752
+ } else if (over.type === "delete") {
8753
+ const newStart = mapIndexOverDelete(start, over.index, over.length);
8754
+ const newEnd = mapIndexOverDelete(end, over.index, over.length);
8755
+ return newEnd - newStart > 0 ? [
8756
+ {
8757
+ type: "format",
8758
+ index: newStart,
8759
+ length: newEnd - newStart,
8760
+ attributes: op.attributes
8761
+ }
8762
+ ] : [];
8763
+ } else {
8764
+ if (order === "after") {
8765
+ return [{ ...op }];
8766
+ }
8767
+ const overlapStart = Math.max(start, over.index);
8768
+ const overlapEnd = Math.min(end, over.index + over.length);
8769
+ if (overlapStart >= overlapEnd) {
8770
+ return [{ ...op }];
8771
+ }
8772
+ const hasConflict = Object.keys(op.attributes).some(
8773
+ (key) => key in over.attributes
8774
+ );
8775
+ if (!hasConflict) {
8776
+ return [{ ...op }];
8777
+ }
8778
+ const reduced = {};
8779
+ for (const [key, value] of Object.entries(op.attributes)) {
8780
+ if (!(key in over.attributes)) {
8781
+ reduced[key] = value;
8782
+ }
8783
+ }
8784
+ const pieces = [];
8785
+ if (start < overlapStart) {
8786
+ pieces.push({
8787
+ type: "format",
8788
+ index: start,
8789
+ length: overlapStart - start,
8790
+ attributes: op.attributes
8791
+ });
8792
+ }
8793
+ if (Object.keys(reduced).length > 0) {
8794
+ pieces.push({
8795
+ type: "format",
8796
+ index: overlapStart,
8797
+ length: overlapEnd - overlapStart,
8798
+ attributes: reduced
8799
+ });
8800
+ }
8801
+ if (overlapEnd < end) {
8802
+ pieces.push({
8803
+ type: "format",
8804
+ index: overlapEnd,
8805
+ length: end - overlapEnd,
8806
+ attributes: op.attributes
8807
+ });
8808
+ }
8809
+ return pieces;
8810
+ }
8811
+ }
8812
+ function transformSingle(op, over, order) {
8813
+ switch (op.type) {
8814
+ case "insert":
8815
+ return transformInsert(op, over, order);
8816
+ case "delete":
8817
+ return transformDelete(op, over);
8818
+ case "format":
8819
+ return transformFormat(op, over, order);
8820
+ }
8821
+ }
8822
+ function transformTextOperationsX(a, b, order) {
8823
+ if (a.length === 0 || b.length === 0) {
8824
+ return [[...a], [...b]];
8825
+ }
8826
+ if (a.length === 1 && b.length === 1) {
8827
+ return [
8828
+ transformSingle(a[0], b[0], order),
8829
+ transformSingle(b[0], a[0], oppositeOrder(order))
8830
+ ];
8831
+ }
8832
+ if (a.length > 1) {
8833
+ const [headA1, b1] = transformTextOperationsX([a[0]], b, order);
8834
+ const [restA1, b2] = transformTextOperationsX(a.slice(1), b1, order);
8835
+ return [[...headA1, ...restA1], b2];
8836
+ }
8837
+ const [a1, headB1] = transformTextOperationsX(a, [b[0]], order);
8838
+ const [a2, restB1] = transformTextOperationsX(a1, b.slice(1), order);
8839
+ return [a2, [...headB1, ...restB1]];
8840
+ }
8841
+ function transformTextOperations(ops, over, order) {
8842
+ return transformTextOperationsX(ops, over, order)[0];
8843
+ }
8844
+ function textOperationsEqual(a, b) {
8845
+ return a === b || stableStringify(a) === stableStringify(b);
8714
8846
  }
8715
8847
  function applyTextOperationsToSegments(segments, ops) {
8716
8848
  let next = [...segments];
@@ -8782,21 +8914,31 @@ function invertTextOperations(segments, ops) {
8782
8914
  }
8783
8915
 
8784
8916
  // src/crdts/LiveText.ts
8917
+ var ACCEPTED_OPS_HISTORY_LIMIT = 1e3;
8785
8918
  var LiveText = class _LiveText extends AbstractCrdt {
8919
+ /** The local document: #confirmed ⊕ #inFlightOps ⊕ #queuedOps. */
8786
8920
  #segments;
8921
+ /** The server-confirmed document (only authoritative ops applied). */
8922
+ #confirmed;
8787
8923
  #version;
8788
- #pendingOps;
8924
+ /** The op currently awaiting server acknowledgement (at most one). */
8925
+ #inFlightOpId;
8926
+ /** Its ops, continuously re-expressed against current server state. */
8927
+ #inFlightOps = [];
8928
+ /** Local edits made while an op is in flight; sent after the ack. */
8929
+ #queuedOps = [];
8930
+ #acceptedOps = [];
8789
8931
  constructor(textOrData = "", version = 0) {
8790
8932
  super();
8791
8933
  this.#segments = typeof textOrData === "string" ? textOrData.length === 0 ? [] : [{ text: textOrData }] : dataToSegments(textOrData);
8934
+ this.#confirmed = [...this.#segments];
8792
8935
  this.#version = version;
8793
- this.#pendingOps = /* @__PURE__ */ new Map();
8794
8936
  }
8795
8937
  get version() {
8796
8938
  return this.#version;
8797
8939
  }
8798
8940
  get length() {
8799
- return this.toString().length;
8941
+ return textLength(this.#segments);
8800
8942
  }
8801
8943
  /** @internal */
8802
8944
  static _deserialize([id, item], _parentToChildren, pool) {
@@ -8847,30 +8989,16 @@ var LiveText = class _LiveText extends AbstractCrdt {
8847
8989
  return super._apply(op, isLocal);
8848
8990
  }
8849
8991
  if (isLocal) {
8850
- this.#pendingOps.set(nn(op.opId), op.ops);
8851
- return this.#applyOperations(op.ops, _nullishCoalesce(op.version, () => ( this.#version)));
8852
- }
8853
- if (op.opId !== void 0) {
8854
- const pending2 = this.#pendingOps.get(op.opId);
8855
- this.#pendingOps.delete(op.opId);
8856
- const otherPending = Array.from(this.#pendingOps.values()).flat();
8857
- if (pending2 !== void 0 && otherPending.length > 0) {
8858
- this.#segments = applyTextOperationsToSegments(
8859
- this.#segments,
8860
- invertTextOperations(this.#segments, pending2)
8861
- );
8862
- const ops2 = rebaseTextOperations(op.ops, otherPending);
8863
- return this.#applyOperations(
8864
- ops2,
8865
- _nullishCoalesce(op.version, () => ( Math.max(this.#version, op.baseVersion + 1)))
8866
- );
8867
- }
8868
- this.#version = _nullishCoalesce(op.version, () => ( Math.max(this.#version, op.baseVersion + 1)));
8992
+ return this.#applyLocal(op);
8993
+ }
8994
+ if (op.opId !== void 0 && op.opId === this.#inFlightOpId) {
8995
+ return this.#applyAck(op);
8996
+ }
8997
+ if (op.opId !== void 0 && this.#acceptedOps.some((entry) => entry.opId === op.opId)) {
8998
+ this.#version = Math.max(this.#version, _nullishCoalesce(op.version, () => ( op.baseVersion + 1)));
8869
8999
  return { modified: false };
8870
9000
  }
8871
- const pending = Array.from(this.#pendingOps.values()).flat();
8872
- const ops = pending.length > 0 ? rebaseTextOperations(op.ops, pending) : op.ops;
8873
- return this.#applyOperations(ops, _nullishCoalesce(op.version, () => ( this.#version + 1)));
9001
+ return this.#applyRemote(op);
8874
9002
  }
8875
9003
  insert(index, text, attributes) {
8876
9004
  const clippedIndex = Math.max(0, Math.min(index, this.length));
@@ -8914,45 +9042,153 @@ var LiveText = class _LiveText extends AbstractCrdt {
8914
9042
  }
8915
9043
  ]);
8916
9044
  }
9045
+ /** Local edits made through the public API. */
8917
9046
  #dispatch(ops) {
8918
9047
  if (ops.length === 0) {
8919
9048
  return;
8920
9049
  }
8921
9050
  _optionalChain([this, 'access', _215 => _215._pool, 'optionalAccess', _216 => _216.assertStorageIsWritable, 'call', _217 => _217()]);
8922
- const baseVersion = this.#version;
8923
- const reverse = this._pool !== void 0 && this._id !== void 0 ? this.#invertOperations(ops) : [];
9051
+ const attached = this._pool !== void 0 && this._id !== void 0;
9052
+ const reverse = attached ? this.#invertOperations(ops) : [];
8924
9053
  const changes = this.#applyOperationsLocally(ops);
8925
- if (this._pool !== void 0 && this._id !== void 0) {
8926
- const opId = this._pool.generateOpId();
8927
- this.#pendingOps.set(opId, ops);
8928
- this._pool.dispatch(
9054
+ if (!attached) {
9055
+ return;
9056
+ }
9057
+ const pool = nn(this._pool);
9058
+ const id = nn(this._id);
9059
+ const updates = /* @__PURE__ */ new Map([
9060
+ [
9061
+ id,
9062
+ {
9063
+ type: "LiveText",
9064
+ node: this,
9065
+ version: this.#version,
9066
+ updates: changes
9067
+ }
9068
+ ]
9069
+ ]);
9070
+ if (this.#inFlightOpId === void 0) {
9071
+ const opId = pool.generateOpId();
9072
+ this.#inFlightOpId = opId;
9073
+ this.#inFlightOps = [...ops];
9074
+ pool.dispatch(
8929
9075
  [
8930
9076
  {
8931
9077
  type: OpCode.UPDATE_TEXT,
8932
- id: this._id,
9078
+ id,
8933
9079
  opId,
8934
- baseVersion,
9080
+ baseVersion: this.#version,
8935
9081
  ops: [...ops]
8936
9082
  }
8937
9083
  ],
8938
9084
  reverse,
8939
- /* @__PURE__ */ new Map([
8940
- [
8941
- this._id,
8942
- {
8943
- type: "LiveText",
8944
- node: this,
8945
- version: this.#version,
8946
- updates: changes
8947
- }
8948
- ]
8949
- ])
9085
+ updates
8950
9086
  );
9087
+ } else {
9088
+ this.#queuedOps.push(...ops);
9089
+ pool.dispatch([], reverse, updates, { clearRedoStack: true });
8951
9090
  }
8952
9091
  }
8953
- #applyOperations(ops, version) {
9092
+ /**
9093
+ * A local replay of an existing wire op: an undo/redo frame, or an
9094
+ * unacknowledged op re-sent after a reconnect.
9095
+ */
9096
+ #applyLocal(op) {
9097
+ const mutableOp = op;
9098
+ if (op.opId !== void 0 && op.opId === this.#inFlightOpId) {
9099
+ this.#inFlightOps = [...this.#inFlightOps, ...this.#queuedOps];
9100
+ this.#queuedOps = [];
9101
+ mutableOp.baseVersion = this.#version;
9102
+ mutableOp.ops = [...this.#inFlightOps];
9103
+ return { modified: false };
9104
+ }
9105
+ let ops = op.ops;
9106
+ for (const entry of this.#acceptedOps) {
9107
+ if (entry.version > op.baseVersion && entry.ops.length > 0) {
9108
+ ops = transformTextOperations(ops, entry.ops, "after");
9109
+ }
9110
+ }
8954
9111
  const reverse = this.#invertOperations(ops);
8955
9112
  const changes = this.#applyOperationsLocally(ops);
9113
+ if (this.#inFlightOpId === void 0 && ops.length > 0) {
9114
+ this.#inFlightOpId = nn(op.opId, "Local ops must have an opId");
9115
+ this.#inFlightOps = [...ops];
9116
+ mutableOp.baseVersion = this.#version;
9117
+ mutableOp.ops = [...ops];
9118
+ } else {
9119
+ this.#queuedOps.push(...ops);
9120
+ mutableOp.baseVersion = this.#version;
9121
+ mutableOp.ops = [];
9122
+ }
9123
+ if (changes.length === 0) {
9124
+ return { modified: false };
9125
+ }
9126
+ return {
9127
+ reverse,
9128
+ modified: {
9129
+ type: "LiveText",
9130
+ node: this,
9131
+ version: this.#version,
9132
+ updates: changes
9133
+ }
9134
+ };
9135
+ }
9136
+ /** Server acknowledgement of our in-flight op. */
9137
+ #applyAck(op) {
9138
+ const ackedVersion = _nullishCoalesce(op.version, () => ( Math.max(this.#version, op.baseVersion + 1)));
9139
+ const predicted = this.#inFlightOps;
9140
+ const opId = this.#inFlightOpId;
9141
+ this.#confirmed = applyTextOperationsToSegments(this.#confirmed, op.ops);
9142
+ this.#inFlightOpId = void 0;
9143
+ this.#inFlightOps = [];
9144
+ let appliedOps = [];
9145
+ let result = { modified: false };
9146
+ if (!textOperationsEqual(op.ops, predicted)) {
9147
+ error2(
9148
+ "LiveText: acknowledgement did not match the local prediction; resynchronizing"
9149
+ );
9150
+ const rebuilt = this.#rebuildLocalFromConfirmed();
9151
+ appliedOps = rebuilt.appliedOps;
9152
+ if (rebuilt.changes.length > 0) {
9153
+ result = {
9154
+ reverse: [],
9155
+ modified: {
9156
+ type: "LiveText",
9157
+ node: this,
9158
+ version: ackedVersion,
9159
+ updates: rebuilt.changes
9160
+ }
9161
+ };
9162
+ }
9163
+ }
9164
+ this.#version = Math.max(this.#version, ackedVersion);
9165
+ this.#recordAccepted(ackedVersion, appliedOps, opId);
9166
+ this.#flushQueued();
9167
+ return result;
9168
+ }
9169
+ /** An accepted op from another client (or a server-fabricated fix op). */
9170
+ #applyRemote(op) {
9171
+ const version = _nullishCoalesce(op.version, () => ( this.#version + 1));
9172
+ this.#confirmed = applyTextOperationsToSegments(this.#confirmed, op.ops);
9173
+ const [overInFlight, inFlight] = transformTextOperationsX(
9174
+ op.ops,
9175
+ this.#inFlightOps,
9176
+ "before"
9177
+ );
9178
+ const [applied, queued] = transformTextOperationsX(
9179
+ overInFlight,
9180
+ this.#queuedOps,
9181
+ "before"
9182
+ );
9183
+ this.#inFlightOps = inFlight;
9184
+ this.#queuedOps = queued;
9185
+ this.#recordAccepted(version, applied, op.opId);
9186
+ if (applied.length === 0) {
9187
+ this.#version = Math.max(this.#version, version);
9188
+ return { modified: false };
9189
+ }
9190
+ const reverse = this.#invertOperations(applied);
9191
+ const changes = this.#applyOperationsLocally(applied);
8956
9192
  this.#version = Math.max(this.#version, version);
8957
9193
  return {
8958
9194
  reverse,
@@ -8964,6 +9200,129 @@ var LiveText = class _LiveText extends AbstractCrdt {
8964
9200
  }
8965
9201
  };
8966
9202
  }
9203
+ /** Send the queued ops as the next in-flight op (after an ack). */
9204
+ #flushQueued() {
9205
+ if (this.#queuedOps.length === 0 || this._pool === void 0 || this._id === void 0) {
9206
+ return;
9207
+ }
9208
+ const opId = this._pool.generateOpId();
9209
+ this.#inFlightOpId = opId;
9210
+ this.#inFlightOps = this.#queuedOps;
9211
+ this.#queuedOps = [];
9212
+ this._pool.dispatch(
9213
+ [
9214
+ {
9215
+ type: OpCode.UPDATE_TEXT,
9216
+ id: this._id,
9217
+ opId,
9218
+ baseVersion: this.#version,
9219
+ ops: [...this.#inFlightOps]
9220
+ }
9221
+ ],
9222
+ [],
9223
+ /* @__PURE__ */ new Map(),
9224
+ // The local content was already applied (and made undoable) when the
9225
+ // edits happened; this is purely an outbound flush.
9226
+ { clearRedoStack: false }
9227
+ );
9228
+ }
9229
+ /**
9230
+ * Rebuild the local document as confirmed ⊕ queued ops, returning the
9231
+ * coarse delta that was applied. Only used by defensive recovery paths.
9232
+ */
9233
+ #rebuildLocalFromConfirmed() {
9234
+ const before2 = this.#segments;
9235
+ const after2 = applyTextOperationsToSegments(this.#confirmed, [
9236
+ ...this.#inFlightOps,
9237
+ ...this.#queuedOps
9238
+ ]);
9239
+ if (stableStringify(segmentsToData(before2)) === stableStringify(segmentsToData(after2))) {
9240
+ this.#segments = after2;
9241
+ return { appliedOps: [], changes: [] };
9242
+ }
9243
+ const beforeText = before2.map((segment) => segment.text).join("");
9244
+ this.#segments = after2;
9245
+ this.invalidate();
9246
+ const appliedOps = [];
9247
+ const changes = [];
9248
+ if (beforeText.length > 0) {
9249
+ appliedOps.push({ type: "delete", index: 0, length: beforeText.length });
9250
+ changes.push({
9251
+ type: "delete",
9252
+ index: 0,
9253
+ length: beforeText.length,
9254
+ deletedText: beforeText
9255
+ });
9256
+ }
9257
+ let index = 0;
9258
+ for (const segment of after2) {
9259
+ appliedOps.push({
9260
+ type: "insert",
9261
+ index,
9262
+ text: segment.text,
9263
+ attributes: segment.attributes
9264
+ });
9265
+ changes.push({
9266
+ type: "insert",
9267
+ index,
9268
+ text: segment.text,
9269
+ attributes: segment.attributes
9270
+ });
9271
+ index += segment.text.length;
9272
+ }
9273
+ return { appliedOps, changes };
9274
+ }
9275
+ /**
9276
+ * Reconcile this node against an authoritative storage snapshot (e.g.
9277
+ * after a reconnect). The confirmed state and version are replaced by the
9278
+ * snapshot's; pending (in-flight + queued) ops are preserved on top and
9279
+ * will be re-sent by the offline-ops replay.
9280
+ *
9281
+ * @internal
9282
+ */
9283
+ _resyncText(data, version) {
9284
+ this.#confirmed = dataToSegments(data);
9285
+ this.#version = version;
9286
+ this.#acceptedOps = [];
9287
+ const rebuilt = this.#rebuildLocalFromConfirmed();
9288
+ if (rebuilt.changes.length === 0) {
9289
+ return void 0;
9290
+ }
9291
+ return {
9292
+ type: "LiveText",
9293
+ node: this,
9294
+ version: this.#version,
9295
+ updates: rebuilt.changes
9296
+ };
9297
+ }
9298
+ /**
9299
+ * Called when the server rejected one of our ops. Drops all pending state
9300
+ * for this node (edits queued behind a rejected op cannot be trusted
9301
+ * either); the room follows up with a storage resync.
9302
+ *
9303
+ * @internal
9304
+ */
9305
+ _rejectPendingOp(opId) {
9306
+ if (opId !== this.#inFlightOpId) {
9307
+ return;
9308
+ }
9309
+ this.#inFlightOpId = void 0;
9310
+ this.#inFlightOps = [];
9311
+ this.#queuedOps = [];
9312
+ }
9313
+ #recordAccepted(version, ops, opId) {
9314
+ if (this.#acceptedOps.some((entry) => entry.version === version)) {
9315
+ return;
9316
+ }
9317
+ this.#acceptedOps.push({ version, opId, ops: [...ops] });
9318
+ this.#acceptedOps.sort((left, right) => left.version - right.version);
9319
+ if (this.#acceptedOps.length > ACCEPTED_OPS_HISTORY_LIMIT) {
9320
+ this.#acceptedOps.splice(
9321
+ 0,
9322
+ this.#acceptedOps.length - ACCEPTED_OPS_HISTORY_LIMIT
9323
+ );
9324
+ }
9325
+ }
8967
9326
  #applyOperationsLocally(ops) {
8968
9327
  const changes = [];
8969
9328
  for (const op of ops) {
@@ -9236,40 +9595,6 @@ function getTreesDiffOperations(currentItems, newItems) {
9236
9595
  }
9237
9596
  }
9238
9597
  }
9239
- if (crdt.type === CrdtType.TEXT) {
9240
- if (currentCrdt.type !== CrdtType.TEXT || stringifyOrLog(crdt.data) !== stringifyOrLog(currentCrdt.data) || crdt.version !== currentCrdt.version) {
9241
- ops.push({
9242
- type: OpCode.UPDATE_TEXT,
9243
- id,
9244
- baseVersion: currentCrdt.type === CrdtType.TEXT ? currentCrdt.version : 0,
9245
- version: crdt.version,
9246
- ops: [
9247
- {
9248
- type: "delete",
9249
- index: 0,
9250
- length: currentCrdt.type === CrdtType.TEXT ? currentCrdt.data.reduce(
9251
- (sum, segment) => sum + segment[0].length,
9252
- 0
9253
- ) : 0
9254
- },
9255
- ...crdt.data.map((segment, index, items) => {
9256
- const [text, attributes] = segment;
9257
- const insertIndex = items.slice(0, index).reduce((sum, item) => sum + item[0].length, 0);
9258
- return attributes === void 0 ? {
9259
- type: "insert",
9260
- index: insertIndex,
9261
- text
9262
- } : {
9263
- type: "insert",
9264
- index: insertIndex,
9265
- text,
9266
- attributes
9267
- };
9268
- })
9269
- ]
9270
- });
9271
- }
9272
- }
9273
9598
  if (crdt.parentKey !== currentCrdt.parentKey) {
9274
9599
  ops.push({
9275
9600
  type: OpCode.SET_PARENT_KEY,
@@ -10400,7 +10725,7 @@ function createRoom(options, config) {
10400
10725
  }
10401
10726
  }
10402
10727
  });
10403
- function onDispatch(ops, reverse, storageUpdates) {
10728
+ function onDispatch(ops, reverse, storageUpdates, options2) {
10404
10729
  if (context.activeBatch) {
10405
10730
  for (const op of ops) {
10406
10731
  context.activeBatch.ops.push(op);
@@ -10419,15 +10744,17 @@ function createRoom(options, config) {
10419
10744
  if (reverse.length > 0) {
10420
10745
  addToUndoStack(reverse);
10421
10746
  }
10422
- if (ops.length > 0) {
10747
+ if (_nullishCoalesce(_optionalChain([options2, 'optionalAccess', _229 => _229.clearRedoStack]), () => ( ops.length > 0))) {
10423
10748
  context.redoStack.length = 0;
10749
+ }
10750
+ if (ops.length > 0) {
10424
10751
  dispatchOps(ops);
10425
10752
  }
10426
10753
  notify({ storageUpdates });
10427
10754
  }
10428
10755
  }
10429
10756
  function isStorageWritable() {
10430
- const scopes = _optionalChain([context, 'access', _229 => _229.dynamicSessionInfoSig, 'access', _230 => _230.get, 'call', _231 => _231(), 'optionalAccess', _232 => _232.scopes]);
10757
+ const scopes = _optionalChain([context, 'access', _230 => _230.dynamicSessionInfoSig, 'access', _231 => _231.get, 'call', _232 => _232(), 'optionalAccess', _233 => _233.scopes]);
10431
10758
  return scopes !== void 0 ? canWriteStorage(scopes) : true;
10432
10759
  }
10433
10760
  const eventHub = {
@@ -10524,6 +10851,23 @@ function createRoom(options, config) {
10524
10851
  }
10525
10852
  const ops = getTreesDiffOperations(currentItems, nodes);
10526
10853
  const result = applyRemoteOps(ops);
10854
+ for (const [id, crdt] of nodes) {
10855
+ if (crdt.type === CrdtType.TEXT) {
10856
+ const node = context.pool.nodes.get(id);
10857
+ if (node !== void 0 && isLiveText(node)) {
10858
+ const update = node._resyncText(crdt.data, crdt.version);
10859
+ if (update !== void 0) {
10860
+ result.updates.storageUpdates.set(
10861
+ id,
10862
+ mergeStorageUpdates(
10863
+ result.updates.storageUpdates.get(id),
10864
+ update
10865
+ )
10866
+ );
10867
+ }
10868
+ }
10869
+ }
10870
+ }
10527
10871
  notify(result.updates);
10528
10872
  } else {
10529
10873
  context.root = LiveObject._fromItems(
@@ -10531,7 +10875,7 @@ function createRoom(options, config) {
10531
10875
  context.pool
10532
10876
  );
10533
10877
  }
10534
- const canWrite = _nullishCoalesce(_optionalChain([self, 'access', _233 => _233.get, 'call', _234 => _234(), 'optionalAccess', _235 => _235.canWrite]), () => ( true));
10878
+ const canWrite = _nullishCoalesce(_optionalChain([self, 'access', _234 => _234.get, 'call', _235 => _235(), 'optionalAccess', _236 => _236.canWrite]), () => ( true));
10535
10879
  const root = context.root;
10536
10880
  disableHistory(() => {
10537
10881
  for (const key in context.initialStorage) {
@@ -10738,7 +11082,7 @@ function createRoom(options, config) {
10738
11082
  }
10739
11083
  context.myPresence.patch(patch);
10740
11084
  if (context.activeBatch) {
10741
- if (_optionalChain([options2, 'optionalAccess', _236 => _236.addToHistory])) {
11085
+ if (_optionalChain([options2, 'optionalAccess', _237 => _237.addToHistory])) {
10742
11086
  context.activeBatch.reverseOps.pushLeft({
10743
11087
  type: "presence",
10744
11088
  data: oldValues
@@ -10747,7 +11091,7 @@ function createRoom(options, config) {
10747
11091
  context.activeBatch.updates.presence = true;
10748
11092
  } else {
10749
11093
  flushNowOrSoon();
10750
- if (_optionalChain([options2, 'optionalAccess', _237 => _237.addToHistory])) {
11094
+ if (_optionalChain([options2, 'optionalAccess', _238 => _238.addToHistory])) {
10751
11095
  addToUndoStack([{ type: "presence", data: oldValues }]);
10752
11096
  }
10753
11097
  notify({ presence: true });
@@ -10924,11 +11268,11 @@ function createRoom(options, config) {
10924
11268
  break;
10925
11269
  }
10926
11270
  case ServerMsgCode.STORAGE_CHUNK:
10927
- _optionalChain([stopwatch, 'optionalAccess', _238 => _238.lap, 'call', _239 => _239()]);
11271
+ _optionalChain([stopwatch, 'optionalAccess', _239 => _239.lap, 'call', _240 => _240()]);
10928
11272
  nodeMapBuffer.append(compactNodesToNodeStream(message.nodes));
10929
11273
  break;
10930
11274
  case ServerMsgCode.STORAGE_STREAM_END: {
10931
- const timing = _optionalChain([stopwatch, 'optionalAccess', _240 => _240.stop, 'call', _241 => _241()]);
11275
+ const timing = _optionalChain([stopwatch, 'optionalAccess', _241 => _241.stop, 'call', _242 => _242()]);
10932
11276
  if (timing) {
10933
11277
  const ms = (v) => `${v.toFixed(1)}ms`;
10934
11278
  const rest = timing.laps.slice(1);
@@ -10955,16 +11299,36 @@ function createRoom(options, config) {
10955
11299
  }
10956
11300
  break;
10957
11301
  }
10958
- // Receiving a RejectedOps message in the client means that the server is no
10959
- // longer in sync with the client. Trying to synchronize the client again by
10960
- // rolling back particular Ops may be hard/impossible. It's fine to not try and
10961
- // accept the out-of-sync reality and throw an error.
11302
+ // Receiving a RejectedOps message means the server refused some of
11303
+ // our ops, so our optimistic local state is out of sync with the
11304
+ // server. For LiveText ops this is a normal (if rare) situation
11305
+ // e.g. a client that was offline long enough to fall outside the
11306
+ // server's retained history window — and we can recover: drop the
11307
+ // rejected pending state and re-fetch the authoritative storage
11308
+ // snapshot. For other ops (e.g. permission rejections), rolling back
11309
+ // particular Ops is hard/impossible, so we keep the old behavior of
11310
+ // accepting the out-of-sync reality and surfacing an error.
10962
11311
  case ServerMsgCode.REJECT_STORAGE_OP: {
10963
11312
  errorWithTitle(
10964
11313
  "Storage mutation rejection error",
10965
11314
  message.reason
10966
11315
  );
10967
- if (process.env.NODE_ENV !== "production") {
11316
+ let needsStorageResync = false;
11317
+ for (const opId of message.opIds) {
11318
+ const rejectedOp = context.unacknowledgedOps.get(opId);
11319
+ context.unacknowledgedOps.delete(opId);
11320
+ context.buffer.storageOperations = context.buffer.storageOperations.filter((op) => op.opId !== opId);
11321
+ if (rejectedOp !== void 0 && rejectedOp.type === OpCode.UPDATE_TEXT) {
11322
+ const node = context.pool.nodes.get(rejectedOp.id);
11323
+ if (node !== void 0 && isLiveText(node)) {
11324
+ node._rejectPendingOp(opId);
11325
+ needsStorageResync = true;
11326
+ }
11327
+ }
11328
+ }
11329
+ if (needsStorageResync) {
11330
+ refreshStorage({ flush: true });
11331
+ } else if (process.env.NODE_ENV !== "production") {
10968
11332
  throw new Error(
10969
11333
  `Storage mutations rejected by server: ${message.reason}`
10970
11334
  );
@@ -11063,11 +11427,11 @@ function createRoom(options, config) {
11063
11427
  } else if (pendingFeedsRequests.has(requestId)) {
11064
11428
  const pending = pendingFeedsRequests.get(requestId);
11065
11429
  pendingFeedsRequests.delete(requestId);
11066
- _optionalChain([pending, 'optionalAccess', _242 => _242.reject, 'call', _243 => _243(err)]);
11430
+ _optionalChain([pending, 'optionalAccess', _243 => _243.reject, 'call', _244 => _244(err)]);
11067
11431
  } else if (pendingFeedMessagesRequests.has(requestId)) {
11068
11432
  const pending = pendingFeedMessagesRequests.get(requestId);
11069
11433
  pendingFeedMessagesRequests.delete(requestId);
11070
- _optionalChain([pending, 'optionalAccess', _244 => _244.reject, 'call', _245 => _245(err)]);
11434
+ _optionalChain([pending, 'optionalAccess', _245 => _245.reject, 'call', _246 => _246(err)]);
11071
11435
  }
11072
11436
  eventHub.feeds.notify(message);
11073
11437
  break;
@@ -11221,10 +11585,10 @@ function createRoom(options, config) {
11221
11585
  timeoutId,
11222
11586
  kind,
11223
11587
  feedId,
11224
- messageId: _optionalChain([options2, 'optionalAccess', _246 => _246.messageId]),
11225
- expectedClientMessageId: _optionalChain([options2, 'optionalAccess', _247 => _247.expectedClientMessageId])
11588
+ messageId: _optionalChain([options2, 'optionalAccess', _247 => _247.messageId]),
11589
+ expectedClientMessageId: _optionalChain([options2, 'optionalAccess', _248 => _248.expectedClientMessageId])
11226
11590
  });
11227
- if (kind === "add-message" && _optionalChain([options2, 'optionalAccess', _248 => _248.expectedClientMessageId]) === void 0) {
11591
+ if (kind === "add-message" && _optionalChain([options2, 'optionalAccess', _249 => _249.expectedClientMessageId]) === void 0) {
11228
11592
  const q = _nullishCoalesce(pendingAddMessageFifoByFeed.get(feedId), () => ( []));
11229
11593
  q.push(requestId);
11230
11594
  pendingAddMessageFifoByFeed.set(feedId, q);
@@ -11275,10 +11639,10 @@ function createRoom(options, config) {
11275
11639
  }
11276
11640
  if (!matched) {
11277
11641
  const q = pendingAddMessageFifoByFeed.get(message.feedId);
11278
- const headId = _optionalChain([q, 'optionalAccess', _249 => _249[0]]);
11642
+ const headId = _optionalChain([q, 'optionalAccess', _250 => _250[0]]);
11279
11643
  if (headId !== void 0) {
11280
11644
  const pending = pendingFeedMutations.get(headId);
11281
- if (_optionalChain([pending, 'optionalAccess', _250 => _250.kind]) === "add-message" && pending.expectedClientMessageId === void 0) {
11645
+ if (_optionalChain([pending, 'optionalAccess', _251 => _251.kind]) === "add-message" && pending.expectedClientMessageId === void 0) {
11282
11646
  settleFeedMutation(headId, "ok");
11283
11647
  }
11284
11648
  }
@@ -11314,7 +11678,7 @@ function createRoom(options, config) {
11314
11678
  const unacknowledgedOps2 = [...context.unacknowledgedOps.values()];
11315
11679
  createOrUpdateRootFromMessage(nodes);
11316
11680
  applyAndSendOfflineOps(unacknowledgedOps2);
11317
- _optionalChain([_resolveStoragePromise, 'optionalCall', _251 => _251()]);
11681
+ _optionalChain([_resolveStoragePromise, 'optionalCall', _252 => _252()]);
11318
11682
  notifyStorageStatus();
11319
11683
  eventHub.storageDidLoad.notify();
11320
11684
  }
@@ -11332,7 +11696,7 @@ function createRoom(options, config) {
11332
11696
  } else if (!messages.some((msg) => msg.type === ClientMsgCode.FETCH_STORAGE)) {
11333
11697
  messages.push({ type: ClientMsgCode.FETCH_STORAGE });
11334
11698
  nodeMapBuffer.take();
11335
- _optionalChain([stopwatch, 'optionalAccess', _252 => _252.start, 'call', _253 => _253()]);
11699
+ _optionalChain([stopwatch, 'optionalAccess', _253 => _253.start, 'call', _254 => _254()]);
11336
11700
  }
11337
11701
  if (options2.flush) {
11338
11702
  flushNowOrSoon();
@@ -11388,10 +11752,10 @@ function createRoom(options, config) {
11388
11752
  const message = {
11389
11753
  type: ClientMsgCode.FETCH_FEEDS,
11390
11754
  requestId,
11391
- cursor: _optionalChain([options2, 'optionalAccess', _254 => _254.cursor]),
11392
- since: _optionalChain([options2, 'optionalAccess', _255 => _255.since]),
11393
- limit: _optionalChain([options2, 'optionalAccess', _256 => _256.limit]),
11394
- metadata: _optionalChain([options2, 'optionalAccess', _257 => _257.metadata])
11755
+ cursor: _optionalChain([options2, 'optionalAccess', _255 => _255.cursor]),
11756
+ since: _optionalChain([options2, 'optionalAccess', _256 => _256.since]),
11757
+ limit: _optionalChain([options2, 'optionalAccess', _257 => _257.limit]),
11758
+ metadata: _optionalChain([options2, 'optionalAccess', _258 => _258.metadata])
11395
11759
  };
11396
11760
  context.buffer.messages.push(message);
11397
11761
  flushNowOrSoon();
@@ -11411,9 +11775,9 @@ function createRoom(options, config) {
11411
11775
  type: ClientMsgCode.FETCH_FEED_MESSAGES,
11412
11776
  requestId,
11413
11777
  feedId,
11414
- cursor: _optionalChain([options2, 'optionalAccess', _258 => _258.cursor]),
11415
- since: _optionalChain([options2, 'optionalAccess', _259 => _259.since]),
11416
- limit: _optionalChain([options2, 'optionalAccess', _260 => _260.limit])
11778
+ cursor: _optionalChain([options2, 'optionalAccess', _259 => _259.cursor]),
11779
+ since: _optionalChain([options2, 'optionalAccess', _260 => _260.since]),
11780
+ limit: _optionalChain([options2, 'optionalAccess', _261 => _261.limit])
11417
11781
  };
11418
11782
  context.buffer.messages.push(message);
11419
11783
  flushNowOrSoon();
@@ -11432,8 +11796,8 @@ function createRoom(options, config) {
11432
11796
  type: ClientMsgCode.ADD_FEED,
11433
11797
  requestId,
11434
11798
  feedId,
11435
- metadata: _optionalChain([options2, 'optionalAccess', _261 => _261.metadata]),
11436
- createdAt: _optionalChain([options2, 'optionalAccess', _262 => _262.createdAt])
11799
+ metadata: _optionalChain([options2, 'optionalAccess', _262 => _262.metadata]),
11800
+ createdAt: _optionalChain([options2, 'optionalAccess', _263 => _263.createdAt])
11437
11801
  };
11438
11802
  context.buffer.messages.push(message);
11439
11803
  flushNowOrSoon();
@@ -11467,15 +11831,15 @@ function createRoom(options, config) {
11467
11831
  function addFeedMessage(feedId, data, options2) {
11468
11832
  const requestId = nanoid();
11469
11833
  const promise = registerFeedMutation(requestId, "add-message", feedId, {
11470
- expectedClientMessageId: _optionalChain([options2, 'optionalAccess', _263 => _263.id])
11834
+ expectedClientMessageId: _optionalChain([options2, 'optionalAccess', _264 => _264.id])
11471
11835
  });
11472
11836
  const message = {
11473
11837
  type: ClientMsgCode.ADD_FEED_MESSAGE,
11474
11838
  requestId,
11475
11839
  feedId,
11476
11840
  data,
11477
- id: _optionalChain([options2, 'optionalAccess', _264 => _264.id]),
11478
- createdAt: _optionalChain([options2, 'optionalAccess', _265 => _265.createdAt])
11841
+ id: _optionalChain([options2, 'optionalAccess', _265 => _265.id]),
11842
+ createdAt: _optionalChain([options2, 'optionalAccess', _266 => _266.createdAt])
11479
11843
  };
11480
11844
  context.buffer.messages.push(message);
11481
11845
  flushNowOrSoon();
@@ -11492,7 +11856,7 @@ function createRoom(options, config) {
11492
11856
  feedId,
11493
11857
  messageId,
11494
11858
  data,
11495
- updatedAt: _optionalChain([options2, 'optionalAccess', _266 => _266.updatedAt])
11859
+ updatedAt: _optionalChain([options2, 'optionalAccess', _267 => _267.updatedAt])
11496
11860
  };
11497
11861
  context.buffer.messages.push(message);
11498
11862
  flushNowOrSoon();
@@ -11699,8 +12063,8 @@ function createRoom(options, config) {
11699
12063
  async function getThreads(options2) {
11700
12064
  return httpClient.getThreads({
11701
12065
  roomId,
11702
- query: _optionalChain([options2, 'optionalAccess', _267 => _267.query]),
11703
- cursor: _optionalChain([options2, 'optionalAccess', _268 => _268.cursor])
12066
+ query: _optionalChain([options2, 'optionalAccess', _268 => _268.query]),
12067
+ cursor: _optionalChain([options2, 'optionalAccess', _269 => _269.cursor])
11704
12068
  });
11705
12069
  }
11706
12070
  async function getThread(threadId) {
@@ -11822,7 +12186,7 @@ function createRoom(options, config) {
11822
12186
  function getSubscriptionSettings(options2) {
11823
12187
  return httpClient.getSubscriptionSettings({
11824
12188
  roomId,
11825
- signal: _optionalChain([options2, 'optionalAccess', _269 => _269.signal])
12189
+ signal: _optionalChain([options2, 'optionalAccess', _270 => _270.signal])
11826
12190
  });
11827
12191
  }
11828
12192
  function updateSubscriptionSettings(settings) {
@@ -11844,7 +12208,7 @@ function createRoom(options, config) {
11844
12208
  {
11845
12209
  [kInternal]: {
11846
12210
  get presenceBuffer() {
11847
- return deepClone(_nullishCoalesce(_optionalChain([context, 'access', _270 => _270.buffer, 'access', _271 => _271.presenceUpdates, 'optionalAccess', _272 => _272.data]), () => ( null)));
12211
+ return deepClone(_nullishCoalesce(_optionalChain([context, 'access', _271 => _271.buffer, 'access', _272 => _272.presenceUpdates, 'optionalAccess', _273 => _273.data]), () => ( null)));
11848
12212
  },
11849
12213
  // prettier-ignore
11850
12214
  get undoStack() {
@@ -11859,9 +12223,9 @@ function createRoom(options, config) {
11859
12223
  return context.yjsProvider;
11860
12224
  },
11861
12225
  setYjsProvider(newProvider) {
11862
- _optionalChain([context, 'access', _273 => _273.yjsProvider, 'optionalAccess', _274 => _274.off, 'call', _275 => _275("status", yjsStatusDidChange)]);
12226
+ _optionalChain([context, 'access', _274 => _274.yjsProvider, 'optionalAccess', _275 => _275.off, 'call', _276 => _276("status", yjsStatusDidChange)]);
11863
12227
  context.yjsProvider = newProvider;
11864
- _optionalChain([newProvider, 'optionalAccess', _276 => _276.on, 'call', _277 => _277("status", yjsStatusDidChange)]);
12228
+ _optionalChain([newProvider, 'optionalAccess', _277 => _277.on, 'call', _278 => _278("status", yjsStatusDidChange)]);
11865
12229
  context.yjsProviderDidChange.notify();
11866
12230
  },
11867
12231
  yjsProviderDidChange: context.yjsProviderDidChange.observable,
@@ -11919,7 +12283,7 @@ ${dumpPool(context.pool)}`;
11919
12283
  source.dispose();
11920
12284
  }
11921
12285
  eventHub.roomWillDestroy.notify();
11922
- _optionalChain([context, 'access', _278 => _278.yjsProvider, 'optionalAccess', _279 => _279.off, 'call', _280 => _280("status", yjsStatusDidChange)]);
12286
+ _optionalChain([context, 'access', _279 => _279.yjsProvider, 'optionalAccess', _280 => _280.off, 'call', _281 => _281("status", yjsStatusDidChange)]);
11923
12287
  syncSourceForStorage.destroy();
11924
12288
  syncSourceForYjs.destroy();
11925
12289
  uninstallBgTabSpy();
@@ -12079,7 +12443,7 @@ function makeClassicSubscribeFn(roomId, events, errorEvents) {
12079
12443
  }
12080
12444
  if (isLiveNode(first)) {
12081
12445
  const node = first;
12082
- if (_optionalChain([options, 'optionalAccess', _281 => _281.isDeep])) {
12446
+ if (_optionalChain([options, 'optionalAccess', _282 => _282.isDeep])) {
12083
12447
  const storageCallback = second;
12084
12448
  return subscribeToLiveStructureDeeply(node, storageCallback);
12085
12449
  } else {
@@ -12165,8 +12529,8 @@ function createClient(options) {
12165
12529
  const authManager = createAuthManager(options, (token) => {
12166
12530
  currentUserId.set(() => token.uid);
12167
12531
  });
12168
- const fetchPolyfill = _optionalChain([clientOptions, 'access', _282 => _282.polyfills, 'optionalAccess', _283 => _283.fetch]) || /* istanbul ignore next */
12169
- _optionalChain([globalThis, 'access', _284 => _284.fetch, 'optionalAccess', _285 => _285.bind, 'call', _286 => _286(globalThis)]);
12532
+ const fetchPolyfill = _optionalChain([clientOptions, 'access', _283 => _283.polyfills, 'optionalAccess', _284 => _284.fetch]) || /* istanbul ignore next */
12533
+ _optionalChain([globalThis, 'access', _285 => _285.fetch, 'optionalAccess', _286 => _286.bind, 'call', _287 => _287(globalThis)]);
12170
12534
  const httpClient = createApiClient({
12171
12535
  baseUrl,
12172
12536
  fetchPolyfill,
@@ -12184,7 +12548,7 @@ function createClient(options) {
12184
12548
  delegates: {
12185
12549
  createSocket: makeCreateSocketDelegateForAi(
12186
12550
  baseUrl,
12187
- _optionalChain([clientOptions, 'access', _287 => _287.polyfills, 'optionalAccess', _288 => _288.WebSocket])
12551
+ _optionalChain([clientOptions, 'access', _288 => _288.polyfills, 'optionalAccess', _289 => _289.WebSocket])
12188
12552
  ),
12189
12553
  authenticate: async () => {
12190
12554
  const resp = await authManager.getAuthValue({
@@ -12254,7 +12618,7 @@ function createClient(options) {
12254
12618
  createSocket: makeCreateSocketDelegateForRoom(
12255
12619
  roomId,
12256
12620
  baseUrl,
12257
- _optionalChain([clientOptions, 'access', _289 => _289.polyfills, 'optionalAccess', _290 => _290.WebSocket])
12621
+ _optionalChain([clientOptions, 'access', _290 => _290.polyfills, 'optionalAccess', _291 => _291.WebSocket])
12258
12622
  ),
12259
12623
  authenticate: makeAuthDelegateForRoom(roomId, authManager)
12260
12624
  })),
@@ -12277,7 +12641,7 @@ function createClient(options) {
12277
12641
  const shouldConnect = _nullishCoalesce(options2.autoConnect, () => ( true));
12278
12642
  if (shouldConnect) {
12279
12643
  if (typeof atob === "undefined") {
12280
- if (_optionalChain([clientOptions, 'access', _291 => _291.polyfills, 'optionalAccess', _292 => _292.atob]) === void 0) {
12644
+ if (_optionalChain([clientOptions, 'access', _292 => _292.polyfills, 'optionalAccess', _293 => _293.atob]) === void 0) {
12281
12645
  throw new Error(
12282
12646
  "You need to polyfill atob to use the client in your environment. Please follow the instructions at https://liveblocks.io/docs/errors/liveblocks-client/atob-polyfill"
12283
12647
  );
@@ -12289,7 +12653,7 @@ function createClient(options) {
12289
12653
  return leaseRoom(newRoomDetails);
12290
12654
  }
12291
12655
  function getRoom(roomId) {
12292
- const room = _optionalChain([roomsById, 'access', _293 => _293.get, 'call', _294 => _294(roomId), 'optionalAccess', _295 => _295.room]);
12656
+ const room = _optionalChain([roomsById, 'access', _294 => _294.get, 'call', _295 => _295(roomId), 'optionalAccess', _296 => _296.room]);
12293
12657
  return room ? room : null;
12294
12658
  }
12295
12659
  function logout() {
@@ -12305,7 +12669,7 @@ function createClient(options) {
12305
12669
  const batchedResolveUsers = new Batch(
12306
12670
  async (batchedUserIds) => {
12307
12671
  const userIds = batchedUserIds.flat();
12308
- const users = await _optionalChain([resolveUsers, 'optionalCall', _296 => _296({ userIds })]);
12672
+ const users = await _optionalChain([resolveUsers, 'optionalCall', _297 => _297({ userIds })]);
12309
12673
  warnOnceIf(
12310
12674
  !resolveUsers,
12311
12675
  "Set the resolveUsers option in createClient to specify user info."
@@ -12322,7 +12686,7 @@ function createClient(options) {
12322
12686
  const batchedResolveRoomsInfo = new Batch(
12323
12687
  async (batchedRoomIds) => {
12324
12688
  const roomIds = batchedRoomIds.flat();
12325
- const roomsInfo = await _optionalChain([resolveRoomsInfo, 'optionalCall', _297 => _297({ roomIds })]);
12689
+ const roomsInfo = await _optionalChain([resolveRoomsInfo, 'optionalCall', _298 => _298({ roomIds })]);
12326
12690
  warnOnceIf(
12327
12691
  !resolveRoomsInfo,
12328
12692
  "Set the resolveRoomsInfo option in createClient to specify room info."
@@ -12339,7 +12703,7 @@ function createClient(options) {
12339
12703
  const batchedResolveGroupsInfo = new Batch(
12340
12704
  async (batchedGroupIds) => {
12341
12705
  const groupIds = batchedGroupIds.flat();
12342
- const groupsInfo = await _optionalChain([resolveGroupsInfo, 'optionalCall', _298 => _298({ groupIds })]);
12706
+ const groupsInfo = await _optionalChain([resolveGroupsInfo, 'optionalCall', _299 => _299({ groupIds })]);
12343
12707
  warnOnceIf(
12344
12708
  !resolveGroupsInfo,
12345
12709
  "Set the resolveGroupsInfo option in createClient to specify group info."
@@ -12398,7 +12762,7 @@ function createClient(options) {
12398
12762
  }
12399
12763
  };
12400
12764
  const win = typeof window !== "undefined" ? window : void 0;
12401
- _optionalChain([win, 'optionalAccess', _299 => _299.addEventListener, 'call', _300 => _300("beforeunload", maybePreventClose)]);
12765
+ _optionalChain([win, 'optionalAccess', _300 => _300.addEventListener, 'call', _301 => _301("beforeunload", maybePreventClose)]);
12402
12766
  }
12403
12767
  async function getNotificationSettings(options2) {
12404
12768
  const plainSettings = await httpClient.getNotificationSettings(options2);
@@ -12526,7 +12890,7 @@ var commentBodyElementsTypes = {
12526
12890
  mention: "inline"
12527
12891
  };
12528
12892
  function traverseCommentBody(body, elementOrVisitor, possiblyVisitor) {
12529
- if (!body || !_optionalChain([body, 'optionalAccess', _301 => _301.content])) {
12893
+ if (!body || !_optionalChain([body, 'optionalAccess', _302 => _302.content])) {
12530
12894
  return;
12531
12895
  }
12532
12896
  const element = typeof elementOrVisitor === "string" ? elementOrVisitor : void 0;
@@ -12536,13 +12900,13 @@ function traverseCommentBody(body, elementOrVisitor, possiblyVisitor) {
12536
12900
  for (const block of body.content) {
12537
12901
  if (type === "all" || type === "block") {
12538
12902
  if (guard(block)) {
12539
- _optionalChain([visitor, 'optionalCall', _302 => _302(block)]);
12903
+ _optionalChain([visitor, 'optionalCall', _303 => _303(block)]);
12540
12904
  }
12541
12905
  }
12542
12906
  if (type === "all" || type === "inline") {
12543
12907
  for (const inline of block.children) {
12544
12908
  if (guard(inline)) {
12545
- _optionalChain([visitor, 'optionalCall', _303 => _303(inline)]);
12909
+ _optionalChain([visitor, 'optionalCall', _304 => _304(inline)]);
12546
12910
  }
12547
12911
  }
12548
12912
  }
@@ -12712,7 +13076,7 @@ var stringifyCommentBodyPlainElements = {
12712
13076
  text: ({ element }) => element.text,
12713
13077
  link: ({ element }) => _nullishCoalesce(element.text, () => ( element.url)),
12714
13078
  mention: ({ element, user, group }) => {
12715
- return `@${_nullishCoalesce(_nullishCoalesce(_optionalChain([user, 'optionalAccess', _304 => _304.name]), () => ( _optionalChain([group, 'optionalAccess', _305 => _305.name]))), () => ( element.id))}`;
13079
+ return `@${_nullishCoalesce(_nullishCoalesce(_optionalChain([user, 'optionalAccess', _305 => _305.name]), () => ( _optionalChain([group, 'optionalAccess', _306 => _306.name]))), () => ( element.id))}`;
12716
13080
  }
12717
13081
  };
12718
13082
  var stringifyCommentBodyHtmlElements = {
@@ -12742,7 +13106,7 @@ var stringifyCommentBodyHtmlElements = {
12742
13106
  return html`<a href="${href}" target="_blank" rel="noopener noreferrer">${element.text ? html`${element.text}` : element.url}</a>`;
12743
13107
  },
12744
13108
  mention: ({ element, user, group }) => {
12745
- return html`<span data-mention>@${_optionalChain([user, 'optionalAccess', _306 => _306.name]) ? html`${_optionalChain([user, 'optionalAccess', _307 => _307.name])}` : _optionalChain([group, 'optionalAccess', _308 => _308.name]) ? html`${_optionalChain([group, 'optionalAccess', _309 => _309.name])}` : element.id}</span>`;
13109
+ return html`<span data-mention>@${_optionalChain([user, 'optionalAccess', _307 => _307.name]) ? html`${_optionalChain([user, 'optionalAccess', _308 => _308.name])}` : _optionalChain([group, 'optionalAccess', _309 => _309.name]) ? html`${_optionalChain([group, 'optionalAccess', _310 => _310.name])}` : element.id}</span>`;
12746
13110
  }
12747
13111
  };
12748
13112
  var stringifyCommentBodyMarkdownElements = {
@@ -12772,20 +13136,20 @@ var stringifyCommentBodyMarkdownElements = {
12772
13136
  return markdown`[${_nullishCoalesce(element.text, () => ( element.url))}](${href})`;
12773
13137
  },
12774
13138
  mention: ({ element, user, group }) => {
12775
- return markdown`@${_nullishCoalesce(_nullishCoalesce(_optionalChain([user, 'optionalAccess', _310 => _310.name]), () => ( _optionalChain([group, 'optionalAccess', _311 => _311.name]))), () => ( element.id))}`;
13139
+ return markdown`@${_nullishCoalesce(_nullishCoalesce(_optionalChain([user, 'optionalAccess', _311 => _311.name]), () => ( _optionalChain([group, 'optionalAccess', _312 => _312.name]))), () => ( element.id))}`;
12776
13140
  }
12777
13141
  };
12778
13142
  async function stringifyCommentBody(body, options) {
12779
- const format = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _312 => _312.format]), () => ( "plain"));
12780
- const separator = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _313 => _313.separator]), () => ( (format === "markdown" ? "\n\n" : "\n")));
13143
+ const format = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _313 => _313.format]), () => ( "plain"));
13144
+ const separator = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _314 => _314.separator]), () => ( (format === "markdown" ? "\n\n" : "\n")));
12781
13145
  const elements = {
12782
13146
  ...format === "html" ? stringifyCommentBodyHtmlElements : format === "markdown" ? stringifyCommentBodyMarkdownElements : stringifyCommentBodyPlainElements,
12783
- ..._optionalChain([options, 'optionalAccess', _314 => _314.elements])
13147
+ ..._optionalChain([options, 'optionalAccess', _315 => _315.elements])
12784
13148
  };
12785
13149
  const { users: resolvedUsers, groups: resolvedGroupsInfo } = await resolveMentionsInCommentBody(
12786
13150
  body,
12787
- _optionalChain([options, 'optionalAccess', _315 => _315.resolveUsers]),
12788
- _optionalChain([options, 'optionalAccess', _316 => _316.resolveGroupsInfo])
13151
+ _optionalChain([options, 'optionalAccess', _316 => _316.resolveUsers]),
13152
+ _optionalChain([options, 'optionalAccess', _317 => _317.resolveGroupsInfo])
12789
13153
  );
12790
13154
  const blocks = body.content.flatMap((block, blockIndex) => {
12791
13155
  switch (block.type) {
@@ -12926,9 +13290,9 @@ function makePoller(callback, intervalMs, options) {
12926
13290
  const startTime = performance.now();
12927
13291
  const doc = typeof document !== "undefined" ? document : void 0;
12928
13292
  const win = typeof window !== "undefined" ? window : void 0;
12929
- const maxStaleTimeMs = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _317 => _317.maxStaleTimeMs]), () => ( Number.POSITIVE_INFINITY));
13293
+ const maxStaleTimeMs = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _318 => _318.maxStaleTimeMs]), () => ( Number.POSITIVE_INFINITY));
12930
13294
  const context = {
12931
- inForeground: _optionalChain([doc, 'optionalAccess', _318 => _318.visibilityState]) !== "hidden",
13295
+ inForeground: _optionalChain([doc, 'optionalAccess', _319 => _319.visibilityState]) !== "hidden",
12932
13296
  lastSuccessfulPollAt: startTime,
12933
13297
  count: 0,
12934
13298
  backoff: 0
@@ -13009,11 +13373,11 @@ function makePoller(callback, intervalMs, options) {
13009
13373
  pollNowIfStale();
13010
13374
  }
13011
13375
  function onVisibilityChange() {
13012
- setInForeground(_optionalChain([doc, 'optionalAccess', _319 => _319.visibilityState]) !== "hidden");
13376
+ setInForeground(_optionalChain([doc, 'optionalAccess', _320 => _320.visibilityState]) !== "hidden");
13013
13377
  }
13014
- _optionalChain([doc, 'optionalAccess', _320 => _320.addEventListener, 'call', _321 => _321("visibilitychange", onVisibilityChange)]);
13015
- _optionalChain([win, 'optionalAccess', _322 => _322.addEventListener, 'call', _323 => _323("online", onVisibilityChange)]);
13016
- _optionalChain([win, 'optionalAccess', _324 => _324.addEventListener, 'call', _325 => _325("focus", pollNowIfStale)]);
13378
+ _optionalChain([doc, 'optionalAccess', _321 => _321.addEventListener, 'call', _322 => _322("visibilitychange", onVisibilityChange)]);
13379
+ _optionalChain([win, 'optionalAccess', _323 => _323.addEventListener, 'call', _324 => _324("online", onVisibilityChange)]);
13380
+ _optionalChain([win, 'optionalAccess', _325 => _325.addEventListener, 'call', _326 => _326("focus", pollNowIfStale)]);
13017
13381
  fsm.start();
13018
13382
  return {
13019
13383
  inc,