@liveblocks/core 3.23.1 → 3.24.0

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.23.1";
9
+ var PKG_VERSION = "3.24.0";
10
10
  var PKG_FORMAT = "cjs";
11
11
 
12
12
  // src/dupe-detection.ts
@@ -988,14 +988,15 @@ var OpCode = Object.freeze({
988
988
  DELETE_OBJECT_KEY: 6,
989
989
  CREATE_MAP: 7,
990
990
  CREATE_REGISTER: 8,
991
- // TODO: 9 and 10 are used by LiveText, wait until it's merged.
991
+ CREATE_TEXT: 9,
992
+ UPDATE_TEXT: 10,
992
993
  CREATE_FILE: 11
993
994
  });
994
995
  function isIgnoredOp(op) {
995
996
  return op.type === OpCode.DELETE_CRDT && op.id === "ACK";
996
997
  }
997
998
  function isCreateOp(op) {
998
- return op.type === OpCode.CREATE_OBJECT || op.type === OpCode.CREATE_REGISTER || op.type === OpCode.CREATE_FILE || op.type === OpCode.CREATE_MAP || op.type === OpCode.CREATE_LIST;
999
+ return op.type === OpCode.CREATE_OBJECT || op.type === OpCode.CREATE_REGISTER || op.type === OpCode.CREATE_FILE || op.type === OpCode.CREATE_MAP || op.type === OpCode.CREATE_LIST || op.type === OpCode.CREATE_TEXT;
999
1000
  }
1000
1001
 
1001
1002
  // src/protocol/StorageNode.ts
@@ -1004,7 +1005,7 @@ var CrdtType = Object.freeze({
1004
1005
  LIST: 1,
1005
1006
  MAP: 2,
1006
1007
  REGISTER: 3,
1007
- // TODO: 4 is used by LiveText, wait until it's merged.
1008
+ TEXT: 4,
1008
1009
  FILE: 5
1009
1010
  });
1010
1011
  function isRootStorageNode(node) {
@@ -1022,6 +1023,9 @@ function isMapStorageNode(node) {
1022
1023
  function isRegisterStorageNode(node) {
1023
1024
  return node[1].type === CrdtType.REGISTER;
1024
1025
  }
1026
+ function isTextStorageNode(node) {
1027
+ return node[1].type === CrdtType.TEXT;
1028
+ }
1025
1029
  function isFileStorageNode(node) {
1026
1030
  return node[1].type === CrdtType.FILE;
1027
1031
  }
@@ -1047,6 +1051,9 @@ function* compactNodesToNodeStream(compactNodes) {
1047
1051
  case CrdtType.REGISTER:
1048
1052
  yield [cnode[0], { type: CrdtType.REGISTER, parentId: cnode[2], parentKey: cnode[3], data: cnode[4] }];
1049
1053
  break;
1054
+ case CrdtType.TEXT:
1055
+ yield [cnode[0], { type: CrdtType.TEXT, parentId: cnode[2], parentKey: cnode[3], data: cnode[4], version: cnode[5] }];
1056
+ break;
1050
1057
  case CrdtType.FILE:
1051
1058
  yield [cnode[0], { type: CrdtType.FILE, parentId: cnode[2], parentKey: cnode[3], data: cnode[4] }];
1052
1059
  break;
@@ -1078,6 +1085,17 @@ function* nodeStreamToCompactNodes(nodes) {
1078
1085
  const id = node[0];
1079
1086
  const crdt = node[1];
1080
1087
  yield [id, CrdtType.REGISTER, crdt.parentId, crdt.parentKey, crdt.data];
1088
+ } else if (isTextStorageNode(node)) {
1089
+ const id = node[0];
1090
+ const crdt = node[1];
1091
+ yield [
1092
+ id,
1093
+ CrdtType.TEXT,
1094
+ crdt.parentId,
1095
+ crdt.parentKey,
1096
+ crdt.data,
1097
+ crdt.version
1098
+ ];
1081
1099
  } else if (isFileStorageNode(node)) {
1082
1100
  const id = node[0];
1083
1101
  const crdt = node[1];
@@ -1087,6 +1105,42 @@ function* nodeStreamToCompactNodes(nodes) {
1087
1105
  }
1088
1106
  }
1089
1107
 
1108
+ // src/internal.ts
1109
+ var kInternal = /* @__PURE__ */ Symbol();
1110
+
1111
+ // src/lib/fancy-console.ts
1112
+ var fancy_console_exports = {};
1113
+ __export(fancy_console_exports, {
1114
+ error: () => error2,
1115
+ errorWithTitle: () => errorWithTitle,
1116
+ warn: () => warn,
1117
+ warnWithTitle: () => warnWithTitle
1118
+ });
1119
+ var badge = "background:#0e0d12;border-radius:9999px;color:#fff;padding:3px 7px;font-family:sans-serif;font-weight:600;";
1120
+ var bold = "font-weight:600";
1121
+ function wrap(method) {
1122
+ return typeof window === "undefined" || process.env.NODE_ENV === "test" ? console[method] : (
1123
+ /* istanbul ignore next */
1124
+ (message, ...args) => console[method]("%cLiveblocks", badge, message, ...args)
1125
+ );
1126
+ }
1127
+ var warn = wrap("warn");
1128
+ var error2 = wrap("error");
1129
+ function wrapWithTitle(method) {
1130
+ return typeof window === "undefined" || process.env.NODE_ENV === "test" ? console[method] : (
1131
+ /* istanbul ignore next */
1132
+ (title, message, ...args) => console[method](
1133
+ `%cLiveblocks%c ${title}`,
1134
+ badge,
1135
+ bold,
1136
+ message,
1137
+ ...args
1138
+ )
1139
+ );
1140
+ }
1141
+ var warnWithTitle = wrapWithTitle("warn");
1142
+ var errorWithTitle = wrapWithTitle("error");
1143
+
1090
1144
  // src/lib/position.ts
1091
1145
  var MIN_CODE = 32;
1092
1146
  var MAX_CODE = 126;
@@ -1266,6 +1320,18 @@ function asPos(str) {
1266
1320
  return isPos(str) ? str : convertToPos(str);
1267
1321
  }
1268
1322
 
1323
+ // src/crdts/StorageUpdates.ts
1324
+ var REMOTE = freeze({ origin: "remote" });
1325
+ var LOCAL_EDIT = freeze({ origin: "local", via: "edit" });
1326
+ var LOCAL_UNDO = freeze({ origin: "local", via: "undo" });
1327
+ var LOCAL_REDO = freeze({ origin: "local", via: "redo" });
1328
+ function toUpdateSource(source) {
1329
+ return source.origin === "remote" ? source : (
1330
+ // Removes `optimistic` field, which is not public
1331
+ { origin: "local", via: source.via }
1332
+ );
1333
+ }
1334
+
1269
1335
  // src/crdts/UnacknowledgedOps.ts
1270
1336
  var UnacknowledgedOps = class {
1271
1337
  // opId -> op
@@ -1284,6 +1350,10 @@ ${parentKey}`;
1284
1350
  get size() {
1285
1351
  return this.#byOpId.size;
1286
1352
  }
1353
+ /** The still-unacknowledged op with the given opId, if any. */
1354
+ get(opId) {
1355
+ return this.#byOpId.get(opId);
1356
+ }
1287
1357
  /**
1288
1358
  * Mark the given Op as still unacknowledged.
1289
1359
  */
@@ -1366,6 +1436,8 @@ ${parentKey}`;
1366
1436
  };
1367
1437
 
1368
1438
  // src/crdts/AbstractCrdt.ts
1439
+ var warnedOrphanedNodes = /* @__PURE__ */ new WeakSet();
1440
+ var ORPHANED_NODE_WARNING = "Cannot sync changes made to this Live structure because it is no longer part of Storage. Retrieve the current value from its parent before mutating it.";
1369
1441
  function createManagedPool(options) {
1370
1442
  const {
1371
1443
  getCurrentConnectionId,
@@ -1383,8 +1455,8 @@ function createManagedPool(options) {
1383
1455
  deleteNode: (id) => void nodes.delete(id),
1384
1456
  generateId: () => `${getCurrentConnectionId()}:${clock++}`,
1385
1457
  generateOpId: () => `${getCurrentConnectionId()}:${opClock++}`,
1386
- dispatch(ops, reverse, storageUpdates) {
1387
- _optionalChain([onDispatch, 'optionalCall', _23 => _23(ops, reverse, storageUpdates)]);
1458
+ dispatch(ops, reverse, storageUpdates, options2) {
1459
+ _optionalChain([onDispatch, 'optionalCall', _23 => _23(ops, reverse, storageUpdates, options2)]);
1388
1460
  },
1389
1461
  assertStorageIsWritable: () => {
1390
1462
  if (!isStorageWritable()) {
@@ -1407,10 +1479,17 @@ function Orphaned(oldKey, oldPos = asPos(oldKey)) {
1407
1479
  return Object.freeze({ type: "Orphaned", oldKey, oldPos });
1408
1480
  }
1409
1481
  var AbstractCrdt = class {
1410
- // ^^^^^^^^^^^^ TODO: Make this an interface
1411
1482
  #pool;
1412
1483
  #id;
1413
1484
  #parent = NoParent;
1485
+ constructor() {
1486
+ Object.defineProperty(this, kInternal, {
1487
+ value: {
1488
+ getId: () => this.#id
1489
+ },
1490
+ enumerable: false
1491
+ });
1492
+ }
1414
1493
  /** @internal */
1415
1494
  _getParentKeyOrThrow() {
1416
1495
  switch (this.parent.type) {
@@ -1450,6 +1529,17 @@ var AbstractCrdt = class {
1450
1529
  return this.#parent;
1451
1530
  }
1452
1531
  /** @internal */
1532
+ _warnIfOrphaned() {
1533
+ const node = crdtAsLiveNode(this);
1534
+ if (this.parent.type === "Orphaned" && !warnedOrphanedNodes.has(node)) {
1535
+ warnedOrphanedNodes.add(node);
1536
+ warn(ORPHANED_NODE_WARNING, {
1537
+ type: node.constructor.name,
1538
+ formerParentKey: this.parent.oldKey
1539
+ });
1540
+ }
1541
+ }
1542
+ /** @internal */
1453
1543
  get _parentKey() {
1454
1544
  switch (this.parent.type) {
1455
1545
  case "HasParent":
@@ -1463,11 +1553,14 @@ var AbstractCrdt = class {
1463
1553
  }
1464
1554
  }
1465
1555
  /** @internal */
1466
- _apply(op, _isLocal) {
1556
+ _apply(op, source) {
1467
1557
  switch (op.type) {
1468
1558
  case OpCode.DELETE_CRDT: {
1469
1559
  if (this.parent.type === "HasParent") {
1470
- return this.parent.node._detachChild(crdtAsLiveNode(this));
1560
+ return this.parent.node._detachChild(
1561
+ crdtAsLiveNode(this),
1562
+ toUpdateSource(source)
1563
+ );
1471
1564
  }
1472
1565
  return { modified: false };
1473
1566
  }
@@ -1657,8 +1750,8 @@ var LiveFile = class _LiveFile extends AbstractCrdt {
1657
1750
  throw new Error("A LiveFile node cannot have children");
1658
1751
  }
1659
1752
  /** @internal */
1660
- _apply(op, isLocal) {
1661
- return super._apply(op, isLocal);
1753
+ _apply(op, source) {
1754
+ return super._apply(op, source);
1662
1755
  }
1663
1756
  /** @internal */
1664
1757
  _toTreeNode(key) {
@@ -1678,39 +1771,6 @@ var LiveFile = class _LiveFile extends AbstractCrdt {
1678
1771
  }
1679
1772
  };
1680
1773
 
1681
- // src/lib/fancy-console.ts
1682
- var fancy_console_exports = {};
1683
- __export(fancy_console_exports, {
1684
- error: () => error2,
1685
- errorWithTitle: () => errorWithTitle,
1686
- warn: () => warn,
1687
- warnWithTitle: () => warnWithTitle
1688
- });
1689
- var badge = "background:#0e0d12;border-radius:9999px;color:#fff;padding:3px 7px;font-family:sans-serif;font-weight:600;";
1690
- var bold = "font-weight:600";
1691
- function wrap(method) {
1692
- return typeof window === "undefined" || process.env.NODE_ENV === "test" ? console[method] : (
1693
- /* istanbul ignore next */
1694
- (message, ...args) => console[method]("%cLiveblocks", badge, message, ...args)
1695
- );
1696
- }
1697
- var warn = wrap("warn");
1698
- var error2 = wrap("error");
1699
- function wrapWithTitle(method) {
1700
- return typeof window === "undefined" || process.env.NODE_ENV === "test" ? console[method] : (
1701
- /* istanbul ignore next */
1702
- (title, message, ...args) => console[method](
1703
- `%cLiveblocks%c ${title}`,
1704
- badge,
1705
- bold,
1706
- message,
1707
- ...args
1708
- )
1709
- );
1710
- }
1711
- var warnWithTitle = wrapWithTitle("warn");
1712
- var errorWithTitle = wrapWithTitle("error");
1713
-
1714
1774
  // src/lib/guards.ts
1715
1775
  function isDefined(value) {
1716
1776
  return value !== null && value !== void 0;
@@ -4877,9 +4937,6 @@ var ManagedSocket = class {
4877
4937
  }
4878
4938
  };
4879
4939
 
4880
- // src/internal.ts
4881
- var kInternal = /* @__PURE__ */ Symbol();
4882
-
4883
4940
  // src/lib/IncrementalJsonParser.ts
4884
4941
  var EMPTY_OBJECT = Object.freeze({});
4885
4942
  var NULL_KEYWORD_CHARS = Array.from(new Set("null"));
@@ -6881,8 +6938,8 @@ var LiveRegister = class _LiveRegister extends AbstractCrdt {
6881
6938
  throw new Error("Method not implemented.");
6882
6939
  }
6883
6940
  /** @internal */
6884
- _apply(op, isLocal) {
6885
- return super._apply(op, isLocal);
6941
+ _apply(op, source) {
6942
+ return super._apply(op, source);
6886
6943
  }
6887
6944
  /** @internal */
6888
6945
  _toTreeNode(key) {
@@ -7043,7 +7100,7 @@ var LiveList = class _LiveList extends AbstractCrdt {
7043
7100
  item._detach();
7044
7101
  }
7045
7102
  }
7046
- #applySetRemote(op) {
7103
+ #applyRemoteSet(op) {
7047
7104
  if (this._pool === void 0) {
7048
7105
  throw new Error("Can't attach child if managed pool is not present");
7049
7106
  }
@@ -7061,9 +7118,11 @@ var LiveList = class _LiveList extends AbstractCrdt {
7061
7118
  itemWithSamePosition._detach();
7062
7119
  this.#items.add(child);
7063
7120
  return {
7064
- modified: makeUpdate(this, [
7065
- setDelta(indexOfItemWithSamePosition, child)
7066
- ]),
7121
+ modified: makeUpdate(
7122
+ this,
7123
+ [setDelta(indexOfItemWithSamePosition, child)],
7124
+ REMOTE
7125
+ ),
7067
7126
  reverse: []
7068
7127
  };
7069
7128
  } else {
@@ -7074,20 +7133,22 @@ var LiveList = class _LiveList extends AbstractCrdt {
7074
7133
  setDelta(indexOfItemWithSamePosition, child)
7075
7134
  ];
7076
7135
  const deleteDelta2 = this.#detachItemAssociatedToSetOperation(
7077
- op.deletedId
7136
+ op.deletedId,
7137
+ REMOTE
7078
7138
  );
7079
7139
  if (deleteDelta2) {
7080
7140
  delta.push(deleteDelta2);
7081
7141
  }
7082
7142
  return {
7083
- modified: makeUpdate(this, delta),
7143
+ modified: makeUpdate(this, delta, REMOTE),
7084
7144
  reverse: []
7085
7145
  };
7086
7146
  }
7087
7147
  } else {
7088
7148
  const updates = [];
7089
7149
  const deleteDelta2 = this.#detachItemAssociatedToSetOperation(
7090
- op.deletedId
7150
+ op.deletedId,
7151
+ REMOTE
7091
7152
  );
7092
7153
  if (deleteDelta2) {
7093
7154
  updates.push(deleteDelta2);
@@ -7096,29 +7157,32 @@ var LiveList = class _LiveList extends AbstractCrdt {
7096
7157
  updates.push(insertDelta(this._indexOfPosition(key), child));
7097
7158
  return {
7098
7159
  reverse: [],
7099
- modified: makeUpdate(this, updates)
7160
+ modified: makeUpdate(this, updates, REMOTE)
7100
7161
  };
7101
7162
  }
7102
7163
  }
7103
- #applySetAck(op) {
7164
+ #applySetAck(op, source) {
7104
7165
  if (this._pool === void 0) {
7105
7166
  throw new Error("Can't attach child if managed pool is not present");
7106
7167
  }
7107
7168
  const delta = [];
7108
- const deletedDelta = this.#detachItemAssociatedToSetOperation(op.deletedId);
7169
+ const deletedDelta = this.#detachItemAssociatedToSetOperation(
7170
+ op.deletedId,
7171
+ source
7172
+ );
7109
7173
  if (deletedDelta) {
7110
7174
  delta.push(deletedDelta);
7111
7175
  }
7112
7176
  const unacknowledgedOpId = this.#unacknowledgedSetOpIdAt(op.parentKey);
7113
7177
  if (unacknowledgedOpId !== void 0 && unacknowledgedOpId !== op.opId) {
7114
- return delta.length === 0 ? { modified: false } : { modified: makeUpdate(this, delta), reverse: [] };
7178
+ return delta.length === 0 ? { modified: false } : { modified: makeUpdate(this, delta, source), reverse: [] };
7115
7179
  }
7116
7180
  const indexOfItemWithSamePosition = this._indexOfPosition(op.parentKey);
7117
7181
  const existingItem = this.#items.find((item) => item._id === op.id);
7118
7182
  if (existingItem !== void 0) {
7119
7183
  if (existingItem._parentKey === op.parentKey) {
7120
7184
  return {
7121
- modified: delta.length > 0 ? makeUpdate(this, delta) : false,
7185
+ modified: delta.length > 0 ? makeUpdate(this, delta, source) : false,
7122
7186
  reverse: []
7123
7187
  };
7124
7188
  }
@@ -7136,7 +7200,7 @@ var LiveList = class _LiveList extends AbstractCrdt {
7136
7200
  delta.push(moveDelta(prevIndex, newIndex, existingItem));
7137
7201
  }
7138
7202
  return {
7139
- modified: delta.length > 0 ? makeUpdate(this, delta) : false,
7203
+ modified: delta.length > 0 ? makeUpdate(this, delta, source) : false,
7140
7204
  reverse: []
7141
7205
  };
7142
7206
  } else {
@@ -7146,11 +7210,15 @@ var LiveList = class _LiveList extends AbstractCrdt {
7146
7210
  this.#implicitlyDeletedItems.delete(orphan);
7147
7211
  const recreatedItemIndex = this.#insert(orphan);
7148
7212
  return {
7149
- modified: makeUpdate(this, [
7150
- // If there is an item at this position, update is a set, else it's an insert
7151
- indexOfItemWithSamePosition === -1 ? insertDelta(recreatedItemIndex, orphan) : setDelta(recreatedItemIndex, orphan),
7152
- ...delta
7153
- ]),
7213
+ modified: makeUpdate(
7214
+ this,
7215
+ [
7216
+ // If there is an item at this position, update is a set, else it's an insert
7217
+ indexOfItemWithSamePosition === -1 ? insertDelta(recreatedItemIndex, orphan) : setDelta(recreatedItemIndex, orphan),
7218
+ ...delta
7219
+ ],
7220
+ source
7221
+ ),
7154
7222
  reverse: []
7155
7223
  };
7156
7224
  } else {
@@ -7165,11 +7233,15 @@ var LiveList = class _LiveList extends AbstractCrdt {
7165
7233
  op.parentKey
7166
7234
  );
7167
7235
  return {
7168
- modified: makeUpdate(this, [
7169
- // If there is an item at this position, update is a set, else it's an insert
7170
- indexOfItemWithSamePosition === -1 ? insertDelta(newIndex, newItem) : setDelta(newIndex, newItem),
7171
- ...delta
7172
- ]),
7236
+ modified: makeUpdate(
7237
+ this,
7238
+ [
7239
+ // If there is an item at this position, update is a set, else it's an insert
7240
+ indexOfItemWithSamePosition === -1 ? insertDelta(newIndex, newItem) : setDelta(newIndex, newItem),
7241
+ ...delta
7242
+ ],
7243
+ source
7244
+ ),
7173
7245
  reverse: []
7174
7246
  };
7175
7247
  }
@@ -7178,7 +7250,7 @@ var LiveList = class _LiveList extends AbstractCrdt {
7178
7250
  /**
7179
7251
  * Returns the update delta of the deletion or null
7180
7252
  */
7181
- #detachItemAssociatedToSetOperation(deletedId) {
7253
+ #detachItemAssociatedToSetOperation(deletedId, source) {
7182
7254
  if (deletedId === void 0 || this._pool === void 0) {
7183
7255
  return null;
7184
7256
  }
@@ -7186,7 +7258,7 @@ var LiveList = class _LiveList extends AbstractCrdt {
7186
7258
  if (deletedItem === void 0) {
7187
7259
  return null;
7188
7260
  }
7189
- const result = this._detachChild(deletedItem);
7261
+ const result = this._detachChild(deletedItem, source);
7190
7262
  if (result.modified === false) {
7191
7263
  return null;
7192
7264
  }
@@ -7204,10 +7276,11 @@ var LiveList = class _LiveList extends AbstractCrdt {
7204
7276
  const { newItem, newIndex } = this.#createAttachItemAndSort(op, key);
7205
7277
  const bumpDeltas = this.#bumpUnackedPushesAbove(key);
7206
7278
  return {
7207
- modified: makeUpdate(this, [
7208
- insertDelta(newIndex, newItem),
7209
- ...bumpDeltas
7210
- ]),
7279
+ modified: makeUpdate(
7280
+ this,
7281
+ [insertDelta(newIndex, newItem), ...bumpDeltas],
7282
+ REMOTE
7283
+ ),
7211
7284
  reverse: []
7212
7285
  };
7213
7286
  }
@@ -7288,7 +7361,7 @@ var LiveList = class _LiveList extends AbstractCrdt {
7288
7361
  }
7289
7362
  return deltas;
7290
7363
  }
7291
- #applyInsertAck(op) {
7364
+ #applyInsertAck(op, source) {
7292
7365
  const existingItem = this.#items.find((item) => item._id === op.id);
7293
7366
  const key = asPos(op.parentKey);
7294
7367
  const itemIndexAtPosition = this._indexOfPosition(key);
@@ -7310,9 +7383,11 @@ var LiveList = class _LiveList extends AbstractCrdt {
7310
7383
  return { modified: false };
7311
7384
  }
7312
7385
  return {
7313
- modified: makeUpdate(this, [
7314
- moveDelta(oldPositionIndex, newIndex, existingItem)
7315
- ]),
7386
+ modified: makeUpdate(
7387
+ this,
7388
+ [moveDelta(oldPositionIndex, newIndex, existingItem)],
7389
+ source
7390
+ ),
7316
7391
  reverse: []
7317
7392
  };
7318
7393
  }
@@ -7324,7 +7399,7 @@ var LiveList = class _LiveList extends AbstractCrdt {
7324
7399
  this.#insert(orphan);
7325
7400
  const newIndex = this._indexOfPosition(key);
7326
7401
  return {
7327
- modified: makeUpdate(this, [insertDelta(newIndex, orphan)]),
7402
+ modified: makeUpdate(this, [insertDelta(newIndex, orphan)], source),
7328
7403
  reverse: []
7329
7404
  };
7330
7405
  } else {
@@ -7333,13 +7408,13 @@ var LiveList = class _LiveList extends AbstractCrdt {
7333
7408
  }
7334
7409
  const { newItem, newIndex } = this.#createAttachItemAndSort(op, key);
7335
7410
  return {
7336
- modified: makeUpdate(this, [insertDelta(newIndex, newItem)]),
7411
+ modified: makeUpdate(this, [insertDelta(newIndex, newItem)], source),
7337
7412
  reverse: []
7338
7413
  };
7339
7414
  }
7340
7415
  }
7341
7416
  }
7342
- #applyInsertUndoRedo(op) {
7417
+ #applyLocalInsert(op, source) {
7343
7418
  const { id, parentKey: key } = op;
7344
7419
  const child = creationOpToLiveNode(op);
7345
7420
  if (_optionalChain([this, 'access', _151 => _151._pool, 'optionalAccess', _152 => _152.getNode, 'call', _153 => _153(id)]) !== void 0) {
@@ -7358,11 +7433,11 @@ var LiveList = class _LiveList extends AbstractCrdt {
7358
7433
  this.#insert(child);
7359
7434
  const newIndex = this._indexOfPosition(newKey);
7360
7435
  return {
7361
- modified: makeUpdate(this, [insertDelta(newIndex, child)]),
7436
+ modified: makeUpdate(this, [insertDelta(newIndex, child)], source),
7362
7437
  reverse: [{ type: OpCode.DELETE_CRDT, id }]
7363
7438
  };
7364
7439
  }
7365
- #applySetUndoRedo(op) {
7440
+ #applyLocalSet(op, source) {
7366
7441
  const { id, parentKey: key } = op;
7367
7442
  const child = creationOpToLiveNode(op);
7368
7443
  if (_optionalChain([this, 'access', _162 => _162._pool, 'optionalAccess', _163 => _163.getNode, 'call', _164 => _164(id)]) !== void 0) {
@@ -7384,46 +7459,48 @@ var LiveList = class _LiveList extends AbstractCrdt {
7384
7459
  );
7385
7460
  const delta = [setDelta(indexOfItemWithSameKey, child)];
7386
7461
  const deletedDelta = this.#detachItemAssociatedToSetOperation(
7387
- op.deletedId
7462
+ op.deletedId,
7463
+ source
7388
7464
  );
7389
7465
  if (deletedDelta) {
7390
7466
  delta.push(deletedDelta);
7391
7467
  }
7392
7468
  return {
7393
- modified: makeUpdate(this, delta),
7469
+ modified: makeUpdate(this, delta, source),
7394
7470
  reverse
7395
7471
  };
7396
7472
  } else {
7397
7473
  this.#insert(child);
7398
- this.#detachItemAssociatedToSetOperation(op.deletedId);
7474
+ this.#detachItemAssociatedToSetOperation(op.deletedId, source);
7399
7475
  const newIndex = this._indexOfPosition(newKey);
7400
7476
  return {
7401
7477
  reverse: [{ type: OpCode.DELETE_CRDT, id }],
7402
- modified: makeUpdate(this, [insertDelta(newIndex, child)])
7478
+ modified: makeUpdate(this, [insertDelta(newIndex, child)], source)
7403
7479
  };
7404
7480
  }
7405
7481
  }
7406
7482
  /** @internal */
7407
- _attachChild(op, source) {
7483
+ _attachChild(op, opSource) {
7484
+ const source = toUpdateSource(opSource);
7408
7485
  if (this._pool === void 0) {
7409
7486
  throw new Error("Can't attach child if managed pool is not present");
7410
7487
  }
7411
7488
  let result;
7412
7489
  if (op.intent === "set") {
7413
- if (source === 1 /* THEIRS */) {
7414
- result = this.#applySetRemote(op);
7415
- } else if (source === 2 /* OURS */) {
7416
- result = this.#applySetAck(op);
7490
+ if (opSource.origin === "remote") {
7491
+ result = this.#applyRemoteSet(op);
7492
+ } else if (!opSource.optimistic) {
7493
+ result = this.#applySetAck(op, source);
7417
7494
  } else {
7418
- result = this.#applySetUndoRedo(op);
7495
+ result = this.#applyLocalSet(op, source);
7419
7496
  }
7420
7497
  } else {
7421
- if (source === 1 /* THEIRS */) {
7498
+ if (opSource.origin === "remote") {
7422
7499
  result = this.#applyRemoteInsert(op);
7423
- } else if (source === 2 /* OURS */) {
7424
- result = this.#applyInsertAck(op);
7500
+ } else if (!opSource.optimistic) {
7501
+ result = this.#applyInsertAck(op, source);
7425
7502
  } else {
7426
- result = this.#applyInsertUndoRedo(op);
7503
+ result = this.#applyLocalInsert(op, source);
7427
7504
  }
7428
7505
  }
7429
7506
  if (result.modified !== false) {
@@ -7432,7 +7509,7 @@ var LiveList = class _LiveList extends AbstractCrdt {
7432
7509
  return result;
7433
7510
  }
7434
7511
  /** @internal */
7435
- _detachChild(child) {
7512
+ _detachChild(child, source) {
7436
7513
  if (child) {
7437
7514
  const parentKey = nn(child._parentKey);
7438
7515
  const reverse = child._toOps(nn(this._id), parentKey);
@@ -7447,19 +7524,23 @@ var LiveList = class _LiveList extends AbstractCrdt {
7447
7524
  this.invalidate();
7448
7525
  child._detach();
7449
7526
  return {
7450
- modified: makeUpdate(this, [deleteDelta(indexToDelete, previousNode)]),
7527
+ modified: makeUpdate(
7528
+ this,
7529
+ [deleteDelta(indexToDelete, previousNode)],
7530
+ source
7531
+ ),
7451
7532
  reverse
7452
7533
  };
7453
7534
  }
7454
7535
  return { modified: false };
7455
7536
  }
7456
- #applySetChildKeyRemote(newKey, child) {
7537
+ #applyRemoteSetChildKey(newKey, child) {
7457
7538
  if (this.#implicitlyDeletedItems.has(child)) {
7458
7539
  this.#implicitlyDeletedItems.delete(child);
7459
7540
  child._setParentLink(this, newKey);
7460
7541
  const newIndex = this.#insert(child);
7461
7542
  return {
7462
- modified: makeUpdate(this, [insertDelta(newIndex, child)]),
7543
+ modified: makeUpdate(this, [insertDelta(newIndex, child)], REMOTE),
7463
7544
  reverse: []
7464
7545
  };
7465
7546
  }
@@ -7480,7 +7561,11 @@ var LiveList = class _LiveList extends AbstractCrdt {
7480
7561
  };
7481
7562
  }
7482
7563
  return {
7483
- modified: makeUpdate(this, [moveDelta(previousIndex, newIndex, child)]),
7564
+ modified: makeUpdate(
7565
+ this,
7566
+ [moveDelta(previousIndex, newIndex, child)],
7567
+ REMOTE
7568
+ ),
7484
7569
  reverse: []
7485
7570
  };
7486
7571
  } else {
@@ -7497,12 +7582,16 @@ var LiveList = class _LiveList extends AbstractCrdt {
7497
7582
  };
7498
7583
  }
7499
7584
  return {
7500
- modified: makeUpdate(this, [moveDelta(previousIndex, newIndex, child)]),
7585
+ modified: makeUpdate(
7586
+ this,
7587
+ [moveDelta(previousIndex, newIndex, child)],
7588
+ REMOTE
7589
+ ),
7501
7590
  reverse: []
7502
7591
  };
7503
7592
  }
7504
7593
  }
7505
- #applySetChildKeyAck(newKey, child) {
7594
+ #applySetChildKeyAck(newKey, child, source) {
7506
7595
  const previousKey = nn(child._parentKey);
7507
7596
  if (this.#implicitlyDeletedItems.has(child)) {
7508
7597
  const existingItemIndex = this._indexOfPosition(newKey);
@@ -7521,7 +7610,7 @@ var LiveList = class _LiveList extends AbstractCrdt {
7521
7610
  child._setParentLink(this, newKey);
7522
7611
  const newIndex = this.#insert(child);
7523
7612
  return {
7524
- modified: makeUpdate(this, [insertDelta(newIndex, child)]),
7613
+ modified: makeUpdate(this, [insertDelta(newIndex, child)], source),
7525
7614
  reverse: []
7526
7615
  };
7527
7616
  } else {
@@ -7549,15 +7638,17 @@ var LiveList = class _LiveList extends AbstractCrdt {
7549
7638
  };
7550
7639
  } else {
7551
7640
  return {
7552
- modified: makeUpdate(this, [
7553
- moveDelta(previousIndex, newIndex, child)
7554
- ]),
7641
+ modified: makeUpdate(
7642
+ this,
7643
+ [moveDelta(previousIndex, newIndex, child)],
7644
+ source
7645
+ ),
7555
7646
  reverse: []
7556
7647
  };
7557
7648
  }
7558
7649
  }
7559
7650
  }
7560
- #applySetChildKeyUndoRedo(newKey, child) {
7651
+ #applyLocalSetChildKey(newKey, child, source) {
7561
7652
  const previousKey = nn(child._parentKey);
7562
7653
  const previousIndex = this.#items.findIndex((item) => item === child);
7563
7654
  const existingItemIndex = this._indexOfPosition(newKey);
@@ -7576,7 +7667,11 @@ var LiveList = class _LiveList extends AbstractCrdt {
7576
7667
  };
7577
7668
  }
7578
7669
  return {
7579
- modified: makeUpdate(this, [moveDelta(previousIndex, newIndex, child)]),
7670
+ modified: makeUpdate(
7671
+ this,
7672
+ [moveDelta(previousIndex, newIndex, child)],
7673
+ source
7674
+ ),
7580
7675
  reverse: [
7581
7676
  {
7582
7677
  type: OpCode.SET_PARENT_KEY,
@@ -7587,18 +7682,19 @@ var LiveList = class _LiveList extends AbstractCrdt {
7587
7682
  };
7588
7683
  }
7589
7684
  /** @internal */
7590
- _setChildKey(newKey, child, source) {
7591
- if (source === 1 /* THEIRS */) {
7592
- return this.#applySetChildKeyRemote(newKey, child);
7593
- } else if (source === 2 /* OURS */) {
7594
- return this.#applySetChildKeyAck(newKey, child);
7685
+ _setChildKey(newKey, child, opSource) {
7686
+ const source = toUpdateSource(opSource);
7687
+ if (opSource.origin === "remote") {
7688
+ return this.#applyRemoteSetChildKey(newKey, child);
7689
+ } else if (!opSource.optimistic) {
7690
+ return this.#applySetChildKeyAck(newKey, child, source);
7595
7691
  } else {
7596
- return this.#applySetChildKeyUndoRedo(newKey, child);
7692
+ return this.#applyLocalSetChildKey(newKey, child, source);
7597
7693
  }
7598
7694
  }
7599
7695
  /** @internal */
7600
- _apply(op, isLocal) {
7601
- return super._apply(op, isLocal);
7696
+ _apply(op, source) {
7697
+ return super._apply(op, source);
7602
7698
  }
7603
7699
  /** @internal */
7604
7700
  _serialize() {
@@ -7639,6 +7735,7 @@ var LiveList = class _LiveList extends AbstractCrdt {
7639
7735
  * instead of resolving its position against the client's stale view.
7640
7736
  */
7641
7737
  #injectAt(element, index, intent) {
7738
+ this._warnIfOrphaned();
7642
7739
  _optionalChain([this, 'access', _181 => _181._pool, 'optionalAccess', _182 => _182.assertStorageIsWritable, 'call', _183 => _183()]);
7643
7740
  if (index < 0 || index > this.#items.length) {
7644
7741
  throw new Error(
@@ -7659,7 +7756,7 @@ var LiveList = class _LiveList extends AbstractCrdt {
7659
7756
  intent === "push" ? addIntentToRootOp(ops, "push") : ops,
7660
7757
  [{ type: OpCode.DELETE_CRDT, id }],
7661
7758
  /* @__PURE__ */ new Map([
7662
- [this._id, makeUpdate(this, [insertDelta(index, value)])]
7759
+ [this._id, makeUpdate(this, [insertDelta(index, value)], LOCAL_EDIT)]
7663
7760
  ])
7664
7761
  );
7665
7762
  }
@@ -7670,6 +7767,7 @@ var LiveList = class _LiveList extends AbstractCrdt {
7670
7767
  * @param targetIndex The index where the element should be after moving.
7671
7768
  */
7672
7769
  move(index, targetIndex) {
7770
+ this._warnIfOrphaned();
7673
7771
  _optionalChain([this, 'access', _192 => _192._pool, 'optionalAccess', _193 => _193.assertStorageIsWritable, 'call', _194 => _194()]);
7674
7772
  if (targetIndex < 0) {
7675
7773
  throw new Error("targetIndex cannot be less than 0");
@@ -7700,7 +7798,10 @@ var LiveList = class _LiveList extends AbstractCrdt {
7700
7798
  this.#updateItemPositionAt(index, position);
7701
7799
  if (this._pool && this._id) {
7702
7800
  const storageUpdates = /* @__PURE__ */ new Map([
7703
- [this._id, makeUpdate(this, [moveDelta(index, targetIndex, item)])]
7801
+ [
7802
+ this._id,
7803
+ makeUpdate(this, [moveDelta(index, targetIndex, item)], LOCAL_EDIT)
7804
+ ]
7704
7805
  ]);
7705
7806
  this._pool.dispatch(
7706
7807
  [
@@ -7727,6 +7828,7 @@ var LiveList = class _LiveList extends AbstractCrdt {
7727
7828
  * @param index The index of the element to delete
7728
7829
  */
7729
7830
  delete(index) {
7831
+ this._warnIfOrphaned();
7730
7832
  _optionalChain([this, 'access', _203 => _203._pool, 'optionalAccess', _204 => _204.assertStorageIsWritable, 'call', _205 => _205()]);
7731
7833
  if (index < 0 || index >= this.#items.length) {
7732
7834
  throw new Error(
@@ -7743,7 +7845,7 @@ var LiveList = class _LiveList extends AbstractCrdt {
7743
7845
  const storageUpdates = /* @__PURE__ */ new Map();
7744
7846
  storageUpdates.set(
7745
7847
  nn(this._id),
7746
- makeUpdate(this, [deleteDelta(index, item)])
7848
+ makeUpdate(this, [deleteDelta(index, item)], LOCAL_EDIT)
7747
7849
  );
7748
7850
  this._pool.dispatch(
7749
7851
  [
@@ -7760,6 +7862,7 @@ var LiveList = class _LiveList extends AbstractCrdt {
7760
7862
  }
7761
7863
  }
7762
7864
  clear() {
7865
+ this._warnIfOrphaned();
7763
7866
  _optionalChain([this, 'access', _206 => _206._pool, 'optionalAccess', _207 => _207.assertStorageIsWritable, 'call', _208 => _208()]);
7764
7867
  if (this._pool) {
7765
7868
  const ops = [];
@@ -7783,7 +7886,10 @@ var LiveList = class _LiveList extends AbstractCrdt {
7783
7886
  this.#items.clear();
7784
7887
  this.invalidate();
7785
7888
  const storageUpdates = /* @__PURE__ */ new Map();
7786
- storageUpdates.set(nn(this._id), makeUpdate(this, updateDelta));
7889
+ storageUpdates.set(
7890
+ nn(this._id),
7891
+ makeUpdate(this, updateDelta, LOCAL_EDIT)
7892
+ );
7787
7893
  this._pool.dispatch(ops, reverseOps, storageUpdates);
7788
7894
  } else {
7789
7895
  for (const item of this.#items) {
@@ -7794,6 +7900,7 @@ var LiveList = class _LiveList extends AbstractCrdt {
7794
7900
  }
7795
7901
  }
7796
7902
  set(index, item) {
7903
+ this._warnIfOrphaned();
7797
7904
  _optionalChain([this, 'access', _209 => _209._pool, 'optionalAccess', _210 => _210.assertStorageIsWritable, 'call', _211 => _211()]);
7798
7905
  if (index < 0 || index >= this.#items.length) {
7799
7906
  throw new Error(
@@ -7813,7 +7920,10 @@ var LiveList = class _LiveList extends AbstractCrdt {
7813
7920
  const id = this._pool.generateId();
7814
7921
  value._attach(id, this._pool);
7815
7922
  const storageUpdates = /* @__PURE__ */ new Map();
7816
- storageUpdates.set(this._id, makeUpdate(this, [setDelta(index, value)]));
7923
+ storageUpdates.set(
7924
+ this._id,
7925
+ makeUpdate(this, [setDelta(index, value)], LOCAL_EDIT)
7926
+ );
7817
7927
  const ops = addIntentToRootOp(
7818
7928
  value._toOpsWithOpId(this._id, position, this._pool),
7819
7929
  "set",
@@ -7986,11 +8096,12 @@ var LiveList = class _LiveList extends AbstractCrdt {
7986
8096
  );
7987
8097
  }
7988
8098
  };
7989
- function makeUpdate(liveList, deltaUpdates) {
8099
+ function makeUpdate(liveList, deltaUpdates, source) {
7990
8100
  return {
7991
8101
  node: liveList,
7992
8102
  type: "LiveList",
7993
- updates: deltaUpdates
8103
+ updates: deltaUpdates,
8104
+ source
7994
8105
  };
7995
8106
  }
7996
8107
  function setDelta(index, item) {
@@ -8109,7 +8220,9 @@ var LiveMap = class _LiveMap extends AbstractCrdt {
8109
8220
  if (this._pool.getNode(id) !== void 0) {
8110
8221
  return { modified: false };
8111
8222
  }
8112
- if (source === 2 /* OURS */) {
8223
+ if (source.origin === "remote") {
8224
+ this.#unacknowledgedSet.delete(key);
8225
+ } else if (!source.optimistic) {
8113
8226
  const lastUpdateOpId = this.#unacknowledgedSet.get(key);
8114
8227
  if (lastUpdateOpId === opId) {
8115
8228
  this.#unacknowledgedSet.delete(key);
@@ -8117,8 +8230,6 @@ var LiveMap = class _LiveMap extends AbstractCrdt {
8117
8230
  } else if (lastUpdateOpId !== void 0) {
8118
8231
  return { modified: false };
8119
8232
  }
8120
- } else if (source === 1 /* THEIRS */) {
8121
- this.#unacknowledgedSet.delete(key);
8122
8233
  }
8123
8234
  const previousValue = this.#map.get(key);
8124
8235
  let reverse;
@@ -8137,7 +8248,8 @@ var LiveMap = class _LiveMap extends AbstractCrdt {
8137
8248
  modified: {
8138
8249
  node: this,
8139
8250
  type: "LiveMap",
8140
- updates: { [key]: { type: "update" } }
8251
+ updates: { [key]: { type: "update" } },
8252
+ source: toUpdateSource(source)
8141
8253
  },
8142
8254
  reverse
8143
8255
  };
@@ -8150,7 +8262,7 @@ var LiveMap = class _LiveMap extends AbstractCrdt {
8150
8262
  }
8151
8263
  }
8152
8264
  /** @internal */
8153
- _detachChild(child) {
8265
+ _detachChild(child, source) {
8154
8266
  const id = nn(this._id);
8155
8267
  const parentKey = nn(child._parentKey);
8156
8268
  const reverse = child._toOps(id, parentKey);
@@ -8169,7 +8281,8 @@ var LiveMap = class _LiveMap extends AbstractCrdt {
8169
8281
  type: "delete",
8170
8282
  deletedItem: liveNodeToLson(child)
8171
8283
  }
8172
- }
8284
+ },
8285
+ source
8173
8286
  };
8174
8287
  return { modified: storageUpdate, reverse };
8175
8288
  }
@@ -8202,6 +8315,7 @@ var LiveMap = class _LiveMap extends AbstractCrdt {
8202
8315
  * @param value The value of the element to add. Should be serializable to JSON.
8203
8316
  */
8204
8317
  set(key, value) {
8318
+ this._warnIfOrphaned();
8205
8319
  _optionalChain([this, 'access', _216 => _216._pool, 'optionalAccess', _217 => _217.assertStorageIsWritable, 'call', _218 => _218()]);
8206
8320
  const oldValue = this.#map.get(key);
8207
8321
  if (oldValue) {
@@ -8218,7 +8332,8 @@ var LiveMap = class _LiveMap extends AbstractCrdt {
8218
8332
  storageUpdates.set(this._id, {
8219
8333
  node: this,
8220
8334
  type: "LiveMap",
8221
- updates: { [key]: { type: "update" } }
8335
+ updates: { [key]: { type: "update" } },
8336
+ source: LOCAL_EDIT
8222
8337
  });
8223
8338
  const ops = item._toOpsWithOpId(this._id, key, this._pool);
8224
8339
  this.#unacknowledgedSet.set(key, nn(ops[0].opId));
@@ -8248,6 +8363,7 @@ var LiveMap = class _LiveMap extends AbstractCrdt {
8248
8363
  * @returns true if an element existed and has been removed, or false if the element does not exist.
8249
8364
  */
8250
8365
  delete(key) {
8366
+ this._warnIfOrphaned();
8251
8367
  _optionalChain([this, 'access', _219 => _219._pool, 'optionalAccess', _220 => _220.assertStorageIsWritable, 'call', _221 => _221()]);
8252
8368
  const item = this.#map.get(key);
8253
8369
  if (item === void 0) {
@@ -8267,7 +8383,8 @@ var LiveMap = class _LiveMap extends AbstractCrdt {
8267
8383
  type: "delete",
8268
8384
  deletedItem: liveNodeToLson(item)
8269
8385
  }
8270
- }
8386
+ },
8387
+ source: LOCAL_EDIT
8271
8388
  });
8272
8389
  this._pool.dispatch(
8273
8390
  [
@@ -8632,7 +8749,7 @@ var LiveObject = (_class2 = class _LiveObject extends AbstractCrdt {
8632
8749
  }
8633
8750
  return { modified: false };
8634
8751
  }
8635
- if (source === 0 /* LOCAL */) {
8752
+ if (source.origin === "local" && source.optimistic) {
8636
8753
  this.#unackedOpsByKey.set(key, nn(opId));
8637
8754
  } else if (this.#unackedOpsByKey.get(key) === void 0) {
8638
8755
  } else if (this.#unackedOpsByKey.get(key) === opId) {
@@ -8670,12 +8787,13 @@ var LiveObject = (_class2 = class _LiveObject extends AbstractCrdt {
8670
8787
  modified: {
8671
8788
  node: this,
8672
8789
  type: "LiveObject",
8673
- updates: { [key]: { type: "update" } }
8790
+ updates: { [key]: { type: "update" } },
8791
+ source: toUpdateSource(source)
8674
8792
  }
8675
8793
  };
8676
8794
  }
8677
8795
  /** @internal */
8678
- _detachChild(child) {
8796
+ _detachChild(child, source) {
8679
8797
  if (child) {
8680
8798
  const id = nn(this._id);
8681
8799
  const parentKey = nn(child._parentKey);
@@ -8693,7 +8811,8 @@ var LiveObject = (_class2 = class _LiveObject extends AbstractCrdt {
8693
8811
  type: "LiveObject",
8694
8812
  updates: {
8695
8813
  [parentKey]: { type: "delete", deletedItem }
8696
- }
8814
+ },
8815
+ source
8697
8816
  };
8698
8817
  return { modified: storageUpdate, reverse };
8699
8818
  }
@@ -8709,13 +8828,13 @@ var LiveObject = (_class2 = class _LiveObject extends AbstractCrdt {
8709
8828
  }
8710
8829
  }
8711
8830
  /** @internal */
8712
- _apply(op, isLocal) {
8831
+ _apply(op, source) {
8713
8832
  if (op.type === OpCode.UPDATE_OBJECT) {
8714
- return this.#applyUpdate(op, isLocal);
8833
+ return this.#applyUpdate(op, source);
8715
8834
  } else if (op.type === OpCode.DELETE_OBJECT_KEY) {
8716
- return this.#applyDeleteObjectKey(op, isLocal);
8835
+ return this.#applyDeleteObjectKey(op, source);
8717
8836
  }
8718
- return super._apply(op, isLocal);
8837
+ return super._apply(op, source);
8719
8838
  }
8720
8839
  /** @internal */
8721
8840
  _serialize() {
@@ -8739,7 +8858,7 @@ var LiveObject = (_class2 = class _LiveObject extends AbstractCrdt {
8739
8858
  };
8740
8859
  }
8741
8860
  }
8742
- #applyUpdate(op, isLocal) {
8861
+ #applyUpdate(op, source) {
8743
8862
  let isModified = false;
8744
8863
  const id = nn(this._id);
8745
8864
  const reverse = [];
@@ -8767,7 +8886,7 @@ var LiveObject = (_class2 = class _LiveObject extends AbstractCrdt {
8767
8886
  if (value === void 0) {
8768
8887
  continue;
8769
8888
  }
8770
- if (isLocal) {
8889
+ if (source.origin === "local" && source.optimistic) {
8771
8890
  this.#unackedOpsByKey.set(key, nn(op.opId));
8772
8891
  } else if (this.#unackedOpsByKey.get(key) === void 0) {
8773
8892
  isModified = true;
@@ -8794,18 +8913,19 @@ var LiveObject = (_class2 = class _LiveObject extends AbstractCrdt {
8794
8913
  modified: {
8795
8914
  node: this,
8796
8915
  type: "LiveObject",
8797
- updates: updateDelta
8916
+ updates: updateDelta,
8917
+ source: toUpdateSource(source)
8798
8918
  },
8799
8919
  reverse
8800
8920
  } : { modified: false };
8801
8921
  }
8802
- #applyDeleteObjectKey(op, isLocal) {
8922
+ #applyDeleteObjectKey(op, source) {
8803
8923
  const key = op.key;
8804
8924
  const oldValue = this.#synced.get(key);
8805
8925
  if (oldValue === void 0) {
8806
8926
  return { modified: false };
8807
8927
  }
8808
- if (!isLocal && this.#unackedOpsByKey.get(key) !== void 0) {
8928
+ if (!(source.origin === "local" && source.optimistic) && this.#unackedOpsByKey.get(key) !== void 0) {
8809
8929
  return { modified: false };
8810
8930
  }
8811
8931
  const id = nn(this._id);
@@ -8831,7 +8951,8 @@ var LiveObject = (_class2 = class _LiveObject extends AbstractCrdt {
8831
8951
  type: "LiveObject",
8832
8952
  updates: {
8833
8953
  [op.key]: { type: "delete", deletedItem: oldValue }
8834
- }
8954
+ },
8955
+ source: toUpdateSource(source)
8835
8956
  },
8836
8957
  reverse
8837
8958
  };
@@ -8862,6 +8983,7 @@ var LiveObject = (_class2 = class _LiveObject extends AbstractCrdt {
8862
8983
  * Caveat: this method will not add changes to the undo/redo stack.
8863
8984
  */
8864
8985
  setLocal(key, value) {
8986
+ this._warnIfOrphaned();
8865
8987
  _optionalChain([this, 'access', _222 => _222._pool, 'optionalAccess', _223 => _223.assertStorageIsWritable, 'call', _224 => _224()]);
8866
8988
  const deleteResult = this.#prepareDelete(key);
8867
8989
  this.#local.set(key, value);
@@ -8877,7 +8999,8 @@ var LiveObject = (_class2 = class _LiveObject extends AbstractCrdt {
8877
8999
  updates: {
8878
9000
  ..._optionalChain([existing, 'optionalAccess', _228 => _228.updates]),
8879
9001
  [key]: { type: "update" }
8880
- }
9002
+ },
9003
+ source: LOCAL_EDIT
8881
9004
  });
8882
9005
  this._pool.dispatch(ops, reverse, storageUpdates);
8883
9006
  }
@@ -8895,6 +9018,7 @@ var LiveObject = (_class2 = class _LiveObject extends AbstractCrdt {
8895
9018
  * #synced or pool/id are unavailable. Does NOT dispatch.
8896
9019
  */
8897
9020
  #prepareDelete(key) {
9021
+ this._warnIfOrphaned();
8898
9022
  _optionalChain([this, 'access', _229 => _229._pool, 'optionalAccess', _230 => _230.assertStorageIsWritable, 'call', _231 => _231()]);
8899
9023
  const k = key;
8900
9024
  if (this.#local.has(k) && !this.#synced.has(k)) {
@@ -8911,7 +9035,8 @@ var LiveObject = (_class2 = class _LiveObject extends AbstractCrdt {
8911
9035
  type: "delete",
8912
9036
  deletedItem: oldValue2
8913
9037
  }
8914
- }
9038
+ },
9039
+ source: LOCAL_EDIT
8915
9040
  });
8916
9041
  return [[], [], storageUpdates2];
8917
9042
  }
@@ -8959,7 +9084,8 @@ var LiveObject = (_class2 = class _LiveObject extends AbstractCrdt {
8959
9084
  type: "LiveObject",
8960
9085
  updates: {
8961
9086
  [key]: { type: "delete", deletedItem: oldValue }
8962
- }
9087
+ },
9088
+ source: LOCAL_EDIT
8963
9089
  });
8964
9090
  return [ops, reverse, storageUpdates];
8965
9091
  }
@@ -8979,6 +9105,7 @@ var LiveObject = (_class2 = class _LiveObject extends AbstractCrdt {
8979
9105
  * @param patch The object used to overrides properties
8980
9106
  */
8981
9107
  update(patch) {
9108
+ this._warnIfOrphaned();
8982
9109
  _optionalChain([this, 'access', _235 => _235._pool, 'optionalAccess', _236 => _236.assertStorageIsWritable, 'call', _237 => _237()]);
8983
9110
  if (_LiveObject.detectLargeObjects) {
8984
9111
  const data = {};
@@ -9097,7 +9224,8 @@ var LiveObject = (_class2 = class _LiveObject extends AbstractCrdt {
9097
9224
  storageUpdates.set(this._id, {
9098
9225
  node: this,
9099
9226
  type: "LiveObject",
9100
- updates: updateDelta
9227
+ updates: updateDelta,
9228
+ source: LOCAL_EDIT
9101
9229
  });
9102
9230
  this._pool.dispatch(ops, reverseOps, storageUpdates);
9103
9231
  }
@@ -9179,123 +9307,1313 @@ var LiveObject = (_class2 = class _LiveObject extends AbstractCrdt {
9179
9307
  }
9180
9308
  }, _class2.__initStatic(), _class2);
9181
9309
 
9182
- // src/crdts/liveblocks-helpers.ts
9183
- function creationOpToLiveNode(op) {
9184
- return lsonToLiveNode(creationOpToLson(op));
9185
- }
9186
- function creationOpToLson(op) {
9187
- switch (op.type) {
9188
- case OpCode.CREATE_FILE:
9189
- return new LiveFile(op.data);
9190
- case OpCode.CREATE_REGISTER:
9191
- return op.data;
9192
- case OpCode.CREATE_OBJECT:
9193
- return new LiveObject(op.data);
9194
- case OpCode.CREATE_MAP:
9195
- return new LiveMap();
9196
- case OpCode.CREATE_LIST:
9197
- return new LiveList([]);
9198
- default:
9199
- return assertNever(op, "Unknown creation Op");
9200
- }
9201
- }
9202
- function isSameNodeOrChildOf(node, parent) {
9203
- if (node === parent) {
9310
+ // src/crdts/liveTextOps.ts
9311
+ function attributesEqual(left, right) {
9312
+ if (left === right) {
9204
9313
  return true;
9205
9314
  }
9206
- if (node.parent.type === "HasParent") {
9207
- return isSameNodeOrChildOf(node.parent.node, parent);
9315
+ if (left === void 0 || right === void 0) {
9316
+ return false;
9208
9317
  }
9209
- return false;
9210
- }
9211
- function liveObjectFromNodeStream(nodes) {
9212
- const pool = createManagedPool({
9213
- getCurrentConnectionId: () => {
9214
- throw new Error(
9215
- "Cannot mutate a historic storage version: it is a read-only snapshot"
9216
- );
9318
+ const leftKeys = Object.keys(left);
9319
+ const rightKeys = Object.keys(right);
9320
+ if (leftKeys.length !== rightKeys.length) {
9321
+ return false;
9322
+ }
9323
+ for (const key of leftKeys) {
9324
+ if (left[key] !== right[key]) {
9325
+ return false;
9217
9326
  }
9218
- });
9219
- return LiveObject._fromItems(nodes, pool);
9220
- }
9221
- function deserialize(node, parentToChildren, pool) {
9222
- if (isObjectStorageNode(node)) {
9223
- return LiveObject._deserialize(node, parentToChildren, pool);
9224
- } else if (isListStorageNode(node)) {
9225
- return LiveList._deserialize(node, parentToChildren, pool);
9226
- } else if (isMapStorageNode(node)) {
9227
- return LiveMap._deserialize(node, parentToChildren, pool);
9228
- } else if (isRegisterStorageNode(node)) {
9229
- return LiveRegister._deserialize(node, parentToChildren, pool);
9230
- } else if (isFileStorageNode(node)) {
9231
- return LiveFile._deserialize(node, parentToChildren, pool);
9232
- } else {
9233
- throw new Error("Unexpected CRDT type");
9234
9327
  }
9328
+ return true;
9235
9329
  }
9236
- function deserializeToLson(node, parentToChildren, pool) {
9237
- if (isObjectStorageNode(node)) {
9238
- return LiveObject._deserialize(node, parentToChildren, pool);
9239
- } else if (isListStorageNode(node)) {
9240
- return LiveList._deserialize(node, parentToChildren, pool);
9241
- } else if (isMapStorageNode(node)) {
9242
- return LiveMap._deserialize(node, parentToChildren, pool);
9243
- } else if (isRegisterStorageNode(node)) {
9244
- return node[1].data;
9245
- } else if (isFileStorageNode(node)) {
9246
- return LiveFile._deserialize(node, parentToChildren, pool);
9247
- } else {
9248
- throw new Error("Unexpected CRDT type");
9330
+ function cloneAttributes(attributes) {
9331
+ return attributes === void 0 ? void 0 : freeze({ ...attributes });
9332
+ }
9333
+ function normalizeSegments(segments) {
9334
+ const normalized = [];
9335
+ for (const segment of segments) {
9336
+ if (segment.text.length === 0) {
9337
+ continue;
9338
+ }
9339
+ const last = normalized.at(-1);
9340
+ const attributes = cloneAttributes(segment.attributes);
9341
+ if (last !== void 0 && attributesEqual(last.attributes, attributes)) {
9342
+ last.text += segment.text;
9343
+ } else {
9344
+ normalized.push({ text: segment.text, attributes });
9345
+ }
9249
9346
  }
9347
+ return normalized;
9250
9348
  }
9251
- function isLiveStructure(value) {
9252
- return isLiveList(value) || isLiveMap(value) || isLiveObject(value) || isLiveFile(value);
9349
+ function dataToSegments(data) {
9350
+ return normalizeSegments(
9351
+ data.map(([text, attributes]) => ({
9352
+ text,
9353
+ attributes
9354
+ }))
9355
+ );
9253
9356
  }
9254
- function isLiveNode(value) {
9255
- return isLiveStructure(value) || isLiveRegister(value);
9357
+ function segmentsToData(segments) {
9358
+ return segments.map(
9359
+ (segment) => segment.attributes === void 0 ? [segment.text] : [segment.text, { ...segment.attributes }]
9360
+ );
9256
9361
  }
9257
- function isLiveList(value) {
9258
- return value instanceof LiveList;
9362
+ function textLength(segments) {
9363
+ return segments.reduce((sum, segment) => sum + segment.text.length, 0);
9259
9364
  }
9260
- function isLiveMap(value) {
9261
- return value instanceof LiveMap;
9365
+ function splitSegmentsAt(segments, index) {
9366
+ const result = [];
9367
+ let offset = 0;
9368
+ for (const segment of segments) {
9369
+ const end = offset + segment.text.length;
9370
+ if (index > offset && index < end) {
9371
+ const before2 = segment.text.slice(0, index - offset);
9372
+ const after2 = segment.text.slice(index - offset);
9373
+ result.push({ text: before2, attributes: segment.attributes });
9374
+ result.push({ text: after2, attributes: segment.attributes });
9375
+ } else {
9376
+ result.push({ text: segment.text, attributes: segment.attributes });
9377
+ }
9378
+ offset = end;
9379
+ }
9380
+ return result;
9262
9381
  }
9263
- function isLiveObject(value) {
9264
- return value instanceof LiveObject;
9382
+ function clipRange(index, length, contentLength) {
9383
+ const clippedIndex = Math.max(0, Math.min(index, contentLength));
9384
+ const clippedEnd = Math.max(
9385
+ clippedIndex,
9386
+ Math.min(index + length, contentLength)
9387
+ );
9388
+ return { index: clippedIndex, length: clippedEnd - clippedIndex };
9265
9389
  }
9266
- function isLiveFile(value) {
9267
- return value instanceof LiveFile;
9390
+ function isInSurrogatePair(text, index) {
9391
+ const previous = text.charCodeAt(index - 1);
9392
+ const next = text.charCodeAt(index);
9393
+ return previous >= 55296 && previous <= 56319 && next >= 56320 && next <= 57343;
9268
9394
  }
9269
- function isLiveRegister(value) {
9270
- return value instanceof LiveRegister;
9395
+ function clipIndexToCodePointBoundary(text, index) {
9396
+ const clippedIndex = Math.max(0, Math.min(index, text.length));
9397
+ return isInSurrogatePair(text, clippedIndex) ? clippedIndex - 1 : clippedIndex;
9271
9398
  }
9272
- function cloneLson(value) {
9273
- return value === void 0 ? void 0 : isLiveStructure(value) ? value.clone() : deepClone(value);
9399
+ function clipRangeToCodePointBoundaries(text, index, length) {
9400
+ const clipped = clipRange(index, length, text.length);
9401
+ if (clipped.length === 0) {
9402
+ return {
9403
+ index: clipIndexToCodePointBoundary(text, clipped.index),
9404
+ length: 0
9405
+ };
9406
+ }
9407
+ const clippedEnd = clipped.index + clipped.length;
9408
+ const normalizedIndex = isInSurrogatePair(text, clipped.index) ? clipped.index - 1 : clipped.index;
9409
+ const normalizedEnd = isInSurrogatePair(text, clippedEnd) ? clippedEnd + 1 : clippedEnd;
9410
+ return {
9411
+ index: normalizedIndex,
9412
+ length: normalizedEnd - normalizedIndex
9413
+ };
9274
9414
  }
9275
- function liveNodeToLson(obj) {
9276
- if (obj instanceof LiveRegister) {
9277
- return obj.data;
9278
- } else if (obj instanceof LiveList || obj instanceof LiveMap || obj instanceof LiveObject || obj instanceof LiveFile) {
9279
- return obj;
9280
- } else {
9281
- return assertNever(obj, "Unknown AbstractCrdt");
9415
+ function applyInsert(segments, index, text, attributes) {
9416
+ if (text.length === 0) {
9417
+ return normalizeSegments(segments);
9418
+ }
9419
+ const split = splitSegmentsAt(segments, index);
9420
+ const result = [];
9421
+ let offset = 0;
9422
+ let inserted = false;
9423
+ for (const segment of split) {
9424
+ if (!inserted && offset === index) {
9425
+ result.push({ text, attributes });
9426
+ inserted = true;
9427
+ }
9428
+ result.push(segment);
9429
+ offset += segment.text.length;
9282
9430
  }
9431
+ if (!inserted) {
9432
+ result.push({ text, attributes });
9433
+ }
9434
+ return normalizeSegments(result);
9283
9435
  }
9284
- function lsonToLiveNode(value) {
9285
- if (value instanceof LiveObject || value instanceof LiveMap || value instanceof LiveList || value instanceof LiveFile) {
9286
- return value;
9287
- } else {
9288
- return new LiveRegister(value);
9436
+ function extractDeletedSegments(segments, index, length) {
9437
+ const split = splitSegmentsAt(
9438
+ splitSegmentsAt(segments, index),
9439
+ index + length
9440
+ );
9441
+ const deleted = [];
9442
+ let offset = 0;
9443
+ for (const segment of split) {
9444
+ const end = offset + segment.text.length;
9445
+ if (offset >= index && end <= index + length) {
9446
+ deleted.push({
9447
+ text: segment.text,
9448
+ attributes: segment.attributes
9449
+ });
9450
+ }
9451
+ offset = end;
9289
9452
  }
9453
+ return normalizeSegments(deleted);
9290
9454
  }
9291
- function dumpPool(pool) {
9292
- const rows = Array.from(pool.nodes.values(), (node) => {
9293
- const parent = node.parent;
9294
- const parentId = parent.type === "HasParent" ? _nullishCoalesce(parent.node._id, () => ( "?")) : parent.type === "Orphaned" ? "<orphaned>" : "-";
9295
- let value;
9296
- if (node instanceof LiveRegister) {
9297
- value = stringifyOrLog(node.data);
9298
- } else if (node instanceof LiveList) {
9455
+ function applyDelete(segments, index, length) {
9456
+ const deletedSegments = extractDeletedSegments(segments, index, length);
9457
+ const split = splitSegmentsAt(
9458
+ splitSegmentsAt(segments, index),
9459
+ index + length
9460
+ );
9461
+ const result = [];
9462
+ let offset = 0;
9463
+ let deletedText = "";
9464
+ for (const segment of split) {
9465
+ const end = offset + segment.text.length;
9466
+ if (offset >= index && end <= index + length) {
9467
+ deletedText += segment.text;
9468
+ } else {
9469
+ result.push(segment);
9470
+ }
9471
+ offset = end;
9472
+ }
9473
+ return {
9474
+ segments: normalizeSegments(result),
9475
+ deletedText,
9476
+ deletedSegments
9477
+ };
9478
+ }
9479
+ function applyFormat(segments, index, length, attributes) {
9480
+ const split = splitSegmentsAt(
9481
+ splitSegmentsAt(segments, index),
9482
+ index + length
9483
+ );
9484
+ const result = [];
9485
+ let offset = 0;
9486
+ for (const segment of split) {
9487
+ const end = offset + segment.text.length;
9488
+ if (offset >= index && end <= index + length) {
9489
+ const nextAttributes = {
9490
+ ..._nullishCoalesce(segment.attributes, () => ( {}))
9491
+ };
9492
+ for (const [key, value] of Object.entries(attributes)) {
9493
+ if (value === null) {
9494
+ delete nextAttributes[key];
9495
+ } else {
9496
+ nextAttributes[key] = value;
9497
+ }
9498
+ }
9499
+ result.push({
9500
+ text: segment.text,
9501
+ attributes: Object.keys(nextAttributes).length === 0 ? void 0 : freeze(nextAttributes)
9502
+ });
9503
+ } else {
9504
+ result.push(segment);
9505
+ }
9506
+ offset = end;
9507
+ }
9508
+ return normalizeSegments(result);
9509
+ }
9510
+ function formatReverseOperations(segments, index, length, patch) {
9511
+ const split = splitSegmentsAt(
9512
+ splitSegmentsAt(segments, index),
9513
+ index + length
9514
+ );
9515
+ const result = [];
9516
+ let offset = 0;
9517
+ for (const segment of split) {
9518
+ const end = offset + segment.text.length;
9519
+ if (offset >= index && end <= index + length) {
9520
+ const attributes = {};
9521
+ const current = _nullishCoalesce(segment.attributes, () => ( {}));
9522
+ for (const key of Object.keys(patch)) {
9523
+ const value = Object.hasOwn(current, key) ? current[key] : void 0;
9524
+ attributes[key] = _nullishCoalesce(value, () => ( null));
9525
+ }
9526
+ result.push({
9527
+ type: "format",
9528
+ index: offset,
9529
+ length: segment.text.length,
9530
+ attributes
9531
+ });
9532
+ }
9533
+ offset = end;
9534
+ }
9535
+ return result;
9536
+ }
9537
+ function mapIndexThroughOperation(index, op) {
9538
+ if (op.type === "insert") {
9539
+ return op.index <= index ? index + op.text.length : index;
9540
+ } else if (op.type === "delete") {
9541
+ if (op.index >= index) {
9542
+ return index;
9543
+ }
9544
+ return Math.max(op.index, index - op.length);
9545
+ } else {
9546
+ return index;
9547
+ }
9548
+ }
9549
+ function mapTextIndexThroughOperations(index, ops) {
9550
+ let mapped = index;
9551
+ for (const op of ops) {
9552
+ mapped = mapIndexThroughOperation(mapped, op);
9553
+ }
9554
+ return mapped;
9555
+ }
9556
+ function inverseMapIndexThroughOperation(index, op) {
9557
+ if (op.type === "insert") {
9558
+ if (index <= op.index) {
9559
+ return index;
9560
+ }
9561
+ return Math.max(op.index, index - op.text.length);
9562
+ } else if (op.type === "delete") {
9563
+ return op.index <= index ? index + op.length : index;
9564
+ } else {
9565
+ return index;
9566
+ }
9567
+ }
9568
+ function inverseMapTextIndexThroughOperations(index, ops) {
9569
+ let mapped = index;
9570
+ for (let i = ops.length - 1; i >= 0; i--) {
9571
+ mapped = inverseMapIndexThroughOperation(mapped, ops[i]);
9572
+ }
9573
+ return mapped;
9574
+ }
9575
+ function oppositeOrder(order) {
9576
+ return order === "before" ? "after" : "before";
9577
+ }
9578
+ function mapIndexOverDelete(index, deleteIndex, deleteLength) {
9579
+ if (deleteIndex >= index) {
9580
+ return index;
9581
+ }
9582
+ return Math.max(deleteIndex, index - deleteLength);
9583
+ }
9584
+ function transformInsert(op, over, order) {
9585
+ if (over.type === "insert") {
9586
+ const shifts = over.index < op.index || over.index === op.index && order === "after";
9587
+ return [shifts ? { ...op, index: op.index + over.text.length } : { ...op }];
9588
+ } else if (over.type === "delete") {
9589
+ return [
9590
+ { ...op, index: mapIndexOverDelete(op.index, over.index, over.length) }
9591
+ ];
9592
+ } else {
9593
+ return [{ ...op }];
9594
+ }
9595
+ }
9596
+ function transformDelete(op, over) {
9597
+ const start = op.index;
9598
+ const end = op.index + op.length;
9599
+ if (over.type === "insert") {
9600
+ const at = over.index;
9601
+ const len = over.text.length;
9602
+ if (at <= start) {
9603
+ return [{ ...op, index: start + len }];
9604
+ }
9605
+ if (at >= end) {
9606
+ return [{ ...op }];
9607
+ }
9608
+ return [
9609
+ { type: "delete", index: start, length: at - start },
9610
+ { type: "delete", index: start + len, length: end - at }
9611
+ ];
9612
+ } else if (over.type === "delete") {
9613
+ const newStart = mapIndexOverDelete(start, over.index, over.length);
9614
+ const newEnd = mapIndexOverDelete(end, over.index, over.length);
9615
+ return newEnd - newStart > 0 ? [{ type: "delete", index: newStart, length: newEnd - newStart }] : [];
9616
+ } else {
9617
+ return [{ ...op }];
9618
+ }
9619
+ }
9620
+ function transformFormat(op, over, order) {
9621
+ const start = op.index;
9622
+ const end = op.index + op.length;
9623
+ if (over.type === "insert") {
9624
+ const at = over.index;
9625
+ const len = over.text.length;
9626
+ if (at <= start) {
9627
+ return [{ ...op, index: start + len }];
9628
+ }
9629
+ if (at >= end) {
9630
+ return [{ ...op }];
9631
+ }
9632
+ return [
9633
+ {
9634
+ type: "format",
9635
+ index: start,
9636
+ length: at - start,
9637
+ attributes: op.attributes
9638
+ },
9639
+ {
9640
+ type: "format",
9641
+ index: at + len,
9642
+ length: end - at,
9643
+ attributes: op.attributes
9644
+ }
9645
+ ];
9646
+ } else if (over.type === "delete") {
9647
+ const newStart = mapIndexOverDelete(start, over.index, over.length);
9648
+ const newEnd = mapIndexOverDelete(end, over.index, over.length);
9649
+ return newEnd - newStart > 0 ? [
9650
+ {
9651
+ type: "format",
9652
+ index: newStart,
9653
+ length: newEnd - newStart,
9654
+ attributes: op.attributes
9655
+ }
9656
+ ] : [];
9657
+ } else {
9658
+ if (order === "after") {
9659
+ return [{ ...op }];
9660
+ }
9661
+ const overlapStart = Math.max(start, over.index);
9662
+ const overlapEnd = Math.min(end, over.index + over.length);
9663
+ if (overlapStart >= overlapEnd) {
9664
+ return [{ ...op }];
9665
+ }
9666
+ const hasConflict = Object.keys(op.attributes).some(
9667
+ (key) => Object.hasOwn(over.attributes, key)
9668
+ );
9669
+ if (!hasConflict) {
9670
+ return [{ ...op }];
9671
+ }
9672
+ const reduced = {};
9673
+ for (const [key, value] of Object.entries(op.attributes)) {
9674
+ if (!Object.hasOwn(over.attributes, key)) {
9675
+ reduced[key] = value;
9676
+ }
9677
+ }
9678
+ const pieces = [];
9679
+ if (start < overlapStart) {
9680
+ pieces.push({
9681
+ type: "format",
9682
+ index: start,
9683
+ length: overlapStart - start,
9684
+ attributes: op.attributes
9685
+ });
9686
+ }
9687
+ if (Object.keys(reduced).length > 0) {
9688
+ pieces.push({
9689
+ type: "format",
9690
+ index: overlapStart,
9691
+ length: overlapEnd - overlapStart,
9692
+ attributes: reduced
9693
+ });
9694
+ }
9695
+ if (overlapEnd < end) {
9696
+ pieces.push({
9697
+ type: "format",
9698
+ index: overlapEnd,
9699
+ length: end - overlapEnd,
9700
+ attributes: op.attributes
9701
+ });
9702
+ }
9703
+ return pieces;
9704
+ }
9705
+ }
9706
+ function transformSingle(op, over, order) {
9707
+ switch (op.type) {
9708
+ case "insert":
9709
+ return transformInsert(op, over, order);
9710
+ case "delete":
9711
+ return transformDelete(op, over);
9712
+ case "format":
9713
+ return transformFormat(op, over, order);
9714
+ }
9715
+ }
9716
+ function transformTextOperationsX(a, b, order) {
9717
+ if (a.length === 0 || b.length === 0) {
9718
+ return [[...a], [...b]];
9719
+ }
9720
+ if (a.length === 1 && b.length === 1) {
9721
+ return [
9722
+ transformSingle(a[0], b[0], order),
9723
+ transformSingle(b[0], a[0], oppositeOrder(order))
9724
+ ];
9725
+ }
9726
+ if (a.length > 1) {
9727
+ const [headA1, b1] = transformTextOperationsX([a[0]], b, order);
9728
+ const [restA1, b2] = transformTextOperationsX(a.slice(1), b1, order);
9729
+ return [[...headA1, ...restA1], b2];
9730
+ }
9731
+ const [a1, headB1] = transformTextOperationsX(a, [b[0]], order);
9732
+ const [a2, restB1] = transformTextOperationsX(a1, b.slice(1), order);
9733
+ return [a2, [...headB1, ...restB1]];
9734
+ }
9735
+ function transformTextOperations(ops, over, order) {
9736
+ return transformTextOperationsX(ops, over, order)[0];
9737
+ }
9738
+ function textOperationsEqual(a, b) {
9739
+ return a === b || stableStringify(a) === stableStringify(b);
9740
+ }
9741
+ function applyTextOperationsToSegments(segments, ops) {
9742
+ let next = [...segments];
9743
+ for (const op of ops) {
9744
+ if (op.type === "insert") {
9745
+ const index = Math.max(0, Math.min(op.index, textLength(next)));
9746
+ next = applyInsert(next, index, op.text, op.attributes);
9747
+ } else if (op.type === "delete") {
9748
+ const index = Math.max(0, Math.min(op.index, textLength(next)));
9749
+ const clipped = clipRange(index, op.length, textLength(next));
9750
+ next = applyDelete(next, clipped.index, clipped.length).segments;
9751
+ } else {
9752
+ const index = Math.max(0, Math.min(op.index, textLength(next)));
9753
+ const clipped = clipRange(index, op.length, textLength(next));
9754
+ next = applyFormat(next, clipped.index, clipped.length, op.attributes);
9755
+ }
9756
+ }
9757
+ return next;
9758
+ }
9759
+ function applyLiveTextOperations(data, ops) {
9760
+ return segmentsToData(
9761
+ applyTextOperationsToSegments(dataToSegments(data), ops)
9762
+ );
9763
+ }
9764
+ function normalizeLiveTextOperations(data, operations) {
9765
+ let shadow = dataToSegments(data);
9766
+ const normalized = [];
9767
+ for (const operation of operations) {
9768
+ const text = shadow.map((segment) => segment.text).join("");
9769
+ let normalizedOperation;
9770
+ if (operation.type === "insert") {
9771
+ normalizedOperation = {
9772
+ ...operation,
9773
+ index: clipIndexToCodePointBoundary(text, operation.index)
9774
+ };
9775
+ } else {
9776
+ const range = clipRangeToCodePointBoundaries(
9777
+ text,
9778
+ operation.index,
9779
+ operation.length
9780
+ );
9781
+ normalizedOperation = {
9782
+ ...operation,
9783
+ index: range.index,
9784
+ length: range.length
9785
+ };
9786
+ }
9787
+ normalized.push(normalizedOperation);
9788
+ shadow = applyTextOperationsToSegments(shadow, [normalizedOperation]);
9789
+ }
9790
+ return normalized;
9791
+ }
9792
+ function invertTextOperations(segments, ops) {
9793
+ let shadow = [...segments];
9794
+ const reverse = [];
9795
+ for (const op of ops) {
9796
+ if (op.type === "insert") {
9797
+ shadow = applyInsert(shadow, op.index, op.text, op.attributes);
9798
+ reverse.unshift({
9799
+ type: "delete",
9800
+ index: op.index,
9801
+ length: op.text.length
9802
+ });
9803
+ } else if (op.type === "delete") {
9804
+ const deletedSegments = extractDeletedSegments(
9805
+ shadow,
9806
+ op.index,
9807
+ op.length
9808
+ );
9809
+ shadow = applyDelete(shadow, op.index, op.length).segments;
9810
+ const inserts = [];
9811
+ let insertIndex = op.index;
9812
+ for (const segment of deletedSegments) {
9813
+ inserts.push({
9814
+ type: "insert",
9815
+ index: insertIndex,
9816
+ text: segment.text,
9817
+ attributes: segment.attributes
9818
+ });
9819
+ insertIndex += segment.text.length;
9820
+ }
9821
+ for (let index = inserts.length - 1; index >= 0; index--) {
9822
+ reverse.unshift(inserts[index]);
9823
+ }
9824
+ } else {
9825
+ const inverse = formatReverseOperations(
9826
+ shadow,
9827
+ op.index,
9828
+ op.length,
9829
+ op.attributes
9830
+ );
9831
+ shadow = applyFormat(shadow, op.index, op.length, op.attributes);
9832
+ reverse.unshift(...inverse.reverse());
9833
+ }
9834
+ }
9835
+ return reverse;
9836
+ }
9837
+
9838
+ // src/crdts/LiveText.ts
9839
+ var ACCEPTED_OPS_HISTORY_LIMIT = 1e3;
9840
+ var LiveText = class _LiveText extends AbstractCrdt {
9841
+ /** The local document: #confirmed ⊕ #inFlightOps ⊕ #queuedOps. */
9842
+ #segments;
9843
+ /** The server-confirmed document (only authoritative ops applied). */
9844
+ #confirmed;
9845
+ #version;
9846
+ /** The op currently awaiting server acknowledgement (at most one). */
9847
+ #inFlightOpId;
9848
+ /** Its ops, continuously re-expressed against current server state. */
9849
+ #inFlightOps = [];
9850
+ /** Local edits made while an op is in flight; sent after the ack. */
9851
+ #queuedOps = [];
9852
+ #acceptedOps = [];
9853
+ /**
9854
+ * Creates a new LiveText document.
9855
+ *
9856
+ * @param textOrData Initial plain text, or an array of `[text]` /
9857
+ * `[text, attributes]` segments. Defaults to an empty document.
9858
+ *
9859
+ * @example
9860
+ * new LiveText();
9861
+ * new LiveText("Hello world");
9862
+ * new LiveText([["Hello ", { bold: true }], ["world"]]);
9863
+ */
9864
+ constructor(textOrData = "", version = 0) {
9865
+ super();
9866
+ this.#segments = typeof textOrData === "string" ? textOrData.length === 0 ? [] : [{ text: textOrData }] : dataToSegments(textOrData);
9867
+ this.#confirmed = [...this.#segments];
9868
+ this.#version = version;
9869
+ Object.assign(this[kInternal], {
9870
+ encodeIndex: (localIndex) => this.#encodeIndex(localIndex),
9871
+ decodeIndex: (index, fromVersion) => this.#decodeIndex(index, fromVersion)
9872
+ });
9873
+ }
9874
+ get version() {
9875
+ return this.#version;
9876
+ }
9877
+ get length() {
9878
+ return textLength(this.#segments);
9879
+ }
9880
+ /** @internal */
9881
+ static _deserialize([id, item], _parentToChildren, pool) {
9882
+ const text = new _LiveText(item.data, item.version);
9883
+ text._attach(id, pool);
9884
+ return text;
9885
+ }
9886
+ /** @internal */
9887
+ _toOps(parentId, parentKey) {
9888
+ if (this._id === void 0) {
9889
+ throw new Error("Cannot serialize LiveText if it is not attached");
9890
+ }
9891
+ return [
9892
+ {
9893
+ type: OpCode.CREATE_TEXT,
9894
+ id: this._id,
9895
+ parentId,
9896
+ parentKey,
9897
+ data: this.toJSON(),
9898
+ version: this.#version
9899
+ }
9900
+ ];
9901
+ }
9902
+ /** @internal */
9903
+ _serialize() {
9904
+ if (this.parent.type !== "HasParent") {
9905
+ throw new Error("Cannot serialize LiveText if parent is missing");
9906
+ }
9907
+ return {
9908
+ type: CrdtType.TEXT,
9909
+ parentId: nn(this.parent.node._id, "Parent node expected to have ID"),
9910
+ parentKey: this.parent.key,
9911
+ data: this.toJSON(),
9912
+ version: this.#version
9913
+ };
9914
+ }
9915
+ /** @internal */
9916
+ _attachChild(_op) {
9917
+ throw new Error("LiveText cannot contain child nodes");
9918
+ }
9919
+ /** @internal */
9920
+ _detachChild(_crdt) {
9921
+ throw new Error("LiveText cannot contain child nodes");
9922
+ }
9923
+ /** @internal */
9924
+ _apply(op, source) {
9925
+ if (op.type !== OpCode.UPDATE_TEXT) {
9926
+ return super._apply(op, source);
9927
+ }
9928
+ if (source.origin === "local" && source.optimistic) {
9929
+ return this.#applyLocal(op, toUpdateSource(source));
9930
+ }
9931
+ if (op.opId !== void 0 && op.opId === this.#inFlightOpId) {
9932
+ return this.#applyAck(op, toUpdateSource(source));
9933
+ }
9934
+ if (op.opId !== void 0 && this.#acceptedOps.some((entry) => entry.opId === op.opId)) {
9935
+ this.#version = Math.max(this.#version, _nullishCoalesce(op.version, () => ( op.baseVersion + 1)));
9936
+ return { modified: false };
9937
+ }
9938
+ return this.#applyRemote(op, toUpdateSource(source));
9939
+ }
9940
+ /**
9941
+ * Inserts text at the given index.
9942
+ *
9943
+ * @param index Character index at which to insert. Values outside the
9944
+ * document range are clipped.
9945
+ * @param text Text to insert.
9946
+ * @param attributes Optional inline attributes for the inserted text.
9947
+ *
9948
+ * @example
9949
+ * const text = new LiveText("Hello");
9950
+ * text.insert(5, " world");
9951
+ * text.insert(0, "Say: ", { italic: true });
9952
+ */
9953
+ insert(index, text, attributes) {
9954
+ const clippedIndex = clipIndexToCodePointBoundary(this.toString(), index);
9955
+ this.#dispatch([{ type: "insert", index: clippedIndex, text, attributes }]);
9956
+ }
9957
+ /**
9958
+ * Deletes `length` characters starting at `index`.
9959
+ *
9960
+ * @example
9961
+ * const text = new LiveText("Hello world");
9962
+ * text.delete(5, 6); // "Hello"
9963
+ */
9964
+ delete(index, length) {
9965
+ const clipped = clipRangeToCodePointBoundaries(
9966
+ this.toString(),
9967
+ index,
9968
+ length
9969
+ );
9970
+ if (clipped.length === 0) {
9971
+ return;
9972
+ }
9973
+ this.#dispatch([
9974
+ { type: "delete", index: clipped.index, length: clipped.length }
9975
+ ]);
9976
+ }
9977
+ /**
9978
+ * Replaces a range of text with new text.
9979
+ *
9980
+ * @example
9981
+ * const text = new LiveText("Hello world");
9982
+ * text.replace(0, 5, "Hi"); // "Hi world"
9983
+ */
9984
+ replace(index, length, text, attributes) {
9985
+ const clipped = clipRangeToCodePointBoundaries(
9986
+ this.toString(),
9987
+ index,
9988
+ length
9989
+ );
9990
+ const ops = [];
9991
+ if (clipped.length > 0) {
9992
+ ops.push({
9993
+ type: "delete",
9994
+ index: clipped.index,
9995
+ length: clipped.length
9996
+ });
9997
+ }
9998
+ if (text.length > 0) {
9999
+ ops.push({ type: "insert", index: clipped.index, text, attributes });
10000
+ }
10001
+ this.#dispatch(ops);
10002
+ }
10003
+ /**
10004
+ * Encode a local-document index (an offset into this LiveText's current
10005
+ * #segments, which CodeMirror or any consumer mirrors as its document)
10006
+ * into server-confirmed coordinates suitable for broadcasting to peers via
10007
+ * presence or any other side channel.
10008
+ *
10009
+ * The returned index is in this LiveText's current #confirmed coordinates
10010
+ * — that is, with this client's local pending ops inverse-mapped out.
10011
+ * Pair it with the current {@link LiveText.version} when sending so the
10012
+ * receiver can call {@link PrivateLiveTextApi.decodeIndex} to land the
10013
+ * position in their own local document coordinates regardless of their
10014
+ * private pending ops.
10015
+ *
10016
+ * Index ambiguity at boundaries is resolved by an inverse-of-forward
10017
+ * convention: a position at or before a local insertion is reported as
10018
+ * the position right before the insertion in #confirmed; a position past
10019
+ * the insertion shifts left by the insertion's length. Positions inside
10020
+ * an own-pending insertion collapse to the insertion point.
10021
+ */
10022
+ #encodeIndex(localIndex) {
10023
+ let mapped = Math.max(0, Math.min(localIndex, this.length));
10024
+ mapped = inverseMapTextIndexThroughOperations(mapped, this.#queuedOps);
10025
+ mapped = inverseMapTextIndexThroughOperations(mapped, this.#inFlightOps);
10026
+ return mapped;
10027
+ }
10028
+ /**
10029
+ * Decode an `(index, fromVersion)` pair produced by
10030
+ * {@link PrivateLiveTextApi.encodeIndex} — typically on a peer — into an
10031
+ * offset in this LiveText's current local document (an index suitable for
10032
+ * placing a CodeMirror marker, an annotation anchor, or anything else that
10033
+ * lives over #segments).
10034
+ *
10035
+ * Composes the accepted ops applied since `fromVersion` (drawn from
10036
+ * #acceptedOps in locally-applied form) with this client's own local
10037
+ * pending ops, in that order. The result is in current #segments
10038
+ * coordinates.
10039
+ *
10040
+ * Returns `null` when the position cannot be decoded against the current
10041
+ * state:
10042
+ * - `fromVersion` is greater than this LiveText's current version: the
10043
+ * peer is ahead of us. The caller should park the message and retry
10044
+ * after more accepted ops arrive.
10045
+ * - `fromVersion` falls outside the retained accepted-ops history. This
10046
+ * only happens after very long-lived disconnections; the caller can
10047
+ * fall back to using the raw index and letting subsequent local
10048
+ * transactions map it (with bounded drift).
10049
+ */
10050
+ #decodeIndex(index, fromVersion) {
10051
+ if (fromVersion > this.#version) {
10052
+ return null;
10053
+ }
10054
+ if (fromVersion < this.#version) {
10055
+ const oldest = _optionalChain([this, 'access', _238 => _238.#acceptedOps, 'access', _239 => _239[0], 'optionalAccess', _240 => _240.version]);
10056
+ if (oldest === void 0 || oldest > fromVersion + 1) {
10057
+ return null;
10058
+ }
10059
+ }
10060
+ let mapped = index;
10061
+ for (const entry of this.#acceptedOps) {
10062
+ if (entry.version <= fromVersion) continue;
10063
+ if (entry.version > this.#version) break;
10064
+ if (entry.ops.length === 0) continue;
10065
+ mapped = mapTextIndexThroughOperations(mapped, entry.ops);
10066
+ }
10067
+ mapped = mapTextIndexThroughOperations(mapped, this.#inFlightOps);
10068
+ mapped = mapTextIndexThroughOperations(mapped, this.#queuedOps);
10069
+ return Math.max(0, Math.min(mapped, this.length));
10070
+ }
10071
+ /**
10072
+ * Applies or removes inline attributes on a range of text.
10073
+ *
10074
+ * Set an attribute to `null` to remove it from the range.
10075
+ *
10076
+ * @example
10077
+ * const text = new LiveText("Hello world");
10078
+ * text.format(0, 5, { bold: true });
10079
+ * text.format(0, 5, { bold: null });
10080
+ */
10081
+ format(index, length, attributes) {
10082
+ const clipped = clipRangeToCodePointBoundaries(
10083
+ this.toString(),
10084
+ index,
10085
+ length
10086
+ );
10087
+ if (clipped.length === 0) {
10088
+ return;
10089
+ }
10090
+ this.#dispatch([
10091
+ {
10092
+ type: "format",
10093
+ index: clipped.index,
10094
+ length: clipped.length,
10095
+ attributes
10096
+ }
10097
+ ]);
10098
+ }
10099
+ /** Local edits made through the public API. */
10100
+ #dispatch(ops) {
10101
+ if (ops.length === 0) {
10102
+ return;
10103
+ }
10104
+ this._warnIfOrphaned();
10105
+ _optionalChain([this, 'access', _241 => _241._pool, 'optionalAccess', _242 => _242.assertStorageIsWritable, 'call', _243 => _243()]);
10106
+ const attached = this._pool !== void 0 && this._id !== void 0;
10107
+ const reverse = attached ? this.#invertOperations(ops) : [];
10108
+ const changes = this.#applyOperationsLocally(ops);
10109
+ if (!attached) {
10110
+ return;
10111
+ }
10112
+ const pool = nn(this._pool);
10113
+ const id = nn(this._id);
10114
+ const updates = /* @__PURE__ */ new Map([
10115
+ [
10116
+ id,
10117
+ {
10118
+ type: "LiveText",
10119
+ node: this,
10120
+ version: this.#version,
10121
+ updates: changes,
10122
+ source: LOCAL_EDIT
10123
+ }
10124
+ ]
10125
+ ]);
10126
+ if (this.#inFlightOpId === void 0) {
10127
+ const opId = pool.generateOpId();
10128
+ this.#inFlightOpId = opId;
10129
+ this.#inFlightOps = [...ops];
10130
+ pool.dispatch(
10131
+ [
10132
+ {
10133
+ type: OpCode.UPDATE_TEXT,
10134
+ id,
10135
+ opId,
10136
+ baseVersion: this.#version,
10137
+ ops: [...ops]
10138
+ }
10139
+ ],
10140
+ reverse,
10141
+ updates
10142
+ );
10143
+ } else {
10144
+ this.#queuedOps.push(...ops);
10145
+ pool.dispatch([], reverse, updates, { clearRedoStack: true });
10146
+ }
10147
+ }
10148
+ /**
10149
+ * A local replay of an existing wire op: an undo/redo frame, or an
10150
+ * unacknowledged op re-sent after a reconnect.
10151
+ */
10152
+ #applyLocal(op, source) {
10153
+ const mutableOp = op;
10154
+ if (op.opId !== void 0 && op.opId === this.#inFlightOpId) {
10155
+ this.#inFlightOps = [...this.#inFlightOps, ...this.#queuedOps];
10156
+ this.#queuedOps = [];
10157
+ mutableOp.baseVersion = this.#version;
10158
+ mutableOp.ops = [...this.#inFlightOps];
10159
+ return { modified: false };
10160
+ }
10161
+ let ops = op.ops;
10162
+ for (const entry of this.#acceptedOps) {
10163
+ if (entry.version > op.baseVersion && entry.ops.length > 0) {
10164
+ ops = transformTextOperations(ops, entry.ops, "after");
10165
+ }
10166
+ }
10167
+ const reverse = this.#invertOperations(ops);
10168
+ const changes = this.#applyOperationsLocally(ops);
10169
+ if (this.#inFlightOpId === void 0 && ops.length > 0) {
10170
+ this.#inFlightOpId = nn(op.opId, "Local ops must have an opId");
10171
+ this.#inFlightOps = [...ops];
10172
+ mutableOp.baseVersion = this.#version;
10173
+ mutableOp.ops = [...ops];
10174
+ } else {
10175
+ this.#queuedOps.push(...ops);
10176
+ mutableOp.baseVersion = this.#version;
10177
+ mutableOp.ops = [];
10178
+ }
10179
+ if (changes.length === 0) {
10180
+ return { modified: false };
10181
+ }
10182
+ return {
10183
+ reverse,
10184
+ modified: {
10185
+ type: "LiveText",
10186
+ node: this,
10187
+ version: this.#version,
10188
+ updates: changes,
10189
+ source
10190
+ }
10191
+ };
10192
+ }
10193
+ /** Server acknowledgement of our in-flight op. */
10194
+ #applyAck(op, source) {
10195
+ const ackedVersion = _nullishCoalesce(op.version, () => ( Math.max(this.#version, op.baseVersion + 1)));
10196
+ const predicted = this.#inFlightOps;
10197
+ const opId = this.#inFlightOpId;
10198
+ this.#confirmed = applyTextOperationsToSegments(this.#confirmed, op.ops);
10199
+ this.#inFlightOpId = void 0;
10200
+ this.#inFlightOps = [];
10201
+ let appliedOps = [];
10202
+ let result = { modified: false };
10203
+ if (!textOperationsEqual(op.ops, predicted)) {
10204
+ error2(
10205
+ "LiveText: acknowledgement did not match the local prediction; resynchronizing"
10206
+ );
10207
+ const rebuilt = this.#rebuildLocalFromConfirmed();
10208
+ appliedOps = rebuilt.appliedOps;
10209
+ if (rebuilt.changes.length > 0) {
10210
+ result = {
10211
+ reverse: [],
10212
+ modified: {
10213
+ type: "LiveText",
10214
+ node: this,
10215
+ version: ackedVersion,
10216
+ updates: rebuilt.changes,
10217
+ source
10218
+ }
10219
+ };
10220
+ }
10221
+ }
10222
+ this.#version = Math.max(this.#version, ackedVersion);
10223
+ this.#recordAccepted(ackedVersion, appliedOps, opId);
10224
+ this.#flushQueued();
10225
+ return result;
10226
+ }
10227
+ /** An accepted op from another client (or a server-fabricated fix op). */
10228
+ #applyRemote(op, source) {
10229
+ const version = _nullishCoalesce(op.version, () => ( this.#version + 1));
10230
+ this.#confirmed = applyTextOperationsToSegments(this.#confirmed, op.ops);
10231
+ const [overInFlight, inFlight] = transformTextOperationsX(
10232
+ op.ops,
10233
+ this.#inFlightOps,
10234
+ "before"
10235
+ );
10236
+ const [applied, queued] = transformTextOperationsX(
10237
+ overInFlight,
10238
+ this.#queuedOps,
10239
+ "before"
10240
+ );
10241
+ this.#inFlightOps = inFlight;
10242
+ this.#queuedOps = queued;
10243
+ this.#recordAccepted(version, applied, op.opId);
10244
+ if (applied.length === 0) {
10245
+ this.#version = Math.max(this.#version, version);
10246
+ return { modified: false };
10247
+ }
10248
+ const reverse = this.#invertOperations(applied);
10249
+ const changes = this.#applyOperationsLocally(applied);
10250
+ this.#version = Math.max(this.#version, version);
10251
+ return {
10252
+ reverse,
10253
+ modified: {
10254
+ type: "LiveText",
10255
+ node: this,
10256
+ version: this.#version,
10257
+ updates: changes,
10258
+ source
10259
+ }
10260
+ };
10261
+ }
10262
+ /** Send the queued ops as the next in-flight op (after an ack). */
10263
+ #flushQueued() {
10264
+ if (this.#queuedOps.length === 0 || this._pool === void 0 || this._id === void 0) {
10265
+ return;
10266
+ }
10267
+ const opId = this._pool.generateOpId();
10268
+ this.#inFlightOpId = opId;
10269
+ this.#inFlightOps = this.#queuedOps;
10270
+ this.#queuedOps = [];
10271
+ this._pool.dispatch(
10272
+ [
10273
+ {
10274
+ type: OpCode.UPDATE_TEXT,
10275
+ id: this._id,
10276
+ opId,
10277
+ baseVersion: this.#version,
10278
+ ops: [...this.#inFlightOps]
10279
+ }
10280
+ ],
10281
+ [],
10282
+ /* @__PURE__ */ new Map(),
10283
+ // The local content was already applied (and made undoable) when the
10284
+ // edits happened; this is purely an outbound flush.
10285
+ { clearRedoStack: false }
10286
+ );
10287
+ }
10288
+ /**
10289
+ * Rebuild the local document as confirmed ⊕ queued ops, returning the
10290
+ * coarse delta that was applied. Only used by defensive recovery paths.
10291
+ */
10292
+ #rebuildLocalFromConfirmed() {
10293
+ const before2 = this.#segments;
10294
+ const after2 = applyTextOperationsToSegments(this.#confirmed, [
10295
+ ...this.#inFlightOps,
10296
+ ...this.#queuedOps
10297
+ ]);
10298
+ if (stableStringify(segmentsToData(before2)) === stableStringify(segmentsToData(after2))) {
10299
+ this.#segments = after2;
10300
+ return { appliedOps: [], changes: [] };
10301
+ }
10302
+ const beforeText = before2.map((segment) => segment.text).join("");
10303
+ this.#segments = after2;
10304
+ this.invalidate();
10305
+ const appliedOps = [];
10306
+ const changes = [];
10307
+ if (beforeText.length > 0) {
10308
+ appliedOps.push({ type: "delete", index: 0, length: beforeText.length });
10309
+ changes.push({
10310
+ type: "delete",
10311
+ index: 0,
10312
+ length: beforeText.length,
10313
+ deletedText: beforeText
10314
+ });
10315
+ }
10316
+ let index = 0;
10317
+ for (const segment of after2) {
10318
+ appliedOps.push({
10319
+ type: "insert",
10320
+ index,
10321
+ text: segment.text,
10322
+ attributes: segment.attributes
10323
+ });
10324
+ changes.push({
10325
+ type: "insert",
10326
+ index,
10327
+ text: segment.text,
10328
+ attributes: segment.attributes
10329
+ });
10330
+ index += segment.text.length;
10331
+ }
10332
+ return { appliedOps, changes };
10333
+ }
10334
+ /**
10335
+ * Reconcile this node against an authoritative storage snapshot (e.g.
10336
+ * after a reconnect). The confirmed state and version are replaced by the
10337
+ * snapshot's; pending (in-flight + queued) ops are preserved on top and
10338
+ * will be re-sent by the offline-ops replay.
10339
+ *
10340
+ * @internal
10341
+ */
10342
+ _resyncText(data, version, source) {
10343
+ this.#confirmed = dataToSegments(data);
10344
+ this.#version = version;
10345
+ this.#acceptedOps = [];
10346
+ const rebuilt = this.#rebuildLocalFromConfirmed();
10347
+ if (rebuilt.changes.length === 0) {
10348
+ return void 0;
10349
+ }
10350
+ return {
10351
+ type: "LiveText",
10352
+ node: this,
10353
+ version: this.#version,
10354
+ updates: rebuilt.changes,
10355
+ source
10356
+ };
10357
+ }
10358
+ /**
10359
+ * Called when the server rejected one of our ops. Drops all pending state
10360
+ * for this node (edits queued behind a rejected op cannot be trusted
10361
+ * either); the room follows up with a storage resync.
10362
+ *
10363
+ * @internal
10364
+ */
10365
+ _rejectPendingOp(opId) {
10366
+ if (opId !== this.#inFlightOpId) {
10367
+ return;
10368
+ }
10369
+ this.#inFlightOpId = void 0;
10370
+ this.#inFlightOps = [];
10371
+ this.#queuedOps = [];
10372
+ }
10373
+ #recordAccepted(version, ops, opId) {
10374
+ if (this.#acceptedOps.some((entry) => entry.version === version)) {
10375
+ return;
10376
+ }
10377
+ this.#acceptedOps.push({ version, opId, ops: [...ops] });
10378
+ this.#acceptedOps.sort((left, right) => left.version - right.version);
10379
+ if (this.#acceptedOps.length > ACCEPTED_OPS_HISTORY_LIMIT) {
10380
+ this.#acceptedOps.splice(
10381
+ 0,
10382
+ this.#acceptedOps.length - ACCEPTED_OPS_HISTORY_LIMIT
10383
+ );
10384
+ }
10385
+ }
10386
+ #applyOperationsLocally(ops) {
10387
+ const changes = [];
10388
+ for (const op of ops) {
10389
+ if (op.type === "insert") {
10390
+ this.#segments = applyInsert(
10391
+ this.#segments,
10392
+ op.index,
10393
+ op.text,
10394
+ op.attributes
10395
+ );
10396
+ changes.push({
10397
+ type: "insert",
10398
+ index: op.index,
10399
+ text: op.text,
10400
+ attributes: op.attributes
10401
+ });
10402
+ } else if (op.type === "delete") {
10403
+ const result = applyDelete(this.#segments, op.index, op.length);
10404
+ this.#segments = result.segments;
10405
+ changes.push({
10406
+ type: "delete",
10407
+ index: op.index,
10408
+ length: op.length,
10409
+ deletedText: result.deletedText
10410
+ });
10411
+ } else {
10412
+ this.#segments = applyFormat(
10413
+ this.#segments,
10414
+ op.index,
10415
+ op.length,
10416
+ op.attributes
10417
+ );
10418
+ changes.push({
10419
+ type: "format",
10420
+ index: op.index,
10421
+ length: op.length,
10422
+ attributes: op.attributes
10423
+ });
10424
+ }
10425
+ }
10426
+ this.invalidate();
10427
+ return changes;
10428
+ }
10429
+ #invertOperations(ops) {
10430
+ return [
10431
+ {
10432
+ type: OpCode.UPDATE_TEXT,
10433
+ id: nn(this._id),
10434
+ baseVersion: this.#version,
10435
+ ops: invertTextOperations(this.#segments, ops)
10436
+ }
10437
+ ];
10438
+ }
10439
+ /** Returns the plain text content without attributes. Equivalent to joining the text from each segment in {@link LiveText.toJSON}. */
10440
+ toString() {
10441
+ return this.#segments.map((segment) => segment.text).join("");
10442
+ }
10443
+ /**
10444
+ * Returns a JSON-compatible snapshot of the document as a {@link LiveTextData}
10445
+ * array.
10446
+ *
10447
+ * @example
10448
+ * new LiveText([["Hello ", { bold: true }], ["world"]]).toJSON();
10449
+ * // [["Hello ", { bold: true }], ["world"]]
10450
+ */
10451
+ toJSON() {
10452
+ return super.toJSON();
10453
+ }
10454
+ /** @internal */
10455
+ _toJSON() {
10456
+ return segmentsToData(this.#segments);
10457
+ }
10458
+ /** @internal */
10459
+ toTreeNode(key) {
10460
+ return super.toTreeNode(key);
10461
+ }
10462
+ /** @internal */
10463
+ _toTreeNode(key) {
10464
+ const nodeId = _nullishCoalesce(this._id, () => ( nanoid()));
10465
+ const payload = this.toJSON().map(
10466
+ (segment, index) => ({
10467
+ type: "Json",
10468
+ id: `${nodeId}:${index}`,
10469
+ key: String(index),
10470
+ payload: segment
10471
+ })
10472
+ );
10473
+ payload.push({
10474
+ type: "Json",
10475
+ id: `${nodeId}:version`,
10476
+ key: "version",
10477
+ payload: this.version
10478
+ });
10479
+ return {
10480
+ type: "LiveText",
10481
+ id: nodeId,
10482
+ key,
10483
+ payload
10484
+ };
10485
+ }
10486
+ clone() {
10487
+ return new _LiveText(this.toJSON(), this.#version);
10488
+ }
10489
+ };
10490
+
10491
+ // src/crdts/liveblocks-helpers.ts
10492
+ function creationOpToLiveNode(op) {
10493
+ return lsonToLiveNode(creationOpToLson(op));
10494
+ }
10495
+ function creationOpToLson(op) {
10496
+ switch (op.type) {
10497
+ case OpCode.CREATE_FILE:
10498
+ return new LiveFile(op.data);
10499
+ case OpCode.CREATE_REGISTER:
10500
+ return op.data;
10501
+ case OpCode.CREATE_OBJECT:
10502
+ return new LiveObject(op.data);
10503
+ case OpCode.CREATE_MAP:
10504
+ return new LiveMap();
10505
+ case OpCode.CREATE_LIST:
10506
+ return new LiveList([]);
10507
+ case OpCode.CREATE_TEXT:
10508
+ return new LiveText(op.data, op.version);
10509
+ default:
10510
+ return assertNever(op, "Unknown creation Op");
10511
+ }
10512
+ }
10513
+ function isSameNodeOrChildOf(node, parent) {
10514
+ if (node === parent) {
10515
+ return true;
10516
+ }
10517
+ if (node.parent.type === "HasParent") {
10518
+ return isSameNodeOrChildOf(node.parent.node, parent);
10519
+ }
10520
+ return false;
10521
+ }
10522
+ function liveObjectFromNodeStream(nodes) {
10523
+ const pool = createManagedPool({
10524
+ getCurrentConnectionId: () => {
10525
+ throw new Error(
10526
+ "Cannot mutate a historic storage version: it is a read-only snapshot"
10527
+ );
10528
+ }
10529
+ });
10530
+ return LiveObject._fromItems(nodes, pool);
10531
+ }
10532
+ function deserialize(node, parentToChildren, pool) {
10533
+ if (isObjectStorageNode(node)) {
10534
+ return LiveObject._deserialize(node, parentToChildren, pool);
10535
+ } else if (isListStorageNode(node)) {
10536
+ return LiveList._deserialize(node, parentToChildren, pool);
10537
+ } else if (isMapStorageNode(node)) {
10538
+ return LiveMap._deserialize(node, parentToChildren, pool);
10539
+ } else if (isRegisterStorageNode(node)) {
10540
+ return LiveRegister._deserialize(node, parentToChildren, pool);
10541
+ } else if (isTextStorageNode(node)) {
10542
+ return LiveText._deserialize(node, parentToChildren, pool);
10543
+ } else if (isFileStorageNode(node)) {
10544
+ return LiveFile._deserialize(node, parentToChildren, pool);
10545
+ } else {
10546
+ throw new Error("Unexpected CRDT type");
10547
+ }
10548
+ }
10549
+ function deserializeToLson(node, parentToChildren, pool) {
10550
+ if (isObjectStorageNode(node)) {
10551
+ return LiveObject._deserialize(node, parentToChildren, pool);
10552
+ } else if (isListStorageNode(node)) {
10553
+ return LiveList._deserialize(node, parentToChildren, pool);
10554
+ } else if (isMapStorageNode(node)) {
10555
+ return LiveMap._deserialize(node, parentToChildren, pool);
10556
+ } else if (isRegisterStorageNode(node)) {
10557
+ return node[1].data;
10558
+ } else if (isTextStorageNode(node)) {
10559
+ return LiveText._deserialize(node, parentToChildren, pool);
10560
+ } else if (isFileStorageNode(node)) {
10561
+ return LiveFile._deserialize(node, parentToChildren, pool);
10562
+ } else {
10563
+ throw new Error("Unexpected CRDT type");
10564
+ }
10565
+ }
10566
+ function isLiveStructure(value) {
10567
+ return isLiveList(value) || isLiveMap(value) || isLiveObject(value) || isLiveText(value) || isLiveFile(value);
10568
+ }
10569
+ function isLiveNode(value) {
10570
+ return isLiveStructure(value) || isLiveRegister(value);
10571
+ }
10572
+ function isLiveList(value) {
10573
+ return value instanceof LiveList;
10574
+ }
10575
+ function isLiveMap(value) {
10576
+ return value instanceof LiveMap;
10577
+ }
10578
+ function isLiveObject(value) {
10579
+ return value instanceof LiveObject;
10580
+ }
10581
+ function isLiveText(value) {
10582
+ return value instanceof LiveText;
10583
+ }
10584
+ function isLiveFile(value) {
10585
+ return value instanceof LiveFile;
10586
+ }
10587
+ function isLiveRegister(value) {
10588
+ return value instanceof LiveRegister;
10589
+ }
10590
+ function cloneLson(value) {
10591
+ return value === void 0 ? void 0 : isLiveStructure(value) ? value.clone() : deepClone(value);
10592
+ }
10593
+ function liveNodeToLson(obj) {
10594
+ if (obj instanceof LiveRegister) {
10595
+ return obj.data;
10596
+ } else if (obj instanceof LiveList || obj instanceof LiveMap || obj instanceof LiveObject || obj instanceof LiveText || obj instanceof LiveFile) {
10597
+ return obj;
10598
+ } else {
10599
+ return assertNever(obj, "Unknown AbstractCrdt");
10600
+ }
10601
+ }
10602
+ function lsonToLiveNode(value) {
10603
+ if (value instanceof LiveObject || value instanceof LiveMap || value instanceof LiveList || value instanceof LiveText || value instanceof LiveFile) {
10604
+ return value;
10605
+ } else {
10606
+ return new LiveRegister(value);
10607
+ }
10608
+ }
10609
+ function dumpPool(pool) {
10610
+ const rows = Array.from(pool.nodes.values(), (node) => {
10611
+ const parent = node.parent;
10612
+ const parentId = parent.type === "HasParent" ? _nullishCoalesce(parent.node._id, () => ( "?")) : parent.type === "Orphaned" ? "<orphaned>" : "-";
10613
+ let value;
10614
+ if (node instanceof LiveRegister) {
10615
+ value = stringifyOrLog(node.data);
10616
+ } else if (node instanceof LiveList) {
9299
10617
  value = "<LiveList>";
9300
10618
  } else if (node instanceof LiveMap) {
9301
10619
  value = "<LiveMap>";
@@ -9344,7 +10662,26 @@ function isJsonEq(a, b) {
9344
10662
  }
9345
10663
  return true;
9346
10664
  }
9347
- function diffNodeMap(prev, next) {
10665
+ function liveTextDataToReplaceOps(before2, after2) {
10666
+ const ops = [];
10667
+ const beforeLength = before2.reduce(
10668
+ (length, [text]) => length + text.length,
10669
+ 0
10670
+ );
10671
+ if (beforeLength > 0) {
10672
+ ops.push({ type: "delete", index: 0, length: beforeLength });
10673
+ }
10674
+ let index = 0;
10675
+ for (const [text, attributes] of after2) {
10676
+ if (text.length === 0) {
10677
+ continue;
10678
+ }
10679
+ ops.push({ type: "insert", index, text, attributes });
10680
+ index += text.length;
10681
+ }
10682
+ return ops;
10683
+ }
10684
+ function diffNodeMap(prev, next, options) {
9348
10685
  const ops = [];
9349
10686
  const idsToRecreate = /* @__PURE__ */ new Set();
9350
10687
  next.forEach((nextCrdt, id) => {
@@ -9436,6 +10773,16 @@ function diffNodeMap(prev, next) {
9436
10773
  parentKey: crdt.parentKey
9437
10774
  });
9438
10775
  break;
10776
+ case CrdtType.TEXT:
10777
+ ops.push({
10778
+ type: OpCode.CREATE_TEXT,
10779
+ id,
10780
+ parentId: crdt.parentId,
10781
+ parentKey: crdt.parentKey,
10782
+ data: crdt.data,
10783
+ version: crdt.version
10784
+ });
10785
+ break;
9439
10786
  }
9440
10787
  }
9441
10788
  next.forEach((crdt, id) => {
@@ -9462,6 +10809,17 @@ function diffNodeMap(prev, next) {
9462
10809
  }
9463
10810
  }
9464
10811
  }
10812
+ if (_optionalChain([options, 'optionalAccess', _244 => _244.includeLiveTextUpdates]) === true && crdt.type === CrdtType.TEXT && currentCrdt.type === CrdtType.TEXT && !isJsonEq(crdt.data, currentCrdt.data)) {
10813
+ ops.push({
10814
+ type: OpCode.UPDATE_TEXT,
10815
+ id,
10816
+ // A restore is a new edit in the current timeline. The version from
10817
+ // the historic snapshot describes its old timeline and must not move
10818
+ // this node's current version backwards.
10819
+ baseVersion: currentCrdt.version,
10820
+ ops: liveTextDataToReplaceOps(currentCrdt.data, crdt.data)
10821
+ });
10822
+ }
9465
10823
  if (crdt.parentKey !== currentCrdt.parentKey) {
9466
10824
  ops.push({
9467
10825
  type: OpCode.SET_PARENT_KEY,
@@ -9502,19 +10860,36 @@ function mergeListStorageUpdates(first, second) {
9502
10860
  updates: updates.concat(second.updates)
9503
10861
  };
9504
10862
  }
10863
+ function mergeTextStorageUpdates(first, second) {
10864
+ return {
10865
+ ...second,
10866
+ updates: first.updates.concat(second.updates)
10867
+ };
10868
+ }
10869
+ function mergeUpdateSources(first, second) {
10870
+ if (first.origin === "remote" || second.origin === "remote") {
10871
+ return REMOTE;
10872
+ }
10873
+ if (second.via !== "edit") return second;
10874
+ if (first.via !== "edit") return first;
10875
+ return LOCAL_EDIT;
10876
+ }
9505
10877
  function mergeStorageUpdates(first, second) {
9506
10878
  if (first === void 0) {
9507
10879
  return second;
9508
10880
  }
10881
+ const source = mergeUpdateSources(first.source, second.source);
9509
10882
  if (first.type === "LiveObject" && second.type === "LiveObject") {
9510
- return mergeObjectStorageUpdates(first, second);
10883
+ return { ...mergeObjectStorageUpdates(first, second), source };
9511
10884
  } else if (first.type === "LiveMap" && second.type === "LiveMap") {
9512
- return mergeMapStorageUpdates(first, second);
10885
+ return { ...mergeMapStorageUpdates(first, second), source };
9513
10886
  } else if (first.type === "LiveList" && second.type === "LiveList") {
9514
- return mergeListStorageUpdates(first, second);
10887
+ return { ...mergeListStorageUpdates(first, second), source };
10888
+ } else if (first.type === "LiveText" && second.type === "LiveText") {
10889
+ return { ...mergeTextStorageUpdates(first, second), source };
9515
10890
  } else {
10891
+ return { ...second, source };
9516
10892
  }
9517
- return second;
9518
10893
  }
9519
10894
 
9520
10895
  // src/devtools/bridge.ts
@@ -9530,7 +10905,7 @@ function sendToPanel(message, options) {
9530
10905
  ...message,
9531
10906
  source: "liveblocks-devtools-client"
9532
10907
  };
9533
- if (!(_optionalChain([options, 'optionalAccess', _238 => _238.force]) || _bridgeActive)) {
10908
+ if (!(_optionalChain([options, 'optionalAccess', _245 => _245.force]) || _bridgeActive)) {
9534
10909
  return;
9535
10910
  }
9536
10911
  window.postMessage(fullMsg, "*");
@@ -9538,7 +10913,7 @@ function sendToPanel(message, options) {
9538
10913
  var eventSource = makeEventSource();
9539
10914
  if (process.env.NODE_ENV !== "production" && typeof window !== "undefined") {
9540
10915
  window.addEventListener("message", (event) => {
9541
- if (event.source === window && _optionalChain([event, 'access', _239 => _239.data, 'optionalAccess', _240 => _240.source]) === "liveblocks-devtools-panel") {
10916
+ if (event.source === window && _optionalChain([event, 'access', _246 => _246.data, 'optionalAccess', _247 => _247.source]) === "liveblocks-devtools-panel") {
9542
10917
  eventSource.notify(event.data);
9543
10918
  } else {
9544
10919
  }
@@ -9680,7 +11055,7 @@ function fullSync(room) {
9680
11055
  msg: "room::sync::full",
9681
11056
  roomId: room.id,
9682
11057
  status: room.getStatus(),
9683
- storage: _nullishCoalesce(_optionalChain([root, 'optionalAccess', _241 => _241.toTreeNode, 'call', _242 => _242("root"), 'access', _243 => _243.payload]), () => ( null)),
11058
+ storage: _nullishCoalesce(_optionalChain([root, 'optionalAccess', _248 => _248.toTreeNode, 'call', _249 => _249("root"), 'access', _250 => _250.payload]), () => ( null)),
9684
11059
  me,
9685
11060
  others
9686
11061
  });
@@ -10367,15 +11742,15 @@ function installBackgroundTabSpy() {
10367
11742
  const doc = typeof document !== "undefined" ? document : void 0;
10368
11743
  const inBackgroundSince = { current: null };
10369
11744
  function onVisibilityChange() {
10370
- if (_optionalChain([doc, 'optionalAccess', _244 => _244.visibilityState]) === "hidden") {
11745
+ if (_optionalChain([doc, 'optionalAccess', _251 => _251.visibilityState]) === "hidden") {
10371
11746
  inBackgroundSince.current = _nullishCoalesce(inBackgroundSince.current, () => ( Date.now()));
10372
11747
  } else {
10373
11748
  inBackgroundSince.current = null;
10374
11749
  }
10375
11750
  }
10376
- _optionalChain([doc, 'optionalAccess', _245 => _245.addEventListener, 'call', _246 => _246("visibilitychange", onVisibilityChange)]);
11751
+ _optionalChain([doc, 'optionalAccess', _252 => _252.addEventListener, 'call', _253 => _253("visibilitychange", onVisibilityChange)]);
10377
11752
  const unsub = () => {
10378
- _optionalChain([doc, 'optionalAccess', _247 => _247.removeEventListener, 'call', _248 => _248("visibilitychange", onVisibilityChange)]);
11753
+ _optionalChain([doc, 'optionalAccess', _254 => _254.removeEventListener, 'call', _255 => _255("visibilitychange", onVisibilityChange)]);
10379
11754
  };
10380
11755
  return [inBackgroundSince, unsub];
10381
11756
  }
@@ -10399,7 +11774,7 @@ function makeNodeMapBuffer() {
10399
11774
  function topLevelKeysOf(nodes) {
10400
11775
  const keys2 = /* @__PURE__ */ new Set();
10401
11776
  const root = nodes.get("root");
10402
- for (const key in _optionalChain([root, 'optionalAccess', _249 => _249.data])) {
11777
+ for (const key in _optionalChain([root, 'optionalAccess', _256 => _256.data])) {
10403
11778
  keys2.add(key);
10404
11779
  }
10405
11780
  for (const node of nodes.values()) {
@@ -10471,6 +11846,8 @@ function createRoom(options, config) {
10471
11846
  activeBatch: null,
10472
11847
  unacknowledgedOps
10473
11848
  };
11849
+ let nextHistoryItemId = 0;
11850
+ let historyDisabled = 0;
10474
11851
  const nodeMapBuffer = makeNodeMapBuffer();
10475
11852
  const stopwatch = config.enableDebugLogging ? makeStopWatch() : void 0;
10476
11853
  let lastTokenKey;
@@ -10555,7 +11932,7 @@ function createRoom(options, config) {
10555
11932
  }
10556
11933
  }
10557
11934
  });
10558
- function onDispatch(ops, reverse, storageUpdates) {
11935
+ function onDispatch(ops, reverse, storageUpdates, options2) {
10559
11936
  if (context.activeBatch) {
10560
11937
  for (const op of ops) {
10561
11938
  context.activeBatch.ops.push(op);
@@ -10570,19 +11947,24 @@ function createRoom(options, config) {
10570
11947
  );
10571
11948
  }
10572
11949
  context.activeBatch.reverseOps.pushLeft(reverse);
11950
+ if (_optionalChain([options2, 'optionalAccess', _257 => _257.clearRedoStack])) {
11951
+ context.activeBatch.clearRedoStack = true;
11952
+ }
10573
11953
  } else {
10574
11954
  if (reverse.length > 0) {
10575
11955
  addToUndoStack(reverse);
10576
11956
  }
11957
+ if (_nullishCoalesce(_optionalChain([options2, 'optionalAccess', _258 => _258.clearRedoStack]), () => ( ops.length > 0))) {
11958
+ clearRedoStack();
11959
+ }
10577
11960
  if (ops.length > 0) {
10578
- context.redoStack.length = 0;
10579
11961
  dispatchOps(ops);
10580
11962
  }
10581
11963
  notify({ storageUpdates });
10582
11964
  }
10583
11965
  }
10584
11966
  function isStorageWritable() {
10585
- const permissionMatrix = _optionalChain([context, 'access', _250 => _250.dynamicSessionInfoSig, 'access', _251 => _251.get, 'call', _252 => _252(), 'optionalAccess', _253 => _253.permissionMatrix]);
11967
+ const permissionMatrix = _optionalChain([context, 'access', _259 => _259.dynamicSessionInfoSig, 'access', _260 => _260.get, 'call', _261 => _261(), 'optionalAccess', _262 => _262.permissionMatrix]);
10586
11968
  return permissionMatrix !== void 0 ? hasPermissionAccess(permissionMatrix, "storage", "write") : true;
10587
11969
  }
10588
11970
  const eventHub = {
@@ -10595,6 +11977,7 @@ function createRoom(options, config) {
10595
11977
  others: makeEventSource(),
10596
11978
  storageBatch: makeEventSource(),
10597
11979
  history: makeEventSource(),
11980
+ privateHistory: makeEventSource(),
10598
11981
  storageDidLoad: makeEventSource(),
10599
11982
  storageStatus: makeEventSource(),
10600
11983
  ydoc: makeEventSource(),
@@ -10682,12 +12065,12 @@ function createRoom(options, config) {
10682
12065
  self,
10683
12066
  (me) => me !== null ? userToTreeNode("Me", me) : null
10684
12067
  );
10685
- function diffCurrentStorageAgainst(target) {
12068
+ function diffCurrentStorageAgainst(target, options2) {
10686
12069
  const current = /* @__PURE__ */ new Map();
10687
12070
  for (const [id, crdt] of context.pool.nodes) {
10688
12071
  current.set(id, crdt._serialize());
10689
12072
  }
10690
- return diffNodeMap(current, target);
12073
+ return diffNodeMap(current, target, options2);
10691
12074
  }
10692
12075
  function createOrUpdateRootFromMessage(nodes) {
10693
12076
  if (nodes.size === 0) {
@@ -10695,6 +12078,23 @@ function createRoom(options, config) {
10695
12078
  }
10696
12079
  if (context.root !== void 0) {
10697
12080
  const result = applyRemoteOps(diffCurrentStorageAgainst(nodes));
12081
+ for (const [id, crdt] of nodes) {
12082
+ if (crdt.type === CrdtType.TEXT) {
12083
+ const node = context.pool.nodes.get(id);
12084
+ if (node !== void 0 && isLiveText(node)) {
12085
+ const update = node._resyncText(crdt.data, crdt.version, REMOTE);
12086
+ if (update !== void 0) {
12087
+ result.updates.storageUpdates.set(
12088
+ id,
12089
+ mergeStorageUpdates(
12090
+ result.updates.storageUpdates.get(id),
12091
+ update
12092
+ )
12093
+ );
12094
+ }
12095
+ }
12096
+ }
12097
+ }
10698
12098
  notify(result.updates);
10699
12099
  } else {
10700
12100
  context.root = LiveObject._fromItems(
@@ -10702,7 +12102,7 @@ function createRoom(options, config) {
10702
12102
  context.pool
10703
12103
  );
10704
12104
  }
10705
- const canWrite = _nullishCoalesce(_optionalChain([self, 'access', _254 => _254.get, 'call', _255 => _255(), 'optionalAccess', _256 => _256.canWrite]), () => ( true));
12105
+ const canWrite = _nullishCoalesce(_optionalChain([self, 'access', _263 => _263.get, 'call', _264 => _264(), 'optionalAccess', _265 => _265.canWrite]), () => ( true));
10706
12106
  const serverTopLevelKeys = topLevelKeysOf(nodes);
10707
12107
  const root = context.root;
10708
12108
  disableHistory(() => {
@@ -10719,12 +12119,23 @@ function createRoom(options, config) {
10719
12119
  }
10720
12120
  });
10721
12121
  }
12122
+ function notifyPrivateHistory(event) {
12123
+ if (historyDisabled > 0) return;
12124
+ eventHub.privateHistory.notify(event);
12125
+ }
12126
+ function clearRedoStack() {
12127
+ if (context.redoStack.length === 0) return;
12128
+ const ids = context.redoStack.map((item) => item.id);
12129
+ context.redoStack.length = 0;
12130
+ notifyPrivateHistory({ action: "discard", ids });
12131
+ }
10722
12132
  function reconcileStorageWithNodes(nodes) {
10723
12133
  if (context.root === void 0) {
10724
12134
  throw new Error("Cannot reconcile storage before it is loaded");
10725
12135
  }
10726
12136
  const ops = diffCurrentStorageAgainst(
10727
- new Map(nodes)
12137
+ new Map(nodes),
12138
+ { includeLiveTextUpdates: true }
10728
12139
  );
10729
12140
  if (ops.length === 0) {
10730
12141
  return;
@@ -10743,9 +12154,14 @@ function createRoom(options, config) {
10743
12154
  }
10744
12155
  function _addToRealUndoStack(frames) {
10745
12156
  if (context.undoStack.length >= 50) {
10746
- context.undoStack.shift();
12157
+ const evicted = context.undoStack.shift();
12158
+ if (evicted !== void 0) {
12159
+ notifyPrivateHistory({ action: "discard", ids: [evicted.id] });
12160
+ }
10747
12161
  }
10748
- context.undoStack.push(frames);
12162
+ const id = nextHistoryItemId++;
12163
+ context.undoStack.push({ id, frames });
12164
+ notifyPrivateHistory({ action: "push", id });
10749
12165
  onHistoryChange();
10750
12166
  }
10751
12167
  function addToUndoStack(frames) {
@@ -10769,7 +12185,10 @@ function createRoom(options, config) {
10769
12185
  eventHub.myPresence.notify(context.myPresence.get());
10770
12186
  }
10771
12187
  if (storageUpdates !== void 0 && storageUpdates.size > 0) {
10772
- const updates2 = Array.from(storageUpdates.values());
12188
+ const updates2 = Array.from(storageUpdates.values(), (update) => ({
12189
+ ...update,
12190
+ source: toUpdateSource(update.source)
12191
+ }));
10773
12192
  eventHub.storageBatch.notify(updates2);
10774
12193
  }
10775
12194
  notifyStorageStatus();
@@ -10783,19 +12202,69 @@ function createRoom(options, config) {
10783
12202
  "Internal. Tried to get connection id but connection was never open"
10784
12203
  );
10785
12204
  }
10786
- function applyLocalOps(frames) {
12205
+ const viaByOpId = /* @__PURE__ */ new Map();
12206
+ function viaOfAckedOp(opId) {
12207
+ const via = viaByOpId.get(opId);
12208
+ if (via === void 0) {
12209
+ return "edit";
12210
+ }
12211
+ viaByOpId.delete(opId);
12212
+ return via;
12213
+ }
12214
+ function applyLocalOps(frames, localSource = LOCAL_EDIT) {
10787
12215
  const [pframes, ops] = partition(
10788
12216
  frames,
10789
12217
  (f) => f.type === "presence"
10790
12218
  );
10791
- const opsWithOpIds = ops.map(
12219
+ const restoredTextIds = /* @__PURE__ */ new Map();
12220
+ for (const op of ops) {
12221
+ if (op.type === OpCode.CREATE_TEXT && op.opId === void 0 && context.pool.nodes.get(op.id) === void 0 && !restoredTextIds.has(op.id)) {
12222
+ restoredTextIds.set(op.id, context.pool.generateId());
12223
+ }
12224
+ }
12225
+ const remappedOps = restoredTextIds.size === 0 ? ops : ops.map((op) => {
12226
+ if (op.opId !== void 0) {
12227
+ return op;
12228
+ }
12229
+ const id = restoredTextIds.get(op.id);
12230
+ if (isCreateOp(op)) {
12231
+ const parentId = restoredTextIds.get(op.parentId);
12232
+ const deletedId = op.deletedId === void 0 ? void 0 : restoredTextIds.get(op.deletedId);
12233
+ if (id === void 0 && parentId === void 0 && deletedId === void 0) {
12234
+ return op;
12235
+ }
12236
+ if (op.type === OpCode.CREATE_TEXT && id !== void 0) {
12237
+ return {
12238
+ ...op,
12239
+ id,
12240
+ version: 0,
12241
+ ...parentId === void 0 ? {} : { parentId },
12242
+ ...deletedId === void 0 ? {} : { deletedId }
12243
+ };
12244
+ }
12245
+ return {
12246
+ ...op,
12247
+ ...id === void 0 ? {} : { id },
12248
+ ...parentId === void 0 ? {} : { parentId },
12249
+ ...deletedId === void 0 ? {} : { deletedId }
12250
+ };
12251
+ }
12252
+ return id === void 0 ? op : { ...op, id };
12253
+ });
12254
+ const opsWithOpIds = remappedOps.map(
10792
12255
  (op) => op.opId === void 0 ? { ...op, opId: context.pool.generateOpId() } : op
10793
12256
  );
12257
+ if (localSource.via !== "edit") {
12258
+ for (const op of opsWithOpIds) {
12259
+ viaByOpId.set(op.opId, localSource.via);
12260
+ }
12261
+ }
10794
12262
  const { reverse, updates } = applyOps(
10795
12263
  pframes,
10796
12264
  opsWithOpIds,
10797
12265
  /* isLocal */
10798
- true
12266
+ true,
12267
+ localSource
10799
12268
  );
10800
12269
  return { opsToEmit: opsWithOpIds, reverse, updates };
10801
12270
  }
@@ -10807,7 +12276,7 @@ function createRoom(options, config) {
10807
12276
  false
10808
12277
  );
10809
12278
  }
10810
- function applyOps(pframes, ops, isLocal) {
12279
+ function applyOps(pframes, ops, isLocal, localSource = LOCAL_EDIT) {
10811
12280
  const output = {
10812
12281
  reverse: new Deque(),
10813
12282
  storageUpdates: /* @__PURE__ */ new Map(),
@@ -10836,12 +12305,16 @@ function createRoom(options, config) {
10836
12305
  for (const op of ops) {
10837
12306
  let source;
10838
12307
  if (isLocal) {
10839
- source = 0 /* LOCAL */;
12308
+ source = { ...localSource, optimistic: true };
10840
12309
  } else if (op.opId !== void 0) {
10841
12310
  context.unacknowledgedOps.delete(op.opId);
10842
- source = 2 /* OURS */;
12311
+ source = {
12312
+ origin: "local",
12313
+ via: viaOfAckedOp(op.opId),
12314
+ optimistic: false
12315
+ };
10843
12316
  } else {
10844
- source = 1 /* THEIRS */;
12317
+ source = REMOTE;
10845
12318
  }
10846
12319
  const applyOpResult = applyOp(op, source);
10847
12320
  if (applyOpResult.modified) {
@@ -10856,7 +12329,7 @@ function createRoom(options, config) {
10856
12329
  );
10857
12330
  output.reverse.pushLeft(applyOpResult.reverse);
10858
12331
  }
10859
- if (op.type === OpCode.CREATE_LIST || op.type === OpCode.CREATE_MAP || op.type === OpCode.CREATE_OBJECT || op.type === OpCode.CREATE_FILE) {
12332
+ if (op.type === OpCode.CREATE_LIST || op.type === OpCode.CREATE_MAP || op.type === OpCode.CREATE_OBJECT || op.type === OpCode.CREATE_TEXT || op.type === OpCode.CREATE_FILE) {
10860
12333
  createdNodeIds.add(op.id);
10861
12334
  }
10862
12335
  }
@@ -10876,12 +12349,13 @@ function createRoom(options, config) {
10876
12349
  switch (op.type) {
10877
12350
  case OpCode.DELETE_OBJECT_KEY:
10878
12351
  case OpCode.UPDATE_OBJECT:
12352
+ case OpCode.UPDATE_TEXT:
10879
12353
  case OpCode.DELETE_CRDT: {
10880
12354
  const node = context.pool.nodes.get(op.id);
10881
12355
  if (node === void 0) {
10882
12356
  return { modified: false };
10883
12357
  }
10884
- return node._apply(op, source === 0 /* LOCAL */);
12358
+ return node._apply(op, source);
10885
12359
  }
10886
12360
  case OpCode.SET_PARENT_KEY: {
10887
12361
  const node = context.pool.nodes.get(op.id);
@@ -10900,6 +12374,7 @@ function createRoom(options, config) {
10900
12374
  case OpCode.CREATE_OBJECT:
10901
12375
  case OpCode.CREATE_LIST:
10902
12376
  case OpCode.CREATE_MAP:
12377
+ case OpCode.CREATE_TEXT:
10903
12378
  case OpCode.CREATE_FILE:
10904
12379
  case OpCode.CREATE_REGISTER: {
10905
12380
  if (op.parentId === void 0) {
@@ -10935,7 +12410,7 @@ function createRoom(options, config) {
10935
12410
  }
10936
12411
  context.myPresence.patch(patch);
10937
12412
  if (context.activeBatch) {
10938
- if (_optionalChain([options2, 'optionalAccess', _257 => _257.addToHistory])) {
12413
+ if (_optionalChain([options2, 'optionalAccess', _266 => _266.addToHistory])) {
10939
12414
  context.activeBatch.reverseOps.pushLeft({
10940
12415
  type: "presence",
10941
12416
  data: oldValues
@@ -10944,7 +12419,7 @@ function createRoom(options, config) {
10944
12419
  context.activeBatch.updates.presence = true;
10945
12420
  } else {
10946
12421
  flushNowOrSoon();
10947
- if (_optionalChain([options2, 'optionalAccess', _258 => _258.addToHistory])) {
12422
+ if (_optionalChain([options2, 'optionalAccess', _267 => _267.addToHistory])) {
10948
12423
  addToUndoStack([{ type: "presence", data: oldValues }]);
10949
12424
  }
10950
12425
  notify({ presence: true });
@@ -11123,11 +12598,11 @@ function createRoom(options, config) {
11123
12598
  break;
11124
12599
  }
11125
12600
  case ServerMsgCode.STORAGE_CHUNK:
11126
- _optionalChain([stopwatch, 'optionalAccess', _259 => _259.lap, 'call', _260 => _260()]);
12601
+ _optionalChain([stopwatch, 'optionalAccess', _268 => _268.lap, 'call', _269 => _269()]);
11127
12602
  nodeMapBuffer.append(compactNodesToNodeStream(message.nodes));
11128
12603
  break;
11129
12604
  case ServerMsgCode.STORAGE_STREAM_END: {
11130
- const timing = _optionalChain([stopwatch, 'optionalAccess', _261 => _261.stop, 'call', _262 => _262()]);
12605
+ const timing = _optionalChain([stopwatch, 'optionalAccess', _270 => _270.stop, 'call', _271 => _271()]);
11131
12606
  if (timing) {
11132
12607
  const ms = (v) => `${v.toFixed(1)}ms`;
11133
12608
  const rest = timing.laps.slice(1);
@@ -11154,16 +12629,38 @@ function createRoom(options, config) {
11154
12629
  }
11155
12630
  break;
11156
12631
  }
11157
- // Receiving a RejectedOps message in the client means that the server is no
11158
- // longer in sync with the client. Trying to synchronize the client again by
11159
- // rolling back particular Ops may be hard/impossible. It's fine to not try and
11160
- // accept the out-of-sync reality and throw an error.
12632
+ // Receiving a RejectedOps message means the server refused some of
12633
+ // our ops, so our optimistic local state is out of sync with the
12634
+ // server. For LiveText ops this is a normal (if rare) situation
12635
+ // e.g. a client that was offline long enough to fall outside the
12636
+ // server's retained history window — and we can recover: drop the
12637
+ // rejected pending state and re-fetch the authoritative storage
12638
+ // snapshot. For other ops (e.g. permission rejections), rolling back
12639
+ // particular Ops is hard/impossible, so we keep the old behavior of
12640
+ // accepting the out-of-sync reality and surfacing an error.
11161
12641
  case ServerMsgCode.REJECT_STORAGE_OP: {
11162
12642
  errorWithTitle(
11163
12643
  "Storage mutation rejection error",
11164
12644
  message.reason
11165
12645
  );
11166
- if (process.env.NODE_ENV !== "production") {
12646
+ let needsStorageResync = false;
12647
+ for (const opId of message.opIds) {
12648
+ const rejectedOp = context.unacknowledgedOps.get(opId);
12649
+ context.unacknowledgedOps.delete(opId);
12650
+ context.buffer.storageOperations = context.buffer.storageOperations.filter((op) => op.opId !== opId);
12651
+ viaByOpId.delete(opId);
12652
+ if (rejectedOp !== void 0 && rejectedOp.type === OpCode.UPDATE_TEXT) {
12653
+ const node = context.pool.nodes.get(rejectedOp.id);
12654
+ if (node !== void 0 && isLiveText(node)) {
12655
+ node._rejectPendingOp(opId);
12656
+ needsStorageResync = true;
12657
+ }
12658
+ }
12659
+ }
12660
+ if (needsStorageResync) {
12661
+ refreshStorage();
12662
+ flushNowOrSoon();
12663
+ } else if (process.env.NODE_ENV !== "production") {
11167
12664
  throw new Error(
11168
12665
  `Storage mutations rejected by server: ${message.reason}`
11169
12666
  );
@@ -11262,11 +12759,11 @@ function createRoom(options, config) {
11262
12759
  } else if (pendingFeedsRequests.has(requestId)) {
11263
12760
  const pending = pendingFeedsRequests.get(requestId);
11264
12761
  pendingFeedsRequests.delete(requestId);
11265
- _optionalChain([pending, 'optionalAccess', _263 => _263.reject, 'call', _264 => _264(err)]);
12762
+ _optionalChain([pending, 'optionalAccess', _272 => _272.reject, 'call', _273 => _273(err)]);
11266
12763
  } else if (pendingFeedMessagesRequests.has(requestId)) {
11267
12764
  const pending = pendingFeedMessagesRequests.get(requestId);
11268
12765
  pendingFeedMessagesRequests.delete(requestId);
11269
- _optionalChain([pending, 'optionalAccess', _265 => _265.reject, 'call', _266 => _266(err)]);
12766
+ _optionalChain([pending, 'optionalAccess', _274 => _274.reject, 'call', _275 => _275(err)]);
11270
12767
  }
11271
12768
  eventHub.feeds.notify(message);
11272
12769
  break;
@@ -11420,10 +12917,10 @@ function createRoom(options, config) {
11420
12917
  timeoutId,
11421
12918
  kind,
11422
12919
  feedId,
11423
- messageId: _optionalChain([options2, 'optionalAccess', _267 => _267.messageId]),
11424
- expectedClientMessageId: _optionalChain([options2, 'optionalAccess', _268 => _268.expectedClientMessageId])
12920
+ messageId: _optionalChain([options2, 'optionalAccess', _276 => _276.messageId]),
12921
+ expectedClientMessageId: _optionalChain([options2, 'optionalAccess', _277 => _277.expectedClientMessageId])
11425
12922
  });
11426
- if (kind === "add-message" && _optionalChain([options2, 'optionalAccess', _269 => _269.expectedClientMessageId]) === void 0) {
12923
+ if (kind === "add-message" && _optionalChain([options2, 'optionalAccess', _278 => _278.expectedClientMessageId]) === void 0) {
11427
12924
  const q = _nullishCoalesce(pendingAddMessageFifoByFeed.get(feedId), () => ( []));
11428
12925
  q.push(requestId);
11429
12926
  pendingAddMessageFifoByFeed.set(feedId, q);
@@ -11474,10 +12971,10 @@ function createRoom(options, config) {
11474
12971
  }
11475
12972
  if (!matched) {
11476
12973
  const q = pendingAddMessageFifoByFeed.get(message.feedId);
11477
- const headId = _optionalChain([q, 'optionalAccess', _270 => _270[0]]);
12974
+ const headId = _optionalChain([q, 'optionalAccess', _279 => _279[0]]);
11478
12975
  if (headId !== void 0) {
11479
12976
  const pending = pendingFeedMutations.get(headId);
11480
- if (_optionalChain([pending, 'optionalAccess', _271 => _271.kind]) === "add-message" && pending.expectedClientMessageId === void 0) {
12977
+ if (_optionalChain([pending, 'optionalAccess', _280 => _280.kind]) === "add-message" && pending.expectedClientMessageId === void 0) {
11481
12978
  settleFeedMutation(headId, "ok");
11482
12979
  }
11483
12980
  }
@@ -11513,7 +13010,7 @@ function createRoom(options, config) {
11513
13010
  const unacknowledgedOps2 = [...context.unacknowledgedOps.values()];
11514
13011
  createOrUpdateRootFromMessage(nodes);
11515
13012
  applyAndSendOfflineOps(unacknowledgedOps2);
11516
- _optionalChain([_resolveStoragePromise, 'optionalCall', _272 => _272()]);
13013
+ _optionalChain([_resolveStoragePromise, 'optionalCall', _281 => _281()]);
11517
13014
  notifyStorageStatus();
11518
13015
  eventHub.storageDidLoad.notify();
11519
13016
  }
@@ -11522,7 +13019,7 @@ function createRoom(options, config) {
11522
13019
  if (!messages.some((msg) => msg.type === ClientMsgCode.FETCH_STORAGE)) {
11523
13020
  messages.push({ type: ClientMsgCode.FETCH_STORAGE });
11524
13021
  nodeMapBuffer.take();
11525
- _optionalChain([stopwatch, 'optionalAccess', _273 => _273.start, 'call', _274 => _274()]);
13022
+ _optionalChain([stopwatch, 'optionalAccess', _282 => _282.start, 'call', _283 => _283()]);
11526
13023
  }
11527
13024
  }
11528
13025
  function startLoadingStorage() {
@@ -11576,10 +13073,10 @@ function createRoom(options, config) {
11576
13073
  const message = {
11577
13074
  type: ClientMsgCode.FETCH_FEEDS,
11578
13075
  requestId,
11579
- cursor: _optionalChain([options2, 'optionalAccess', _275 => _275.cursor]),
11580
- since: _optionalChain([options2, 'optionalAccess', _276 => _276.since]),
11581
- limit: _optionalChain([options2, 'optionalAccess', _277 => _277.limit]),
11582
- metadata: _optionalChain([options2, 'optionalAccess', _278 => _278.metadata])
13076
+ cursor: _optionalChain([options2, 'optionalAccess', _284 => _284.cursor]),
13077
+ since: _optionalChain([options2, 'optionalAccess', _285 => _285.since]),
13078
+ limit: _optionalChain([options2, 'optionalAccess', _286 => _286.limit]),
13079
+ metadata: _optionalChain([options2, 'optionalAccess', _287 => _287.metadata])
11583
13080
  };
11584
13081
  context.buffer.messages.push(message);
11585
13082
  flushNowOrSoon();
@@ -11599,9 +13096,9 @@ function createRoom(options, config) {
11599
13096
  type: ClientMsgCode.FETCH_FEED_MESSAGES,
11600
13097
  requestId,
11601
13098
  feedId,
11602
- cursor: _optionalChain([options2, 'optionalAccess', _279 => _279.cursor]),
11603
- since: _optionalChain([options2, 'optionalAccess', _280 => _280.since]),
11604
- limit: _optionalChain([options2, 'optionalAccess', _281 => _281.limit])
13099
+ cursor: _optionalChain([options2, 'optionalAccess', _288 => _288.cursor]),
13100
+ since: _optionalChain([options2, 'optionalAccess', _289 => _289.since]),
13101
+ limit: _optionalChain([options2, 'optionalAccess', _290 => _290.limit])
11605
13102
  };
11606
13103
  context.buffer.messages.push(message);
11607
13104
  flushNowOrSoon();
@@ -11620,8 +13117,8 @@ function createRoom(options, config) {
11620
13117
  type: ClientMsgCode.ADD_FEED,
11621
13118
  requestId,
11622
13119
  feedId,
11623
- metadata: _optionalChain([options2, 'optionalAccess', _282 => _282.metadata]),
11624
- createdAt: _optionalChain([options2, 'optionalAccess', _283 => _283.createdAt])
13120
+ metadata: _optionalChain([options2, 'optionalAccess', _291 => _291.metadata]),
13121
+ createdAt: _optionalChain([options2, 'optionalAccess', _292 => _292.createdAt])
11625
13122
  };
11626
13123
  context.buffer.messages.push(message);
11627
13124
  flushNowOrSoon();
@@ -11655,15 +13152,15 @@ function createRoom(options, config) {
11655
13152
  function addFeedMessage(feedId, data, options2) {
11656
13153
  const requestId = nanoid();
11657
13154
  const promise = registerFeedMutation(requestId, "add-message", feedId, {
11658
- expectedClientMessageId: _optionalChain([options2, 'optionalAccess', _284 => _284.id])
13155
+ expectedClientMessageId: _optionalChain([options2, 'optionalAccess', _293 => _293.id])
11659
13156
  });
11660
13157
  const message = {
11661
13158
  type: ClientMsgCode.ADD_FEED_MESSAGE,
11662
13159
  requestId,
11663
13160
  feedId,
11664
13161
  data,
11665
- id: _optionalChain([options2, 'optionalAccess', _285 => _285.id]),
11666
- createdAt: _optionalChain([options2, 'optionalAccess', _286 => _286.createdAt])
13162
+ id: _optionalChain([options2, 'optionalAccess', _294 => _294.id]),
13163
+ createdAt: _optionalChain([options2, 'optionalAccess', _295 => _295.createdAt])
11667
13164
  };
11668
13165
  context.buffer.messages.push(message);
11669
13166
  flushNowOrSoon();
@@ -11680,7 +13177,7 @@ function createRoom(options, config) {
11680
13177
  feedId,
11681
13178
  messageId,
11682
13179
  data,
11683
- updatedAt: _optionalChain([options2, 'optionalAccess', _287 => _287.updatedAt])
13180
+ updatedAt: _optionalChain([options2, 'optionalAccess', _296 => _296.updatedAt])
11684
13181
  };
11685
13182
  context.buffer.messages.push(message);
11686
13183
  flushNowOrSoon();
@@ -11705,14 +13202,15 @@ function createRoom(options, config) {
11705
13202
  if (context.activeBatch) {
11706
13203
  throw new Error("undo is not allowed during a batch");
11707
13204
  }
11708
- const frames = context.undoStack.pop();
11709
- if (frames === void 0) {
13205
+ const item = context.undoStack.pop();
13206
+ if (item === void 0) {
11710
13207
  return;
11711
13208
  }
11712
13209
  context.pausedHistory = null;
11713
- const result = applyLocalOps(frames);
13210
+ const result = applyLocalOps(item.frames, LOCAL_UNDO);
13211
+ context.redoStack.push({ id: item.id, frames: result.reverse });
13212
+ notifyPrivateHistory({ action: "undo", id: item.id });
11714
13213
  notify(result.updates);
11715
- context.redoStack.push(result.reverse);
11716
13214
  onHistoryChange();
11717
13215
  for (const op of result.opsToEmit) {
11718
13216
  context.buffer.storageOperations.push(op);
@@ -11723,14 +13221,15 @@ function createRoom(options, config) {
11723
13221
  if (context.activeBatch) {
11724
13222
  throw new Error("redo is not allowed during a batch");
11725
13223
  }
11726
- const frames = context.redoStack.pop();
11727
- if (frames === void 0) {
13224
+ const item = context.redoStack.pop();
13225
+ if (item === void 0) {
11728
13226
  return;
11729
13227
  }
11730
13228
  context.pausedHistory = null;
11731
- const result = applyLocalOps(frames);
13229
+ const result = applyLocalOps(item.frames, LOCAL_REDO);
13230
+ context.undoStack.push({ id: item.id, frames: result.reverse });
13231
+ notifyPrivateHistory({ action: "redo", id: item.id });
11732
13232
  notify(result.updates);
11733
- context.undoStack.push(result.reverse);
11734
13233
  onHistoryChange();
11735
13234
  for (const op of result.opsToEmit) {
11736
13235
  context.buffer.storageOperations.push(op);
@@ -11740,6 +13239,8 @@ function createRoom(options, config) {
11740
13239
  function clear() {
11741
13240
  context.undoStack.length = 0;
11742
13241
  context.redoStack.length = 0;
13242
+ notifyPrivateHistory({ action: "clear" });
13243
+ onHistoryChange();
11743
13244
  }
11744
13245
  function batch2(callback) {
11745
13246
  if (context.activeBatch) {
@@ -11767,8 +13268,8 @@ function createRoom(options, config) {
11767
13268
  if (currentBatch.scheduleHistoryResume) {
11768
13269
  commitPausedHistoryToUndoStack();
11769
13270
  }
11770
- if (currentBatch.ops.length > 0) {
11771
- context.redoStack.length = 0;
13271
+ if (currentBatch.ops.length > 0 || currentBatch.clearRedoStack) {
13272
+ clearRedoStack();
11772
13273
  }
11773
13274
  if (currentBatch.ops.length > 0) {
11774
13275
  dispatchOps(currentBatch.ops);
@@ -11797,7 +13298,6 @@ function createRoom(options, config) {
11797
13298
  }
11798
13299
  commitPausedHistoryToUndoStack();
11799
13300
  }
11800
- let historyDisabled = 0;
11801
13301
  function disableHistory(fn) {
11802
13302
  const origUndo = context.undoStack;
11803
13303
  const origRedo = context.redoStack;
@@ -11887,8 +13387,8 @@ function createRoom(options, config) {
11887
13387
  async function getThreads(options2) {
11888
13388
  return httpClient.getThreads({
11889
13389
  roomId,
11890
- query: _optionalChain([options2, 'optionalAccess', _288 => _288.query]),
11891
- cursor: _optionalChain([options2, 'optionalAccess', _289 => _289.cursor])
13390
+ query: _optionalChain([options2, 'optionalAccess', _297 => _297.query]),
13391
+ cursor: _optionalChain([options2, 'optionalAccess', _298 => _298.cursor])
11892
13392
  });
11893
13393
  }
11894
13394
  async function getThread(threadId) {
@@ -12021,7 +13521,7 @@ function createRoom(options, config) {
12021
13521
  function getSubscriptionSettings(options2) {
12022
13522
  return httpClient.getSubscriptionSettings({
12023
13523
  roomId,
12024
- signal: _optionalChain([options2, 'optionalAccess', _290 => _290.signal])
13524
+ signal: _optionalChain([options2, 'optionalAccess', _299 => _299.signal])
12025
13525
  });
12026
13526
  }
12027
13527
  function updateSubscriptionSettings(settings) {
@@ -12043,30 +13543,45 @@ function createRoom(options, config) {
12043
13543
  {
12044
13544
  [kInternal]: {
12045
13545
  get presenceBuffer() {
12046
- return deepClone(_nullishCoalesce(_optionalChain([context, 'access', _291 => _291.buffer, 'access', _292 => _292.presenceUpdates, 'optionalAccess', _293 => _293.data]), () => ( null)));
13546
+ return deepClone(_nullishCoalesce(_optionalChain([context, 'access', _300 => _300.buffer, 'access', _301 => _301.presenceUpdates, 'optionalAccess', _302 => _302.data]), () => ( null)));
12047
13547
  },
12048
13548
  // prettier-ignore
12049
13549
  get undoStack() {
12050
- return deepClone(context.undoStack);
13550
+ return structuredClone(
13551
+ context.undoStack.map((item) => ({
13552
+ id: item.id,
13553
+ frames: item.frames
13554
+ }))
13555
+ );
13556
+ },
13557
+ // prettier-ignore
13558
+ get redoStack() {
13559
+ return structuredClone(
13560
+ context.redoStack.map((item) => ({
13561
+ id: item.id,
13562
+ frames: item.frames
13563
+ }))
13564
+ );
12051
13565
  },
12052
13566
  // prettier-ignore
12053
13567
  get nodeCount() {
12054
13568
  return context.pool.nodes.size;
12055
13569
  },
12056
13570
  // prettier-ignore
13571
+ history: eventHub.privateHistory.observable,
12057
13572
  getYjsProvider() {
12058
13573
  return context.yjsProvider;
12059
13574
  },
12060
13575
  setYjsProvider(newProvider) {
12061
- _optionalChain([context, 'access', _294 => _294.yjsProvider, 'optionalAccess', _295 => _295.off, 'call', _296 => _296("status", yjsStatusDidChange)]);
13576
+ _optionalChain([context, 'access', _303 => _303.yjsProvider, 'optionalAccess', _304 => _304.off, 'call', _305 => _305("status", yjsStatusDidChange)]);
12062
13577
  context.yjsProvider = newProvider;
12063
- _optionalChain([newProvider, 'optionalAccess', _297 => _297.on, 'call', _298 => _298("status", yjsStatusDidChange)]);
13578
+ _optionalChain([newProvider, 'optionalAccess', _306 => _306.on, 'call', _307 => _307("status", yjsStatusDidChange)]);
12064
13579
  context.yjsProviderDidChange.notify();
12065
13580
  },
12066
13581
  yjsProviderDidChange: context.yjsProviderDidChange.observable,
12067
13582
  // send metadata when using a text editor
12068
13583
  reportTextEditor,
12069
- getPermissionMatrix: () => _optionalChain([context, 'access', _299 => _299.dynamicSessionInfoSig, 'access', _300 => _300.get, 'call', _301 => _301(), 'optionalAccess', _302 => _302.permissionMatrix]),
13584
+ getPermissionMatrix: () => _optionalChain([context, 'access', _308 => _308.dynamicSessionInfoSig, 'access', _309 => _309.get, 'call', _310 => _310(), 'optionalAccess', _311 => _311.permissionMatrix]),
12070
13585
  // create a text mention when using a text editor
12071
13586
  createTextMention,
12072
13587
  // delete a text mention when using a text editor
@@ -12129,7 +13644,7 @@ ${dumpPool(
12129
13644
  source.dispose();
12130
13645
  }
12131
13646
  eventHub.roomWillDestroy.notify();
12132
- _optionalChain([context, 'access', _303 => _303.yjsProvider, 'optionalAccess', _304 => _304.off, 'call', _305 => _305("status", yjsStatusDidChange)]);
13647
+ _optionalChain([context, 'access', _312 => _312.yjsProvider, 'optionalAccess', _313 => _313.off, 'call', _314 => _314("status", yjsStatusDidChange)]);
12133
13648
  syncSourceForStorage.destroy();
12134
13649
  syncSourceForYjs.destroy();
12135
13650
  uninstallBgTabSpy();
@@ -12293,7 +13808,7 @@ function makeClassicSubscribeFn(roomId, events, errorEvents) {
12293
13808
  }
12294
13809
  if (isLiveNode(first)) {
12295
13810
  const node = first;
12296
- if (_optionalChain([options, 'optionalAccess', _306 => _306.isDeep])) {
13811
+ if (_optionalChain([options, 'optionalAccess', _315 => _315.isDeep])) {
12297
13812
  const storageCallback = second;
12298
13813
  return subscribeToLiveStructureDeeply(node, storageCallback);
12299
13814
  } else {
@@ -12383,8 +13898,8 @@ function createClient(options) {
12383
13898
  const authManager = createAuthManager(options, (token) => {
12384
13899
  currentUserId.set(() => token.uid);
12385
13900
  });
12386
- const fetchPolyfill = _optionalChain([clientOptions, 'access', _307 => _307.polyfills, 'optionalAccess', _308 => _308.fetch]) || /* istanbul ignore next */
12387
- _optionalChain([globalThis, 'access', _309 => _309.fetch, 'optionalAccess', _310 => _310.bind, 'call', _311 => _311(globalThis)]);
13901
+ const fetchPolyfill = _optionalChain([clientOptions, 'access', _316 => _316.polyfills, 'optionalAccess', _317 => _317.fetch]) || /* istanbul ignore next */
13902
+ _optionalChain([globalThis, 'access', _318 => _318.fetch, 'optionalAccess', _319 => _319.bind, 'call', _320 => _320(globalThis)]);
12388
13903
  const httpClient = createApiClient({
12389
13904
  baseUrl,
12390
13905
  fetchPolyfill,
@@ -12401,7 +13916,7 @@ function createClient(options) {
12401
13916
  delegates: {
12402
13917
  createSocket: makeCreateSocketDelegateForAi(
12403
13918
  baseUrl,
12404
- _optionalChain([clientOptions, 'access', _312 => _312.polyfills, 'optionalAccess', _313 => _313.WebSocket])
13919
+ _optionalChain([clientOptions, 'access', _321 => _321.polyfills, 'optionalAccess', _322 => _322.WebSocket])
12405
13920
  ),
12406
13921
  authenticate: async () => {
12407
13922
  const resp = await authManager.getAuthValue({
@@ -12472,7 +13987,7 @@ function createClient(options) {
12472
13987
  createSocket: makeCreateSocketDelegateForRoom(
12473
13988
  roomId,
12474
13989
  baseUrl,
12475
- _optionalChain([clientOptions, 'access', _314 => _314.polyfills, 'optionalAccess', _315 => _315.WebSocket])
13990
+ _optionalChain([clientOptions, 'access', _323 => _323.polyfills, 'optionalAccess', _324 => _324.WebSocket])
12476
13991
  ),
12477
13992
  authenticate: makeAuthDelegateForRoom(roomId, authManager)
12478
13993
  })),
@@ -12494,7 +14009,7 @@ function createClient(options) {
12494
14009
  const shouldConnect = _nullishCoalesce(options2.autoConnect, () => ( true));
12495
14010
  if (shouldConnect) {
12496
14011
  if (typeof atob === "undefined") {
12497
- if (_optionalChain([clientOptions, 'access', _316 => _316.polyfills, 'optionalAccess', _317 => _317.atob]) === void 0) {
14012
+ if (_optionalChain([clientOptions, 'access', _325 => _325.polyfills, 'optionalAccess', _326 => _326.atob]) === void 0) {
12498
14013
  throw new Error(
12499
14014
  "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"
12500
14015
  );
@@ -12506,7 +14021,7 @@ function createClient(options) {
12506
14021
  return leaseRoom(newRoomDetails);
12507
14022
  }
12508
14023
  function getRoom(roomId) {
12509
- const room = _optionalChain([roomsById, 'access', _318 => _318.get, 'call', _319 => _319(roomId), 'optionalAccess', _320 => _320.room]);
14024
+ const room = _optionalChain([roomsById, 'access', _327 => _327.get, 'call', _328 => _328(roomId), 'optionalAccess', _329 => _329.room]);
12510
14025
  return room ? room : null;
12511
14026
  }
12512
14027
  function logout() {
@@ -12522,7 +14037,7 @@ function createClient(options) {
12522
14037
  const batchedResolveUsers = new Batch(
12523
14038
  async (batchedUserIds) => {
12524
14039
  const userIds = batchedUserIds.flat();
12525
- const users = await _optionalChain([resolveUsers, 'optionalCall', _321 => _321({ userIds })]);
14040
+ const users = await _optionalChain([resolveUsers, 'optionalCall', _330 => _330({ userIds })]);
12526
14041
  warnOnceIf(
12527
14042
  !resolveUsers,
12528
14043
  "Set the resolveUsers option in createClient to specify user info."
@@ -12539,7 +14054,7 @@ function createClient(options) {
12539
14054
  const batchedResolveRoomsInfo = new Batch(
12540
14055
  async (batchedRoomIds) => {
12541
14056
  const roomIds = batchedRoomIds.flat();
12542
- const roomsInfo = await _optionalChain([resolveRoomsInfo, 'optionalCall', _322 => _322({ roomIds })]);
14057
+ const roomsInfo = await _optionalChain([resolveRoomsInfo, 'optionalCall', _331 => _331({ roomIds })]);
12543
14058
  warnOnceIf(
12544
14059
  !resolveRoomsInfo,
12545
14060
  "Set the resolveRoomsInfo option in createClient to specify room info."
@@ -12556,7 +14071,7 @@ function createClient(options) {
12556
14071
  const batchedResolveGroupsInfo = new Batch(
12557
14072
  async (batchedGroupIds) => {
12558
14073
  const groupIds = batchedGroupIds.flat();
12559
- const groupsInfo = await _optionalChain([resolveGroupsInfo, 'optionalCall', _323 => _323({ groupIds })]);
14074
+ const groupsInfo = await _optionalChain([resolveGroupsInfo, 'optionalCall', _332 => _332({ groupIds })]);
12560
14075
  warnOnceIf(
12561
14076
  !resolveGroupsInfo,
12562
14077
  "Set the resolveGroupsInfo option in createClient to specify group info."
@@ -12615,7 +14130,7 @@ function createClient(options) {
12615
14130
  }
12616
14131
  };
12617
14132
  const win = typeof window !== "undefined" ? window : void 0;
12618
- _optionalChain([win, 'optionalAccess', _324 => _324.addEventListener, 'call', _325 => _325("beforeunload", maybePreventClose)]);
14133
+ _optionalChain([win, 'optionalAccess', _333 => _333.addEventListener, 'call', _334 => _334("beforeunload", maybePreventClose)]);
12619
14134
  }
12620
14135
  async function getNotificationSettings(options2) {
12621
14136
  const plainSettings = await httpClient.getNotificationSettings(options2);
@@ -12743,7 +14258,7 @@ var commentBodyElementsTypes = {
12743
14258
  mention: "inline"
12744
14259
  };
12745
14260
  function traverseCommentBody(body, elementOrVisitor, possiblyVisitor) {
12746
- if (!body || !_optionalChain([body, 'optionalAccess', _326 => _326.content])) {
14261
+ if (!body || !_optionalChain([body, 'optionalAccess', _335 => _335.content])) {
12747
14262
  return;
12748
14263
  }
12749
14264
  const element = typeof elementOrVisitor === "string" ? elementOrVisitor : void 0;
@@ -12753,13 +14268,13 @@ function traverseCommentBody(body, elementOrVisitor, possiblyVisitor) {
12753
14268
  for (const block of body.content) {
12754
14269
  if (type === "all" || type === "block") {
12755
14270
  if (guard(block)) {
12756
- _optionalChain([visitor, 'optionalCall', _327 => _327(block)]);
14271
+ _optionalChain([visitor, 'optionalCall', _336 => _336(block)]);
12757
14272
  }
12758
14273
  }
12759
14274
  if (type === "all" || type === "inline") {
12760
14275
  for (const inline of block.children) {
12761
14276
  if (guard(inline)) {
12762
- _optionalChain([visitor, 'optionalCall', _328 => _328(inline)]);
14277
+ _optionalChain([visitor, 'optionalCall', _337 => _337(inline)]);
12763
14278
  }
12764
14279
  }
12765
14280
  }
@@ -12929,7 +14444,7 @@ var stringifyCommentBodyPlainElements = {
12929
14444
  text: ({ element }) => element.text,
12930
14445
  link: ({ element }) => _nullishCoalesce(element.text, () => ( element.url)),
12931
14446
  mention: ({ element, user, group }) => {
12932
- return `@${_nullishCoalesce(_nullishCoalesce(_optionalChain([user, 'optionalAccess', _329 => _329.name]), () => ( _optionalChain([group, 'optionalAccess', _330 => _330.name]))), () => ( element.id))}`;
14447
+ return `@${_nullishCoalesce(_nullishCoalesce(_optionalChain([user, 'optionalAccess', _338 => _338.name]), () => ( _optionalChain([group, 'optionalAccess', _339 => _339.name]))), () => ( element.id))}`;
12933
14448
  }
12934
14449
  };
12935
14450
  var stringifyCommentBodyHtmlElements = {
@@ -12959,7 +14474,7 @@ var stringifyCommentBodyHtmlElements = {
12959
14474
  return html`<a href="${href}" target="_blank" rel="noopener noreferrer">${element.text ? html`${element.text}` : element.url}</a>`;
12960
14475
  },
12961
14476
  mention: ({ element, user, group }) => {
12962
- return html`<span data-mention>@${_optionalChain([user, 'optionalAccess', _331 => _331.name]) ? html`${_optionalChain([user, 'optionalAccess', _332 => _332.name])}` : _optionalChain([group, 'optionalAccess', _333 => _333.name]) ? html`${_optionalChain([group, 'optionalAccess', _334 => _334.name])}` : element.id}</span>`;
14477
+ return html`<span data-mention>@${_optionalChain([user, 'optionalAccess', _340 => _340.name]) ? html`${_optionalChain([user, 'optionalAccess', _341 => _341.name])}` : _optionalChain([group, 'optionalAccess', _342 => _342.name]) ? html`${_optionalChain([group, 'optionalAccess', _343 => _343.name])}` : element.id}</span>`;
12963
14478
  }
12964
14479
  };
12965
14480
  var stringifyCommentBodyMarkdownElements = {
@@ -12989,20 +14504,20 @@ var stringifyCommentBodyMarkdownElements = {
12989
14504
  return markdown`[${_nullishCoalesce(element.text, () => ( element.url))}](${href})`;
12990
14505
  },
12991
14506
  mention: ({ element, user, group }) => {
12992
- return markdown`@${_nullishCoalesce(_nullishCoalesce(_optionalChain([user, 'optionalAccess', _335 => _335.name]), () => ( _optionalChain([group, 'optionalAccess', _336 => _336.name]))), () => ( element.id))}`;
14507
+ return markdown`@${_nullishCoalesce(_nullishCoalesce(_optionalChain([user, 'optionalAccess', _344 => _344.name]), () => ( _optionalChain([group, 'optionalAccess', _345 => _345.name]))), () => ( element.id))}`;
12993
14508
  }
12994
14509
  };
12995
14510
  async function stringifyCommentBody(body, options) {
12996
- const format = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _337 => _337.format]), () => ( "plain"));
12997
- const separator = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _338 => _338.separator]), () => ( (format === "markdown" ? "\n\n" : "\n")));
14511
+ const format = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _346 => _346.format]), () => ( "plain"));
14512
+ const separator = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _347 => _347.separator]), () => ( (format === "markdown" ? "\n\n" : "\n")));
12998
14513
  const elements = {
12999
14514
  ...format === "html" ? stringifyCommentBodyHtmlElements : format === "markdown" ? stringifyCommentBodyMarkdownElements : stringifyCommentBodyPlainElements,
13000
- ..._optionalChain([options, 'optionalAccess', _339 => _339.elements])
14515
+ ..._optionalChain([options, 'optionalAccess', _348 => _348.elements])
13001
14516
  };
13002
14517
  const { users: resolvedUsers, groups: resolvedGroupsInfo } = await resolveMentionsInCommentBody(
13003
14518
  body,
13004
- _optionalChain([options, 'optionalAccess', _340 => _340.resolveUsers]),
13005
- _optionalChain([options, 'optionalAccess', _341 => _341.resolveGroupsInfo])
14519
+ _optionalChain([options, 'optionalAccess', _349 => _349.resolveUsers]),
14520
+ _optionalChain([options, 'optionalAccess', _350 => _350.resolveGroupsInfo])
13006
14521
  );
13007
14522
  const blocks = body.content.flatMap((block, blockIndex) => {
13008
14523
  switch (block.type) {
@@ -13084,6 +14599,12 @@ function toPlainLson(lson) {
13084
14599
  liveblocksType: "LiveList",
13085
14600
  data: [...lson].map((item) => toPlainLson(item))
13086
14601
  };
14602
+ } else if (lson instanceof LiveText) {
14603
+ return {
14604
+ liveblocksType: "LiveText",
14605
+ data: lson.toJSON(),
14606
+ version: lson.version
14607
+ };
13087
14608
  } else if (lson instanceof LiveFile) {
13088
14609
  return {
13089
14610
  liveblocksType: "LiveFile",
@@ -13142,9 +14663,9 @@ function makePoller(callback, intervalMs, options) {
13142
14663
  const startTime = performance.now();
13143
14664
  const doc = typeof document !== "undefined" ? document : void 0;
13144
14665
  const win = typeof window !== "undefined" ? window : void 0;
13145
- const maxStaleTimeMs = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _342 => _342.maxStaleTimeMs]), () => ( Number.POSITIVE_INFINITY));
14666
+ const maxStaleTimeMs = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _351 => _351.maxStaleTimeMs]), () => ( Number.POSITIVE_INFINITY));
13146
14667
  const context = {
13147
- inForeground: _optionalChain([doc, 'optionalAccess', _343 => _343.visibilityState]) !== "hidden",
14668
+ inForeground: _optionalChain([doc, 'optionalAccess', _352 => _352.visibilityState]) !== "hidden",
13148
14669
  lastSuccessfulPollAt: startTime,
13149
14670
  count: 0,
13150
14671
  backoff: 0
@@ -13225,11 +14746,11 @@ function makePoller(callback, intervalMs, options) {
13225
14746
  pollNowIfStale();
13226
14747
  }
13227
14748
  function onVisibilityChange() {
13228
- setInForeground(_optionalChain([doc, 'optionalAccess', _344 => _344.visibilityState]) !== "hidden");
14749
+ setInForeground(_optionalChain([doc, 'optionalAccess', _353 => _353.visibilityState]) !== "hidden");
13229
14750
  }
13230
- _optionalChain([doc, 'optionalAccess', _345 => _345.addEventListener, 'call', _346 => _346("visibilitychange", onVisibilityChange)]);
13231
- _optionalChain([win, 'optionalAccess', _347 => _347.addEventListener, 'call', _348 => _348("online", onVisibilityChange)]);
13232
- _optionalChain([win, 'optionalAccess', _349 => _349.addEventListener, 'call', _350 => _350("focus", pollNowIfStale)]);
14751
+ _optionalChain([doc, 'optionalAccess', _354 => _354.addEventListener, 'call', _355 => _355("visibilitychange", onVisibilityChange)]);
14752
+ _optionalChain([win, 'optionalAccess', _356 => _356.addEventListener, 'call', _357 => _357("online", onVisibilityChange)]);
14753
+ _optionalChain([win, 'optionalAccess', _358 => _358.addEventListener, 'call', _359 => _359("focus", pollNowIfStale)]);
13233
14754
  fsm.start();
13234
14755
  return {
13235
14756
  inc,
@@ -13378,5 +14899,10 @@ detectDupes(PKG_NAME, PKG_VERSION, PKG_FORMAT);
13378
14899
 
13379
14900
 
13380
14901
 
13381
- exports.ClientMsgCode = ClientMsgCode; exports.CrdtType = CrdtType; exports.DefaultMap = DefaultMap; exports.Deque = Deque; exports.DerivedSignal = DerivedSignal; exports.FeedRequestErrorCode = FeedRequestErrorCode; exports.HttpError = HttpError; exports.LiveFile = LiveFile; 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.createStorageFileId = createStorageFileId; 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.getLiveFileId = getLiveFileId; 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.isFileStorageNode = isFileStorageNode; 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;
14902
+
14903
+
14904
+
14905
+
14906
+
14907
+ exports.ClientMsgCode = ClientMsgCode; exports.CrdtType = CrdtType; exports.DefaultMap = DefaultMap; exports.Deque = Deque; exports.DerivedSignal = DerivedSignal; exports.FeedRequestErrorCode = FeedRequestErrorCode; exports.HttpError = HttpError; exports.LiveFile = LiveFile; 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.createStorageFileId = createStorageFileId; 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.getLiveFileId = getLiveFileId; 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.isFileStorageNode = isFileStorageNode; 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.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.normalizeLiveTextOperations = normalizeLiveTextOperations; 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;
13382
14908
  //# sourceMappingURL=index.cjs.map