@liveblocks/core 3.19.5-rc1 → 3.20.0-exp2

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.js 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.19.5-rc1";
9
+ var PKG_VERSION = "3.20.0-exp2";
10
10
  var PKG_FORMAT = "esm";
11
11
 
12
12
  // src/dupe-detection.ts
@@ -5511,7 +5511,9 @@ var OpCode = Object.freeze({
5511
5511
  DELETE_CRDT: 5,
5512
5512
  DELETE_OBJECT_KEY: 6,
5513
5513
  CREATE_MAP: 7,
5514
- CREATE_REGISTER: 8
5514
+ CREATE_REGISTER: 8,
5515
+ CREATE_TEXT: 9,
5516
+ UPDATE_TEXT: 10
5515
5517
  });
5516
5518
  function isIgnoredOp(op) {
5517
5519
  return op.type === OpCode.DELETE_CRDT && op.id === "ACK";
@@ -5525,7 +5527,8 @@ var CrdtType = Object.freeze({
5525
5527
  OBJECT: 0,
5526
5528
  LIST: 1,
5527
5529
  MAP: 2,
5528
- REGISTER: 3
5530
+ REGISTER: 3,
5531
+ TEXT: 4
5529
5532
  });
5530
5533
  function isRootStorageNode(node) {
5531
5534
  return node[0] === "root";
@@ -5542,6 +5545,9 @@ function isMapStorageNode(node) {
5542
5545
  function isRegisterStorageNode(node) {
5543
5546
  return node[1].type === CrdtType.REGISTER;
5544
5547
  }
5548
+ function isTextStorageNode(node) {
5549
+ return node[1].type === CrdtType.TEXT;
5550
+ }
5545
5551
  function isCompactRootNode(node) {
5546
5552
  return node[0] === "root";
5547
5553
  }
@@ -5564,6 +5570,9 @@ function* compactNodesToNodeStream(compactNodes) {
5564
5570
  case CrdtType.REGISTER:
5565
5571
  yield [cnode[0], { type: CrdtType.REGISTER, parentId: cnode[2], parentKey: cnode[3], data: cnode[4] }];
5566
5572
  break;
5573
+ case CrdtType.TEXT:
5574
+ yield [cnode[0], { type: CrdtType.TEXT, parentId: cnode[2], parentKey: cnode[3], data: cnode[4], version: cnode[5] }];
5575
+ break;
5567
5576
  default:
5568
5577
  }
5569
5578
  }
@@ -5592,6 +5601,17 @@ function* nodeStreamToCompactNodes(nodes) {
5592
5601
  const id = node[0];
5593
5602
  const crdt = node[1];
5594
5603
  yield [id, CrdtType.REGISTER, crdt.parentId, crdt.parentKey, crdt.data];
5604
+ } else if (isTextStorageNode(node)) {
5605
+ const id = node[0];
5606
+ const crdt = node[1];
5607
+ yield [
5608
+ id,
5609
+ CrdtType.TEXT,
5610
+ crdt.parentId,
5611
+ crdt.parentKey,
5612
+ crdt.data,
5613
+ crdt.version
5614
+ ];
5595
5615
  } else {
5596
5616
  }
5597
5617
  }
@@ -7928,6 +7948,7 @@ var LiveObject = class _LiveObject extends AbstractCrdt {
7928
7948
  const id = nn(this._id);
7929
7949
  const parentKey = nn(child._parentKey);
7930
7950
  const reverse = child._toOps(id, parentKey);
7951
+ const deletedItem = liveNodeToLson(child);
7931
7952
  for (const [key, value] of this.#synced) {
7932
7953
  if (value === child) {
7933
7954
  this.#synced.delete(key);
@@ -7939,7 +7960,7 @@ var LiveObject = class _LiveObject extends AbstractCrdt {
7939
7960
  node: this,
7940
7961
  type: "LiveObject",
7941
7962
  updates: {
7942
- [parentKey]: { type: "delete" }
7963
+ [parentKey]: { type: "delete", deletedItem }
7943
7964
  }
7944
7965
  };
7945
7966
  return { modified: storageUpdate, reverse };
@@ -8426,6 +8447,583 @@ var LiveObject = class _LiveObject extends AbstractCrdt {
8426
8447
  }
8427
8448
  };
8428
8449
 
8450
+ // src/crdts/liveTextOps.ts
8451
+ function attributesEqual(left, right) {
8452
+ if (left === right) {
8453
+ return true;
8454
+ }
8455
+ if (left === void 0 || right === void 0) {
8456
+ return false;
8457
+ }
8458
+ const leftKeys = Object.keys(left);
8459
+ const rightKeys = Object.keys(right);
8460
+ if (leftKeys.length !== rightKeys.length) {
8461
+ return false;
8462
+ }
8463
+ for (const key of leftKeys) {
8464
+ if (left[key] !== right[key]) {
8465
+ return false;
8466
+ }
8467
+ }
8468
+ return true;
8469
+ }
8470
+ function cloneAttributes(attributes) {
8471
+ return attributes === void 0 ? void 0 : freeze({ ...attributes });
8472
+ }
8473
+ function normalizeSegments(segments) {
8474
+ const normalized = [];
8475
+ for (const segment of segments) {
8476
+ if (segment.text.length === 0) {
8477
+ continue;
8478
+ }
8479
+ const last = normalized.at(-1);
8480
+ const attributes = cloneAttributes(segment.attributes);
8481
+ if (last !== void 0 && attributesEqual(last.attributes, attributes)) {
8482
+ last.text += segment.text;
8483
+ } else {
8484
+ normalized.push({ text: segment.text, attributes });
8485
+ }
8486
+ }
8487
+ return normalized;
8488
+ }
8489
+ function deltaToSegments(delta) {
8490
+ return normalizeSegments(
8491
+ delta.map((item) => ({
8492
+ text: item.text,
8493
+ attributes: item.attributes
8494
+ }))
8495
+ );
8496
+ }
8497
+ function segmentsToDelta(segments) {
8498
+ return segments.map(
8499
+ (segment) => segment.attributes === void 0 ? { text: segment.text } : { text: segment.text, attributes: { ...segment.attributes } }
8500
+ );
8501
+ }
8502
+ function textLength(segments) {
8503
+ return segments.reduce((sum, segment) => sum + segment.text.length, 0);
8504
+ }
8505
+ function splitSegmentsAt(segments, index) {
8506
+ const result = [];
8507
+ let offset = 0;
8508
+ for (const segment of segments) {
8509
+ const end = offset + segment.text.length;
8510
+ if (index > offset && index < end) {
8511
+ const before2 = segment.text.slice(0, index - offset);
8512
+ const after2 = segment.text.slice(index - offset);
8513
+ result.push({ text: before2, attributes: segment.attributes });
8514
+ result.push({ text: after2, attributes: segment.attributes });
8515
+ } else {
8516
+ result.push({ text: segment.text, attributes: segment.attributes });
8517
+ }
8518
+ offset = end;
8519
+ }
8520
+ return result;
8521
+ }
8522
+ function clipRange(index, length, contentLength) {
8523
+ const clippedIndex = Math.max(0, Math.min(index, contentLength));
8524
+ const clippedEnd = Math.max(
8525
+ clippedIndex,
8526
+ Math.min(index + length, contentLength)
8527
+ );
8528
+ return { index: clippedIndex, length: clippedEnd - clippedIndex };
8529
+ }
8530
+ function applyInsert(segments, index, text, attributes) {
8531
+ if (text.length === 0) {
8532
+ return normalizeSegments(segments);
8533
+ }
8534
+ const split = splitSegmentsAt(segments, index);
8535
+ const result = [];
8536
+ let offset = 0;
8537
+ let inserted = false;
8538
+ for (const segment of split) {
8539
+ if (!inserted && offset === index) {
8540
+ result.push({ text, attributes });
8541
+ inserted = true;
8542
+ }
8543
+ result.push(segment);
8544
+ offset += segment.text.length;
8545
+ }
8546
+ if (!inserted) {
8547
+ result.push({ text, attributes });
8548
+ }
8549
+ return normalizeSegments(result);
8550
+ }
8551
+ function extractDeletedSegments(segments, index, length) {
8552
+ const split = splitSegmentsAt(
8553
+ splitSegmentsAt(segments, index),
8554
+ index + length
8555
+ );
8556
+ const deleted = [];
8557
+ let offset = 0;
8558
+ for (const segment of split) {
8559
+ const end = offset + segment.text.length;
8560
+ if (offset >= index && end <= index + length) {
8561
+ deleted.push({
8562
+ text: segment.text,
8563
+ attributes: segment.attributes
8564
+ });
8565
+ }
8566
+ offset = end;
8567
+ }
8568
+ return normalizeSegments(deleted);
8569
+ }
8570
+ function applyDelete(segments, index, length) {
8571
+ const deletedSegments = extractDeletedSegments(segments, index, length);
8572
+ const split = splitSegmentsAt(
8573
+ splitSegmentsAt(segments, index),
8574
+ index + length
8575
+ );
8576
+ const result = [];
8577
+ let offset = 0;
8578
+ let deletedText = "";
8579
+ for (const segment of split) {
8580
+ const end = offset + segment.text.length;
8581
+ if (offset >= index && end <= index + length) {
8582
+ deletedText += segment.text;
8583
+ } else {
8584
+ result.push(segment);
8585
+ }
8586
+ offset = end;
8587
+ }
8588
+ return {
8589
+ segments: normalizeSegments(result),
8590
+ deletedText,
8591
+ deletedSegments
8592
+ };
8593
+ }
8594
+ function applyFormat(segments, index, length, attributes) {
8595
+ const split = splitSegmentsAt(
8596
+ splitSegmentsAt(segments, index),
8597
+ index + length
8598
+ );
8599
+ const result = [];
8600
+ let offset = 0;
8601
+ for (const segment of split) {
8602
+ const end = offset + segment.text.length;
8603
+ if (offset >= index && end <= index + length) {
8604
+ const nextAttributes = {
8605
+ ...segment.attributes ?? {}
8606
+ };
8607
+ for (const [key, value] of Object.entries(attributes)) {
8608
+ if (value === null) {
8609
+ delete nextAttributes[key];
8610
+ } else {
8611
+ nextAttributes[key] = value;
8612
+ }
8613
+ }
8614
+ result.push({
8615
+ text: segment.text,
8616
+ attributes: Object.keys(nextAttributes).length === 0 ? void 0 : freeze(nextAttributes)
8617
+ });
8618
+ } else {
8619
+ result.push(segment);
8620
+ }
8621
+ offset = end;
8622
+ }
8623
+ return normalizeSegments(result);
8624
+ }
8625
+ function formatReverseOperations(segments, index, length, patch) {
8626
+ const split = splitSegmentsAt(
8627
+ splitSegmentsAt(segments, index),
8628
+ index + length
8629
+ );
8630
+ const result = [];
8631
+ let offset = 0;
8632
+ for (const segment of split) {
8633
+ const end = offset + segment.text.length;
8634
+ if (offset >= index && end <= index + length) {
8635
+ const attributes = {};
8636
+ for (const key of Object.keys(patch)) {
8637
+ attributes[key] = segment.attributes?.[key] ?? null;
8638
+ }
8639
+ result.push({
8640
+ type: "format",
8641
+ index: offset,
8642
+ length: segment.text.length,
8643
+ attributes
8644
+ });
8645
+ }
8646
+ offset = end;
8647
+ }
8648
+ return result;
8649
+ }
8650
+ function mapIndexThroughOperation(index, op) {
8651
+ if (op.type === "insert") {
8652
+ return op.index <= index ? index + op.text.length : index;
8653
+ } else if (op.type === "delete") {
8654
+ if (op.index >= index) {
8655
+ return index;
8656
+ }
8657
+ return Math.max(op.index, index - op.length);
8658
+ } else {
8659
+ return index;
8660
+ }
8661
+ }
8662
+ function mapTextIndexThroughOperations(index, ops) {
8663
+ let mapped = index;
8664
+ for (const op of ops) {
8665
+ mapped = mapIndexThroughOperation(mapped, op);
8666
+ }
8667
+ return mapped;
8668
+ }
8669
+ function rebaseTextOperations(ops, acceptedOps) {
8670
+ return ops.map((op) => {
8671
+ if (op.type === "insert") {
8672
+ return {
8673
+ ...op,
8674
+ index: mapTextIndexThroughOperations(op.index, acceptedOps)
8675
+ };
8676
+ } else if (op.type === "delete" || op.type === "format") {
8677
+ const start = mapTextIndexThroughOperations(op.index, acceptedOps);
8678
+ const end = mapTextIndexThroughOperations(
8679
+ op.index + op.length,
8680
+ acceptedOps
8681
+ );
8682
+ return { ...op, index: start, length: Math.max(0, end - start) };
8683
+ } else {
8684
+ return op;
8685
+ }
8686
+ });
8687
+ }
8688
+ function applyTextOperationsToSegments(segments, ops) {
8689
+ let next = [...segments];
8690
+ for (const op of ops) {
8691
+ if (op.type === "insert") {
8692
+ const index = Math.max(0, Math.min(op.index, textLength(next)));
8693
+ next = applyInsert(next, index, op.text, op.attributes);
8694
+ } else if (op.type === "delete") {
8695
+ const index = Math.max(0, Math.min(op.index, textLength(next)));
8696
+ const clipped = clipRange(index, op.length, textLength(next));
8697
+ next = applyDelete(next, clipped.index, clipped.length).segments;
8698
+ } else {
8699
+ const index = Math.max(0, Math.min(op.index, textLength(next)));
8700
+ const clipped = clipRange(index, op.length, textLength(next));
8701
+ next = applyFormat(next, clipped.index, clipped.length, op.attributes);
8702
+ }
8703
+ }
8704
+ return next;
8705
+ }
8706
+ function applyLiveTextOperations(delta, ops) {
8707
+ return segmentsToDelta(
8708
+ applyTextOperationsToSegments(deltaToSegments(delta), ops)
8709
+ );
8710
+ }
8711
+ function invertTextOperations(segments, ops) {
8712
+ let shadow = [...segments];
8713
+ const reverse = [];
8714
+ for (const op of ops) {
8715
+ if (op.type === "insert") {
8716
+ shadow = applyInsert(shadow, op.index, op.text, op.attributes);
8717
+ reverse.unshift({
8718
+ type: "delete",
8719
+ index: op.index,
8720
+ length: op.text.length
8721
+ });
8722
+ } else if (op.type === "delete") {
8723
+ const deletedSegments = extractDeletedSegments(
8724
+ shadow,
8725
+ op.index,
8726
+ op.length
8727
+ );
8728
+ shadow = applyDelete(shadow, op.index, op.length).segments;
8729
+ const inserts = [];
8730
+ let insertIndex = op.index;
8731
+ for (const segment of deletedSegments) {
8732
+ inserts.push({
8733
+ type: "insert",
8734
+ index: insertIndex,
8735
+ text: segment.text,
8736
+ attributes: segment.attributes
8737
+ });
8738
+ insertIndex += segment.text.length;
8739
+ }
8740
+ for (let index = inserts.length - 1; index >= 0; index--) {
8741
+ reverse.unshift(inserts[index]);
8742
+ }
8743
+ } else {
8744
+ const inverse = formatReverseOperations(
8745
+ shadow,
8746
+ op.index,
8747
+ op.length,
8748
+ op.attributes
8749
+ );
8750
+ shadow = applyFormat(shadow, op.index, op.length, op.attributes);
8751
+ reverse.unshift(...inverse.reverse());
8752
+ }
8753
+ }
8754
+ return reverse;
8755
+ }
8756
+
8757
+ // src/crdts/LiveText.ts
8758
+ var LiveText = class _LiveText extends AbstractCrdt {
8759
+ #segments;
8760
+ #version;
8761
+ #pendingOps;
8762
+ constructor(textOrDelta = "", version = 0) {
8763
+ super();
8764
+ this.#segments = typeof textOrDelta === "string" ? textOrDelta.length === 0 ? [] : [{ text: textOrDelta }] : deltaToSegments(textOrDelta);
8765
+ this.#version = version;
8766
+ this.#pendingOps = /* @__PURE__ */ new Map();
8767
+ }
8768
+ get version() {
8769
+ return this.#version;
8770
+ }
8771
+ get length() {
8772
+ return this.toString().length;
8773
+ }
8774
+ /** @internal */
8775
+ static _deserialize([id, item], _parentToChildren, pool) {
8776
+ const text = new _LiveText(item.data, item.version);
8777
+ text._attach(id, pool);
8778
+ return text;
8779
+ }
8780
+ /** @internal */
8781
+ _toOps(parentId, parentKey) {
8782
+ if (this._id === void 0) {
8783
+ throw new Error("Cannot serialize LiveText if it is not attached");
8784
+ }
8785
+ return [
8786
+ {
8787
+ type: OpCode.CREATE_TEXT,
8788
+ id: this._id,
8789
+ parentId,
8790
+ parentKey,
8791
+ data: this.toDelta(),
8792
+ version: this.#version
8793
+ }
8794
+ ];
8795
+ }
8796
+ /** @internal */
8797
+ _serialize() {
8798
+ if (this.parent.type !== "HasParent") {
8799
+ throw new Error("Cannot serialize LiveText if parent is missing");
8800
+ }
8801
+ return {
8802
+ type: CrdtType.TEXT,
8803
+ parentId: nn(this.parent.node._id, "Parent node expected to have ID"),
8804
+ parentKey: this.parent.key,
8805
+ data: this.toDelta(),
8806
+ version: this.#version
8807
+ };
8808
+ }
8809
+ /** @internal */
8810
+ _attachChild(_op) {
8811
+ throw new Error("LiveText cannot contain child nodes");
8812
+ }
8813
+ /** @internal */
8814
+ _detachChild(_crdt) {
8815
+ throw new Error("LiveText cannot contain child nodes");
8816
+ }
8817
+ /** @internal */
8818
+ _apply(op, isLocal) {
8819
+ if (op.type !== OpCode.UPDATE_TEXT) {
8820
+ return super._apply(op, isLocal);
8821
+ }
8822
+ if (isLocal) {
8823
+ this.#pendingOps.set(nn(op.opId), op.ops);
8824
+ return this.#applyOperations(op.ops, op.version ?? this.#version);
8825
+ }
8826
+ if (op.opId !== void 0) {
8827
+ const pending2 = this.#pendingOps.get(op.opId);
8828
+ this.#pendingOps.delete(op.opId);
8829
+ const otherPending = Array.from(this.#pendingOps.values()).flat();
8830
+ if (pending2 !== void 0 && otherPending.length > 0) {
8831
+ this.#segments = applyTextOperationsToSegments(
8832
+ this.#segments,
8833
+ invertTextOperations(this.#segments, pending2)
8834
+ );
8835
+ const ops2 = rebaseTextOperations(op.ops, otherPending);
8836
+ return this.#applyOperations(
8837
+ ops2,
8838
+ op.version ?? Math.max(this.#version, op.baseVersion + 1)
8839
+ );
8840
+ }
8841
+ this.#version = op.version ?? Math.max(this.#version, op.baseVersion + 1);
8842
+ return { modified: false };
8843
+ }
8844
+ const pending = Array.from(this.#pendingOps.values()).flat();
8845
+ const ops = pending.length > 0 ? rebaseTextOperations(op.ops, pending) : op.ops;
8846
+ return this.#applyOperations(ops, op.version ?? this.#version + 1);
8847
+ }
8848
+ insert(index, text, attributes) {
8849
+ const clippedIndex = Math.max(0, Math.min(index, this.length));
8850
+ this.#dispatch([{ type: "insert", index: clippedIndex, text, attributes }]);
8851
+ }
8852
+ delete(index, length) {
8853
+ const clipped = clipRange(index, length, this.length);
8854
+ if (clipped.length === 0) {
8855
+ return;
8856
+ }
8857
+ this.#dispatch([
8858
+ { type: "delete", index: clipped.index, length: clipped.length }
8859
+ ]);
8860
+ }
8861
+ replace(index, length, text, attributes) {
8862
+ const clipped = clipRange(index, length, this.length);
8863
+ const ops = [];
8864
+ if (clipped.length > 0) {
8865
+ ops.push({
8866
+ type: "delete",
8867
+ index: clipped.index,
8868
+ length: clipped.length
8869
+ });
8870
+ }
8871
+ if (text.length > 0) {
8872
+ ops.push({ type: "insert", index: clipped.index, text, attributes });
8873
+ }
8874
+ this.#dispatch(ops);
8875
+ }
8876
+ format(index, length, attributes) {
8877
+ const clipped = clipRange(index, length, this.length);
8878
+ if (clipped.length === 0) {
8879
+ return;
8880
+ }
8881
+ this.#dispatch([
8882
+ {
8883
+ type: "format",
8884
+ index: clipped.index,
8885
+ length: clipped.length,
8886
+ attributes
8887
+ }
8888
+ ]);
8889
+ }
8890
+ #dispatch(ops) {
8891
+ if (ops.length === 0) {
8892
+ return;
8893
+ }
8894
+ this._pool?.assertStorageIsWritable();
8895
+ const baseVersion = this.#version;
8896
+ const reverse = this._pool !== void 0 && this._id !== void 0 ? this.#invertOperations(ops) : [];
8897
+ const changes = this.#applyOperationsLocally(ops);
8898
+ if (this._pool !== void 0 && this._id !== void 0) {
8899
+ const opId = this._pool.generateOpId();
8900
+ this.#pendingOps.set(opId, ops);
8901
+ this._pool.dispatch(
8902
+ [
8903
+ {
8904
+ type: OpCode.UPDATE_TEXT,
8905
+ id: this._id,
8906
+ opId,
8907
+ baseVersion,
8908
+ ops: [...ops]
8909
+ }
8910
+ ],
8911
+ reverse,
8912
+ /* @__PURE__ */ new Map([
8913
+ [
8914
+ this._id,
8915
+ {
8916
+ type: "LiveText",
8917
+ node: this,
8918
+ version: this.#version,
8919
+ updates: changes
8920
+ }
8921
+ ]
8922
+ ])
8923
+ );
8924
+ }
8925
+ }
8926
+ #applyOperations(ops, version) {
8927
+ const reverse = this.#invertOperations(ops);
8928
+ const changes = this.#applyOperationsLocally(ops);
8929
+ this.#version = Math.max(this.#version, version);
8930
+ return {
8931
+ reverse,
8932
+ modified: {
8933
+ type: "LiveText",
8934
+ node: this,
8935
+ version: this.#version,
8936
+ updates: changes
8937
+ }
8938
+ };
8939
+ }
8940
+ #applyOperationsLocally(ops) {
8941
+ const changes = [];
8942
+ for (const op of ops) {
8943
+ if (op.type === "insert") {
8944
+ this.#segments = applyInsert(
8945
+ this.#segments,
8946
+ op.index,
8947
+ op.text,
8948
+ op.attributes
8949
+ );
8950
+ changes.push({
8951
+ type: "insert",
8952
+ index: op.index,
8953
+ text: op.text,
8954
+ attributes: op.attributes
8955
+ });
8956
+ } else if (op.type === "delete") {
8957
+ const result = applyDelete(this.#segments, op.index, op.length);
8958
+ this.#segments = result.segments;
8959
+ changes.push({
8960
+ type: "delete",
8961
+ index: op.index,
8962
+ length: op.length,
8963
+ deletedText: result.deletedText
8964
+ });
8965
+ } else {
8966
+ this.#segments = applyFormat(
8967
+ this.#segments,
8968
+ op.index,
8969
+ op.length,
8970
+ op.attributes
8971
+ );
8972
+ changes.push({
8973
+ type: "format",
8974
+ index: op.index,
8975
+ length: op.length,
8976
+ attributes: op.attributes
8977
+ });
8978
+ }
8979
+ }
8980
+ this.invalidate();
8981
+ return changes;
8982
+ }
8983
+ #invertOperations(ops) {
8984
+ return [
8985
+ {
8986
+ type: OpCode.UPDATE_TEXT,
8987
+ id: nn(this._id),
8988
+ baseVersion: this.#version,
8989
+ ops: invertTextOperations(this.#segments, ops)
8990
+ }
8991
+ ];
8992
+ }
8993
+ toString() {
8994
+ return this.#segments.map((segment) => segment.text).join("");
8995
+ }
8996
+ toDelta() {
8997
+ return segmentsToDelta(this.#segments);
8998
+ }
8999
+ toJSON() {
9000
+ return super.toJSON();
9001
+ }
9002
+ /** @internal */
9003
+ _toJSON() {
9004
+ return this.toDelta();
9005
+ }
9006
+ /** @internal */
9007
+ _toTreeNode(key) {
9008
+ return {
9009
+ type: "LiveText",
9010
+ id: this._id ?? nanoid(),
9011
+ key,
9012
+ payload: [
9013
+ {
9014
+ type: "Json",
9015
+ id: `${this._id ?? nanoid()}:text`,
9016
+ key: "text",
9017
+ payload: this.toString()
9018
+ }
9019
+ ]
9020
+ };
9021
+ }
9022
+ clone() {
9023
+ return new _LiveText(this.toDelta(), this.#version);
9024
+ }
9025
+ };
9026
+
8429
9027
  // src/crdts/liveblocks-helpers.ts
8430
9028
  function creationOpToLiveNode(op) {
8431
9029
  return lsonToLiveNode(creationOpToLson(op));
@@ -8440,6 +9038,8 @@ function creationOpToLson(op) {
8440
9038
  return new LiveMap();
8441
9039
  case OpCode.CREATE_LIST:
8442
9040
  return new LiveList([]);
9041
+ case OpCode.CREATE_TEXT:
9042
+ return new LiveText(op.data, op.version);
8443
9043
  default:
8444
9044
  return assertNever(op, "Unknown creation Op");
8445
9045
  }
@@ -8462,6 +9062,8 @@ function deserialize(node, parentToChildren, pool) {
8462
9062
  return LiveMap._deserialize(node, parentToChildren, pool);
8463
9063
  } else if (isRegisterStorageNode(node)) {
8464
9064
  return LiveRegister._deserialize(node, parentToChildren, pool);
9065
+ } else if (isTextStorageNode(node)) {
9066
+ return LiveText._deserialize(node, parentToChildren, pool);
8465
9067
  } else {
8466
9068
  throw new Error("Unexpected CRDT type");
8467
9069
  }
@@ -8475,12 +9077,14 @@ function deserializeToLson(node, parentToChildren, pool) {
8475
9077
  return LiveMap._deserialize(node, parentToChildren, pool);
8476
9078
  } else if (isRegisterStorageNode(node)) {
8477
9079
  return node[1].data;
9080
+ } else if (isTextStorageNode(node)) {
9081
+ return LiveText._deserialize(node, parentToChildren, pool);
8478
9082
  } else {
8479
9083
  throw new Error("Unexpected CRDT type");
8480
9084
  }
8481
9085
  }
8482
9086
  function isLiveStructure(value) {
8483
- return isLiveList(value) || isLiveMap(value) || isLiveObject(value);
9087
+ return isLiveList(value) || isLiveMap(value) || isLiveObject(value) || isLiveText(value);
8484
9088
  }
8485
9089
  function isLiveNode(value) {
8486
9090
  return isLiveStructure(value) || isLiveRegister(value);
@@ -8494,6 +9098,9 @@ function isLiveMap(value) {
8494
9098
  function isLiveObject(value) {
8495
9099
  return value instanceof LiveObject;
8496
9100
  }
9101
+ function isLiveText(value) {
9102
+ return value instanceof LiveText;
9103
+ }
8497
9104
  function isLiveRegister(value) {
8498
9105
  return value instanceof LiveRegister;
8499
9106
  }
@@ -8503,14 +9110,14 @@ function cloneLson(value) {
8503
9110
  function liveNodeToLson(obj) {
8504
9111
  if (obj instanceof LiveRegister) {
8505
9112
  return obj.data;
8506
- } else if (obj instanceof LiveList || obj instanceof LiveMap || obj instanceof LiveObject) {
9113
+ } else if (obj instanceof LiveList || obj instanceof LiveMap || obj instanceof LiveObject || obj instanceof LiveText) {
8507
9114
  return obj;
8508
9115
  } else {
8509
9116
  return assertNever(obj, "Unknown AbstractCrdt");
8510
9117
  }
8511
9118
  }
8512
9119
  function lsonToLiveNode(value) {
8513
- if (value instanceof LiveObject || value instanceof LiveMap || value instanceof LiveList) {
9120
+ if (value instanceof LiveObject || value instanceof LiveMap || value instanceof LiveList || value instanceof LiveText) {
8514
9121
  return value;
8515
9122
  } else {
8516
9123
  return new LiveRegister(value);
@@ -8541,6 +9148,35 @@ function dumpPool(pool) {
8541
9148
  (r) => ` ${r.id} parent=${r.parentId} key=${r.key || "\u2014"} ${r.value}`
8542
9149
  ).join("\n");
8543
9150
  }
9151
+ function isJsonEq(a, b) {
9152
+ if (a === b) {
9153
+ return true;
9154
+ }
9155
+ if (typeof a !== "object" || a === null || typeof b !== "object" || b === null) {
9156
+ return false;
9157
+ }
9158
+ if (Array.isArray(a) || Array.isArray(b)) {
9159
+ if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) {
9160
+ return false;
9161
+ }
9162
+ for (let i = 0; i < a.length; i++) {
9163
+ if (!isJsonEq(a[i], b[i])) {
9164
+ return false;
9165
+ }
9166
+ }
9167
+ return true;
9168
+ }
9169
+ const aKeys = Object.keys(a);
9170
+ if (aKeys.length !== Object.keys(b).length) {
9171
+ return false;
9172
+ }
9173
+ for (const key of aKeys) {
9174
+ if (!isJsonEq(a[key], b[key])) {
9175
+ return false;
9176
+ }
9177
+ }
9178
+ return true;
9179
+ }
8544
9180
  function getTreesDiffOperations(currentItems, newItems) {
8545
9181
  const ops = [];
8546
9182
  currentItems.forEach((_, id) => {
@@ -8552,11 +9188,59 @@ function getTreesDiffOperations(currentItems, newItems) {
8552
9188
  const currentCrdt = currentItems.get(id);
8553
9189
  if (currentCrdt) {
8554
9190
  if (crdt.type === CrdtType.OBJECT) {
8555
- if (currentCrdt.type !== CrdtType.OBJECT || stringifyOrLog(crdt.data) !== stringifyOrLog(currentCrdt.data)) {
9191
+ if (currentCrdt.type !== CrdtType.OBJECT) {
9192
+ ops.push({ type: OpCode.UPDATE_OBJECT, id, data: crdt.data });
9193
+ } else {
9194
+ const changed = /* @__PURE__ */ new Map();
9195
+ for (const key of Object.keys(crdt.data)) {
9196
+ const value = crdt.data[key];
9197
+ if (value !== void 0 && !isJsonEq(value, currentCrdt.data[key])) {
9198
+ changed.set(key, value);
9199
+ }
9200
+ }
9201
+ if (changed.size > 0) {
9202
+ ops.push({
9203
+ type: OpCode.UPDATE_OBJECT,
9204
+ id,
9205
+ data: Object.fromEntries(changed)
9206
+ });
9207
+ }
9208
+ for (const key of Object.keys(currentCrdt.data)) {
9209
+ if (!(key in crdt.data)) {
9210
+ ops.push({ type: OpCode.DELETE_OBJECT_KEY, id, key });
9211
+ }
9212
+ }
9213
+ }
9214
+ }
9215
+ if (crdt.type === CrdtType.TEXT) {
9216
+ if (currentCrdt.type !== CrdtType.TEXT || stringifyOrLog(crdt.data) !== stringifyOrLog(currentCrdt.data) || crdt.version !== currentCrdt.version) {
8556
9217
  ops.push({
8557
- type: OpCode.UPDATE_OBJECT,
9218
+ type: OpCode.UPDATE_TEXT,
8558
9219
  id,
8559
- data: crdt.data
9220
+ baseVersion: currentCrdt.type === CrdtType.TEXT ? currentCrdt.version : 0,
9221
+ version: crdt.version,
9222
+ ops: [
9223
+ {
9224
+ type: "delete",
9225
+ index: 0,
9226
+ length: currentCrdt.type === CrdtType.TEXT ? currentCrdt.data.reduce(
9227
+ (sum, item) => sum + item.text.length,
9228
+ 0
9229
+ ) : 0
9230
+ },
9231
+ ...crdt.data.map(
9232
+ (item, index, items) => item.attributes === void 0 ? {
9233
+ type: "insert",
9234
+ index: items.slice(0, index).reduce((sum, item2) => sum + item2.text.length, 0),
9235
+ text: item.text
9236
+ } : {
9237
+ type: "insert",
9238
+ index: items.slice(0, index).reduce((sum, item2) => sum + item2.text.length, 0),
9239
+ text: item.text,
9240
+ attributes: item.attributes
9241
+ }
9242
+ )
9243
+ ]
8560
9244
  });
8561
9245
  }
8562
9246
  }
@@ -8608,6 +9292,16 @@ function getTreesDiffOperations(currentItems, newItems) {
8608
9292
  parentKey: crdt.parentKey
8609
9293
  });
8610
9294
  break;
9295
+ case CrdtType.TEXT:
9296
+ ops.push({
9297
+ type: OpCode.CREATE_TEXT,
9298
+ id,
9299
+ parentId: crdt.parentId,
9300
+ parentKey: crdt.parentKey,
9301
+ data: crdt.data,
9302
+ version: crdt.version
9303
+ });
9304
+ break;
8611
9305
  }
8612
9306
  }
8613
9307
  });
@@ -8640,6 +9334,12 @@ function mergeListStorageUpdates(first, second) {
8640
9334
  updates: updates.concat(second.updates)
8641
9335
  };
8642
9336
  }
9337
+ function mergeTextStorageUpdates(first, second) {
9338
+ return {
9339
+ ...second,
9340
+ updates: first.updates.concat(second.updates)
9341
+ };
9342
+ }
8643
9343
  function mergeStorageUpdates(first, second) {
8644
9344
  if (first === void 0) {
8645
9345
  return second;
@@ -8650,6 +9350,8 @@ function mergeStorageUpdates(first, second) {
8650
9350
  return mergeMapStorageUpdates(first, second);
8651
9351
  } else if (first.type === "LiveList" && second.type === "LiveList") {
8652
9352
  return mergeListStorageUpdates(first, second);
9353
+ } else if (first.type === "LiveText" && second.type === "LiveText") {
9354
+ return mergeTextStorageUpdates(first, second);
8653
9355
  } else {
8654
9356
  }
8655
9357
  return second;
@@ -9938,7 +10640,7 @@ function createRoom(options, config) {
9938
10640
  );
9939
10641
  output.reverse.pushLeft(applyOpResult.reverse);
9940
10642
  }
9941
- if (op.type === OpCode.CREATE_LIST || op.type === OpCode.CREATE_MAP || op.type === OpCode.CREATE_OBJECT) {
10643
+ if (op.type === OpCode.CREATE_LIST || op.type === OpCode.CREATE_MAP || op.type === OpCode.CREATE_OBJECT || op.type === OpCode.CREATE_TEXT) {
9942
10644
  createdNodeIds.add(op.id);
9943
10645
  }
9944
10646
  }
@@ -9958,6 +10660,7 @@ function createRoom(options, config) {
9958
10660
  switch (op.type) {
9959
10661
  case OpCode.DELETE_OBJECT_KEY:
9960
10662
  case OpCode.UPDATE_OBJECT:
10663
+ case OpCode.UPDATE_TEXT:
9961
10664
  case OpCode.DELETE_CRDT: {
9962
10665
  const node = context.pool.nodes.get(op.id);
9963
10666
  if (node === void 0) {
@@ -9982,6 +10685,7 @@ function createRoom(options, config) {
9982
10685
  case OpCode.CREATE_OBJECT:
9983
10686
  case OpCode.CREATE_LIST:
9984
10687
  case OpCode.CREATE_MAP:
10688
+ case OpCode.CREATE_TEXT:
9985
10689
  case OpCode.CREATE_REGISTER: {
9986
10690
  if (op.parentId === void 0) {
9987
10691
  return { modified: false };
@@ -12141,6 +12845,12 @@ function toPlainLson(lson) {
12141
12845
  liveblocksType: "LiveList",
12142
12846
  data: [...lson].map((item) => toPlainLson(item))
12143
12847
  };
12848
+ } else if (lson instanceof LiveText) {
12849
+ return {
12850
+ liveblocksType: "LiveText",
12851
+ data: lson.toDelta(),
12852
+ version: lson.version
12853
+ };
12144
12854
  } else {
12145
12855
  return lson;
12146
12856
  }
@@ -12322,6 +13032,7 @@ export {
12322
13032
  LiveList,
12323
13033
  LiveMap,
12324
13034
  LiveObject,
13035
+ LiveText,
12325
13036
  LiveblocksError,
12326
13037
  MENTION_CHARACTER,
12327
13038
  MutableSignal,
@@ -12333,6 +13044,7 @@ export {
12333
13044
  SortedList,
12334
13045
  TextEditorType,
12335
13046
  WebsocketCloseCodes,
13047
+ applyLiveTextOperations,
12336
13048
  asPos,
12337
13049
  assert,
12338
13050
  assertNever,
@@ -12388,6 +13100,7 @@ export {
12388
13100
  isRegisterStorageNode,
12389
13101
  isRootStorageNode,
12390
13102
  isStartsWithOperator,
13103
+ isTextStorageNode,
12391
13104
  isUrl,
12392
13105
  kInternal,
12393
13106
  keys,