@liveblocks/core 3.19.3 → 3.19.4-test1

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.3";
9
+ var PKG_VERSION = "3.19.4-test1";
10
10
  var PKG_FORMAT = "esm";
11
11
 
12
12
  // src/dupe-detection.ts
@@ -2715,7 +2715,6 @@ var HttpClient = class {
2715
2715
  };
2716
2716
 
2717
2717
  // src/lib/fsm.ts
2718
- var IGNORE = /* @__PURE__ */ Symbol("fsm.ignore");
2719
2718
  function distance(state1, state2) {
2720
2719
  if (state1 === state2) {
2721
2720
  return [0, 0];
@@ -2888,7 +2887,7 @@ var FSM = class {
2888
2887
  this.#eventHub = {
2889
2888
  didReceiveEvent: makeEventSource(),
2890
2889
  willTransition: makeEventSource(),
2891
- didIgnoreUnexpectedEvent: makeEventSource(),
2890
+ didIgnoreEvent: makeEventSource(),
2892
2891
  willExitState: makeEventSource(),
2893
2892
  didEnterState: makeEventSource(),
2894
2893
  didExitState: makeEventSource()
@@ -2896,7 +2895,7 @@ var FSM = class {
2896
2895
  this.events = {
2897
2896
  didReceiveEvent: this.#eventHub.didReceiveEvent.observable,
2898
2897
  willTransition: this.#eventHub.willTransition.observable,
2899
- didIgnoreUnexpectedEvent: this.#eventHub.didIgnoreUnexpectedEvent.observable,
2898
+ didIgnoreEvent: this.#eventHub.didIgnoreEvent.observable,
2900
2899
  willExitState: this.#eventHub.willExitState.observable,
2901
2900
  didEnterState: this.#eventHub.didEnterState.observable,
2902
2901
  didExitState: this.#eventHub.didExitState.observable
@@ -3019,14 +3018,9 @@ var FSM = class {
3019
3018
  * `context` params to conditionally decide which next state to transition
3020
3019
  * to.
3021
3020
  *
3022
- * If you don't define a target for a transition, the event is treated
3023
- * as unhandled in this state: `didIgnoreUnexpectedEvent` fires and the
3024
- * state does not change.
3025
- *
3026
- * To declare an event as an intentional silent no-op in this state, use
3027
- * the {@link IGNORE} sentinel — either statically (`{ EVENT: IGNORE }`)
3028
- * or as a return value from a target function. IGNORE'd events do not
3029
- * fire `didIgnoreUnexpectedEvent`.
3021
+ * If you set it to `null`, then the transition will be explicitly forbidden
3022
+ * and throw an error. If you don't define a target for a transition, then
3023
+ * such events will get ignored.
3030
3024
  */
3031
3025
  addTransitions(nameOrPattern, mapping) {
3032
3026
  if (this.#runningState !== 0 /* NOT_STARTED_YET */) {
@@ -3046,12 +3040,7 @@ var FSM = class {
3046
3040
  }
3047
3041
  const target = target_;
3048
3042
  this.#knownEventTypes.add(type);
3049
- if (target === void 0) {
3050
- continue;
3051
- }
3052
- if (target === IGNORE) {
3053
- map.set(type, IGNORE);
3054
- } else {
3043
+ if (target !== void 0) {
3055
3044
  const targetFn = typeof target === "function" ? target : () => target;
3056
3045
  map.set(type, targetFn);
3057
3046
  }
@@ -3150,14 +3139,12 @@ var FSM = class {
3150
3139
  if (this.#runningState === 2 /* STOPPED */) {
3151
3140
  return;
3152
3141
  }
3153
- const entry = this.#getTargetFn(event.type);
3154
- if (entry === IGNORE) {
3155
- return;
3156
- }
3157
- if (entry !== void 0) {
3158
- return this.#transition(event, entry);
3142
+ const targetFn = this.#getTargetFn(event.type);
3143
+ if (targetFn !== void 0) {
3144
+ return this.#transition(event, targetFn);
3145
+ } else {
3146
+ this.#eventHub.didIgnoreEvent.notify(event);
3159
3147
  }
3160
- this.#eventHub.didIgnoreUnexpectedEvent.notify(event);
3161
3148
  }
3162
3149
  #transition(event, target) {
3163
3150
  this.#eventHub.didReceiveEvent.notify(event);
@@ -3166,7 +3153,8 @@ var FSM = class {
3166
3153
  const nextTarget = targetFn(event, this.#currentContext.current);
3167
3154
  let nextState;
3168
3155
  let effects = void 0;
3169
- if (nextTarget === IGNORE) {
3156
+ if (nextTarget === null) {
3157
+ this.#eventHub.didIgnoreEvent.notify(event);
3170
3158
  return;
3171
3159
  }
3172
3160
  if (typeof nextTarget === "string") {
@@ -3385,8 +3373,8 @@ function enableTracing(machine) {
3385
3373
  machine.events.didExitState.subscribe(
3386
3374
  ({ state, durationMs }) => log2(`Exited ${state} after ${durationMs.toFixed(0)}ms`)
3387
3375
  ),
3388
- machine.events.didIgnoreUnexpectedEvent.subscribe(
3389
- (e) => log2("Ignored unexpected event", e.type, e, "(no transition declared)")
3376
+ machine.events.didIgnoreEvent.subscribe(
3377
+ (e) => log2("Ignored event", e.type, e, "(current state won't handle it)")
3390
3378
  )
3391
3379
  ];
3392
3380
  return () => {
@@ -3498,12 +3486,7 @@ function createConnectionStateMachine(delegates, options) {
3498
3486
  );
3499
3487
  const onSocketError = (event) => machine.send({ type: "EXPLICIT_SOCKET_ERROR", event });
3500
3488
  const onSocketClose = (event) => machine.send({ type: "EXPLICIT_SOCKET_CLOSE", event });
3501
- const onSocketMessage = (event) => {
3502
- machine.send({ type: "ALIVE" });
3503
- if (event.data !== "pong") {
3504
- onMessage.notify(event);
3505
- }
3506
- };
3489
+ const onSocketMessage = (event) => event.data === "pong" ? machine.send({ type: "PONG" }) : onMessage.notify(event);
3507
3490
  function teardownSocket(socket) {
3508
3491
  if (socket) {
3509
3492
  socket.removeEventListener("error", onSocketError);
@@ -3673,13 +3656,7 @@ function createConnectionStateMachine(delegates, options) {
3673
3656
  effect: [increaseBackoffDelay, logPrematureErrorOrCloseEvent(err)]
3674
3657
  };
3675
3658
  }
3676
- ).addTransitions("@connecting.busy", {
3677
- // The socket message listener is attached during @connecting.busy (see
3678
- // onEnterAsync above), so server frames (most notably the actor-id
3679
- // handshake) can fire onSocketMessage and emit a ALIVE before we
3680
- // reach @ok.*. That's fine. Heartbeat only matters in @ok.*.
3681
- ALIVE: IGNORE
3682
- });
3659
+ );
3683
3660
  const sendHeartbeat = {
3684
3661
  target: "@ok.awaiting-pong",
3685
3662
  effect: (ctx) => {
@@ -3694,8 +3671,7 @@ function createConnectionStateMachine(delegates, options) {
3694
3671
  machine.addTimedTransition("@ok.connected", HEARTBEAT_INTERVAL, maybeHeartbeat).addTransitions("@ok.connected", {
3695
3672
  NAVIGATOR_OFFLINE: maybeHeartbeat,
3696
3673
  // Don't take the browser's word for it when it says it's offline. Do a ping/pong to make sure.
3697
- WINDOW_GOT_FOCUS: sendHeartbeat,
3698
- ALIVE: IGNORE
3674
+ WINDOW_GOT_FOCUS: sendHeartbeat
3699
3675
  });
3700
3676
  machine.addTransitions("@idle.zombie", {
3701
3677
  WINDOW_GOT_FOCUS: "@connecting.backoff"
@@ -3716,7 +3692,7 @@ function createConnectionStateMachine(delegates, options) {
3716
3692
  clearTimeout(timerID);
3717
3693
  onMessage.pause();
3718
3694
  };
3719
- }).addTransitions("@ok.awaiting-pong", { ALIVE: "@ok.connected" }).addTimedTransition("@ok.awaiting-pong", PONG_TIMEOUT, {
3695
+ }).addTransitions("@ok.awaiting-pong", { PONG: "@ok.connected" }).addTimedTransition("@ok.awaiting-pong", PONG_TIMEOUT, {
3720
3696
  target: "@connecting.busy",
3721
3697
  // Log implicit connection loss and drop the current open socket
3722
3698
  effect: log(
@@ -3729,7 +3705,7 @@ function createConnectionStateMachine(delegates, options) {
3729
3705
  // not. When still OPEN, don't transition.
3730
3706
  EXPLICIT_SOCKET_ERROR: (_, context) => {
3731
3707
  if (context.socket?.readyState === 1) {
3732
- return IGNORE;
3708
+ return null;
3733
3709
  }
3734
3710
  return {
3735
3711
  target: "@connecting.backoff",
@@ -5497,7 +5473,9 @@ var OpCode = Object.freeze({
5497
5473
  DELETE_CRDT: 5,
5498
5474
  DELETE_OBJECT_KEY: 6,
5499
5475
  CREATE_MAP: 7,
5500
- CREATE_REGISTER: 8
5476
+ CREATE_REGISTER: 8,
5477
+ CREATE_TEXT: 9,
5478
+ UPDATE_TEXT: 10
5501
5479
  });
5502
5480
  function isIgnoredOp(op) {
5503
5481
  return op.type === OpCode.DELETE_CRDT && op.id === "ACK";
@@ -5508,7 +5486,8 @@ var CrdtType = Object.freeze({
5508
5486
  OBJECT: 0,
5509
5487
  LIST: 1,
5510
5488
  MAP: 2,
5511
- REGISTER: 3
5489
+ REGISTER: 3,
5490
+ TEXT: 4
5512
5491
  });
5513
5492
  function isRootStorageNode(node) {
5514
5493
  return node[0] === "root";
@@ -5525,6 +5504,9 @@ function isMapStorageNode(node) {
5525
5504
  function isRegisterStorageNode(node) {
5526
5505
  return node[1].type === CrdtType.REGISTER;
5527
5506
  }
5507
+ function isTextStorageNode(node) {
5508
+ return node[1].type === CrdtType.TEXT;
5509
+ }
5528
5510
  function isCompactRootNode(node) {
5529
5511
  return node[0] === "root";
5530
5512
  }
@@ -5547,6 +5529,9 @@ function* compactNodesToNodeStream(compactNodes) {
5547
5529
  case CrdtType.REGISTER:
5548
5530
  yield [cnode[0], { type: CrdtType.REGISTER, parentId: cnode[2], parentKey: cnode[3], data: cnode[4] }];
5549
5531
  break;
5532
+ case CrdtType.TEXT:
5533
+ yield [cnode[0], { type: CrdtType.TEXT, parentId: cnode[2], parentKey: cnode[3], data: cnode[4], version: cnode[5] }];
5534
+ break;
5550
5535
  default:
5551
5536
  }
5552
5537
  }
@@ -5575,6 +5560,17 @@ function* nodeStreamToCompactNodes(nodes) {
5575
5560
  const id = node[0];
5576
5561
  const crdt = node[1];
5577
5562
  yield [id, CrdtType.REGISTER, crdt.parentId, crdt.parentKey, crdt.data];
5563
+ } else if (isTextStorageNode(node)) {
5564
+ const id = node[0];
5565
+ const crdt = node[1];
5566
+ yield [
5567
+ id,
5568
+ CrdtType.TEXT,
5569
+ crdt.parentId,
5570
+ crdt.parentKey,
5571
+ crdt.data,
5572
+ crdt.version
5573
+ ];
5578
5574
  } else {
5579
5575
  }
5580
5576
  }
@@ -8231,6 +8227,581 @@ var LiveObject = class _LiveObject extends AbstractCrdt {
8231
8227
  }
8232
8228
  };
8233
8229
 
8230
+ // src/crdts/liveTextOps.ts
8231
+ function attributesEqual(left, right) {
8232
+ if (left === right) {
8233
+ return true;
8234
+ }
8235
+ if (left === void 0 || right === void 0) {
8236
+ return false;
8237
+ }
8238
+ const leftKeys = Object.keys(left);
8239
+ const rightKeys = Object.keys(right);
8240
+ if (leftKeys.length !== rightKeys.length) {
8241
+ return false;
8242
+ }
8243
+ for (const key of leftKeys) {
8244
+ if (left[key] !== right[key]) {
8245
+ return false;
8246
+ }
8247
+ }
8248
+ return true;
8249
+ }
8250
+ function cloneAttributes(attributes) {
8251
+ return attributes === void 0 ? void 0 : freeze({ ...attributes });
8252
+ }
8253
+ function normalizeSegments(segments) {
8254
+ const normalized = [];
8255
+ for (const segment of segments) {
8256
+ if (segment.text.length === 0) {
8257
+ continue;
8258
+ }
8259
+ const last = normalized.at(-1);
8260
+ const attributes = cloneAttributes(segment.attributes);
8261
+ if (last !== void 0 && attributesEqual(last.attributes, attributes)) {
8262
+ last.text += segment.text;
8263
+ } else {
8264
+ normalized.push({ text: segment.text, attributes });
8265
+ }
8266
+ }
8267
+ return normalized;
8268
+ }
8269
+ function deltaToSegments(delta) {
8270
+ return normalizeSegments(
8271
+ delta.map((item) => ({
8272
+ text: item.text,
8273
+ attributes: item.attributes
8274
+ }))
8275
+ );
8276
+ }
8277
+ function segmentsToDelta(segments) {
8278
+ return segments.map(
8279
+ (segment) => segment.attributes === void 0 ? { text: segment.text } : { text: segment.text, attributes: { ...segment.attributes } }
8280
+ );
8281
+ }
8282
+ function textLength(segments) {
8283
+ return segments.reduce((sum, segment) => sum + segment.text.length, 0);
8284
+ }
8285
+ function splitSegmentsAt(segments, index) {
8286
+ const result = [];
8287
+ let offset = 0;
8288
+ for (const segment of segments) {
8289
+ const end = offset + segment.text.length;
8290
+ if (index > offset && index < end) {
8291
+ const before2 = segment.text.slice(0, index - offset);
8292
+ const after2 = segment.text.slice(index - offset);
8293
+ result.push({ text: before2, attributes: segment.attributes });
8294
+ result.push({ text: after2, attributes: segment.attributes });
8295
+ } else {
8296
+ result.push({ text: segment.text, attributes: segment.attributes });
8297
+ }
8298
+ offset = end;
8299
+ }
8300
+ return result;
8301
+ }
8302
+ function clipRange(index, length, contentLength) {
8303
+ const clippedIndex = Math.max(0, Math.min(index, contentLength));
8304
+ const clippedEnd = Math.max(
8305
+ clippedIndex,
8306
+ Math.min(index + length, contentLength)
8307
+ );
8308
+ return { index: clippedIndex, length: clippedEnd - clippedIndex };
8309
+ }
8310
+ function applyInsert(segments, index, text, attributes) {
8311
+ if (text.length === 0) {
8312
+ return normalizeSegments(segments);
8313
+ }
8314
+ const split = splitSegmentsAt(segments, index);
8315
+ const result = [];
8316
+ let offset = 0;
8317
+ let inserted = false;
8318
+ for (const segment of split) {
8319
+ if (!inserted && offset === index) {
8320
+ result.push({ text, attributes });
8321
+ inserted = true;
8322
+ }
8323
+ result.push(segment);
8324
+ offset += segment.text.length;
8325
+ }
8326
+ if (!inserted) {
8327
+ result.push({ text, attributes });
8328
+ }
8329
+ return normalizeSegments(result);
8330
+ }
8331
+ function extractDeletedSegments(segments, index, length) {
8332
+ const split = splitSegmentsAt(
8333
+ splitSegmentsAt(segments, index),
8334
+ index + length
8335
+ );
8336
+ const deleted = [];
8337
+ let offset = 0;
8338
+ for (const segment of split) {
8339
+ const end = offset + segment.text.length;
8340
+ if (offset >= index && end <= index + length) {
8341
+ deleted.push({
8342
+ text: segment.text,
8343
+ attributes: segment.attributes
8344
+ });
8345
+ }
8346
+ offset = end;
8347
+ }
8348
+ return normalizeSegments(deleted);
8349
+ }
8350
+ function applyDelete(segments, index, length) {
8351
+ const deletedSegments = extractDeletedSegments(segments, index, length);
8352
+ const split = splitSegmentsAt(
8353
+ splitSegmentsAt(segments, index),
8354
+ index + length
8355
+ );
8356
+ const result = [];
8357
+ let offset = 0;
8358
+ let deletedText = "";
8359
+ for (const segment of split) {
8360
+ const end = offset + segment.text.length;
8361
+ if (offset >= index && end <= index + length) {
8362
+ deletedText += segment.text;
8363
+ } else {
8364
+ result.push(segment);
8365
+ }
8366
+ offset = end;
8367
+ }
8368
+ return {
8369
+ segments: normalizeSegments(result),
8370
+ deletedText,
8371
+ deletedSegments
8372
+ };
8373
+ }
8374
+ function applyFormat(segments, index, length, attributes) {
8375
+ const split = splitSegmentsAt(
8376
+ splitSegmentsAt(segments, index),
8377
+ index + length
8378
+ );
8379
+ const result = [];
8380
+ let offset = 0;
8381
+ for (const segment of split) {
8382
+ const end = offset + segment.text.length;
8383
+ if (offset >= index && end <= index + length) {
8384
+ const nextAttributes = {
8385
+ ...segment.attributes ?? {}
8386
+ };
8387
+ for (const [key, value] of Object.entries(attributes)) {
8388
+ if (value === null) {
8389
+ delete nextAttributes[key];
8390
+ } else {
8391
+ nextAttributes[key] = value;
8392
+ }
8393
+ }
8394
+ result.push({
8395
+ text: segment.text,
8396
+ attributes: Object.keys(nextAttributes).length === 0 ? void 0 : freeze(nextAttributes)
8397
+ });
8398
+ } else {
8399
+ result.push(segment);
8400
+ }
8401
+ offset = end;
8402
+ }
8403
+ return normalizeSegments(result);
8404
+ }
8405
+ function formatReverseOperations(segments, index, length, patch) {
8406
+ const split = splitSegmentsAt(
8407
+ splitSegmentsAt(segments, index),
8408
+ index + length
8409
+ );
8410
+ const result = [];
8411
+ let offset = 0;
8412
+ for (const segment of split) {
8413
+ const end = offset + segment.text.length;
8414
+ if (offset >= index && end <= index + length) {
8415
+ const attributes = {};
8416
+ for (const key of Object.keys(patch)) {
8417
+ attributes[key] = segment.attributes?.[key] ?? null;
8418
+ }
8419
+ result.push({
8420
+ type: "format",
8421
+ index: offset,
8422
+ length: segment.text.length,
8423
+ attributes
8424
+ });
8425
+ }
8426
+ offset = end;
8427
+ }
8428
+ return result;
8429
+ }
8430
+ function mapIndexThroughOperation(index, op) {
8431
+ if (op.type === "insert") {
8432
+ return op.index <= index ? index + op.text.length : index;
8433
+ } else if (op.type === "delete") {
8434
+ if (op.index >= index) {
8435
+ return index;
8436
+ }
8437
+ return Math.max(op.index, index - op.length);
8438
+ } else {
8439
+ return index;
8440
+ }
8441
+ }
8442
+ function mapTextIndexThroughOperations(index, ops) {
8443
+ let mapped = index;
8444
+ for (const op of ops) {
8445
+ mapped = mapIndexThroughOperation(mapped, op);
8446
+ }
8447
+ return mapped;
8448
+ }
8449
+ function rebaseTextOperations(ops, acceptedOps) {
8450
+ return ops.map((op) => {
8451
+ if (op.type === "insert") {
8452
+ return {
8453
+ ...op,
8454
+ index: mapTextIndexThroughOperations(op.index, acceptedOps)
8455
+ };
8456
+ } else if (op.type === "delete" || op.type === "format") {
8457
+ const start = mapTextIndexThroughOperations(op.index, acceptedOps);
8458
+ const end = mapTextIndexThroughOperations(
8459
+ op.index + op.length,
8460
+ acceptedOps
8461
+ );
8462
+ return { ...op, index: start, length: Math.max(0, end - start) };
8463
+ } else {
8464
+ return op;
8465
+ }
8466
+ });
8467
+ }
8468
+ function applyTextOperationsToSegments(segments, ops) {
8469
+ let next = [...segments];
8470
+ for (const op of ops) {
8471
+ if (op.type === "insert") {
8472
+ const index = Math.max(0, Math.min(op.index, textLength(next)));
8473
+ next = applyInsert(next, index, op.text, op.attributes);
8474
+ } else if (op.type === "delete") {
8475
+ const index = Math.max(0, Math.min(op.index, textLength(next)));
8476
+ const clipped = clipRange(index, op.length, textLength(next));
8477
+ next = applyDelete(next, clipped.index, clipped.length).segments;
8478
+ } else {
8479
+ const index = Math.max(0, Math.min(op.index, textLength(next)));
8480
+ const clipped = clipRange(index, op.length, textLength(next));
8481
+ next = applyFormat(next, clipped.index, clipped.length, op.attributes);
8482
+ }
8483
+ }
8484
+ return next;
8485
+ }
8486
+ function applyLiveTextOperations(delta, ops) {
8487
+ return segmentsToDelta(applyTextOperationsToSegments(deltaToSegments(delta), ops));
8488
+ }
8489
+ function invertTextOperations(segments, ops) {
8490
+ let shadow = [...segments];
8491
+ const reverse = [];
8492
+ for (const op of ops) {
8493
+ if (op.type === "insert") {
8494
+ shadow = applyInsert(shadow, op.index, op.text, op.attributes);
8495
+ reverse.unshift({
8496
+ type: "delete",
8497
+ index: op.index,
8498
+ length: op.text.length
8499
+ });
8500
+ } else if (op.type === "delete") {
8501
+ const deletedSegments = extractDeletedSegments(
8502
+ shadow,
8503
+ op.index,
8504
+ op.length
8505
+ );
8506
+ shadow = applyDelete(shadow, op.index, op.length).segments;
8507
+ const inserts = [];
8508
+ let insertIndex = op.index;
8509
+ for (const segment of deletedSegments) {
8510
+ inserts.push({
8511
+ type: "insert",
8512
+ index: insertIndex,
8513
+ text: segment.text,
8514
+ attributes: segment.attributes
8515
+ });
8516
+ insertIndex += segment.text.length;
8517
+ }
8518
+ for (let index = inserts.length - 1; index >= 0; index--) {
8519
+ reverse.unshift(inserts[index]);
8520
+ }
8521
+ } else {
8522
+ const inverse = formatReverseOperations(
8523
+ shadow,
8524
+ op.index,
8525
+ op.length,
8526
+ op.attributes
8527
+ );
8528
+ shadow = applyFormat(shadow, op.index, op.length, op.attributes);
8529
+ reverse.unshift(...inverse.reverse());
8530
+ }
8531
+ }
8532
+ return reverse;
8533
+ }
8534
+
8535
+ // src/crdts/LiveText.ts
8536
+ var LiveText = class _LiveText extends AbstractCrdt {
8537
+ #segments;
8538
+ #version;
8539
+ #pendingOps;
8540
+ constructor(textOrDelta = "", version = 0) {
8541
+ super();
8542
+ this.#segments = typeof textOrDelta === "string" ? textOrDelta.length === 0 ? [] : [{ text: textOrDelta }] : deltaToSegments(textOrDelta);
8543
+ this.#version = version;
8544
+ this.#pendingOps = /* @__PURE__ */ new Map();
8545
+ }
8546
+ get version() {
8547
+ return this.#version;
8548
+ }
8549
+ get length() {
8550
+ return this.toString().length;
8551
+ }
8552
+ /** @internal */
8553
+ static _deserialize([id, item], _parentToChildren, pool) {
8554
+ const text = new _LiveText(item.data, item.version);
8555
+ text._attach(id, pool);
8556
+ return text;
8557
+ }
8558
+ /** @internal */
8559
+ _toOps(parentId, parentKey) {
8560
+ if (this._id === void 0) {
8561
+ throw new Error("Cannot serialize LiveText if it is not attached");
8562
+ }
8563
+ return [
8564
+ {
8565
+ type: OpCode.CREATE_TEXT,
8566
+ id: this._id,
8567
+ parentId,
8568
+ parentKey,
8569
+ data: this.toDelta(),
8570
+ version: this.#version
8571
+ }
8572
+ ];
8573
+ }
8574
+ /** @internal */
8575
+ _serialize() {
8576
+ if (this.parent.type !== "HasParent") {
8577
+ throw new Error("Cannot serialize LiveText if parent is missing");
8578
+ }
8579
+ return {
8580
+ type: CrdtType.TEXT,
8581
+ parentId: nn(this.parent.node._id, "Parent node expected to have ID"),
8582
+ parentKey: this.parent.key,
8583
+ data: this.toDelta(),
8584
+ version: this.#version
8585
+ };
8586
+ }
8587
+ /** @internal */
8588
+ _attachChild(_op) {
8589
+ throw new Error("LiveText cannot contain child nodes");
8590
+ }
8591
+ /** @internal */
8592
+ _detachChild(_crdt) {
8593
+ throw new Error("LiveText cannot contain child nodes");
8594
+ }
8595
+ /** @internal */
8596
+ _apply(op, isLocal) {
8597
+ if (op.type !== OpCode.UPDATE_TEXT) {
8598
+ return super._apply(op, isLocal);
8599
+ }
8600
+ if (isLocal) {
8601
+ this.#pendingOps.set(nn(op.opId), op.ops);
8602
+ return this.#applyOperations(op.ops, op.version ?? this.#version);
8603
+ }
8604
+ if (op.opId !== void 0) {
8605
+ const pending2 = this.#pendingOps.get(op.opId);
8606
+ this.#pendingOps.delete(op.opId);
8607
+ const otherPending = Array.from(this.#pendingOps.values()).flat();
8608
+ if (pending2 !== void 0 && otherPending.length > 0) {
8609
+ this.#segments = applyTextOperationsToSegments(
8610
+ this.#segments,
8611
+ invertTextOperations(this.#segments, pending2)
8612
+ );
8613
+ const ops2 = rebaseTextOperations(op.ops, otherPending);
8614
+ return this.#applyOperations(
8615
+ ops2,
8616
+ op.version ?? Math.max(this.#version, op.baseVersion + 1)
8617
+ );
8618
+ }
8619
+ this.#version = op.version ?? Math.max(this.#version, op.baseVersion + 1);
8620
+ return { modified: false };
8621
+ }
8622
+ const pending = Array.from(this.#pendingOps.values()).flat();
8623
+ const ops = pending.length > 0 ? rebaseTextOperations(op.ops, pending) : op.ops;
8624
+ return this.#applyOperations(ops, op.version ?? this.#version + 1);
8625
+ }
8626
+ insert(index, text, attributes) {
8627
+ const clippedIndex = Math.max(0, Math.min(index, this.length));
8628
+ this.#dispatch([{ type: "insert", index: clippedIndex, text, attributes }]);
8629
+ }
8630
+ delete(index, length) {
8631
+ const clipped = clipRange(index, length, this.length);
8632
+ if (clipped.length === 0) {
8633
+ return;
8634
+ }
8635
+ this.#dispatch([
8636
+ { type: "delete", index: clipped.index, length: clipped.length }
8637
+ ]);
8638
+ }
8639
+ replace(index, length, text, attributes) {
8640
+ const clipped = clipRange(index, length, this.length);
8641
+ const ops = [];
8642
+ if (clipped.length > 0) {
8643
+ ops.push({
8644
+ type: "delete",
8645
+ index: clipped.index,
8646
+ length: clipped.length
8647
+ });
8648
+ }
8649
+ if (text.length > 0) {
8650
+ ops.push({ type: "insert", index: clipped.index, text, attributes });
8651
+ }
8652
+ this.#dispatch(ops);
8653
+ }
8654
+ format(index, length, attributes) {
8655
+ const clipped = clipRange(index, length, this.length);
8656
+ if (clipped.length === 0) {
8657
+ return;
8658
+ }
8659
+ this.#dispatch([
8660
+ {
8661
+ type: "format",
8662
+ index: clipped.index,
8663
+ length: clipped.length,
8664
+ attributes
8665
+ }
8666
+ ]);
8667
+ }
8668
+ #dispatch(ops) {
8669
+ if (ops.length === 0) {
8670
+ return;
8671
+ }
8672
+ this._pool?.assertStorageIsWritable();
8673
+ const baseVersion = this.#version;
8674
+ const reverse = this._pool !== void 0 && this._id !== void 0 ? this.#invertOperations(ops) : [];
8675
+ const changes = this.#applyOperationsLocally(ops);
8676
+ if (this._pool !== void 0 && this._id !== void 0) {
8677
+ const opId = this._pool.generateOpId();
8678
+ this.#pendingOps.set(opId, ops);
8679
+ this._pool.dispatch(
8680
+ [
8681
+ {
8682
+ type: OpCode.UPDATE_TEXT,
8683
+ id: this._id,
8684
+ opId,
8685
+ baseVersion,
8686
+ ops: [...ops]
8687
+ }
8688
+ ],
8689
+ reverse,
8690
+ /* @__PURE__ */ new Map([
8691
+ [
8692
+ this._id,
8693
+ {
8694
+ type: "LiveText",
8695
+ node: this,
8696
+ version: this.#version,
8697
+ updates: changes
8698
+ }
8699
+ ]
8700
+ ])
8701
+ );
8702
+ }
8703
+ }
8704
+ #applyOperations(ops, version) {
8705
+ const reverse = this.#invertOperations(ops);
8706
+ const changes = this.#applyOperationsLocally(ops);
8707
+ this.#version = Math.max(this.#version, version);
8708
+ return {
8709
+ reverse,
8710
+ modified: {
8711
+ type: "LiveText",
8712
+ node: this,
8713
+ version: this.#version,
8714
+ updates: changes
8715
+ }
8716
+ };
8717
+ }
8718
+ #applyOperationsLocally(ops) {
8719
+ const changes = [];
8720
+ for (const op of ops) {
8721
+ if (op.type === "insert") {
8722
+ this.#segments = applyInsert(
8723
+ this.#segments,
8724
+ op.index,
8725
+ op.text,
8726
+ op.attributes
8727
+ );
8728
+ changes.push({
8729
+ type: "insert",
8730
+ index: op.index,
8731
+ text: op.text,
8732
+ attributes: op.attributes
8733
+ });
8734
+ } else if (op.type === "delete") {
8735
+ const result = applyDelete(this.#segments, op.index, op.length);
8736
+ this.#segments = result.segments;
8737
+ changes.push({
8738
+ type: "delete",
8739
+ index: op.index,
8740
+ length: op.length,
8741
+ deletedText: result.deletedText
8742
+ });
8743
+ } else {
8744
+ this.#segments = applyFormat(
8745
+ this.#segments,
8746
+ op.index,
8747
+ op.length,
8748
+ op.attributes
8749
+ );
8750
+ changes.push({
8751
+ type: "format",
8752
+ index: op.index,
8753
+ length: op.length,
8754
+ attributes: op.attributes
8755
+ });
8756
+ }
8757
+ }
8758
+ this.invalidate();
8759
+ return changes;
8760
+ }
8761
+ #invertOperations(ops) {
8762
+ return [
8763
+ {
8764
+ type: OpCode.UPDATE_TEXT,
8765
+ id: nn(this._id),
8766
+ baseVersion: this.#version,
8767
+ ops: invertTextOperations(this.#segments, ops)
8768
+ }
8769
+ ];
8770
+ }
8771
+ toString() {
8772
+ return this.#segments.map((segment) => segment.text).join("");
8773
+ }
8774
+ toDelta() {
8775
+ return segmentsToDelta(this.#segments);
8776
+ }
8777
+ toJSON() {
8778
+ return super.toJSON();
8779
+ }
8780
+ /** @internal */
8781
+ _toJSON() {
8782
+ return this.toDelta();
8783
+ }
8784
+ /** @internal */
8785
+ _toTreeNode(key) {
8786
+ return {
8787
+ type: "LiveText",
8788
+ id: this._id ?? nanoid(),
8789
+ key,
8790
+ payload: [
8791
+ {
8792
+ type: "Json",
8793
+ id: `${this._id ?? nanoid()}:text`,
8794
+ key: "text",
8795
+ payload: this.toString()
8796
+ }
8797
+ ]
8798
+ };
8799
+ }
8800
+ clone() {
8801
+ return new _LiveText(this.toDelta(), this.#version);
8802
+ }
8803
+ };
8804
+
8234
8805
  // src/crdts/liveblocks-helpers.ts
8235
8806
  function creationOpToLiveNode(op) {
8236
8807
  return lsonToLiveNode(creationOpToLson(op));
@@ -8245,6 +8816,8 @@ function creationOpToLson(op) {
8245
8816
  return new LiveMap();
8246
8817
  case OpCode.CREATE_LIST:
8247
8818
  return new LiveList([]);
8819
+ case OpCode.CREATE_TEXT:
8820
+ return new LiveText(op.data, op.version);
8248
8821
  default:
8249
8822
  return assertNever(op, "Unknown creation Op");
8250
8823
  }
@@ -8267,6 +8840,8 @@ function deserialize(node, parentToChildren, pool) {
8267
8840
  return LiveMap._deserialize(node, parentToChildren, pool);
8268
8841
  } else if (isRegisterStorageNode(node)) {
8269
8842
  return LiveRegister._deserialize(node, parentToChildren, pool);
8843
+ } else if (isTextStorageNode(node)) {
8844
+ return LiveText._deserialize(node, parentToChildren, pool);
8270
8845
  } else {
8271
8846
  throw new Error("Unexpected CRDT type");
8272
8847
  }
@@ -8280,12 +8855,14 @@ function deserializeToLson(node, parentToChildren, pool) {
8280
8855
  return LiveMap._deserialize(node, parentToChildren, pool);
8281
8856
  } else if (isRegisterStorageNode(node)) {
8282
8857
  return node[1].data;
8858
+ } else if (isTextStorageNode(node)) {
8859
+ return LiveText._deserialize(node, parentToChildren, pool);
8283
8860
  } else {
8284
8861
  throw new Error("Unexpected CRDT type");
8285
8862
  }
8286
8863
  }
8287
8864
  function isLiveStructure(value) {
8288
- return isLiveList(value) || isLiveMap(value) || isLiveObject(value);
8865
+ return isLiveList(value) || isLiveMap(value) || isLiveObject(value) || isLiveText(value);
8289
8866
  }
8290
8867
  function isLiveNode(value) {
8291
8868
  return isLiveStructure(value) || isLiveRegister(value);
@@ -8299,6 +8876,9 @@ function isLiveMap(value) {
8299
8876
  function isLiveObject(value) {
8300
8877
  return value instanceof LiveObject;
8301
8878
  }
8879
+ function isLiveText(value) {
8880
+ return value instanceof LiveText;
8881
+ }
8302
8882
  function isLiveRegister(value) {
8303
8883
  return value instanceof LiveRegister;
8304
8884
  }
@@ -8308,14 +8888,14 @@ function cloneLson(value) {
8308
8888
  function liveNodeToLson(obj) {
8309
8889
  if (obj instanceof LiveRegister) {
8310
8890
  return obj.data;
8311
- } else if (obj instanceof LiveList || obj instanceof LiveMap || obj instanceof LiveObject) {
8891
+ } else if (obj instanceof LiveList || obj instanceof LiveMap || obj instanceof LiveObject || obj instanceof LiveText) {
8312
8892
  return obj;
8313
8893
  } else {
8314
8894
  return assertNever(obj, "Unknown AbstractCrdt");
8315
8895
  }
8316
8896
  }
8317
8897
  function lsonToLiveNode(value) {
8318
- if (value instanceof LiveObject || value instanceof LiveMap || value instanceof LiveList) {
8898
+ if (value instanceof LiveObject || value instanceof LiveMap || value instanceof LiveList || value instanceof LiveText) {
8319
8899
  return value;
8320
8900
  } else {
8321
8901
  return new LiveRegister(value);
@@ -8340,6 +8920,38 @@ function getTreesDiffOperations(currentItems, newItems) {
8340
8920
  });
8341
8921
  }
8342
8922
  }
8923
+ if (crdt.type === CrdtType.TEXT) {
8924
+ if (currentCrdt.type !== CrdtType.TEXT || stringifyOrLog(crdt.data) !== stringifyOrLog(currentCrdt.data) || crdt.version !== currentCrdt.version) {
8925
+ ops.push({
8926
+ type: OpCode.UPDATE_TEXT,
8927
+ id,
8928
+ baseVersion: currentCrdt.type === CrdtType.TEXT ? currentCrdt.version : 0,
8929
+ version: crdt.version,
8930
+ ops: [
8931
+ {
8932
+ type: "delete",
8933
+ index: 0,
8934
+ length: currentCrdt.type === CrdtType.TEXT ? currentCrdt.data.reduce(
8935
+ (sum, item) => sum + item.text.length,
8936
+ 0
8937
+ ) : 0
8938
+ },
8939
+ ...crdt.data.map(
8940
+ (item, index, items) => item.attributes === void 0 ? {
8941
+ type: "insert",
8942
+ index: items.slice(0, index).reduce((sum, item2) => sum + item2.text.length, 0),
8943
+ text: item.text
8944
+ } : {
8945
+ type: "insert",
8946
+ index: items.slice(0, index).reduce((sum, item2) => sum + item2.text.length, 0),
8947
+ text: item.text,
8948
+ attributes: item.attributes
8949
+ }
8950
+ )
8951
+ ]
8952
+ });
8953
+ }
8954
+ }
8343
8955
  if (crdt.parentKey !== currentCrdt.parentKey) {
8344
8956
  ops.push({
8345
8957
  type: OpCode.SET_PARENT_KEY,
@@ -8388,6 +9000,16 @@ function getTreesDiffOperations(currentItems, newItems) {
8388
9000
  parentKey: crdt.parentKey
8389
9001
  });
8390
9002
  break;
9003
+ case CrdtType.TEXT:
9004
+ ops.push({
9005
+ type: OpCode.CREATE_TEXT,
9006
+ id,
9007
+ parentId: crdt.parentId,
9008
+ parentKey: crdt.parentKey,
9009
+ data: crdt.data,
9010
+ version: crdt.version
9011
+ });
9012
+ break;
8391
9013
  }
8392
9014
  }
8393
9015
  });
@@ -8420,6 +9042,12 @@ function mergeListStorageUpdates(first, second) {
8420
9042
  updates: updates.concat(second.updates)
8421
9043
  };
8422
9044
  }
9045
+ function mergeTextStorageUpdates(first, second) {
9046
+ return {
9047
+ ...second,
9048
+ updates: first.updates.concat(second.updates)
9049
+ };
9050
+ }
8423
9051
  function mergeStorageUpdates(first, second) {
8424
9052
  if (first === void 0) {
8425
9053
  return second;
@@ -8430,6 +9058,8 @@ function mergeStorageUpdates(first, second) {
8430
9058
  return mergeMapStorageUpdates(first, second);
8431
9059
  } else if (first.type === "LiveList" && second.type === "LiveList") {
8432
9060
  return mergeListStorageUpdates(first, second);
9061
+ } else if (first.type === "LiveText" && second.type === "LiveText") {
9062
+ return mergeTextStorageUpdates(first, second);
8433
9063
  } else {
8434
9064
  }
8435
9065
  return second;
@@ -9711,7 +10341,7 @@ function createRoom(options, config) {
9711
10341
  );
9712
10342
  output.reverse.pushLeft(applyOpResult.reverse);
9713
10343
  }
9714
- if (op.type === OpCode.CREATE_LIST || op.type === OpCode.CREATE_MAP || op.type === OpCode.CREATE_OBJECT) {
10344
+ if (op.type === OpCode.CREATE_LIST || op.type === OpCode.CREATE_MAP || op.type === OpCode.CREATE_OBJECT || op.type === OpCode.CREATE_TEXT) {
9715
10345
  createdNodeIds.add(op.id);
9716
10346
  }
9717
10347
  }
@@ -9731,6 +10361,7 @@ function createRoom(options, config) {
9731
10361
  switch (op.type) {
9732
10362
  case OpCode.DELETE_OBJECT_KEY:
9733
10363
  case OpCode.UPDATE_OBJECT:
10364
+ case OpCode.UPDATE_TEXT:
9734
10365
  case OpCode.DELETE_CRDT: {
9735
10366
  const node = context.pool.nodes.get(op.id);
9736
10367
  if (node === void 0) {
@@ -9755,6 +10386,7 @@ function createRoom(options, config) {
9755
10386
  case OpCode.CREATE_OBJECT:
9756
10387
  case OpCode.CREATE_LIST:
9757
10388
  case OpCode.CREATE_MAP:
10389
+ case OpCode.CREATE_TEXT:
9758
10390
  case OpCode.CREATE_REGISTER: {
9759
10391
  if (op.parentId === void 0) {
9760
10392
  return { modified: false };
@@ -11909,6 +12541,12 @@ function toPlainLson(lson) {
11909
12541
  liveblocksType: "LiveList",
11910
12542
  data: [...lson].map((item) => toPlainLson(item))
11911
12543
  };
12544
+ } else if (lson instanceof LiveText) {
12545
+ return {
12546
+ liveblocksType: "LiveText",
12547
+ data: lson.toDelta(),
12548
+ version: lson.version
12549
+ };
11912
12550
  } else {
11913
12551
  return lson;
11914
12552
  }
@@ -12090,6 +12728,7 @@ export {
12090
12728
  LiveList,
12091
12729
  LiveMap,
12092
12730
  LiveObject,
12731
+ LiveText,
12093
12732
  LiveblocksError,
12094
12733
  MENTION_CHARACTER,
12095
12734
  MutableSignal,
@@ -12101,6 +12740,7 @@ export {
12101
12740
  SortedList,
12102
12741
  TextEditorType,
12103
12742
  WebsocketCloseCodes,
12743
+ applyLiveTextOperations,
12104
12744
  asPos,
12105
12745
  assert,
12106
12746
  assertNever,
@@ -12156,6 +12796,7 @@ export {
12156
12796
  isRegisterStorageNode,
12157
12797
  isRootStorageNode,
12158
12798
  isStartsWithOperator,
12799
+ isTextStorageNode,
12159
12800
  isUrl,
12160
12801
  kInternal,
12161
12802
  keys,