@liveblocks/core 3.19.5-rc1 → 3.20.0-exp1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.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-exp1";
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,581 @@ 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(applyTextOperationsToSegments(deltaToSegments(delta), ops));
8708
+ }
8709
+ function invertTextOperations(segments, ops) {
8710
+ let shadow = [...segments];
8711
+ const reverse = [];
8712
+ for (const op of ops) {
8713
+ if (op.type === "insert") {
8714
+ shadow = applyInsert(shadow, op.index, op.text, op.attributes);
8715
+ reverse.unshift({
8716
+ type: "delete",
8717
+ index: op.index,
8718
+ length: op.text.length
8719
+ });
8720
+ } else if (op.type === "delete") {
8721
+ const deletedSegments = extractDeletedSegments(
8722
+ shadow,
8723
+ op.index,
8724
+ op.length
8725
+ );
8726
+ shadow = applyDelete(shadow, op.index, op.length).segments;
8727
+ const inserts = [];
8728
+ let insertIndex = op.index;
8729
+ for (const segment of deletedSegments) {
8730
+ inserts.push({
8731
+ type: "insert",
8732
+ index: insertIndex,
8733
+ text: segment.text,
8734
+ attributes: segment.attributes
8735
+ });
8736
+ insertIndex += segment.text.length;
8737
+ }
8738
+ for (let index = inserts.length - 1; index >= 0; index--) {
8739
+ reverse.unshift(inserts[index]);
8740
+ }
8741
+ } else {
8742
+ const inverse = formatReverseOperations(
8743
+ shadow,
8744
+ op.index,
8745
+ op.length,
8746
+ op.attributes
8747
+ );
8748
+ shadow = applyFormat(shadow, op.index, op.length, op.attributes);
8749
+ reverse.unshift(...inverse.reverse());
8750
+ }
8751
+ }
8752
+ return reverse;
8753
+ }
8754
+
8755
+ // src/crdts/LiveText.ts
8756
+ var LiveText = class _LiveText extends AbstractCrdt {
8757
+ #segments;
8758
+ #version;
8759
+ #pendingOps;
8760
+ constructor(textOrDelta = "", version = 0) {
8761
+ super();
8762
+ this.#segments = typeof textOrDelta === "string" ? textOrDelta.length === 0 ? [] : [{ text: textOrDelta }] : deltaToSegments(textOrDelta);
8763
+ this.#version = version;
8764
+ this.#pendingOps = /* @__PURE__ */ new Map();
8765
+ }
8766
+ get version() {
8767
+ return this.#version;
8768
+ }
8769
+ get length() {
8770
+ return this.toString().length;
8771
+ }
8772
+ /** @internal */
8773
+ static _deserialize([id, item], _parentToChildren, pool) {
8774
+ const text = new _LiveText(item.data, item.version);
8775
+ text._attach(id, pool);
8776
+ return text;
8777
+ }
8778
+ /** @internal */
8779
+ _toOps(parentId, parentKey) {
8780
+ if (this._id === void 0) {
8781
+ throw new Error("Cannot serialize LiveText if it is not attached");
8782
+ }
8783
+ return [
8784
+ {
8785
+ type: OpCode.CREATE_TEXT,
8786
+ id: this._id,
8787
+ parentId,
8788
+ parentKey,
8789
+ data: this.toDelta(),
8790
+ version: this.#version
8791
+ }
8792
+ ];
8793
+ }
8794
+ /** @internal */
8795
+ _serialize() {
8796
+ if (this.parent.type !== "HasParent") {
8797
+ throw new Error("Cannot serialize LiveText if parent is missing");
8798
+ }
8799
+ return {
8800
+ type: CrdtType.TEXT,
8801
+ parentId: nn(this.parent.node._id, "Parent node expected to have ID"),
8802
+ parentKey: this.parent.key,
8803
+ data: this.toDelta(),
8804
+ version: this.#version
8805
+ };
8806
+ }
8807
+ /** @internal */
8808
+ _attachChild(_op) {
8809
+ throw new Error("LiveText cannot contain child nodes");
8810
+ }
8811
+ /** @internal */
8812
+ _detachChild(_crdt) {
8813
+ throw new Error("LiveText cannot contain child nodes");
8814
+ }
8815
+ /** @internal */
8816
+ _apply(op, isLocal) {
8817
+ if (op.type !== OpCode.UPDATE_TEXT) {
8818
+ return super._apply(op, isLocal);
8819
+ }
8820
+ if (isLocal) {
8821
+ this.#pendingOps.set(nn(op.opId), op.ops);
8822
+ return this.#applyOperations(op.ops, op.version ?? this.#version);
8823
+ }
8824
+ if (op.opId !== void 0) {
8825
+ const pending2 = this.#pendingOps.get(op.opId);
8826
+ this.#pendingOps.delete(op.opId);
8827
+ const otherPending = Array.from(this.#pendingOps.values()).flat();
8828
+ if (pending2 !== void 0 && otherPending.length > 0) {
8829
+ this.#segments = applyTextOperationsToSegments(
8830
+ this.#segments,
8831
+ invertTextOperations(this.#segments, pending2)
8832
+ );
8833
+ const ops2 = rebaseTextOperations(op.ops, otherPending);
8834
+ return this.#applyOperations(
8835
+ ops2,
8836
+ op.version ?? Math.max(this.#version, op.baseVersion + 1)
8837
+ );
8838
+ }
8839
+ this.#version = op.version ?? Math.max(this.#version, op.baseVersion + 1);
8840
+ return { modified: false };
8841
+ }
8842
+ const pending = Array.from(this.#pendingOps.values()).flat();
8843
+ const ops = pending.length > 0 ? rebaseTextOperations(op.ops, pending) : op.ops;
8844
+ return this.#applyOperations(ops, op.version ?? this.#version + 1);
8845
+ }
8846
+ insert(index, text, attributes) {
8847
+ const clippedIndex = Math.max(0, Math.min(index, this.length));
8848
+ this.#dispatch([{ type: "insert", index: clippedIndex, text, attributes }]);
8849
+ }
8850
+ delete(index, length) {
8851
+ const clipped = clipRange(index, length, this.length);
8852
+ if (clipped.length === 0) {
8853
+ return;
8854
+ }
8855
+ this.#dispatch([
8856
+ { type: "delete", index: clipped.index, length: clipped.length }
8857
+ ]);
8858
+ }
8859
+ replace(index, length, text, attributes) {
8860
+ const clipped = clipRange(index, length, this.length);
8861
+ const ops = [];
8862
+ if (clipped.length > 0) {
8863
+ ops.push({
8864
+ type: "delete",
8865
+ index: clipped.index,
8866
+ length: clipped.length
8867
+ });
8868
+ }
8869
+ if (text.length > 0) {
8870
+ ops.push({ type: "insert", index: clipped.index, text, attributes });
8871
+ }
8872
+ this.#dispatch(ops);
8873
+ }
8874
+ format(index, length, attributes) {
8875
+ const clipped = clipRange(index, length, this.length);
8876
+ if (clipped.length === 0) {
8877
+ return;
8878
+ }
8879
+ this.#dispatch([
8880
+ {
8881
+ type: "format",
8882
+ index: clipped.index,
8883
+ length: clipped.length,
8884
+ attributes
8885
+ }
8886
+ ]);
8887
+ }
8888
+ #dispatch(ops) {
8889
+ if (ops.length === 0) {
8890
+ return;
8891
+ }
8892
+ this._pool?.assertStorageIsWritable();
8893
+ const baseVersion = this.#version;
8894
+ const reverse = this._pool !== void 0 && this._id !== void 0 ? this.#invertOperations(ops) : [];
8895
+ const changes = this.#applyOperationsLocally(ops);
8896
+ if (this._pool !== void 0 && this._id !== void 0) {
8897
+ const opId = this._pool.generateOpId();
8898
+ this.#pendingOps.set(opId, ops);
8899
+ this._pool.dispatch(
8900
+ [
8901
+ {
8902
+ type: OpCode.UPDATE_TEXT,
8903
+ id: this._id,
8904
+ opId,
8905
+ baseVersion,
8906
+ ops: [...ops]
8907
+ }
8908
+ ],
8909
+ reverse,
8910
+ /* @__PURE__ */ new Map([
8911
+ [
8912
+ this._id,
8913
+ {
8914
+ type: "LiveText",
8915
+ node: this,
8916
+ version: this.#version,
8917
+ updates: changes
8918
+ }
8919
+ ]
8920
+ ])
8921
+ );
8922
+ }
8923
+ }
8924
+ #applyOperations(ops, version) {
8925
+ const reverse = this.#invertOperations(ops);
8926
+ const changes = this.#applyOperationsLocally(ops);
8927
+ this.#version = Math.max(this.#version, version);
8928
+ return {
8929
+ reverse,
8930
+ modified: {
8931
+ type: "LiveText",
8932
+ node: this,
8933
+ version: this.#version,
8934
+ updates: changes
8935
+ }
8936
+ };
8937
+ }
8938
+ #applyOperationsLocally(ops) {
8939
+ const changes = [];
8940
+ for (const op of ops) {
8941
+ if (op.type === "insert") {
8942
+ this.#segments = applyInsert(
8943
+ this.#segments,
8944
+ op.index,
8945
+ op.text,
8946
+ op.attributes
8947
+ );
8948
+ changes.push({
8949
+ type: "insert",
8950
+ index: op.index,
8951
+ text: op.text,
8952
+ attributes: op.attributes
8953
+ });
8954
+ } else if (op.type === "delete") {
8955
+ const result = applyDelete(this.#segments, op.index, op.length);
8956
+ this.#segments = result.segments;
8957
+ changes.push({
8958
+ type: "delete",
8959
+ index: op.index,
8960
+ length: op.length,
8961
+ deletedText: result.deletedText
8962
+ });
8963
+ } else {
8964
+ this.#segments = applyFormat(
8965
+ this.#segments,
8966
+ op.index,
8967
+ op.length,
8968
+ op.attributes
8969
+ );
8970
+ changes.push({
8971
+ type: "format",
8972
+ index: op.index,
8973
+ length: op.length,
8974
+ attributes: op.attributes
8975
+ });
8976
+ }
8977
+ }
8978
+ this.invalidate();
8979
+ return changes;
8980
+ }
8981
+ #invertOperations(ops) {
8982
+ return [
8983
+ {
8984
+ type: OpCode.UPDATE_TEXT,
8985
+ id: nn(this._id),
8986
+ baseVersion: this.#version,
8987
+ ops: invertTextOperations(this.#segments, ops)
8988
+ }
8989
+ ];
8990
+ }
8991
+ toString() {
8992
+ return this.#segments.map((segment) => segment.text).join("");
8993
+ }
8994
+ toDelta() {
8995
+ return segmentsToDelta(this.#segments);
8996
+ }
8997
+ toJSON() {
8998
+ return super.toJSON();
8999
+ }
9000
+ /** @internal */
9001
+ _toJSON() {
9002
+ return this.toDelta();
9003
+ }
9004
+ /** @internal */
9005
+ _toTreeNode(key) {
9006
+ return {
9007
+ type: "LiveText",
9008
+ id: this._id ?? nanoid(),
9009
+ key,
9010
+ payload: [
9011
+ {
9012
+ type: "Json",
9013
+ id: `${this._id ?? nanoid()}:text`,
9014
+ key: "text",
9015
+ payload: this.toString()
9016
+ }
9017
+ ]
9018
+ };
9019
+ }
9020
+ clone() {
9021
+ return new _LiveText(this.toDelta(), this.#version);
9022
+ }
9023
+ };
9024
+
8429
9025
  // src/crdts/liveblocks-helpers.ts
8430
9026
  function creationOpToLiveNode(op) {
8431
9027
  return lsonToLiveNode(creationOpToLson(op));
@@ -8440,6 +9036,8 @@ function creationOpToLson(op) {
8440
9036
  return new LiveMap();
8441
9037
  case OpCode.CREATE_LIST:
8442
9038
  return new LiveList([]);
9039
+ case OpCode.CREATE_TEXT:
9040
+ return new LiveText(op.data, op.version);
8443
9041
  default:
8444
9042
  return assertNever(op, "Unknown creation Op");
8445
9043
  }
@@ -8462,6 +9060,8 @@ function deserialize(node, parentToChildren, pool) {
8462
9060
  return LiveMap._deserialize(node, parentToChildren, pool);
8463
9061
  } else if (isRegisterStorageNode(node)) {
8464
9062
  return LiveRegister._deserialize(node, parentToChildren, pool);
9063
+ } else if (isTextStorageNode(node)) {
9064
+ return LiveText._deserialize(node, parentToChildren, pool);
8465
9065
  } else {
8466
9066
  throw new Error("Unexpected CRDT type");
8467
9067
  }
@@ -8475,12 +9075,14 @@ function deserializeToLson(node, parentToChildren, pool) {
8475
9075
  return LiveMap._deserialize(node, parentToChildren, pool);
8476
9076
  } else if (isRegisterStorageNode(node)) {
8477
9077
  return node[1].data;
9078
+ } else if (isTextStorageNode(node)) {
9079
+ return LiveText._deserialize(node, parentToChildren, pool);
8478
9080
  } else {
8479
9081
  throw new Error("Unexpected CRDT type");
8480
9082
  }
8481
9083
  }
8482
9084
  function isLiveStructure(value) {
8483
- return isLiveList(value) || isLiveMap(value) || isLiveObject(value);
9085
+ return isLiveList(value) || isLiveMap(value) || isLiveObject(value) || isLiveText(value);
8484
9086
  }
8485
9087
  function isLiveNode(value) {
8486
9088
  return isLiveStructure(value) || isLiveRegister(value);
@@ -8494,6 +9096,9 @@ function isLiveMap(value) {
8494
9096
  function isLiveObject(value) {
8495
9097
  return value instanceof LiveObject;
8496
9098
  }
9099
+ function isLiveText(value) {
9100
+ return value instanceof LiveText;
9101
+ }
8497
9102
  function isLiveRegister(value) {
8498
9103
  return value instanceof LiveRegister;
8499
9104
  }
@@ -8503,14 +9108,14 @@ function cloneLson(value) {
8503
9108
  function liveNodeToLson(obj) {
8504
9109
  if (obj instanceof LiveRegister) {
8505
9110
  return obj.data;
8506
- } else if (obj instanceof LiveList || obj instanceof LiveMap || obj instanceof LiveObject) {
9111
+ } else if (obj instanceof LiveList || obj instanceof LiveMap || obj instanceof LiveObject || obj instanceof LiveText) {
8507
9112
  return obj;
8508
9113
  } else {
8509
9114
  return assertNever(obj, "Unknown AbstractCrdt");
8510
9115
  }
8511
9116
  }
8512
9117
  function lsonToLiveNode(value) {
8513
- if (value instanceof LiveObject || value instanceof LiveMap || value instanceof LiveList) {
9118
+ if (value instanceof LiveObject || value instanceof LiveMap || value instanceof LiveList || value instanceof LiveText) {
8514
9119
  return value;
8515
9120
  } else {
8516
9121
  return new LiveRegister(value);
@@ -8541,6 +9146,35 @@ function dumpPool(pool) {
8541
9146
  (r) => ` ${r.id} parent=${r.parentId} key=${r.key || "\u2014"} ${r.value}`
8542
9147
  ).join("\n");
8543
9148
  }
9149
+ function isJsonEq(a, b) {
9150
+ if (a === b) {
9151
+ return true;
9152
+ }
9153
+ if (typeof a !== "object" || a === null || typeof b !== "object" || b === null) {
9154
+ return false;
9155
+ }
9156
+ if (Array.isArray(a) || Array.isArray(b)) {
9157
+ if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) {
9158
+ return false;
9159
+ }
9160
+ for (let i = 0; i < a.length; i++) {
9161
+ if (!isJsonEq(a[i], b[i])) {
9162
+ return false;
9163
+ }
9164
+ }
9165
+ return true;
9166
+ }
9167
+ const aKeys = Object.keys(a);
9168
+ if (aKeys.length !== Object.keys(b).length) {
9169
+ return false;
9170
+ }
9171
+ for (const key of aKeys) {
9172
+ if (!isJsonEq(a[key], b[key])) {
9173
+ return false;
9174
+ }
9175
+ }
9176
+ return true;
9177
+ }
8544
9178
  function getTreesDiffOperations(currentItems, newItems) {
8545
9179
  const ops = [];
8546
9180
  currentItems.forEach((_, id) => {
@@ -8552,11 +9186,59 @@ function getTreesDiffOperations(currentItems, newItems) {
8552
9186
  const currentCrdt = currentItems.get(id);
8553
9187
  if (currentCrdt) {
8554
9188
  if (crdt.type === CrdtType.OBJECT) {
8555
- if (currentCrdt.type !== CrdtType.OBJECT || stringifyOrLog(crdt.data) !== stringifyOrLog(currentCrdt.data)) {
9189
+ if (currentCrdt.type !== CrdtType.OBJECT) {
9190
+ ops.push({ type: OpCode.UPDATE_OBJECT, id, data: crdt.data });
9191
+ } else {
9192
+ const changed = /* @__PURE__ */ new Map();
9193
+ for (const key of Object.keys(crdt.data)) {
9194
+ const value = crdt.data[key];
9195
+ if (value !== void 0 && !isJsonEq(value, currentCrdt.data[key])) {
9196
+ changed.set(key, value);
9197
+ }
9198
+ }
9199
+ if (changed.size > 0) {
9200
+ ops.push({
9201
+ type: OpCode.UPDATE_OBJECT,
9202
+ id,
9203
+ data: Object.fromEntries(changed)
9204
+ });
9205
+ }
9206
+ for (const key of Object.keys(currentCrdt.data)) {
9207
+ if (!(key in crdt.data)) {
9208
+ ops.push({ type: OpCode.DELETE_OBJECT_KEY, id, key });
9209
+ }
9210
+ }
9211
+ }
9212
+ }
9213
+ if (crdt.type === CrdtType.TEXT) {
9214
+ if (currentCrdt.type !== CrdtType.TEXT || stringifyOrLog(crdt.data) !== stringifyOrLog(currentCrdt.data) || crdt.version !== currentCrdt.version) {
8556
9215
  ops.push({
8557
- type: OpCode.UPDATE_OBJECT,
9216
+ type: OpCode.UPDATE_TEXT,
8558
9217
  id,
8559
- data: crdt.data
9218
+ baseVersion: currentCrdt.type === CrdtType.TEXT ? currentCrdt.version : 0,
9219
+ version: crdt.version,
9220
+ ops: [
9221
+ {
9222
+ type: "delete",
9223
+ index: 0,
9224
+ length: currentCrdt.type === CrdtType.TEXT ? currentCrdt.data.reduce(
9225
+ (sum, item) => sum + item.text.length,
9226
+ 0
9227
+ ) : 0
9228
+ },
9229
+ ...crdt.data.map(
9230
+ (item, index, items) => item.attributes === void 0 ? {
9231
+ type: "insert",
9232
+ index: items.slice(0, index).reduce((sum, item2) => sum + item2.text.length, 0),
9233
+ text: item.text
9234
+ } : {
9235
+ type: "insert",
9236
+ index: items.slice(0, index).reduce((sum, item2) => sum + item2.text.length, 0),
9237
+ text: item.text,
9238
+ attributes: item.attributes
9239
+ }
9240
+ )
9241
+ ]
8560
9242
  });
8561
9243
  }
8562
9244
  }
@@ -8608,6 +9290,16 @@ function getTreesDiffOperations(currentItems, newItems) {
8608
9290
  parentKey: crdt.parentKey
8609
9291
  });
8610
9292
  break;
9293
+ case CrdtType.TEXT:
9294
+ ops.push({
9295
+ type: OpCode.CREATE_TEXT,
9296
+ id,
9297
+ parentId: crdt.parentId,
9298
+ parentKey: crdt.parentKey,
9299
+ data: crdt.data,
9300
+ version: crdt.version
9301
+ });
9302
+ break;
8611
9303
  }
8612
9304
  }
8613
9305
  });
@@ -8640,6 +9332,12 @@ function mergeListStorageUpdates(first, second) {
8640
9332
  updates: updates.concat(second.updates)
8641
9333
  };
8642
9334
  }
9335
+ function mergeTextStorageUpdates(first, second) {
9336
+ return {
9337
+ ...second,
9338
+ updates: first.updates.concat(second.updates)
9339
+ };
9340
+ }
8643
9341
  function mergeStorageUpdates(first, second) {
8644
9342
  if (first === void 0) {
8645
9343
  return second;
@@ -8650,6 +9348,8 @@ function mergeStorageUpdates(first, second) {
8650
9348
  return mergeMapStorageUpdates(first, second);
8651
9349
  } else if (first.type === "LiveList" && second.type === "LiveList") {
8652
9350
  return mergeListStorageUpdates(first, second);
9351
+ } else if (first.type === "LiveText" && second.type === "LiveText") {
9352
+ return mergeTextStorageUpdates(first, second);
8653
9353
  } else {
8654
9354
  }
8655
9355
  return second;
@@ -9938,7 +10638,7 @@ function createRoom(options, config) {
9938
10638
  );
9939
10639
  output.reverse.pushLeft(applyOpResult.reverse);
9940
10640
  }
9941
- if (op.type === OpCode.CREATE_LIST || op.type === OpCode.CREATE_MAP || op.type === OpCode.CREATE_OBJECT) {
10641
+ if (op.type === OpCode.CREATE_LIST || op.type === OpCode.CREATE_MAP || op.type === OpCode.CREATE_OBJECT || op.type === OpCode.CREATE_TEXT) {
9942
10642
  createdNodeIds.add(op.id);
9943
10643
  }
9944
10644
  }
@@ -9958,6 +10658,7 @@ function createRoom(options, config) {
9958
10658
  switch (op.type) {
9959
10659
  case OpCode.DELETE_OBJECT_KEY:
9960
10660
  case OpCode.UPDATE_OBJECT:
10661
+ case OpCode.UPDATE_TEXT:
9961
10662
  case OpCode.DELETE_CRDT: {
9962
10663
  const node = context.pool.nodes.get(op.id);
9963
10664
  if (node === void 0) {
@@ -9982,6 +10683,7 @@ function createRoom(options, config) {
9982
10683
  case OpCode.CREATE_OBJECT:
9983
10684
  case OpCode.CREATE_LIST:
9984
10685
  case OpCode.CREATE_MAP:
10686
+ case OpCode.CREATE_TEXT:
9985
10687
  case OpCode.CREATE_REGISTER: {
9986
10688
  if (op.parentId === void 0) {
9987
10689
  return { modified: false };
@@ -12141,6 +12843,12 @@ function toPlainLson(lson) {
12141
12843
  liveblocksType: "LiveList",
12142
12844
  data: [...lson].map((item) => toPlainLson(item))
12143
12845
  };
12846
+ } else if (lson instanceof LiveText) {
12847
+ return {
12848
+ liveblocksType: "LiveText",
12849
+ data: lson.toDelta(),
12850
+ version: lson.version
12851
+ };
12144
12852
  } else {
12145
12853
  return lson;
12146
12854
  }
@@ -12322,6 +13030,7 @@ export {
12322
13030
  LiveList,
12323
13031
  LiveMap,
12324
13032
  LiveObject,
13033
+ LiveText,
12325
13034
  LiveblocksError,
12326
13035
  MENTION_CHARACTER,
12327
13036
  MutableSignal,
@@ -12333,6 +13042,7 @@ export {
12333
13042
  SortedList,
12334
13043
  TextEditorType,
12335
13044
  WebsocketCloseCodes,
13045
+ applyLiveTextOperations,
12336
13046
  asPos,
12337
13047
  assert,
12338
13048
  assertNever,
@@ -12388,6 +13098,7 @@ export {
12388
13098
  isRegisterStorageNode,
12389
13099
  isRootStorageNode,
12390
13100
  isStartsWithOperator,
13101
+ isTextStorageNode,
12391
13102
  isUrl,
12392
13103
  kInternal,
12393
13104
  keys,