@liveblocks/core 3.22.0 → 3.23.0-exp1

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.22.0";
9
+ var PKG_VERSION = "3.23.0-exp1";
10
10
  var PKG_FORMAT = "cjs";
11
11
 
12
12
  // src/dupe-detection.ts
@@ -3879,6 +3879,7 @@ var ManagedSocket = class {
3879
3879
 
3880
3880
  // src/internal.ts
3881
3881
  var kInternal = /* @__PURE__ */ Symbol();
3882
+ var kStorageUpdateSource = /* @__PURE__ */ Symbol();
3882
3883
 
3883
3884
  // src/lib/IncrementalJsonParser.ts
3884
3885
  var EMPTY_OBJECT = Object.freeze({});
@@ -5837,13 +5838,15 @@ var OpCode = Object.freeze({
5837
5838
  DELETE_CRDT: 5,
5838
5839
  DELETE_OBJECT_KEY: 6,
5839
5840
  CREATE_MAP: 7,
5840
- CREATE_REGISTER: 8
5841
+ CREATE_REGISTER: 8,
5842
+ CREATE_TEXT: 9,
5843
+ UPDATE_TEXT: 10
5841
5844
  });
5842
5845
  function isIgnoredOp(op) {
5843
5846
  return op.type === OpCode.DELETE_CRDT && op.id === "ACK";
5844
5847
  }
5845
5848
  function isCreateOp(op) {
5846
- return op.type === OpCode.CREATE_OBJECT || op.type === OpCode.CREATE_REGISTER || op.type === OpCode.CREATE_MAP || op.type === OpCode.CREATE_LIST;
5849
+ 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;
5847
5850
  }
5848
5851
 
5849
5852
  // src/protocol/StorageNode.ts
@@ -5851,7 +5854,8 @@ var CrdtType = Object.freeze({
5851
5854
  OBJECT: 0,
5852
5855
  LIST: 1,
5853
5856
  MAP: 2,
5854
- REGISTER: 3
5857
+ REGISTER: 3,
5858
+ TEXT: 4
5855
5859
  });
5856
5860
  function isRootStorageNode(node) {
5857
5861
  return node[0] === "root";
@@ -5868,6 +5872,9 @@ function isMapStorageNode(node) {
5868
5872
  function isRegisterStorageNode(node) {
5869
5873
  return node[1].type === CrdtType.REGISTER;
5870
5874
  }
5875
+ function isTextStorageNode(node) {
5876
+ return node[1].type === CrdtType.TEXT;
5877
+ }
5871
5878
  function isCompactRootNode(node) {
5872
5879
  return node[0] === "root";
5873
5880
  }
@@ -5890,6 +5897,9 @@ function* compactNodesToNodeStream(compactNodes) {
5890
5897
  case CrdtType.REGISTER:
5891
5898
  yield [cnode[0], { type: CrdtType.REGISTER, parentId: cnode[2], parentKey: cnode[3], data: cnode[4] }];
5892
5899
  break;
5900
+ case CrdtType.TEXT:
5901
+ yield [cnode[0], { type: CrdtType.TEXT, parentId: cnode[2], parentKey: cnode[3], data: cnode[4], version: cnode[5] }];
5902
+ break;
5893
5903
  default:
5894
5904
  }
5895
5905
  }
@@ -5918,6 +5928,17 @@ function* nodeStreamToCompactNodes(nodes) {
5918
5928
  const id = node[0];
5919
5929
  const crdt = node[1];
5920
5930
  yield [id, CrdtType.REGISTER, crdt.parentId, crdt.parentKey, crdt.data];
5931
+ } else if (isTextStorageNode(node)) {
5932
+ const id = node[0];
5933
+ const crdt = node[1];
5934
+ yield [
5935
+ id,
5936
+ CrdtType.TEXT,
5937
+ crdt.parentId,
5938
+ crdt.parentKey,
5939
+ crdt.data,
5940
+ crdt.version
5941
+ ];
5921
5942
  } else {
5922
5943
  }
5923
5944
  }
@@ -6120,6 +6141,10 @@ ${parentKey}`;
6120
6141
  get size() {
6121
6142
  return this.#byOpId.size;
6122
6143
  }
6144
+ /** The still-unacknowledged op with the given opId, if any. */
6145
+ get(opId) {
6146
+ return this.#byOpId.get(opId);
6147
+ }
6123
6148
  /**
6124
6149
  * Mark the given Op as still unacknowledged.
6125
6150
  */
@@ -6219,8 +6244,8 @@ function createManagedPool(options) {
6219
6244
  deleteNode: (id) => void nodes.delete(id),
6220
6245
  generateId: () => `${getCurrentConnectionId()}:${clock++}`,
6221
6246
  generateOpId: () => `${getCurrentConnectionId()}:${opClock++}`,
6222
- dispatch(ops, reverse, storageUpdates) {
6223
- _optionalChain([onDispatch, 'optionalCall', _129 => _129(ops, reverse, storageUpdates)]);
6247
+ dispatch(ops, reverse, storageUpdates, options2) {
6248
+ _optionalChain([onDispatch, 'optionalCall', _129 => _129(ops, reverse, storageUpdates, options2)]);
6224
6249
  },
6225
6250
  assertStorageIsWritable: () => {
6226
6251
  if (!isStorageWritable()) {
@@ -6243,10 +6268,17 @@ function Orphaned(oldKey, oldPos = asPos(oldKey)) {
6243
6268
  return Object.freeze({ type: "Orphaned", oldKey, oldPos });
6244
6269
  }
6245
6270
  var AbstractCrdt = class {
6246
- // ^^^^^^^^^^^^ TODO: Make this an interface
6247
6271
  #pool;
6248
6272
  #id;
6249
6273
  #parent = NoParent;
6274
+ constructor() {
6275
+ Object.defineProperty(this, kInternal, {
6276
+ value: {
6277
+ getId: () => this.#id
6278
+ },
6279
+ enumerable: false
6280
+ });
6281
+ }
6250
6282
  /** @internal */
6251
6283
  _getParentKeyOrThrow() {
6252
6284
  switch (this.parent.type) {
@@ -8776,6 +8808,1114 @@ var LiveObject = (_class2 = class _LiveObject extends AbstractCrdt {
8776
8808
  }
8777
8809
  }, _class2.__initStatic(), _class2);
8778
8810
 
8811
+ // src/crdts/liveTextOps.ts
8812
+ function attributesEqual(left, right) {
8813
+ if (left === right) {
8814
+ return true;
8815
+ }
8816
+ if (left === void 0 || right === void 0) {
8817
+ return false;
8818
+ }
8819
+ const leftKeys = Object.keys(left);
8820
+ const rightKeys = Object.keys(right);
8821
+ if (leftKeys.length !== rightKeys.length) {
8822
+ return false;
8823
+ }
8824
+ for (const key of leftKeys) {
8825
+ if (left[key] !== right[key]) {
8826
+ return false;
8827
+ }
8828
+ }
8829
+ return true;
8830
+ }
8831
+ function cloneAttributes(attributes) {
8832
+ return attributes === void 0 ? void 0 : freeze({ ...attributes });
8833
+ }
8834
+ function normalizeSegments(segments) {
8835
+ const normalized = [];
8836
+ for (const segment of segments) {
8837
+ if (segment.text.length === 0) {
8838
+ continue;
8839
+ }
8840
+ const last = normalized.at(-1);
8841
+ const attributes = cloneAttributes(segment.attributes);
8842
+ if (last !== void 0 && attributesEqual(last.attributes, attributes)) {
8843
+ last.text += segment.text;
8844
+ } else {
8845
+ normalized.push({ text: segment.text, attributes });
8846
+ }
8847
+ }
8848
+ return normalized;
8849
+ }
8850
+ function dataToSegments(data) {
8851
+ return normalizeSegments(
8852
+ data.map(([text, attributes]) => ({
8853
+ text,
8854
+ attributes
8855
+ }))
8856
+ );
8857
+ }
8858
+ function segmentsToData(segments) {
8859
+ return segments.map(
8860
+ (segment) => segment.attributes === void 0 ? [segment.text] : [segment.text, { ...segment.attributes }]
8861
+ );
8862
+ }
8863
+ function textLength(segments) {
8864
+ return segments.reduce((sum, segment) => sum + segment.text.length, 0);
8865
+ }
8866
+ function splitSegmentsAt(segments, index) {
8867
+ const result = [];
8868
+ let offset = 0;
8869
+ for (const segment of segments) {
8870
+ const end = offset + segment.text.length;
8871
+ if (index > offset && index < end) {
8872
+ const before2 = segment.text.slice(0, index - offset);
8873
+ const after2 = segment.text.slice(index - offset);
8874
+ result.push({ text: before2, attributes: segment.attributes });
8875
+ result.push({ text: after2, attributes: segment.attributes });
8876
+ } else {
8877
+ result.push({ text: segment.text, attributes: segment.attributes });
8878
+ }
8879
+ offset = end;
8880
+ }
8881
+ return result;
8882
+ }
8883
+ function clipRange(index, length, contentLength) {
8884
+ const clippedIndex = Math.max(0, Math.min(index, contentLength));
8885
+ const clippedEnd = Math.max(
8886
+ clippedIndex,
8887
+ Math.min(index + length, contentLength)
8888
+ );
8889
+ return { index: clippedIndex, length: clippedEnd - clippedIndex };
8890
+ }
8891
+ function applyInsert(segments, index, text, attributes) {
8892
+ if (text.length === 0) {
8893
+ return normalizeSegments(segments);
8894
+ }
8895
+ const split = splitSegmentsAt(segments, index);
8896
+ const result = [];
8897
+ let offset = 0;
8898
+ let inserted = false;
8899
+ for (const segment of split) {
8900
+ if (!inserted && offset === index) {
8901
+ result.push({ text, attributes });
8902
+ inserted = true;
8903
+ }
8904
+ result.push(segment);
8905
+ offset += segment.text.length;
8906
+ }
8907
+ if (!inserted) {
8908
+ result.push({ text, attributes });
8909
+ }
8910
+ return normalizeSegments(result);
8911
+ }
8912
+ function extractDeletedSegments(segments, index, length) {
8913
+ const split = splitSegmentsAt(
8914
+ splitSegmentsAt(segments, index),
8915
+ index + length
8916
+ );
8917
+ const deleted = [];
8918
+ let offset = 0;
8919
+ for (const segment of split) {
8920
+ const end = offset + segment.text.length;
8921
+ if (offset >= index && end <= index + length) {
8922
+ deleted.push({
8923
+ text: segment.text,
8924
+ attributes: segment.attributes
8925
+ });
8926
+ }
8927
+ offset = end;
8928
+ }
8929
+ return normalizeSegments(deleted);
8930
+ }
8931
+ function applyDelete(segments, index, length) {
8932
+ const deletedSegments = extractDeletedSegments(segments, index, length);
8933
+ const split = splitSegmentsAt(
8934
+ splitSegmentsAt(segments, index),
8935
+ index + length
8936
+ );
8937
+ const result = [];
8938
+ let offset = 0;
8939
+ let deletedText = "";
8940
+ for (const segment of split) {
8941
+ const end = offset + segment.text.length;
8942
+ if (offset >= index && end <= index + length) {
8943
+ deletedText += segment.text;
8944
+ } else {
8945
+ result.push(segment);
8946
+ }
8947
+ offset = end;
8948
+ }
8949
+ return {
8950
+ segments: normalizeSegments(result),
8951
+ deletedText,
8952
+ deletedSegments
8953
+ };
8954
+ }
8955
+ function applyFormat(segments, index, length, attributes) {
8956
+ const split = splitSegmentsAt(
8957
+ splitSegmentsAt(segments, index),
8958
+ index + length
8959
+ );
8960
+ const result = [];
8961
+ let offset = 0;
8962
+ for (const segment of split) {
8963
+ const end = offset + segment.text.length;
8964
+ if (offset >= index && end <= index + length) {
8965
+ const nextAttributes = {
8966
+ ..._nullishCoalesce(segment.attributes, () => ( {}))
8967
+ };
8968
+ for (const [key, value] of Object.entries(attributes)) {
8969
+ if (value === null) {
8970
+ delete nextAttributes[key];
8971
+ } else {
8972
+ nextAttributes[key] = value;
8973
+ }
8974
+ }
8975
+ result.push({
8976
+ text: segment.text,
8977
+ attributes: Object.keys(nextAttributes).length === 0 ? void 0 : freeze(nextAttributes)
8978
+ });
8979
+ } else {
8980
+ result.push(segment);
8981
+ }
8982
+ offset = end;
8983
+ }
8984
+ return normalizeSegments(result);
8985
+ }
8986
+ function formatReverseOperations(segments, index, length, patch) {
8987
+ const split = splitSegmentsAt(
8988
+ splitSegmentsAt(segments, index),
8989
+ index + length
8990
+ );
8991
+ const result = [];
8992
+ let offset = 0;
8993
+ for (const segment of split) {
8994
+ const end = offset + segment.text.length;
8995
+ if (offset >= index && end <= index + length) {
8996
+ const attributes = {};
8997
+ for (const key of Object.keys(patch)) {
8998
+ attributes[key] = _nullishCoalesce(_optionalChain([segment, 'access', _217 => _217.attributes, 'optionalAccess', _218 => _218[key]]), () => ( null));
8999
+ }
9000
+ result.push({
9001
+ type: "format",
9002
+ index: offset,
9003
+ length: segment.text.length,
9004
+ attributes
9005
+ });
9006
+ }
9007
+ offset = end;
9008
+ }
9009
+ return result;
9010
+ }
9011
+ function mapIndexThroughOperation(index, op) {
9012
+ if (op.type === "insert") {
9013
+ return op.index <= index ? index + op.text.length : index;
9014
+ } else if (op.type === "delete") {
9015
+ if (op.index >= index) {
9016
+ return index;
9017
+ }
9018
+ return Math.max(op.index, index - op.length);
9019
+ } else {
9020
+ return index;
9021
+ }
9022
+ }
9023
+ function mapTextIndexThroughOperations(index, ops) {
9024
+ let mapped = index;
9025
+ for (const op of ops) {
9026
+ mapped = mapIndexThroughOperation(mapped, op);
9027
+ }
9028
+ return mapped;
9029
+ }
9030
+ function inverseMapIndexThroughOperation(index, op) {
9031
+ if (op.type === "insert") {
9032
+ if (index <= op.index) {
9033
+ return index;
9034
+ }
9035
+ return Math.max(op.index, index - op.text.length);
9036
+ } else if (op.type === "delete") {
9037
+ return op.index <= index ? index + op.length : index;
9038
+ } else {
9039
+ return index;
9040
+ }
9041
+ }
9042
+ function inverseMapTextIndexThroughOperations(index, ops) {
9043
+ let mapped = index;
9044
+ for (let i = ops.length - 1; i >= 0; i--) {
9045
+ mapped = inverseMapIndexThroughOperation(mapped, ops[i]);
9046
+ }
9047
+ return mapped;
9048
+ }
9049
+ function oppositeOrder(order) {
9050
+ return order === "before" ? "after" : "before";
9051
+ }
9052
+ function mapIndexOverDelete(index, deleteIndex, deleteLength) {
9053
+ if (deleteIndex >= index) {
9054
+ return index;
9055
+ }
9056
+ return Math.max(deleteIndex, index - deleteLength);
9057
+ }
9058
+ function transformInsert(op, over, order) {
9059
+ if (over.type === "insert") {
9060
+ const shifts = over.index < op.index || over.index === op.index && order === "after";
9061
+ return [shifts ? { ...op, index: op.index + over.text.length } : { ...op }];
9062
+ } else if (over.type === "delete") {
9063
+ return [
9064
+ { ...op, index: mapIndexOverDelete(op.index, over.index, over.length) }
9065
+ ];
9066
+ } else {
9067
+ return [{ ...op }];
9068
+ }
9069
+ }
9070
+ function transformDelete(op, over) {
9071
+ const start = op.index;
9072
+ const end = op.index + op.length;
9073
+ if (over.type === "insert") {
9074
+ const at = over.index;
9075
+ const len = over.text.length;
9076
+ if (at <= start) {
9077
+ return [{ ...op, index: start + len }];
9078
+ }
9079
+ if (at >= end) {
9080
+ return [{ ...op }];
9081
+ }
9082
+ return [
9083
+ { type: "delete", index: start, length: at - start },
9084
+ { type: "delete", index: start + len, length: end - at }
9085
+ ];
9086
+ } else if (over.type === "delete") {
9087
+ const newStart = mapIndexOverDelete(start, over.index, over.length);
9088
+ const newEnd = mapIndexOverDelete(end, over.index, over.length);
9089
+ return newEnd - newStart > 0 ? [{ type: "delete", index: newStart, length: newEnd - newStart }] : [];
9090
+ } else {
9091
+ return [{ ...op }];
9092
+ }
9093
+ }
9094
+ function transformFormat(op, over, order) {
9095
+ const start = op.index;
9096
+ const end = op.index + op.length;
9097
+ if (over.type === "insert") {
9098
+ const at = over.index;
9099
+ const len = over.text.length;
9100
+ if (at <= start) {
9101
+ return [{ ...op, index: start + len }];
9102
+ }
9103
+ if (at >= end) {
9104
+ return [{ ...op }];
9105
+ }
9106
+ return [
9107
+ {
9108
+ type: "format",
9109
+ index: start,
9110
+ length: at - start,
9111
+ attributes: op.attributes
9112
+ },
9113
+ {
9114
+ type: "format",
9115
+ index: at + len,
9116
+ length: end - at,
9117
+ attributes: op.attributes
9118
+ }
9119
+ ];
9120
+ } else if (over.type === "delete") {
9121
+ const newStart = mapIndexOverDelete(start, over.index, over.length);
9122
+ const newEnd = mapIndexOverDelete(end, over.index, over.length);
9123
+ return newEnd - newStart > 0 ? [
9124
+ {
9125
+ type: "format",
9126
+ index: newStart,
9127
+ length: newEnd - newStart,
9128
+ attributes: op.attributes
9129
+ }
9130
+ ] : [];
9131
+ } else {
9132
+ if (order === "after") {
9133
+ return [{ ...op }];
9134
+ }
9135
+ const overlapStart = Math.max(start, over.index);
9136
+ const overlapEnd = Math.min(end, over.index + over.length);
9137
+ if (overlapStart >= overlapEnd) {
9138
+ return [{ ...op }];
9139
+ }
9140
+ const hasConflict = Object.keys(op.attributes).some(
9141
+ (key) => key in over.attributes
9142
+ );
9143
+ if (!hasConflict) {
9144
+ return [{ ...op }];
9145
+ }
9146
+ const reduced = {};
9147
+ for (const [key, value] of Object.entries(op.attributes)) {
9148
+ if (!(key in over.attributes)) {
9149
+ reduced[key] = value;
9150
+ }
9151
+ }
9152
+ const pieces = [];
9153
+ if (start < overlapStart) {
9154
+ pieces.push({
9155
+ type: "format",
9156
+ index: start,
9157
+ length: overlapStart - start,
9158
+ attributes: op.attributes
9159
+ });
9160
+ }
9161
+ if (Object.keys(reduced).length > 0) {
9162
+ pieces.push({
9163
+ type: "format",
9164
+ index: overlapStart,
9165
+ length: overlapEnd - overlapStart,
9166
+ attributes: reduced
9167
+ });
9168
+ }
9169
+ if (overlapEnd < end) {
9170
+ pieces.push({
9171
+ type: "format",
9172
+ index: overlapEnd,
9173
+ length: end - overlapEnd,
9174
+ attributes: op.attributes
9175
+ });
9176
+ }
9177
+ return pieces;
9178
+ }
9179
+ }
9180
+ function transformSingle(op, over, order) {
9181
+ switch (op.type) {
9182
+ case "insert":
9183
+ return transformInsert(op, over, order);
9184
+ case "delete":
9185
+ return transformDelete(op, over);
9186
+ case "format":
9187
+ return transformFormat(op, over, order);
9188
+ }
9189
+ }
9190
+ function transformTextOperationsX(a, b, order) {
9191
+ if (a.length === 0 || b.length === 0) {
9192
+ return [[...a], [...b]];
9193
+ }
9194
+ if (a.length === 1 && b.length === 1) {
9195
+ return [
9196
+ transformSingle(a[0], b[0], order),
9197
+ transformSingle(b[0], a[0], oppositeOrder(order))
9198
+ ];
9199
+ }
9200
+ if (a.length > 1) {
9201
+ const [headA1, b1] = transformTextOperationsX([a[0]], b, order);
9202
+ const [restA1, b2] = transformTextOperationsX(a.slice(1), b1, order);
9203
+ return [[...headA1, ...restA1], b2];
9204
+ }
9205
+ const [a1, headB1] = transformTextOperationsX(a, [b[0]], order);
9206
+ const [a2, restB1] = transformTextOperationsX(a1, b.slice(1), order);
9207
+ return [a2, [...headB1, ...restB1]];
9208
+ }
9209
+ function transformTextOperations(ops, over, order) {
9210
+ return transformTextOperationsX(ops, over, order)[0];
9211
+ }
9212
+ function textOperationsEqual(a, b) {
9213
+ return a === b || stableStringify(a) === stableStringify(b);
9214
+ }
9215
+ function applyTextOperationsToSegments(segments, ops) {
9216
+ let next = [...segments];
9217
+ for (const op of ops) {
9218
+ if (op.type === "insert") {
9219
+ const index = Math.max(0, Math.min(op.index, textLength(next)));
9220
+ next = applyInsert(next, index, op.text, op.attributes);
9221
+ } else if (op.type === "delete") {
9222
+ const index = Math.max(0, Math.min(op.index, textLength(next)));
9223
+ const clipped = clipRange(index, op.length, textLength(next));
9224
+ next = applyDelete(next, clipped.index, clipped.length).segments;
9225
+ } else {
9226
+ const index = Math.max(0, Math.min(op.index, textLength(next)));
9227
+ const clipped = clipRange(index, op.length, textLength(next));
9228
+ next = applyFormat(next, clipped.index, clipped.length, op.attributes);
9229
+ }
9230
+ }
9231
+ return next;
9232
+ }
9233
+ function applyLiveTextOperations(data, ops) {
9234
+ return segmentsToData(
9235
+ applyTextOperationsToSegments(dataToSegments(data), ops)
9236
+ );
9237
+ }
9238
+ function invertTextOperations(segments, ops) {
9239
+ let shadow = [...segments];
9240
+ const reverse = [];
9241
+ for (const op of ops) {
9242
+ if (op.type === "insert") {
9243
+ shadow = applyInsert(shadow, op.index, op.text, op.attributes);
9244
+ reverse.unshift({
9245
+ type: "delete",
9246
+ index: op.index,
9247
+ length: op.text.length
9248
+ });
9249
+ } else if (op.type === "delete") {
9250
+ const deletedSegments = extractDeletedSegments(
9251
+ shadow,
9252
+ op.index,
9253
+ op.length
9254
+ );
9255
+ shadow = applyDelete(shadow, op.index, op.length).segments;
9256
+ const inserts = [];
9257
+ let insertIndex = op.index;
9258
+ for (const segment of deletedSegments) {
9259
+ inserts.push({
9260
+ type: "insert",
9261
+ index: insertIndex,
9262
+ text: segment.text,
9263
+ attributes: segment.attributes
9264
+ });
9265
+ insertIndex += segment.text.length;
9266
+ }
9267
+ for (let index = inserts.length - 1; index >= 0; index--) {
9268
+ reverse.unshift(inserts[index]);
9269
+ }
9270
+ } else {
9271
+ const inverse = formatReverseOperations(
9272
+ shadow,
9273
+ op.index,
9274
+ op.length,
9275
+ op.attributes
9276
+ );
9277
+ shadow = applyFormat(shadow, op.index, op.length, op.attributes);
9278
+ reverse.unshift(...inverse.reverse());
9279
+ }
9280
+ }
9281
+ return reverse;
9282
+ }
9283
+
9284
+ // src/crdts/LiveText.ts
9285
+ var ACCEPTED_OPS_HISTORY_LIMIT = 1e3;
9286
+ var LiveText = class _LiveText extends AbstractCrdt {
9287
+ /** The local document: #confirmed ⊕ #inFlightOps ⊕ #queuedOps. */
9288
+ #segments;
9289
+ /** The server-confirmed document (only authoritative ops applied). */
9290
+ #confirmed;
9291
+ #version;
9292
+ /** The op currently awaiting server acknowledgement (at most one). */
9293
+ #inFlightOpId;
9294
+ /** Its ops, continuously re-expressed against current server state. */
9295
+ #inFlightOps = [];
9296
+ /** Local edits made while an op is in flight; sent after the ack. */
9297
+ #queuedOps = [];
9298
+ #acceptedOps = [];
9299
+ /**
9300
+ * Creates a new LiveText document.
9301
+ *
9302
+ * @param textOrData Initial plain text, or an array of `[text]` /
9303
+ * `[text, attributes]` segments. Defaults to an empty document.
9304
+ *
9305
+ * @example
9306
+ * new LiveText();
9307
+ * new LiveText("Hello world");
9308
+ * new LiveText([["Hello ", { bold: true }], ["world"]]);
9309
+ */
9310
+ constructor(textOrData = "", version = 0) {
9311
+ super();
9312
+ this.#segments = typeof textOrData === "string" ? textOrData.length === 0 ? [] : [{ text: textOrData }] : dataToSegments(textOrData);
9313
+ this.#confirmed = [...this.#segments];
9314
+ this.#version = version;
9315
+ Object.assign(this[kInternal], {
9316
+ encodeIndex: (localIndex) => this.#encodeIndex(localIndex),
9317
+ decodeIndex: (index, fromVersion) => this.#decodeIndex(index, fromVersion)
9318
+ });
9319
+ }
9320
+ get version() {
9321
+ return this.#version;
9322
+ }
9323
+ get length() {
9324
+ return textLength(this.#segments);
9325
+ }
9326
+ /** @internal */
9327
+ static _deserialize([id, item], _parentToChildren, pool) {
9328
+ const text = new _LiveText(item.data, item.version);
9329
+ text._attach(id, pool);
9330
+ return text;
9331
+ }
9332
+ /** @internal */
9333
+ _toOps(parentId, parentKey) {
9334
+ if (this._id === void 0) {
9335
+ throw new Error("Cannot serialize LiveText if it is not attached");
9336
+ }
9337
+ return [
9338
+ {
9339
+ type: OpCode.CREATE_TEXT,
9340
+ id: this._id,
9341
+ parentId,
9342
+ parentKey,
9343
+ data: this.toJSON(),
9344
+ version: this.#version
9345
+ }
9346
+ ];
9347
+ }
9348
+ /** @internal */
9349
+ _serialize() {
9350
+ if (this.parent.type !== "HasParent") {
9351
+ throw new Error("Cannot serialize LiveText if parent is missing");
9352
+ }
9353
+ return {
9354
+ type: CrdtType.TEXT,
9355
+ parentId: nn(this.parent.node._id, "Parent node expected to have ID"),
9356
+ parentKey: this.parent.key,
9357
+ data: this.toJSON(),
9358
+ version: this.#version
9359
+ };
9360
+ }
9361
+ /** @internal */
9362
+ _attachChild(_op) {
9363
+ throw new Error("LiveText cannot contain child nodes");
9364
+ }
9365
+ /** @internal */
9366
+ _detachChild(_crdt) {
9367
+ throw new Error("LiveText cannot contain child nodes");
9368
+ }
9369
+ /** @internal */
9370
+ _apply(op, isLocal) {
9371
+ if (op.type !== OpCode.UPDATE_TEXT) {
9372
+ return super._apply(op, isLocal);
9373
+ }
9374
+ if (isLocal) {
9375
+ return this.#applyLocal(op);
9376
+ }
9377
+ if (op.opId !== void 0 && op.opId === this.#inFlightOpId) {
9378
+ return this.#applyAck(op);
9379
+ }
9380
+ if (op.opId !== void 0 && this.#acceptedOps.some((entry) => entry.opId === op.opId)) {
9381
+ this.#version = Math.max(this.#version, _nullishCoalesce(op.version, () => ( op.baseVersion + 1)));
9382
+ return { modified: false };
9383
+ }
9384
+ return this.#applyRemote(op);
9385
+ }
9386
+ /**
9387
+ * Inserts text at the given index.
9388
+ *
9389
+ * @param index Character index at which to insert. Values outside the
9390
+ * document range are clipped.
9391
+ * @param text Text to insert.
9392
+ * @param attributes Optional inline attributes for the inserted text.
9393
+ *
9394
+ * @example
9395
+ * const text = new LiveText("Hello");
9396
+ * text.insert(5, " world");
9397
+ * text.insert(0, "Say: ", { italic: true });
9398
+ */
9399
+ insert(index, text, attributes) {
9400
+ const clippedIndex = Math.max(0, Math.min(index, this.length));
9401
+ this.#dispatch([{ type: "insert", index: clippedIndex, text, attributes }]);
9402
+ }
9403
+ /**
9404
+ * Deletes `length` characters starting at `index`.
9405
+ *
9406
+ * @example
9407
+ * const text = new LiveText("Hello world");
9408
+ * text.delete(5, 6); // "Hello"
9409
+ */
9410
+ delete(index, length) {
9411
+ const clipped = clipRange(index, length, this.length);
9412
+ if (clipped.length === 0) {
9413
+ return;
9414
+ }
9415
+ this.#dispatch([
9416
+ { type: "delete", index: clipped.index, length: clipped.length }
9417
+ ]);
9418
+ }
9419
+ /**
9420
+ * Replaces a range of text with new text.
9421
+ *
9422
+ * @example
9423
+ * const text = new LiveText("Hello world");
9424
+ * text.replace(0, 5, "Hi"); // "Hi world"
9425
+ */
9426
+ replace(index, length, text, attributes) {
9427
+ const clipped = clipRange(index, length, this.length);
9428
+ const ops = [];
9429
+ if (clipped.length > 0) {
9430
+ ops.push({
9431
+ type: "delete",
9432
+ index: clipped.index,
9433
+ length: clipped.length
9434
+ });
9435
+ }
9436
+ if (text.length > 0) {
9437
+ ops.push({ type: "insert", index: clipped.index, text, attributes });
9438
+ }
9439
+ this.#dispatch(ops);
9440
+ }
9441
+ /**
9442
+ * Encode a local-document index (an offset into this LiveText's current
9443
+ * #segments, which CodeMirror or any consumer mirrors as its document)
9444
+ * into server-confirmed coordinates suitable for broadcasting to peers via
9445
+ * presence or any other side channel.
9446
+ *
9447
+ * The returned index is in this LiveText's current #confirmed coordinates
9448
+ * — that is, with this client's local pending ops inverse-mapped out.
9449
+ * Pair it with the current {@link LiveText.version} when sending so the
9450
+ * receiver can call {@link PrivateLiveTextApi.decodeIndex} to land the
9451
+ * position in their own local document coordinates regardless of their
9452
+ * private pending ops.
9453
+ *
9454
+ * Index ambiguity at boundaries is resolved by an inverse-of-forward
9455
+ * convention: a position at or before a local insertion is reported as
9456
+ * the position right before the insertion in #confirmed; a position past
9457
+ * the insertion shifts left by the insertion's length. Positions inside
9458
+ * an own-pending insertion collapse to the insertion point.
9459
+ */
9460
+ #encodeIndex(localIndex) {
9461
+ let mapped = Math.max(0, Math.min(localIndex, this.length));
9462
+ mapped = inverseMapTextIndexThroughOperations(mapped, this.#queuedOps);
9463
+ mapped = inverseMapTextIndexThroughOperations(mapped, this.#inFlightOps);
9464
+ return mapped;
9465
+ }
9466
+ /**
9467
+ * Decode an `(index, fromVersion)` pair produced by
9468
+ * {@link PrivateLiveTextApi.encodeIndex} — typically on a peer — into an
9469
+ * offset in this LiveText's current local document (an index suitable for
9470
+ * placing a CodeMirror marker, an annotation anchor, or anything else that
9471
+ * lives over #segments).
9472
+ *
9473
+ * Composes the accepted ops applied since `fromVersion` (drawn from
9474
+ * #acceptedOps in locally-applied form) with this client's own local
9475
+ * pending ops, in that order. The result is in current #segments
9476
+ * coordinates.
9477
+ *
9478
+ * Returns `null` when the position cannot be decoded against the current
9479
+ * state:
9480
+ * - `fromVersion` is greater than this LiveText's current version: the
9481
+ * peer is ahead of us. The caller should park the message and retry
9482
+ * after more accepted ops arrive.
9483
+ * - `fromVersion` falls outside the retained accepted-ops history. This
9484
+ * only happens after very long-lived disconnections; the caller can
9485
+ * fall back to using the raw index and letting subsequent local
9486
+ * transactions map it (with bounded drift).
9487
+ */
9488
+ #decodeIndex(index, fromVersion) {
9489
+ if (fromVersion > this.#version) {
9490
+ return null;
9491
+ }
9492
+ if (fromVersion < this.#version) {
9493
+ const oldest = _optionalChain([this, 'access', _219 => _219.#acceptedOps, 'access', _220 => _220[0], 'optionalAccess', _221 => _221.version]);
9494
+ if (oldest === void 0 || oldest > fromVersion + 1) {
9495
+ return null;
9496
+ }
9497
+ }
9498
+ let mapped = index;
9499
+ for (const entry of this.#acceptedOps) {
9500
+ if (entry.version <= fromVersion) continue;
9501
+ if (entry.version > this.#version) break;
9502
+ if (entry.ops.length === 0) continue;
9503
+ mapped = mapTextIndexThroughOperations(mapped, entry.ops);
9504
+ }
9505
+ mapped = mapTextIndexThroughOperations(mapped, this.#inFlightOps);
9506
+ mapped = mapTextIndexThroughOperations(mapped, this.#queuedOps);
9507
+ return Math.max(0, Math.min(mapped, this.length));
9508
+ }
9509
+ /**
9510
+ * Applies or removes inline attributes on a range of text.
9511
+ *
9512
+ * Set an attribute to `null` to remove it from the range.
9513
+ *
9514
+ * @example
9515
+ * const text = new LiveText("Hello world");
9516
+ * text.format(0, 5, { bold: true });
9517
+ * text.format(0, 5, { bold: null });
9518
+ */
9519
+ format(index, length, attributes) {
9520
+ const clipped = clipRange(index, length, this.length);
9521
+ if (clipped.length === 0) {
9522
+ return;
9523
+ }
9524
+ this.#dispatch([
9525
+ {
9526
+ type: "format",
9527
+ index: clipped.index,
9528
+ length: clipped.length,
9529
+ attributes
9530
+ }
9531
+ ]);
9532
+ }
9533
+ /** Local edits made through the public API. */
9534
+ #dispatch(ops) {
9535
+ if (ops.length === 0) {
9536
+ return;
9537
+ }
9538
+ _optionalChain([this, 'access', _222 => _222._pool, 'optionalAccess', _223 => _223.assertStorageIsWritable, 'call', _224 => _224()]);
9539
+ const attached = this._pool !== void 0 && this._id !== void 0;
9540
+ const reverse = attached ? this.#invertOperations(ops) : [];
9541
+ const changes = this.#applyOperationsLocally(ops);
9542
+ if (!attached) {
9543
+ return;
9544
+ }
9545
+ const pool = nn(this._pool);
9546
+ const id = nn(this._id);
9547
+ const updates = /* @__PURE__ */ new Map([
9548
+ [
9549
+ id,
9550
+ {
9551
+ type: "LiveText",
9552
+ node: this,
9553
+ version: this.#version,
9554
+ updates: changes
9555
+ }
9556
+ ]
9557
+ ]);
9558
+ if (this.#inFlightOpId === void 0) {
9559
+ const opId = pool.generateOpId();
9560
+ this.#inFlightOpId = opId;
9561
+ this.#inFlightOps = [...ops];
9562
+ pool.dispatch(
9563
+ [
9564
+ {
9565
+ type: OpCode.UPDATE_TEXT,
9566
+ id,
9567
+ opId,
9568
+ baseVersion: this.#version,
9569
+ ops: [...ops]
9570
+ }
9571
+ ],
9572
+ reverse,
9573
+ updates
9574
+ );
9575
+ } else {
9576
+ this.#queuedOps.push(...ops);
9577
+ pool.dispatch([], reverse, updates, { clearRedoStack: true });
9578
+ }
9579
+ }
9580
+ /**
9581
+ * A local replay of an existing wire op: an undo/redo frame, or an
9582
+ * unacknowledged op re-sent after a reconnect.
9583
+ */
9584
+ #applyLocal(op) {
9585
+ const mutableOp = op;
9586
+ if (op.opId !== void 0 && op.opId === this.#inFlightOpId) {
9587
+ this.#inFlightOps = [...this.#inFlightOps, ...this.#queuedOps];
9588
+ this.#queuedOps = [];
9589
+ mutableOp.baseVersion = this.#version;
9590
+ mutableOp.ops = [...this.#inFlightOps];
9591
+ return { modified: false };
9592
+ }
9593
+ let ops = op.ops;
9594
+ for (const entry of this.#acceptedOps) {
9595
+ if (entry.version > op.baseVersion && entry.ops.length > 0) {
9596
+ ops = transformTextOperations(ops, entry.ops, "after");
9597
+ }
9598
+ }
9599
+ const reverse = this.#invertOperations(ops);
9600
+ const changes = this.#applyOperationsLocally(ops);
9601
+ if (this.#inFlightOpId === void 0 && ops.length > 0) {
9602
+ this.#inFlightOpId = nn(op.opId, "Local ops must have an opId");
9603
+ this.#inFlightOps = [...ops];
9604
+ mutableOp.baseVersion = this.#version;
9605
+ mutableOp.ops = [...ops];
9606
+ } else {
9607
+ this.#queuedOps.push(...ops);
9608
+ mutableOp.baseVersion = this.#version;
9609
+ mutableOp.ops = [];
9610
+ }
9611
+ if (changes.length === 0) {
9612
+ return { modified: false };
9613
+ }
9614
+ return {
9615
+ reverse,
9616
+ modified: {
9617
+ type: "LiveText",
9618
+ node: this,
9619
+ version: this.#version,
9620
+ updates: changes
9621
+ }
9622
+ };
9623
+ }
9624
+ /** Server acknowledgement of our in-flight op. */
9625
+ #applyAck(op) {
9626
+ const ackedVersion = _nullishCoalesce(op.version, () => ( Math.max(this.#version, op.baseVersion + 1)));
9627
+ const predicted = this.#inFlightOps;
9628
+ const opId = this.#inFlightOpId;
9629
+ this.#confirmed = applyTextOperationsToSegments(this.#confirmed, op.ops);
9630
+ this.#inFlightOpId = void 0;
9631
+ this.#inFlightOps = [];
9632
+ let appliedOps = [];
9633
+ let result = { modified: false };
9634
+ if (!textOperationsEqual(op.ops, predicted)) {
9635
+ error2(
9636
+ "LiveText: acknowledgement did not match the local prediction; resynchronizing"
9637
+ );
9638
+ const rebuilt = this.#rebuildLocalFromConfirmed();
9639
+ appliedOps = rebuilt.appliedOps;
9640
+ if (rebuilt.changes.length > 0) {
9641
+ result = {
9642
+ reverse: [],
9643
+ modified: {
9644
+ type: "LiveText",
9645
+ node: this,
9646
+ version: ackedVersion,
9647
+ updates: rebuilt.changes
9648
+ }
9649
+ };
9650
+ }
9651
+ }
9652
+ this.#version = Math.max(this.#version, ackedVersion);
9653
+ this.#recordAccepted(ackedVersion, appliedOps, opId);
9654
+ this.#flushQueued();
9655
+ return result;
9656
+ }
9657
+ /** An accepted op from another client (or a server-fabricated fix op). */
9658
+ #applyRemote(op) {
9659
+ const version = _nullishCoalesce(op.version, () => ( this.#version + 1));
9660
+ this.#confirmed = applyTextOperationsToSegments(this.#confirmed, op.ops);
9661
+ const [overInFlight, inFlight] = transformTextOperationsX(
9662
+ op.ops,
9663
+ this.#inFlightOps,
9664
+ "before"
9665
+ );
9666
+ const [applied, queued] = transformTextOperationsX(
9667
+ overInFlight,
9668
+ this.#queuedOps,
9669
+ "before"
9670
+ );
9671
+ this.#inFlightOps = inFlight;
9672
+ this.#queuedOps = queued;
9673
+ this.#recordAccepted(version, applied, op.opId);
9674
+ if (applied.length === 0) {
9675
+ this.#version = Math.max(this.#version, version);
9676
+ return { modified: false };
9677
+ }
9678
+ const reverse = this.#invertOperations(applied);
9679
+ const changes = this.#applyOperationsLocally(applied);
9680
+ this.#version = Math.max(this.#version, version);
9681
+ return {
9682
+ reverse,
9683
+ modified: {
9684
+ type: "LiveText",
9685
+ node: this,
9686
+ version: this.#version,
9687
+ updates: changes
9688
+ }
9689
+ };
9690
+ }
9691
+ /** Send the queued ops as the next in-flight op (after an ack). */
9692
+ #flushQueued() {
9693
+ if (this.#queuedOps.length === 0 || this._pool === void 0 || this._id === void 0) {
9694
+ return;
9695
+ }
9696
+ const opId = this._pool.generateOpId();
9697
+ this.#inFlightOpId = opId;
9698
+ this.#inFlightOps = this.#queuedOps;
9699
+ this.#queuedOps = [];
9700
+ this._pool.dispatch(
9701
+ [
9702
+ {
9703
+ type: OpCode.UPDATE_TEXT,
9704
+ id: this._id,
9705
+ opId,
9706
+ baseVersion: this.#version,
9707
+ ops: [...this.#inFlightOps]
9708
+ }
9709
+ ],
9710
+ [],
9711
+ /* @__PURE__ */ new Map(),
9712
+ // The local content was already applied (and made undoable) when the
9713
+ // edits happened; this is purely an outbound flush.
9714
+ { clearRedoStack: false }
9715
+ );
9716
+ }
9717
+ /**
9718
+ * Rebuild the local document as confirmed ⊕ queued ops, returning the
9719
+ * coarse delta that was applied. Only used by defensive recovery paths.
9720
+ */
9721
+ #rebuildLocalFromConfirmed() {
9722
+ const before2 = this.#segments;
9723
+ const after2 = applyTextOperationsToSegments(this.#confirmed, [
9724
+ ...this.#inFlightOps,
9725
+ ...this.#queuedOps
9726
+ ]);
9727
+ if (stableStringify(segmentsToData(before2)) === stableStringify(segmentsToData(after2))) {
9728
+ this.#segments = after2;
9729
+ return { appliedOps: [], changes: [] };
9730
+ }
9731
+ const beforeText = before2.map((segment) => segment.text).join("");
9732
+ this.#segments = after2;
9733
+ this.invalidate();
9734
+ const appliedOps = [];
9735
+ const changes = [];
9736
+ if (beforeText.length > 0) {
9737
+ appliedOps.push({ type: "delete", index: 0, length: beforeText.length });
9738
+ changes.push({
9739
+ type: "delete",
9740
+ index: 0,
9741
+ length: beforeText.length,
9742
+ deletedText: beforeText
9743
+ });
9744
+ }
9745
+ let index = 0;
9746
+ for (const segment of after2) {
9747
+ appliedOps.push({
9748
+ type: "insert",
9749
+ index,
9750
+ text: segment.text,
9751
+ attributes: segment.attributes
9752
+ });
9753
+ changes.push({
9754
+ type: "insert",
9755
+ index,
9756
+ text: segment.text,
9757
+ attributes: segment.attributes
9758
+ });
9759
+ index += segment.text.length;
9760
+ }
9761
+ return { appliedOps, changes };
9762
+ }
9763
+ /**
9764
+ * Reconcile this node against an authoritative storage snapshot (e.g.
9765
+ * after a reconnect). The confirmed state and version are replaced by the
9766
+ * snapshot's; pending (in-flight + queued) ops are preserved on top and
9767
+ * will be re-sent by the offline-ops replay.
9768
+ *
9769
+ * @internal
9770
+ */
9771
+ _resyncText(data, version) {
9772
+ this.#confirmed = dataToSegments(data);
9773
+ this.#version = version;
9774
+ this.#acceptedOps = [];
9775
+ const rebuilt = this.#rebuildLocalFromConfirmed();
9776
+ if (rebuilt.changes.length === 0) {
9777
+ return void 0;
9778
+ }
9779
+ return {
9780
+ type: "LiveText",
9781
+ node: this,
9782
+ version: this.#version,
9783
+ updates: rebuilt.changes
9784
+ };
9785
+ }
9786
+ /**
9787
+ * Called when the server rejected one of our ops. Drops all pending state
9788
+ * for this node (edits queued behind a rejected op cannot be trusted
9789
+ * either); the room follows up with a storage resync.
9790
+ *
9791
+ * @internal
9792
+ */
9793
+ _rejectPendingOp(opId) {
9794
+ if (opId !== this.#inFlightOpId) {
9795
+ return;
9796
+ }
9797
+ this.#inFlightOpId = void 0;
9798
+ this.#inFlightOps = [];
9799
+ this.#queuedOps = [];
9800
+ }
9801
+ #recordAccepted(version, ops, opId) {
9802
+ if (this.#acceptedOps.some((entry) => entry.version === version)) {
9803
+ return;
9804
+ }
9805
+ this.#acceptedOps.push({ version, opId, ops: [...ops] });
9806
+ this.#acceptedOps.sort((left, right) => left.version - right.version);
9807
+ if (this.#acceptedOps.length > ACCEPTED_OPS_HISTORY_LIMIT) {
9808
+ this.#acceptedOps.splice(
9809
+ 0,
9810
+ this.#acceptedOps.length - ACCEPTED_OPS_HISTORY_LIMIT
9811
+ );
9812
+ }
9813
+ }
9814
+ #applyOperationsLocally(ops) {
9815
+ const changes = [];
9816
+ for (const op of ops) {
9817
+ if (op.type === "insert") {
9818
+ this.#segments = applyInsert(
9819
+ this.#segments,
9820
+ op.index,
9821
+ op.text,
9822
+ op.attributes
9823
+ );
9824
+ changes.push({
9825
+ type: "insert",
9826
+ index: op.index,
9827
+ text: op.text,
9828
+ attributes: op.attributes
9829
+ });
9830
+ } else if (op.type === "delete") {
9831
+ const result = applyDelete(this.#segments, op.index, op.length);
9832
+ this.#segments = result.segments;
9833
+ changes.push({
9834
+ type: "delete",
9835
+ index: op.index,
9836
+ length: op.length,
9837
+ deletedText: result.deletedText
9838
+ });
9839
+ } else {
9840
+ this.#segments = applyFormat(
9841
+ this.#segments,
9842
+ op.index,
9843
+ op.length,
9844
+ op.attributes
9845
+ );
9846
+ changes.push({
9847
+ type: "format",
9848
+ index: op.index,
9849
+ length: op.length,
9850
+ attributes: op.attributes
9851
+ });
9852
+ }
9853
+ }
9854
+ this.invalidate();
9855
+ return changes;
9856
+ }
9857
+ #invertOperations(ops) {
9858
+ return [
9859
+ {
9860
+ type: OpCode.UPDATE_TEXT,
9861
+ id: nn(this._id),
9862
+ baseVersion: this.#version,
9863
+ ops: invertTextOperations(this.#segments, ops)
9864
+ }
9865
+ ];
9866
+ }
9867
+ /** Returns the plain text content without attributes. Equivalent to joining the text from each segment in {@link LiveText.toJSON}. */
9868
+ toString() {
9869
+ return this.#segments.map((segment) => segment.text).join("");
9870
+ }
9871
+ /**
9872
+ * Returns a JSON-compatible snapshot of the document as a {@link LiveTextData}
9873
+ * array.
9874
+ *
9875
+ * @example
9876
+ * new LiveText([["Hello ", { bold: true }], ["world"]]).toJSON();
9877
+ * // [["Hello ", { bold: true }], ["world"]]
9878
+ */
9879
+ toJSON() {
9880
+ return super.toJSON();
9881
+ }
9882
+ /** @internal */
9883
+ _toJSON() {
9884
+ return segmentsToData(this.#segments);
9885
+ }
9886
+ /** @internal */
9887
+ toTreeNode(key) {
9888
+ return super.toTreeNode(key);
9889
+ }
9890
+ /** @internal */
9891
+ _toTreeNode(key) {
9892
+ const nodeId = _nullishCoalesce(this._id, () => ( nanoid()));
9893
+ const payload = this.toJSON().map(
9894
+ (segment, index) => ({
9895
+ type: "Json",
9896
+ id: `${nodeId}:${index}`,
9897
+ key: String(index),
9898
+ payload: segment
9899
+ })
9900
+ );
9901
+ payload.push({
9902
+ type: "Json",
9903
+ id: `${nodeId}:version`,
9904
+ key: "version",
9905
+ payload: this.version
9906
+ });
9907
+ return {
9908
+ type: "LiveText",
9909
+ id: nodeId,
9910
+ key,
9911
+ payload
9912
+ };
9913
+ }
9914
+ clone() {
9915
+ return new _LiveText(this.toJSON(), this.#version);
9916
+ }
9917
+ };
9918
+
8779
9919
  // src/crdts/liveblocks-helpers.ts
8780
9920
  function creationOpToLiveNode(op) {
8781
9921
  return lsonToLiveNode(creationOpToLson(op));
@@ -8790,6 +9930,8 @@ function creationOpToLson(op) {
8790
9930
  return new LiveMap();
8791
9931
  case OpCode.CREATE_LIST:
8792
9932
  return new LiveList([]);
9933
+ case OpCode.CREATE_TEXT:
9934
+ return new LiveText(op.data, op.version);
8793
9935
  default:
8794
9936
  return assertNever(op, "Unknown creation Op");
8795
9937
  }
@@ -8822,6 +9964,8 @@ function deserialize(node, parentToChildren, pool) {
8822
9964
  return LiveMap._deserialize(node, parentToChildren, pool);
8823
9965
  } else if (isRegisterStorageNode(node)) {
8824
9966
  return LiveRegister._deserialize(node, parentToChildren, pool);
9967
+ } else if (isTextStorageNode(node)) {
9968
+ return LiveText._deserialize(node, parentToChildren, pool);
8825
9969
  } else {
8826
9970
  throw new Error("Unexpected CRDT type");
8827
9971
  }
@@ -8835,12 +9979,14 @@ function deserializeToLson(node, parentToChildren, pool) {
8835
9979
  return LiveMap._deserialize(node, parentToChildren, pool);
8836
9980
  } else if (isRegisterStorageNode(node)) {
8837
9981
  return node[1].data;
9982
+ } else if (isTextStorageNode(node)) {
9983
+ return LiveText._deserialize(node, parentToChildren, pool);
8838
9984
  } else {
8839
9985
  throw new Error("Unexpected CRDT type");
8840
9986
  }
8841
9987
  }
8842
9988
  function isLiveStructure(value) {
8843
- return isLiveList(value) || isLiveMap(value) || isLiveObject(value);
9989
+ return isLiveList(value) || isLiveMap(value) || isLiveObject(value) || isLiveText(value);
8844
9990
  }
8845
9991
  function isLiveNode(value) {
8846
9992
  return isLiveStructure(value) || isLiveRegister(value);
@@ -8854,6 +10000,9 @@ function isLiveMap(value) {
8854
10000
  function isLiveObject(value) {
8855
10001
  return value instanceof LiveObject;
8856
10002
  }
10003
+ function isLiveText(value) {
10004
+ return value instanceof LiveText;
10005
+ }
8857
10006
  function isLiveRegister(value) {
8858
10007
  return value instanceof LiveRegister;
8859
10008
  }
@@ -8863,14 +10012,14 @@ function cloneLson(value) {
8863
10012
  function liveNodeToLson(obj) {
8864
10013
  if (obj instanceof LiveRegister) {
8865
10014
  return obj.data;
8866
- } else if (obj instanceof LiveList || obj instanceof LiveMap || obj instanceof LiveObject) {
10015
+ } else if (obj instanceof LiveList || obj instanceof LiveMap || obj instanceof LiveObject || obj instanceof LiveText) {
8867
10016
  return obj;
8868
10017
  } else {
8869
10018
  return assertNever(obj, "Unknown AbstractCrdt");
8870
10019
  }
8871
10020
  }
8872
10021
  function lsonToLiveNode(value) {
8873
- if (value instanceof LiveObject || value instanceof LiveMap || value instanceof LiveList) {
10022
+ if (value instanceof LiveObject || value instanceof LiveMap || value instanceof LiveList || value instanceof LiveText) {
8874
10023
  return value;
8875
10024
  } else {
8876
10025
  return new LiveRegister(value);
@@ -8990,6 +10139,16 @@ function diffNodeMap(prev, next) {
8990
10139
  parentKey: crdt.parentKey
8991
10140
  });
8992
10141
  break;
10142
+ case CrdtType.TEXT:
10143
+ ops.push({
10144
+ type: OpCode.CREATE_TEXT,
10145
+ id,
10146
+ parentId: crdt.parentId,
10147
+ parentKey: crdt.parentKey,
10148
+ data: crdt.data,
10149
+ version: crdt.version
10150
+ });
10151
+ break;
8993
10152
  }
8994
10153
  }
8995
10154
  next.forEach((crdt, id) => {
@@ -9060,19 +10219,43 @@ function mergeListStorageUpdates(first, second) {
9060
10219
  updates: updates.concat(second.updates)
9061
10220
  };
9062
10221
  }
10222
+ function mergeTextStorageUpdates(first, second) {
10223
+ return {
10224
+ ...second,
10225
+ updates: first.updates.concat(second.updates)
10226
+ };
10227
+ }
9063
10228
  function mergeStorageUpdates(first, second) {
9064
10229
  if (first === void 0) {
9065
10230
  return second;
9066
10231
  }
10232
+ let merged;
9067
10233
  if (first.type === "LiveObject" && second.type === "LiveObject") {
9068
- return mergeObjectStorageUpdates(first, second);
10234
+ merged = mergeObjectStorageUpdates(first, second);
9069
10235
  } else if (first.type === "LiveMap" && second.type === "LiveMap") {
9070
- return mergeMapStorageUpdates(first, second);
10236
+ merged = mergeMapStorageUpdates(first, second);
9071
10237
  } else if (first.type === "LiveList" && second.type === "LiveList") {
9072
- return mergeListStorageUpdates(first, second);
10238
+ merged = mergeListStorageUpdates(first, second);
10239
+ } else if (first.type === "LiveText" && second.type === "LiveText") {
10240
+ merged = mergeTextStorageUpdates(first, second);
9073
10241
  } else {
10242
+ merged = second;
10243
+ }
10244
+ const sa = first[kStorageUpdateSource];
10245
+ const sb = second[kStorageUpdateSource];
10246
+ if (sa !== void 0 || sb !== void 0) {
10247
+ if (_optionalChain([sa, 'optionalAccess', _225 => _225.origin]) === "remote" || _optionalChain([sb, 'optionalAccess', _226 => _226.origin]) === "remote") {
10248
+ merged[kStorageUpdateSource] = { origin: "remote" };
10249
+ } else if (_optionalChain([sa, 'optionalAccess', _227 => _227.via]) === "history" || _optionalChain([sb, 'optionalAccess', _228 => _228.via]) === "history") {
10250
+ const historySource = _optionalChain([sb, 'optionalAccess', _229 => _229.via]) === "history" ? sb : _optionalChain([sa, 'optionalAccess', _230 => _230.via]) === "history" ? sa : void 0;
10251
+ if (_optionalChain([historySource, 'optionalAccess', _231 => _231.via]) === "history") {
10252
+ merged[kStorageUpdateSource] = historySource;
10253
+ }
10254
+ } else {
10255
+ merged[kStorageUpdateSource] = { origin: "local", via: "mutation" };
10256
+ }
9074
10257
  }
9075
- return second;
10258
+ return merged;
9076
10259
  }
9077
10260
 
9078
10261
  // src/devtools/bridge.ts
@@ -9088,7 +10271,7 @@ function sendToPanel(message, options) {
9088
10271
  ...message,
9089
10272
  source: "liveblocks-devtools-client"
9090
10273
  };
9091
- if (!(_optionalChain([options, 'optionalAccess', _217 => _217.force]) || _bridgeActive)) {
10274
+ if (!(_optionalChain([options, 'optionalAccess', _232 => _232.force]) || _bridgeActive)) {
9092
10275
  return;
9093
10276
  }
9094
10277
  window.postMessage(fullMsg, "*");
@@ -9096,7 +10279,7 @@ function sendToPanel(message, options) {
9096
10279
  var eventSource = makeEventSource();
9097
10280
  if (process.env.NODE_ENV !== "production" && typeof window !== "undefined") {
9098
10281
  window.addEventListener("message", (event) => {
9099
- if (event.source === window && _optionalChain([event, 'access', _218 => _218.data, 'optionalAccess', _219 => _219.source]) === "liveblocks-devtools-panel") {
10282
+ if (event.source === window && _optionalChain([event, 'access', _233 => _233.data, 'optionalAccess', _234 => _234.source]) === "liveblocks-devtools-panel") {
9100
10283
  eventSource.notify(event.data);
9101
10284
  } else {
9102
10285
  }
@@ -9238,7 +10421,7 @@ function fullSync(room) {
9238
10421
  msg: "room::sync::full",
9239
10422
  roomId: room.id,
9240
10423
  status: room.getStatus(),
9241
- storage: _nullishCoalesce(_optionalChain([root, 'optionalAccess', _220 => _220.toTreeNode, 'call', _221 => _221("root"), 'access', _222 => _222.payload]), () => ( null)),
10424
+ storage: _nullishCoalesce(_optionalChain([root, 'optionalAccess', _235 => _235.toTreeNode, 'call', _236 => _236("root"), 'access', _237 => _237.payload]), () => ( null)),
9242
10425
  me,
9243
10426
  others
9244
10427
  });
@@ -9925,15 +11108,15 @@ function installBackgroundTabSpy() {
9925
11108
  const doc = typeof document !== "undefined" ? document : void 0;
9926
11109
  const inBackgroundSince = { current: null };
9927
11110
  function onVisibilityChange() {
9928
- if (_optionalChain([doc, 'optionalAccess', _223 => _223.visibilityState]) === "hidden") {
11111
+ if (_optionalChain([doc, 'optionalAccess', _238 => _238.visibilityState]) === "hidden") {
9929
11112
  inBackgroundSince.current = _nullishCoalesce(inBackgroundSince.current, () => ( Date.now()));
9930
11113
  } else {
9931
11114
  inBackgroundSince.current = null;
9932
11115
  }
9933
11116
  }
9934
- _optionalChain([doc, 'optionalAccess', _224 => _224.addEventListener, 'call', _225 => _225("visibilitychange", onVisibilityChange)]);
11117
+ _optionalChain([doc, 'optionalAccess', _239 => _239.addEventListener, 'call', _240 => _240("visibilitychange", onVisibilityChange)]);
9935
11118
  const unsub = () => {
9936
- _optionalChain([doc, 'optionalAccess', _226 => _226.removeEventListener, 'call', _227 => _227("visibilitychange", onVisibilityChange)]);
11119
+ _optionalChain([doc, 'optionalAccess', _241 => _241.removeEventListener, 'call', _242 => _242("visibilitychange", onVisibilityChange)]);
9937
11120
  };
9938
11121
  return [inBackgroundSince, unsub];
9939
11122
  }
@@ -9957,7 +11140,7 @@ function makeNodeMapBuffer() {
9957
11140
  function topLevelKeysOf(nodes) {
9958
11141
  const keys2 = /* @__PURE__ */ new Set();
9959
11142
  const root = nodes.get("root");
9960
- for (const key in _optionalChain([root, 'optionalAccess', _228 => _228.data])) {
11143
+ for (const key in _optionalChain([root, 'optionalAccess', _243 => _243.data])) {
9961
11144
  keys2.add(key);
9962
11145
  }
9963
11146
  for (const node of nodes.values()) {
@@ -10029,6 +11212,8 @@ function createRoom(options, config) {
10029
11212
  activeBatch: null,
10030
11213
  unacknowledgedOps
10031
11214
  };
11215
+ let nextHistoryItemId = 0;
11216
+ let historyDisabled = 0;
10032
11217
  const nodeMapBuffer = makeNodeMapBuffer();
10033
11218
  const stopwatch = config.enableDebugLogging ? makeStopWatch() : void 0;
10034
11219
  let lastTokenKey;
@@ -10113,7 +11298,10 @@ function createRoom(options, config) {
10113
11298
  }
10114
11299
  }
10115
11300
  });
10116
- function onDispatch(ops, reverse, storageUpdates) {
11301
+ function onDispatch(ops, reverse, storageUpdates, options2) {
11302
+ for (const value of storageUpdates.values()) {
11303
+ value[kStorageUpdateSource] = { origin: "local", via: "mutation" };
11304
+ }
10117
11305
  if (context.activeBatch) {
10118
11306
  for (const op of ops) {
10119
11307
  context.activeBatch.ops.push(op);
@@ -10132,15 +11320,17 @@ function createRoom(options, config) {
10132
11320
  if (reverse.length > 0) {
10133
11321
  addToUndoStack(reverse);
10134
11322
  }
11323
+ if (_nullishCoalesce(_optionalChain([options2, 'optionalAccess', _244 => _244.clearRedoStack]), () => ( ops.length > 0))) {
11324
+ clearRedoStack();
11325
+ }
10135
11326
  if (ops.length > 0) {
10136
- context.redoStack.length = 0;
10137
11327
  dispatchOps(ops);
10138
11328
  }
10139
11329
  notify({ storageUpdates });
10140
11330
  }
10141
11331
  }
10142
11332
  function isStorageWritable() {
10143
- const permissionMatrix = _optionalChain([context, 'access', _229 => _229.dynamicSessionInfoSig, 'access', _230 => _230.get, 'call', _231 => _231(), 'optionalAccess', _232 => _232.permissionMatrix]);
11333
+ const permissionMatrix = _optionalChain([context, 'access', _245 => _245.dynamicSessionInfoSig, 'access', _246 => _246.get, 'call', _247 => _247(), 'optionalAccess', _248 => _248.permissionMatrix]);
10144
11334
  return permissionMatrix !== void 0 ? hasPermissionAccess(permissionMatrix, "storage", "write") : true;
10145
11335
  }
10146
11336
  const eventHub = {
@@ -10153,6 +11343,7 @@ function createRoom(options, config) {
10153
11343
  others: makeEventSource(),
10154
11344
  storageBatch: makeEventSource(),
10155
11345
  history: makeEventSource(),
11346
+ privateHistory: makeEventSource(),
10156
11347
  storageDidLoad: makeEventSource(),
10157
11348
  storageStatus: makeEventSource(),
10158
11349
  ydoc: makeEventSource(),
@@ -10253,6 +11444,23 @@ function createRoom(options, config) {
10253
11444
  }
10254
11445
  if (context.root !== void 0) {
10255
11446
  const result = applyRemoteOps(diffCurrentStorageAgainst(nodes));
11447
+ for (const [id, crdt] of nodes) {
11448
+ if (crdt.type === CrdtType.TEXT) {
11449
+ const node = context.pool.nodes.get(id);
11450
+ if (node !== void 0 && isLiveText(node)) {
11451
+ const update = node._resyncText(crdt.data, crdt.version);
11452
+ if (update !== void 0) {
11453
+ result.updates.storageUpdates.set(
11454
+ id,
11455
+ mergeStorageUpdates(
11456
+ result.updates.storageUpdates.get(id),
11457
+ update
11458
+ )
11459
+ );
11460
+ }
11461
+ }
11462
+ }
11463
+ }
10256
11464
  notify(result.updates);
10257
11465
  } else {
10258
11466
  context.root = LiveObject._fromItems(
@@ -10260,7 +11468,7 @@ function createRoom(options, config) {
10260
11468
  context.pool
10261
11469
  );
10262
11470
  }
10263
- const canWrite = _nullishCoalesce(_optionalChain([self, 'access', _233 => _233.get, 'call', _234 => _234(), 'optionalAccess', _235 => _235.canWrite]), () => ( true));
11471
+ const canWrite = _nullishCoalesce(_optionalChain([self, 'access', _249 => _249.get, 'call', _250 => _250(), 'optionalAccess', _251 => _251.canWrite]), () => ( true));
10264
11472
  const serverTopLevelKeys = topLevelKeysOf(nodes);
10265
11473
  const root = context.root;
10266
11474
  disableHistory(() => {
@@ -10277,6 +11485,16 @@ function createRoom(options, config) {
10277
11485
  }
10278
11486
  });
10279
11487
  }
11488
+ function notifyPrivateHistory(event) {
11489
+ if (historyDisabled > 0) return;
11490
+ eventHub.privateHistory.notify(event);
11491
+ }
11492
+ function clearRedoStack() {
11493
+ if (context.redoStack.length === 0) return;
11494
+ const ids = context.redoStack.map((item) => item.id);
11495
+ context.redoStack.length = 0;
11496
+ notifyPrivateHistory({ action: "discard", ids });
11497
+ }
10280
11498
  function reconcileStorageWithNodes(nodes) {
10281
11499
  if (context.root === void 0) {
10282
11500
  throw new Error("Cannot reconcile storage before it is loaded");
@@ -10301,9 +11519,14 @@ function createRoom(options, config) {
10301
11519
  }
10302
11520
  function _addToRealUndoStack(frames) {
10303
11521
  if (context.undoStack.length >= 50) {
10304
- context.undoStack.shift();
11522
+ const evicted = context.undoStack.shift();
11523
+ if (evicted !== void 0) {
11524
+ notifyPrivateHistory({ action: "discard", ids: [evicted.id] });
11525
+ }
10305
11526
  }
10306
- context.undoStack.push(frames);
11527
+ const id = nextHistoryItemId++;
11528
+ context.undoStack.push({ id, frames });
11529
+ notifyPrivateHistory({ action: "push", id });
10307
11530
  onHistoryChange();
10308
11531
  }
10309
11532
  function addToUndoStack(frames) {
@@ -10341,7 +11564,7 @@ function createRoom(options, config) {
10341
11564
  "Internal. Tried to get connection id but connection was never open"
10342
11565
  );
10343
11566
  }
10344
- function applyLocalOps(frames) {
11567
+ function applyLocalOps(frames, localStorageUpdateSource = { origin: "local", via: "mutation" }) {
10345
11568
  const [pframes, ops] = partition(
10346
11569
  frames,
10347
11570
  (f) => f.type === "presence"
@@ -10353,7 +11576,8 @@ function createRoom(options, config) {
10353
11576
  pframes,
10354
11577
  opsWithOpIds,
10355
11578
  /* isLocal */
10356
- true
11579
+ true,
11580
+ localStorageUpdateSource
10357
11581
  );
10358
11582
  return { opsToEmit: opsWithOpIds, reverse, updates };
10359
11583
  }
@@ -10365,7 +11589,7 @@ function createRoom(options, config) {
10365
11589
  false
10366
11590
  );
10367
11591
  }
10368
- function applyOps(pframes, ops, isLocal) {
11592
+ function applyOps(pframes, ops, isLocal, localStorageUpdateSource = { origin: "local", via: "mutation" }) {
10369
11593
  const output = {
10370
11594
  reverse: new Deque(),
10371
11595
  storageUpdates: /* @__PURE__ */ new Map(),
@@ -10403,6 +11627,7 @@ function createRoom(options, config) {
10403
11627
  }
10404
11628
  const applyOpResult = applyOp(op, source);
10405
11629
  if (applyOpResult.modified) {
11630
+ applyOpResult.modified[kStorageUpdateSource] = source === 1 /* THEIRS */ ? { origin: "remote" } : localStorageUpdateSource;
10406
11631
  const nodeId = applyOpResult.modified.node._id;
10407
11632
  if (!(nodeId && createdNodeIds.has(nodeId))) {
10408
11633
  output.storageUpdates.set(
@@ -10414,7 +11639,7 @@ function createRoom(options, config) {
10414
11639
  );
10415
11640
  output.reverse.pushLeft(applyOpResult.reverse);
10416
11641
  }
10417
- if (op.type === OpCode.CREATE_LIST || op.type === OpCode.CREATE_MAP || op.type === OpCode.CREATE_OBJECT) {
11642
+ if (op.type === OpCode.CREATE_LIST || op.type === OpCode.CREATE_MAP || op.type === OpCode.CREATE_OBJECT || op.type === OpCode.CREATE_TEXT) {
10418
11643
  createdNodeIds.add(op.id);
10419
11644
  }
10420
11645
  }
@@ -10434,6 +11659,7 @@ function createRoom(options, config) {
10434
11659
  switch (op.type) {
10435
11660
  case OpCode.DELETE_OBJECT_KEY:
10436
11661
  case OpCode.UPDATE_OBJECT:
11662
+ case OpCode.UPDATE_TEXT:
10437
11663
  case OpCode.DELETE_CRDT: {
10438
11664
  const node = context.pool.nodes.get(op.id);
10439
11665
  if (node === void 0) {
@@ -10458,6 +11684,7 @@ function createRoom(options, config) {
10458
11684
  case OpCode.CREATE_OBJECT:
10459
11685
  case OpCode.CREATE_LIST:
10460
11686
  case OpCode.CREATE_MAP:
11687
+ case OpCode.CREATE_TEXT:
10461
11688
  case OpCode.CREATE_REGISTER: {
10462
11689
  if (op.parentId === void 0) {
10463
11690
  return { modified: false };
@@ -10492,7 +11719,7 @@ function createRoom(options, config) {
10492
11719
  }
10493
11720
  context.myPresence.patch(patch);
10494
11721
  if (context.activeBatch) {
10495
- if (_optionalChain([options2, 'optionalAccess', _236 => _236.addToHistory])) {
11722
+ if (_optionalChain([options2, 'optionalAccess', _252 => _252.addToHistory])) {
10496
11723
  context.activeBatch.reverseOps.pushLeft({
10497
11724
  type: "presence",
10498
11725
  data: oldValues
@@ -10501,7 +11728,7 @@ function createRoom(options, config) {
10501
11728
  context.activeBatch.updates.presence = true;
10502
11729
  } else {
10503
11730
  flushNowOrSoon();
10504
- if (_optionalChain([options2, 'optionalAccess', _237 => _237.addToHistory])) {
11731
+ if (_optionalChain([options2, 'optionalAccess', _253 => _253.addToHistory])) {
10505
11732
  addToUndoStack([{ type: "presence", data: oldValues }]);
10506
11733
  }
10507
11734
  notify({ presence: true });
@@ -10680,11 +11907,11 @@ function createRoom(options, config) {
10680
11907
  break;
10681
11908
  }
10682
11909
  case ServerMsgCode.STORAGE_CHUNK:
10683
- _optionalChain([stopwatch, 'optionalAccess', _238 => _238.lap, 'call', _239 => _239()]);
11910
+ _optionalChain([stopwatch, 'optionalAccess', _254 => _254.lap, 'call', _255 => _255()]);
10684
11911
  nodeMapBuffer.append(compactNodesToNodeStream(message.nodes));
10685
11912
  break;
10686
11913
  case ServerMsgCode.STORAGE_STREAM_END: {
10687
- const timing = _optionalChain([stopwatch, 'optionalAccess', _240 => _240.stop, 'call', _241 => _241()]);
11914
+ const timing = _optionalChain([stopwatch, 'optionalAccess', _256 => _256.stop, 'call', _257 => _257()]);
10688
11915
  if (timing) {
10689
11916
  const ms = (v) => `${v.toFixed(1)}ms`;
10690
11917
  const rest = timing.laps.slice(1);
@@ -10711,16 +11938,37 @@ function createRoom(options, config) {
10711
11938
  }
10712
11939
  break;
10713
11940
  }
10714
- // Receiving a RejectedOps message in the client means that the server is no
10715
- // longer in sync with the client. Trying to synchronize the client again by
10716
- // rolling back particular Ops may be hard/impossible. It's fine to not try and
10717
- // accept the out-of-sync reality and throw an error.
11941
+ // Receiving a RejectedOps message means the server refused some of
11942
+ // our ops, so our optimistic local state is out of sync with the
11943
+ // server. For LiveText ops this is a normal (if rare) situation
11944
+ // e.g. a client that was offline long enough to fall outside the
11945
+ // server's retained history window — and we can recover: drop the
11946
+ // rejected pending state and re-fetch the authoritative storage
11947
+ // snapshot. For other ops (e.g. permission rejections), rolling back
11948
+ // particular Ops is hard/impossible, so we keep the old behavior of
11949
+ // accepting the out-of-sync reality and surfacing an error.
10718
11950
  case ServerMsgCode.REJECT_STORAGE_OP: {
10719
11951
  errorWithTitle(
10720
11952
  "Storage mutation rejection error",
10721
11953
  message.reason
10722
11954
  );
10723
- if (process.env.NODE_ENV !== "production") {
11955
+ let needsStorageResync = false;
11956
+ for (const opId of message.opIds) {
11957
+ const rejectedOp = context.unacknowledgedOps.get(opId);
11958
+ context.unacknowledgedOps.delete(opId);
11959
+ context.buffer.storageOperations = context.buffer.storageOperations.filter((op) => op.opId !== opId);
11960
+ if (rejectedOp !== void 0 && rejectedOp.type === OpCode.UPDATE_TEXT) {
11961
+ const node = context.pool.nodes.get(rejectedOp.id);
11962
+ if (node !== void 0 && isLiveText(node)) {
11963
+ node._rejectPendingOp(opId);
11964
+ needsStorageResync = true;
11965
+ }
11966
+ }
11967
+ }
11968
+ if (needsStorageResync) {
11969
+ refreshStorage();
11970
+ flushNowOrSoon();
11971
+ } else if (process.env.NODE_ENV !== "production") {
10724
11972
  throw new Error(
10725
11973
  `Storage mutations rejected by server: ${message.reason}`
10726
11974
  );
@@ -10819,11 +12067,11 @@ function createRoom(options, config) {
10819
12067
  } else if (pendingFeedsRequests.has(requestId)) {
10820
12068
  const pending = pendingFeedsRequests.get(requestId);
10821
12069
  pendingFeedsRequests.delete(requestId);
10822
- _optionalChain([pending, 'optionalAccess', _242 => _242.reject, 'call', _243 => _243(err)]);
12070
+ _optionalChain([pending, 'optionalAccess', _258 => _258.reject, 'call', _259 => _259(err)]);
10823
12071
  } else if (pendingFeedMessagesRequests.has(requestId)) {
10824
12072
  const pending = pendingFeedMessagesRequests.get(requestId);
10825
12073
  pendingFeedMessagesRequests.delete(requestId);
10826
- _optionalChain([pending, 'optionalAccess', _244 => _244.reject, 'call', _245 => _245(err)]);
12074
+ _optionalChain([pending, 'optionalAccess', _260 => _260.reject, 'call', _261 => _261(err)]);
10827
12075
  }
10828
12076
  eventHub.feeds.notify(message);
10829
12077
  break;
@@ -10977,10 +12225,10 @@ function createRoom(options, config) {
10977
12225
  timeoutId,
10978
12226
  kind,
10979
12227
  feedId,
10980
- messageId: _optionalChain([options2, 'optionalAccess', _246 => _246.messageId]),
10981
- expectedClientMessageId: _optionalChain([options2, 'optionalAccess', _247 => _247.expectedClientMessageId])
12228
+ messageId: _optionalChain([options2, 'optionalAccess', _262 => _262.messageId]),
12229
+ expectedClientMessageId: _optionalChain([options2, 'optionalAccess', _263 => _263.expectedClientMessageId])
10982
12230
  });
10983
- if (kind === "add-message" && _optionalChain([options2, 'optionalAccess', _248 => _248.expectedClientMessageId]) === void 0) {
12231
+ if (kind === "add-message" && _optionalChain([options2, 'optionalAccess', _264 => _264.expectedClientMessageId]) === void 0) {
10984
12232
  const q = _nullishCoalesce(pendingAddMessageFifoByFeed.get(feedId), () => ( []));
10985
12233
  q.push(requestId);
10986
12234
  pendingAddMessageFifoByFeed.set(feedId, q);
@@ -11031,10 +12279,10 @@ function createRoom(options, config) {
11031
12279
  }
11032
12280
  if (!matched) {
11033
12281
  const q = pendingAddMessageFifoByFeed.get(message.feedId);
11034
- const headId = _optionalChain([q, 'optionalAccess', _249 => _249[0]]);
12282
+ const headId = _optionalChain([q, 'optionalAccess', _265 => _265[0]]);
11035
12283
  if (headId !== void 0) {
11036
12284
  const pending = pendingFeedMutations.get(headId);
11037
- if (_optionalChain([pending, 'optionalAccess', _250 => _250.kind]) === "add-message" && pending.expectedClientMessageId === void 0) {
12285
+ if (_optionalChain([pending, 'optionalAccess', _266 => _266.kind]) === "add-message" && pending.expectedClientMessageId === void 0) {
11038
12286
  settleFeedMutation(headId, "ok");
11039
12287
  }
11040
12288
  }
@@ -11070,7 +12318,7 @@ function createRoom(options, config) {
11070
12318
  const unacknowledgedOps2 = [...context.unacknowledgedOps.values()];
11071
12319
  createOrUpdateRootFromMessage(nodes);
11072
12320
  applyAndSendOfflineOps(unacknowledgedOps2);
11073
- _optionalChain([_resolveStoragePromise, 'optionalCall', _251 => _251()]);
12321
+ _optionalChain([_resolveStoragePromise, 'optionalCall', _267 => _267()]);
11074
12322
  notifyStorageStatus();
11075
12323
  eventHub.storageDidLoad.notify();
11076
12324
  }
@@ -11079,7 +12327,7 @@ function createRoom(options, config) {
11079
12327
  if (!messages.some((msg) => msg.type === ClientMsgCode.FETCH_STORAGE)) {
11080
12328
  messages.push({ type: ClientMsgCode.FETCH_STORAGE });
11081
12329
  nodeMapBuffer.take();
11082
- _optionalChain([stopwatch, 'optionalAccess', _252 => _252.start, 'call', _253 => _253()]);
12330
+ _optionalChain([stopwatch, 'optionalAccess', _268 => _268.start, 'call', _269 => _269()]);
11083
12331
  }
11084
12332
  }
11085
12333
  function startLoadingStorage() {
@@ -11133,10 +12381,10 @@ function createRoom(options, config) {
11133
12381
  const message = {
11134
12382
  type: ClientMsgCode.FETCH_FEEDS,
11135
12383
  requestId,
11136
- cursor: _optionalChain([options2, 'optionalAccess', _254 => _254.cursor]),
11137
- since: _optionalChain([options2, 'optionalAccess', _255 => _255.since]),
11138
- limit: _optionalChain([options2, 'optionalAccess', _256 => _256.limit]),
11139
- metadata: _optionalChain([options2, 'optionalAccess', _257 => _257.metadata])
12384
+ cursor: _optionalChain([options2, 'optionalAccess', _270 => _270.cursor]),
12385
+ since: _optionalChain([options2, 'optionalAccess', _271 => _271.since]),
12386
+ limit: _optionalChain([options2, 'optionalAccess', _272 => _272.limit]),
12387
+ metadata: _optionalChain([options2, 'optionalAccess', _273 => _273.metadata])
11140
12388
  };
11141
12389
  context.buffer.messages.push(message);
11142
12390
  flushNowOrSoon();
@@ -11156,9 +12404,9 @@ function createRoom(options, config) {
11156
12404
  type: ClientMsgCode.FETCH_FEED_MESSAGES,
11157
12405
  requestId,
11158
12406
  feedId,
11159
- cursor: _optionalChain([options2, 'optionalAccess', _258 => _258.cursor]),
11160
- since: _optionalChain([options2, 'optionalAccess', _259 => _259.since]),
11161
- limit: _optionalChain([options2, 'optionalAccess', _260 => _260.limit])
12407
+ cursor: _optionalChain([options2, 'optionalAccess', _274 => _274.cursor]),
12408
+ since: _optionalChain([options2, 'optionalAccess', _275 => _275.since]),
12409
+ limit: _optionalChain([options2, 'optionalAccess', _276 => _276.limit])
11162
12410
  };
11163
12411
  context.buffer.messages.push(message);
11164
12412
  flushNowOrSoon();
@@ -11177,8 +12425,8 @@ function createRoom(options, config) {
11177
12425
  type: ClientMsgCode.ADD_FEED,
11178
12426
  requestId,
11179
12427
  feedId,
11180
- metadata: _optionalChain([options2, 'optionalAccess', _261 => _261.metadata]),
11181
- createdAt: _optionalChain([options2, 'optionalAccess', _262 => _262.createdAt])
12428
+ metadata: _optionalChain([options2, 'optionalAccess', _277 => _277.metadata]),
12429
+ createdAt: _optionalChain([options2, 'optionalAccess', _278 => _278.createdAt])
11182
12430
  };
11183
12431
  context.buffer.messages.push(message);
11184
12432
  flushNowOrSoon();
@@ -11212,15 +12460,15 @@ function createRoom(options, config) {
11212
12460
  function addFeedMessage(feedId, data, options2) {
11213
12461
  const requestId = nanoid();
11214
12462
  const promise = registerFeedMutation(requestId, "add-message", feedId, {
11215
- expectedClientMessageId: _optionalChain([options2, 'optionalAccess', _263 => _263.id])
12463
+ expectedClientMessageId: _optionalChain([options2, 'optionalAccess', _279 => _279.id])
11216
12464
  });
11217
12465
  const message = {
11218
12466
  type: ClientMsgCode.ADD_FEED_MESSAGE,
11219
12467
  requestId,
11220
12468
  feedId,
11221
12469
  data,
11222
- id: _optionalChain([options2, 'optionalAccess', _264 => _264.id]),
11223
- createdAt: _optionalChain([options2, 'optionalAccess', _265 => _265.createdAt])
12470
+ id: _optionalChain([options2, 'optionalAccess', _280 => _280.id]),
12471
+ createdAt: _optionalChain([options2, 'optionalAccess', _281 => _281.createdAt])
11224
12472
  };
11225
12473
  context.buffer.messages.push(message);
11226
12474
  flushNowOrSoon();
@@ -11237,7 +12485,7 @@ function createRoom(options, config) {
11237
12485
  feedId,
11238
12486
  messageId,
11239
12487
  data,
11240
- updatedAt: _optionalChain([options2, 'optionalAccess', _266 => _266.updatedAt])
12488
+ updatedAt: _optionalChain([options2, 'optionalAccess', _282 => _282.updatedAt])
11241
12489
  };
11242
12490
  context.buffer.messages.push(message);
11243
12491
  flushNowOrSoon();
@@ -11262,14 +12510,19 @@ function createRoom(options, config) {
11262
12510
  if (context.activeBatch) {
11263
12511
  throw new Error("undo is not allowed during a batch");
11264
12512
  }
11265
- const frames = context.undoStack.pop();
11266
- if (frames === void 0) {
12513
+ const item = context.undoStack.pop();
12514
+ if (item === void 0) {
11267
12515
  return;
11268
12516
  }
11269
12517
  context.pausedHistory = null;
11270
- const result = applyLocalOps(frames);
12518
+ const result = applyLocalOps(item.frames, {
12519
+ origin: "local",
12520
+ via: "history",
12521
+ action: "undo"
12522
+ });
12523
+ context.redoStack.push({ id: item.id, frames: result.reverse });
12524
+ notifyPrivateHistory({ action: "undo", id: item.id });
11271
12525
  notify(result.updates);
11272
- context.redoStack.push(result.reverse);
11273
12526
  onHistoryChange();
11274
12527
  for (const op of result.opsToEmit) {
11275
12528
  context.buffer.storageOperations.push(op);
@@ -11280,14 +12533,19 @@ function createRoom(options, config) {
11280
12533
  if (context.activeBatch) {
11281
12534
  throw new Error("redo is not allowed during a batch");
11282
12535
  }
11283
- const frames = context.redoStack.pop();
11284
- if (frames === void 0) {
12536
+ const item = context.redoStack.pop();
12537
+ if (item === void 0) {
11285
12538
  return;
11286
12539
  }
11287
12540
  context.pausedHistory = null;
11288
- const result = applyLocalOps(frames);
12541
+ const result = applyLocalOps(item.frames, {
12542
+ origin: "local",
12543
+ via: "history",
12544
+ action: "redo"
12545
+ });
12546
+ context.undoStack.push({ id: item.id, frames: result.reverse });
12547
+ notifyPrivateHistory({ action: "redo", id: item.id });
11289
12548
  notify(result.updates);
11290
- context.undoStack.push(result.reverse);
11291
12549
  onHistoryChange();
11292
12550
  for (const op of result.opsToEmit) {
11293
12551
  context.buffer.storageOperations.push(op);
@@ -11297,6 +12555,8 @@ function createRoom(options, config) {
11297
12555
  function clear() {
11298
12556
  context.undoStack.length = 0;
11299
12557
  context.redoStack.length = 0;
12558
+ notifyPrivateHistory({ action: "clear" });
12559
+ onHistoryChange();
11300
12560
  }
11301
12561
  function batch2(callback) {
11302
12562
  if (context.activeBatch) {
@@ -11325,7 +12585,7 @@ function createRoom(options, config) {
11325
12585
  commitPausedHistoryToUndoStack();
11326
12586
  }
11327
12587
  if (currentBatch.ops.length > 0) {
11328
- context.redoStack.length = 0;
12588
+ clearRedoStack();
11329
12589
  }
11330
12590
  if (currentBatch.ops.length > 0) {
11331
12591
  dispatchOps(currentBatch.ops);
@@ -11354,7 +12614,6 @@ function createRoom(options, config) {
11354
12614
  }
11355
12615
  commitPausedHistoryToUndoStack();
11356
12616
  }
11357
- let historyDisabled = 0;
11358
12617
  function disableHistory(fn) {
11359
12618
  const origUndo = context.undoStack;
11360
12619
  const origRedo = context.redoStack;
@@ -11444,8 +12703,8 @@ function createRoom(options, config) {
11444
12703
  async function getThreads(options2) {
11445
12704
  return httpClient.getThreads({
11446
12705
  roomId,
11447
- query: _optionalChain([options2, 'optionalAccess', _267 => _267.query]),
11448
- cursor: _optionalChain([options2, 'optionalAccess', _268 => _268.cursor])
12706
+ query: _optionalChain([options2, 'optionalAccess', _283 => _283.query]),
12707
+ cursor: _optionalChain([options2, 'optionalAccess', _284 => _284.cursor])
11449
12708
  });
11450
12709
  }
11451
12710
  async function getThread(threadId) {
@@ -11568,7 +12827,7 @@ function createRoom(options, config) {
11568
12827
  function getSubscriptionSettings(options2) {
11569
12828
  return httpClient.getSubscriptionSettings({
11570
12829
  roomId,
11571
- signal: _optionalChain([options2, 'optionalAccess', _269 => _269.signal])
12830
+ signal: _optionalChain([options2, 'optionalAccess', _285 => _285.signal])
11572
12831
  });
11573
12832
  }
11574
12833
  function updateSubscriptionSettings(settings) {
@@ -11590,30 +12849,45 @@ function createRoom(options, config) {
11590
12849
  {
11591
12850
  [kInternal]: {
11592
12851
  get presenceBuffer() {
11593
- return deepClone(_nullishCoalesce(_optionalChain([context, 'access', _270 => _270.buffer, 'access', _271 => _271.presenceUpdates, 'optionalAccess', _272 => _272.data]), () => ( null)));
12852
+ return deepClone(_nullishCoalesce(_optionalChain([context, 'access', _286 => _286.buffer, 'access', _287 => _287.presenceUpdates, 'optionalAccess', _288 => _288.data]), () => ( null)));
11594
12853
  },
11595
12854
  // prettier-ignore
11596
12855
  get undoStack() {
11597
- return deepClone(context.undoStack);
12856
+ return structuredClone(
12857
+ context.undoStack.map((item) => ({
12858
+ id: item.id,
12859
+ frames: item.frames
12860
+ }))
12861
+ );
12862
+ },
12863
+ // prettier-ignore
12864
+ get redoStack() {
12865
+ return structuredClone(
12866
+ context.redoStack.map((item) => ({
12867
+ id: item.id,
12868
+ frames: item.frames
12869
+ }))
12870
+ );
11598
12871
  },
11599
12872
  // prettier-ignore
11600
12873
  get nodeCount() {
11601
12874
  return context.pool.nodes.size;
11602
12875
  },
11603
12876
  // prettier-ignore
12877
+ history: eventHub.privateHistory.observable,
11604
12878
  getYjsProvider() {
11605
12879
  return context.yjsProvider;
11606
12880
  },
11607
12881
  setYjsProvider(newProvider) {
11608
- _optionalChain([context, 'access', _273 => _273.yjsProvider, 'optionalAccess', _274 => _274.off, 'call', _275 => _275("status", yjsStatusDidChange)]);
12882
+ _optionalChain([context, 'access', _289 => _289.yjsProvider, 'optionalAccess', _290 => _290.off, 'call', _291 => _291("status", yjsStatusDidChange)]);
11609
12883
  context.yjsProvider = newProvider;
11610
- _optionalChain([newProvider, 'optionalAccess', _276 => _276.on, 'call', _277 => _277("status", yjsStatusDidChange)]);
12884
+ _optionalChain([newProvider, 'optionalAccess', _292 => _292.on, 'call', _293 => _293("status", yjsStatusDidChange)]);
11611
12885
  context.yjsProviderDidChange.notify();
11612
12886
  },
11613
12887
  yjsProviderDidChange: context.yjsProviderDidChange.observable,
11614
12888
  // send metadata when using a text editor
11615
12889
  reportTextEditor,
11616
- getPermissionMatrix: () => _optionalChain([context, 'access', _278 => _278.dynamicSessionInfoSig, 'access', _279 => _279.get, 'call', _280 => _280(), 'optionalAccess', _281 => _281.permissionMatrix]),
12890
+ getPermissionMatrix: () => _optionalChain([context, 'access', _294 => _294.dynamicSessionInfoSig, 'access', _295 => _295.get, 'call', _296 => _296(), 'optionalAccess', _297 => _297.permissionMatrix]),
11617
12891
  // create a text mention when using a text editor
11618
12892
  createTextMention,
11619
12893
  // delete a text mention when using a text editor
@@ -11675,7 +12949,7 @@ ${dumpPool(
11675
12949
  source.dispose();
11676
12950
  }
11677
12951
  eventHub.roomWillDestroy.notify();
11678
- _optionalChain([context, 'access', _282 => _282.yjsProvider, 'optionalAccess', _283 => _283.off, 'call', _284 => _284("status", yjsStatusDidChange)]);
12952
+ _optionalChain([context, 'access', _298 => _298.yjsProvider, 'optionalAccess', _299 => _299.off, 'call', _300 => _300("status", yjsStatusDidChange)]);
11679
12953
  syncSourceForStorage.destroy();
11680
12954
  syncSourceForYjs.destroy();
11681
12955
  uninstallBgTabSpy();
@@ -11837,7 +13111,7 @@ function makeClassicSubscribeFn(roomId, events, errorEvents) {
11837
13111
  }
11838
13112
  if (isLiveNode(first)) {
11839
13113
  const node = first;
11840
- if (_optionalChain([options, 'optionalAccess', _285 => _285.isDeep])) {
13114
+ if (_optionalChain([options, 'optionalAccess', _301 => _301.isDeep])) {
11841
13115
  const storageCallback = second;
11842
13116
  return subscribeToLiveStructureDeeply(node, storageCallback);
11843
13117
  } else {
@@ -11927,8 +13201,8 @@ function createClient(options) {
11927
13201
  const authManager = createAuthManager(options, (token) => {
11928
13202
  currentUserId.set(() => token.uid);
11929
13203
  });
11930
- const fetchPolyfill = _optionalChain([clientOptions, 'access', _286 => _286.polyfills, 'optionalAccess', _287 => _287.fetch]) || /* istanbul ignore next */
11931
- _optionalChain([globalThis, 'access', _288 => _288.fetch, 'optionalAccess', _289 => _289.bind, 'call', _290 => _290(globalThis)]);
13204
+ const fetchPolyfill = _optionalChain([clientOptions, 'access', _302 => _302.polyfills, 'optionalAccess', _303 => _303.fetch]) || /* istanbul ignore next */
13205
+ _optionalChain([globalThis, 'access', _304 => _304.fetch, 'optionalAccess', _305 => _305.bind, 'call', _306 => _306(globalThis)]);
11932
13206
  const httpClient = createApiClient({
11933
13207
  baseUrl,
11934
13208
  fetchPolyfill,
@@ -11945,7 +13219,7 @@ function createClient(options) {
11945
13219
  delegates: {
11946
13220
  createSocket: makeCreateSocketDelegateForAi(
11947
13221
  baseUrl,
11948
- _optionalChain([clientOptions, 'access', _291 => _291.polyfills, 'optionalAccess', _292 => _292.WebSocket])
13222
+ _optionalChain([clientOptions, 'access', _307 => _307.polyfills, 'optionalAccess', _308 => _308.WebSocket])
11949
13223
  ),
11950
13224
  authenticate: async () => {
11951
13225
  const resp = await authManager.getAuthValue({
@@ -12016,7 +13290,7 @@ function createClient(options) {
12016
13290
  createSocket: makeCreateSocketDelegateForRoom(
12017
13291
  roomId,
12018
13292
  baseUrl,
12019
- _optionalChain([clientOptions, 'access', _293 => _293.polyfills, 'optionalAccess', _294 => _294.WebSocket])
13293
+ _optionalChain([clientOptions, 'access', _309 => _309.polyfills, 'optionalAccess', _310 => _310.WebSocket])
12020
13294
  ),
12021
13295
  authenticate: makeAuthDelegateForRoom(roomId, authManager)
12022
13296
  })),
@@ -12038,7 +13312,7 @@ function createClient(options) {
12038
13312
  const shouldConnect = _nullishCoalesce(options2.autoConnect, () => ( true));
12039
13313
  if (shouldConnect) {
12040
13314
  if (typeof atob === "undefined") {
12041
- if (_optionalChain([clientOptions, 'access', _295 => _295.polyfills, 'optionalAccess', _296 => _296.atob]) === void 0) {
13315
+ if (_optionalChain([clientOptions, 'access', _311 => _311.polyfills, 'optionalAccess', _312 => _312.atob]) === void 0) {
12042
13316
  throw new Error(
12043
13317
  "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"
12044
13318
  );
@@ -12050,7 +13324,7 @@ function createClient(options) {
12050
13324
  return leaseRoom(newRoomDetails);
12051
13325
  }
12052
13326
  function getRoom(roomId) {
12053
- const room = _optionalChain([roomsById, 'access', _297 => _297.get, 'call', _298 => _298(roomId), 'optionalAccess', _299 => _299.room]);
13327
+ const room = _optionalChain([roomsById, 'access', _313 => _313.get, 'call', _314 => _314(roomId), 'optionalAccess', _315 => _315.room]);
12054
13328
  return room ? room : null;
12055
13329
  }
12056
13330
  function logout() {
@@ -12066,7 +13340,7 @@ function createClient(options) {
12066
13340
  const batchedResolveUsers = new Batch(
12067
13341
  async (batchedUserIds) => {
12068
13342
  const userIds = batchedUserIds.flat();
12069
- const users = await _optionalChain([resolveUsers, 'optionalCall', _300 => _300({ userIds })]);
13343
+ const users = await _optionalChain([resolveUsers, 'optionalCall', _316 => _316({ userIds })]);
12070
13344
  warnOnceIf(
12071
13345
  !resolveUsers,
12072
13346
  "Set the resolveUsers option in createClient to specify user info."
@@ -12083,7 +13357,7 @@ function createClient(options) {
12083
13357
  const batchedResolveRoomsInfo = new Batch(
12084
13358
  async (batchedRoomIds) => {
12085
13359
  const roomIds = batchedRoomIds.flat();
12086
- const roomsInfo = await _optionalChain([resolveRoomsInfo, 'optionalCall', _301 => _301({ roomIds })]);
13360
+ const roomsInfo = await _optionalChain([resolveRoomsInfo, 'optionalCall', _317 => _317({ roomIds })]);
12087
13361
  warnOnceIf(
12088
13362
  !resolveRoomsInfo,
12089
13363
  "Set the resolveRoomsInfo option in createClient to specify room info."
@@ -12100,7 +13374,7 @@ function createClient(options) {
12100
13374
  const batchedResolveGroupsInfo = new Batch(
12101
13375
  async (batchedGroupIds) => {
12102
13376
  const groupIds = batchedGroupIds.flat();
12103
- const groupsInfo = await _optionalChain([resolveGroupsInfo, 'optionalCall', _302 => _302({ groupIds })]);
13377
+ const groupsInfo = await _optionalChain([resolveGroupsInfo, 'optionalCall', _318 => _318({ groupIds })]);
12104
13378
  warnOnceIf(
12105
13379
  !resolveGroupsInfo,
12106
13380
  "Set the resolveGroupsInfo option in createClient to specify group info."
@@ -12159,7 +13433,7 @@ function createClient(options) {
12159
13433
  }
12160
13434
  };
12161
13435
  const win = typeof window !== "undefined" ? window : void 0;
12162
- _optionalChain([win, 'optionalAccess', _303 => _303.addEventListener, 'call', _304 => _304("beforeunload", maybePreventClose)]);
13436
+ _optionalChain([win, 'optionalAccess', _319 => _319.addEventListener, 'call', _320 => _320("beforeunload", maybePreventClose)]);
12163
13437
  }
12164
13438
  async function getNotificationSettings(options2) {
12165
13439
  const plainSettings = await httpClient.getNotificationSettings(options2);
@@ -12287,7 +13561,7 @@ var commentBodyElementsTypes = {
12287
13561
  mention: "inline"
12288
13562
  };
12289
13563
  function traverseCommentBody(body, elementOrVisitor, possiblyVisitor) {
12290
- if (!body || !_optionalChain([body, 'optionalAccess', _305 => _305.content])) {
13564
+ if (!body || !_optionalChain([body, 'optionalAccess', _321 => _321.content])) {
12291
13565
  return;
12292
13566
  }
12293
13567
  const element = typeof elementOrVisitor === "string" ? elementOrVisitor : void 0;
@@ -12297,13 +13571,13 @@ function traverseCommentBody(body, elementOrVisitor, possiblyVisitor) {
12297
13571
  for (const block of body.content) {
12298
13572
  if (type === "all" || type === "block") {
12299
13573
  if (guard(block)) {
12300
- _optionalChain([visitor, 'optionalCall', _306 => _306(block)]);
13574
+ _optionalChain([visitor, 'optionalCall', _322 => _322(block)]);
12301
13575
  }
12302
13576
  }
12303
13577
  if (type === "all" || type === "inline") {
12304
13578
  for (const inline of block.children) {
12305
13579
  if (guard(inline)) {
12306
- _optionalChain([visitor, 'optionalCall', _307 => _307(inline)]);
13580
+ _optionalChain([visitor, 'optionalCall', _323 => _323(inline)]);
12307
13581
  }
12308
13582
  }
12309
13583
  }
@@ -12473,7 +13747,7 @@ var stringifyCommentBodyPlainElements = {
12473
13747
  text: ({ element }) => element.text,
12474
13748
  link: ({ element }) => _nullishCoalesce(element.text, () => ( element.url)),
12475
13749
  mention: ({ element, user, group }) => {
12476
- return `@${_nullishCoalesce(_nullishCoalesce(_optionalChain([user, 'optionalAccess', _308 => _308.name]), () => ( _optionalChain([group, 'optionalAccess', _309 => _309.name]))), () => ( element.id))}`;
13750
+ return `@${_nullishCoalesce(_nullishCoalesce(_optionalChain([user, 'optionalAccess', _324 => _324.name]), () => ( _optionalChain([group, 'optionalAccess', _325 => _325.name]))), () => ( element.id))}`;
12477
13751
  }
12478
13752
  };
12479
13753
  var stringifyCommentBodyHtmlElements = {
@@ -12503,7 +13777,7 @@ var stringifyCommentBodyHtmlElements = {
12503
13777
  return html`<a href="${href}" target="_blank" rel="noopener noreferrer">${element.text ? html`${element.text}` : element.url}</a>`;
12504
13778
  },
12505
13779
  mention: ({ element, user, group }) => {
12506
- return html`<span data-mention>@${_optionalChain([user, 'optionalAccess', _310 => _310.name]) ? html`${_optionalChain([user, 'optionalAccess', _311 => _311.name])}` : _optionalChain([group, 'optionalAccess', _312 => _312.name]) ? html`${_optionalChain([group, 'optionalAccess', _313 => _313.name])}` : element.id}</span>`;
13780
+ return html`<span data-mention>@${_optionalChain([user, 'optionalAccess', _326 => _326.name]) ? html`${_optionalChain([user, 'optionalAccess', _327 => _327.name])}` : _optionalChain([group, 'optionalAccess', _328 => _328.name]) ? html`${_optionalChain([group, 'optionalAccess', _329 => _329.name])}` : element.id}</span>`;
12507
13781
  }
12508
13782
  };
12509
13783
  var stringifyCommentBodyMarkdownElements = {
@@ -12533,20 +13807,20 @@ var stringifyCommentBodyMarkdownElements = {
12533
13807
  return markdown`[${_nullishCoalesce(element.text, () => ( element.url))}](${href})`;
12534
13808
  },
12535
13809
  mention: ({ element, user, group }) => {
12536
- return markdown`@${_nullishCoalesce(_nullishCoalesce(_optionalChain([user, 'optionalAccess', _314 => _314.name]), () => ( _optionalChain([group, 'optionalAccess', _315 => _315.name]))), () => ( element.id))}`;
13810
+ return markdown`@${_nullishCoalesce(_nullishCoalesce(_optionalChain([user, 'optionalAccess', _330 => _330.name]), () => ( _optionalChain([group, 'optionalAccess', _331 => _331.name]))), () => ( element.id))}`;
12537
13811
  }
12538
13812
  };
12539
13813
  async function stringifyCommentBody(body, options) {
12540
- const format = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _316 => _316.format]), () => ( "plain"));
12541
- const separator = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _317 => _317.separator]), () => ( (format === "markdown" ? "\n\n" : "\n")));
13814
+ const format = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _332 => _332.format]), () => ( "plain"));
13815
+ const separator = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _333 => _333.separator]), () => ( (format === "markdown" ? "\n\n" : "\n")));
12542
13816
  const elements = {
12543
13817
  ...format === "html" ? stringifyCommentBodyHtmlElements : format === "markdown" ? stringifyCommentBodyMarkdownElements : stringifyCommentBodyPlainElements,
12544
- ..._optionalChain([options, 'optionalAccess', _318 => _318.elements])
13818
+ ..._optionalChain([options, 'optionalAccess', _334 => _334.elements])
12545
13819
  };
12546
13820
  const { users: resolvedUsers, groups: resolvedGroupsInfo } = await resolveMentionsInCommentBody(
12547
13821
  body,
12548
- _optionalChain([options, 'optionalAccess', _319 => _319.resolveUsers]),
12549
- _optionalChain([options, 'optionalAccess', _320 => _320.resolveGroupsInfo])
13822
+ _optionalChain([options, 'optionalAccess', _335 => _335.resolveUsers]),
13823
+ _optionalChain([options, 'optionalAccess', _336 => _336.resolveGroupsInfo])
12550
13824
  );
12551
13825
  const blocks = body.content.flatMap((block, blockIndex) => {
12552
13826
  switch (block.type) {
@@ -12628,6 +13902,12 @@ function toPlainLson(lson) {
12628
13902
  liveblocksType: "LiveList",
12629
13903
  data: [...lson].map((item) => toPlainLson(item))
12630
13904
  };
13905
+ } else if (lson instanceof LiveText) {
13906
+ return {
13907
+ liveblocksType: "LiveText",
13908
+ data: lson.toJSON(),
13909
+ version: lson.version
13910
+ };
12631
13911
  } else {
12632
13912
  return lson;
12633
13913
  }
@@ -12681,9 +13961,9 @@ function makePoller(callback, intervalMs, options) {
12681
13961
  const startTime = performance.now();
12682
13962
  const doc = typeof document !== "undefined" ? document : void 0;
12683
13963
  const win = typeof window !== "undefined" ? window : void 0;
12684
- const maxStaleTimeMs = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _321 => _321.maxStaleTimeMs]), () => ( Number.POSITIVE_INFINITY));
13964
+ const maxStaleTimeMs = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _337 => _337.maxStaleTimeMs]), () => ( Number.POSITIVE_INFINITY));
12685
13965
  const context = {
12686
- inForeground: _optionalChain([doc, 'optionalAccess', _322 => _322.visibilityState]) !== "hidden",
13966
+ inForeground: _optionalChain([doc, 'optionalAccess', _338 => _338.visibilityState]) !== "hidden",
12687
13967
  lastSuccessfulPollAt: startTime,
12688
13968
  count: 0,
12689
13969
  backoff: 0
@@ -12764,11 +14044,11 @@ function makePoller(callback, intervalMs, options) {
12764
14044
  pollNowIfStale();
12765
14045
  }
12766
14046
  function onVisibilityChange() {
12767
- setInForeground(_optionalChain([doc, 'optionalAccess', _323 => _323.visibilityState]) !== "hidden");
14047
+ setInForeground(_optionalChain([doc, 'optionalAccess', _339 => _339.visibilityState]) !== "hidden");
12768
14048
  }
12769
- _optionalChain([doc, 'optionalAccess', _324 => _324.addEventListener, 'call', _325 => _325("visibilitychange", onVisibilityChange)]);
12770
- _optionalChain([win, 'optionalAccess', _326 => _326.addEventListener, 'call', _327 => _327("online", onVisibilityChange)]);
12771
- _optionalChain([win, 'optionalAccess', _328 => _328.addEventListener, 'call', _329 => _329("focus", pollNowIfStale)]);
14049
+ _optionalChain([doc, 'optionalAccess', _340 => _340.addEventListener, 'call', _341 => _341("visibilitychange", onVisibilityChange)]);
14050
+ _optionalChain([win, 'optionalAccess', _342 => _342.addEventListener, 'call', _343 => _343("online", onVisibilityChange)]);
14051
+ _optionalChain([win, 'optionalAccess', _344 => _344.addEventListener, 'call', _345 => _345("focus", pollNowIfStale)]);
12772
14052
  fsm.start();
12773
14053
  return {
12774
14054
  inc,
@@ -12913,5 +14193,10 @@ detectDupes(PKG_NAME, PKG_VERSION, PKG_FORMAT);
12913
14193
 
12914
14194
 
12915
14195
 
12916
- exports.ClientMsgCode = ClientMsgCode; exports.CrdtType = CrdtType; exports.DefaultMap = DefaultMap; exports.Deque = Deque; exports.DerivedSignal = DerivedSignal; exports.FeedRequestErrorCode = FeedRequestErrorCode; exports.HttpError = HttpError; exports.LiveList = LiveList; exports.LiveMap = LiveMap; exports.LiveObject = LiveObject; exports.LiveblocksError = LiveblocksError; exports.MENTION_CHARACTER = MENTION_CHARACTER; exports.MutableSignal = MutableSignal; exports.OpCode = OpCode; exports.Permission = Permission; exports.Promise_withResolvers = Promise_withResolvers; exports.ServerMsgCode = ServerMsgCode; exports.Signal = Signal; exports.SortedList = SortedList; exports.TextEditorType = TextEditorType; exports.WebsocketCloseCodes = WebsocketCloseCodes; exports.asPos = asPos; exports.assert = assert; exports.assertNever = assertNever; exports.autoRetry = autoRetry; exports.b64decode = b64decode; exports.batch = batch; exports.checkBounds = checkBounds; exports.chunk = chunk; exports.cloneLson = cloneLson; exports.compactNodesToNodeStream = compactNodesToNodeStream; exports.compactObject = compactObject; exports.console = fancy_console_exports; exports.convertToCommentData = convertToCommentData; exports.convertToCommentUserReaction = convertToCommentUserReaction; exports.convertToGroupData = convertToGroupData; exports.convertToInboxNotificationData = convertToInboxNotificationData; exports.convertToSubscriptionData = convertToSubscriptionData; exports.convertToThreadData = convertToThreadData; exports.convertToUserSubscriptionData = convertToUserSubscriptionData; exports.createClient = createClient; exports.createCommentAttachmentId = createCommentAttachmentId; exports.createCommentId = createCommentId; exports.createInboxNotificationId = createInboxNotificationId; exports.createManagedPool = createManagedPool; exports.createNotificationSettings = createNotificationSettings; exports.createThreadId = createThreadId; exports.deepLiveify = deepLiveify; exports.defineAiTool = defineAiTool; exports.deprecate = deprecate; exports.deprecateIf = deprecateIf; exports.detectDupes = detectDupes; exports.entries = entries; exports.errorIf = errorIf; exports.findLastIndex = findLastIndex; exports.freeze = freeze; exports.generateUrl = generateUrl; exports.getMentionsFromCommentBody = getMentionsFromCommentBody; exports.getSubscriptionKey = getSubscriptionKey; exports.hasPermissionAccess = hasPermissionAccess; exports.html = html; exports.htmlSafe = htmlSafe; exports.isCommentBodyLink = isCommentBodyLink; exports.isCommentBodyMention = isCommentBodyMention; exports.isCommentBodyText = isCommentBodyText; exports.isJsonArray = isJsonArray; exports.isJsonObject = isJsonObject; exports.isJsonScalar = isJsonScalar; exports.isListStorageNode = isListStorageNode; exports.isLiveNode = isLiveNode; exports.isMapStorageNode = isMapStorageNode; exports.isNotificationChannelEnabled = isNotificationChannelEnabled; exports.isNumberOperator = isNumberOperator; exports.isObjectStorageNode = isObjectStorageNode; exports.isPlainObject = isPlainObject; exports.isRegisterStorageNode = isRegisterStorageNode; exports.isRootStorageNode = isRootStorageNode; exports.isStartsWithOperator = isStartsWithOperator; exports.isUrl = isUrl; exports.kInternal = kInternal; exports.keys = keys; exports.makeAbortController = makeAbortController; exports.makeEventSource = makeEventSource; exports.makePoller = makePoller; exports.makePosition = makePosition; exports.mapValues = mapValues; exports.memoizeOnSuccess = memoizeOnSuccess; exports.mergeRoomPermissionScopes = mergeRoomPermissionScopes; exports.nanoid = nanoid; exports.nn = nn; exports.nodeStreamToCompactNodes = nodeStreamToCompactNodes; exports.normalizeRoomAccesses = normalizeRoomAccesses; exports.normalizeRoomPermissions = normalizeRoomPermissions; exports.normalizeUpdateRoomAccesses = normalizeUpdateRoomAccesses; exports.objectToQuery = objectToQuery; exports.patchNotificationSettings = patchNotificationSettings; exports.permissionMatrixFromScopes = permissionMatrixFromScopes; exports.raise = raise; exports.resolveMentionsInCommentBody = resolveMentionsInCommentBody; exports.sanitizeUrl = sanitizeUrl; exports.shallow = shallow; exports.shallow2 = shallow2; exports.stableStringify = stableStringify; exports.stringifyCommentBody = stringifyCommentBody; exports.throwUsageError = throwUsageError; exports.toPlainLson = toPlainLson; exports.tryParseJson = tryParseJson; exports.url = url; exports.urljoin = urljoin; exports.validatePermissionsSet = validatePermissionsSet; exports.wait = wait; exports.warnOnce = warnOnce; exports.warnOnceIf = warnOnceIf; exports.withTimeout = withTimeout;
14196
+
14197
+
14198
+
14199
+
14200
+
14201
+ exports.ClientMsgCode = ClientMsgCode; exports.CrdtType = CrdtType; exports.DefaultMap = DefaultMap; exports.Deque = Deque; exports.DerivedSignal = DerivedSignal; exports.FeedRequestErrorCode = FeedRequestErrorCode; exports.HttpError = HttpError; exports.LiveList = LiveList; exports.LiveMap = LiveMap; exports.LiveObject = LiveObject; exports.LiveText = LiveText; exports.LiveblocksError = LiveblocksError; exports.MENTION_CHARACTER = MENTION_CHARACTER; exports.MutableSignal = MutableSignal; exports.OpCode = OpCode; exports.Permission = Permission; exports.Promise_withResolvers = Promise_withResolvers; exports.ServerMsgCode = ServerMsgCode; exports.Signal = Signal; exports.SortedList = SortedList; exports.TextEditorType = TextEditorType; exports.WebsocketCloseCodes = WebsocketCloseCodes; exports.applyLiveTextOperations = applyLiveTextOperations; exports.asPos = asPos; exports.assert = assert; exports.assertNever = assertNever; exports.autoRetry = autoRetry; exports.b64decode = b64decode; exports.batch = batch; exports.checkBounds = checkBounds; exports.chunk = chunk; exports.cloneLson = cloneLson; exports.compactNodesToNodeStream = compactNodesToNodeStream; exports.compactObject = compactObject; exports.console = fancy_console_exports; exports.convertToCommentData = convertToCommentData; exports.convertToCommentUserReaction = convertToCommentUserReaction; exports.convertToGroupData = convertToGroupData; exports.convertToInboxNotificationData = convertToInboxNotificationData; exports.convertToSubscriptionData = convertToSubscriptionData; exports.convertToThreadData = convertToThreadData; exports.convertToUserSubscriptionData = convertToUserSubscriptionData; exports.createClient = createClient; exports.createCommentAttachmentId = createCommentAttachmentId; exports.createCommentId = createCommentId; exports.createInboxNotificationId = createInboxNotificationId; exports.createManagedPool = createManagedPool; exports.createNotificationSettings = createNotificationSettings; exports.createThreadId = createThreadId; exports.deepLiveify = deepLiveify; exports.defineAiTool = defineAiTool; exports.deprecate = deprecate; exports.deprecateIf = deprecateIf; exports.detectDupes = detectDupes; exports.entries = entries; exports.errorIf = errorIf; exports.findLastIndex = findLastIndex; exports.freeze = freeze; exports.generateUrl = generateUrl; exports.getMentionsFromCommentBody = getMentionsFromCommentBody; exports.getSubscriptionKey = getSubscriptionKey; exports.hasPermissionAccess = hasPermissionAccess; exports.html = html; exports.htmlSafe = htmlSafe; exports.isCommentBodyLink = isCommentBodyLink; exports.isCommentBodyMention = isCommentBodyMention; exports.isCommentBodyText = isCommentBodyText; exports.isJsonArray = isJsonArray; exports.isJsonObject = isJsonObject; exports.isJsonScalar = isJsonScalar; exports.isListStorageNode = isListStorageNode; exports.isLiveNode = isLiveNode; exports.isMapStorageNode = isMapStorageNode; exports.isNotificationChannelEnabled = isNotificationChannelEnabled; exports.isNumberOperator = isNumberOperator; exports.isObjectStorageNode = isObjectStorageNode; exports.isPlainObject = isPlainObject; exports.isRegisterStorageNode = isRegisterStorageNode; exports.isRootStorageNode = isRootStorageNode; exports.isStartsWithOperator = isStartsWithOperator; exports.isTextStorageNode = isTextStorageNode; exports.isUrl = isUrl; exports.kInternal = kInternal; exports.kStorageUpdateSource = kStorageUpdateSource; exports.keys = keys; exports.makeAbortController = makeAbortController; exports.makeEventSource = makeEventSource; exports.makePoller = makePoller; exports.makePosition = makePosition; exports.mapValues = mapValues; exports.memoizeOnSuccess = memoizeOnSuccess; exports.mergeRoomPermissionScopes = mergeRoomPermissionScopes; exports.nanoid = nanoid; exports.nn = nn; exports.nodeStreamToCompactNodes = nodeStreamToCompactNodes; exports.normalizeRoomAccesses = normalizeRoomAccesses; exports.normalizeRoomPermissions = normalizeRoomPermissions; exports.normalizeUpdateRoomAccesses = normalizeUpdateRoomAccesses; exports.objectToQuery = objectToQuery; exports.patchNotificationSettings = patchNotificationSettings; exports.permissionMatrixFromScopes = permissionMatrixFromScopes; exports.raise = raise; exports.resolveMentionsInCommentBody = resolveMentionsInCommentBody; exports.sanitizeUrl = sanitizeUrl; exports.shallow = shallow; exports.shallow2 = shallow2; exports.stableStringify = stableStringify; exports.stringifyCommentBody = stringifyCommentBody; exports.throwUsageError = throwUsageError; exports.toPlainLson = toPlainLson; exports.transformTextOperations = transformTextOperations; exports.tryParseJson = tryParseJson; exports.url = url; exports.urljoin = urljoin; exports.validatePermissionsSet = validatePermissionsSet; exports.wait = wait; exports.warnOnce = warnOnce; exports.warnOnceIf = warnOnceIf; exports.withTimeout = withTimeout;
12917
14202
  //# sourceMappingURL=index.cjs.map