@liveblocks/core 3.24.2 → 3.24.3-livetextreplay

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -6,7 +6,7 @@ var __export = (target, all) => {
6
6
 
7
7
  // src/version.ts
8
8
  var PKG_NAME = "@liveblocks/core";
9
- var PKG_VERSION = "3.24.2";
9
+ var PKG_VERSION = "3.24.3-livetextreplay";
10
10
  var PKG_FORMAT = "cjs";
11
11
 
12
12
  // src/dupe-detection.ts
@@ -9850,6 +9850,9 @@ var LiveText = class _LiveText extends AbstractCrdt {
9850
9850
  /** Local edits made while an op is in flight; sent after the ack. */
9851
9851
  #queuedOps = [];
9852
9852
  #acceptedOps = [];
9853
+ /** Wait for replay history before applying post-snapshot remote ops. */
9854
+ #reconnecting = false;
9855
+ #bufferedRemoteOps = [];
9853
9856
  /**
9854
9857
  * Creates a new LiveText document.
9855
9858
  *
@@ -10152,8 +10155,6 @@ var LiveText = class _LiveText extends AbstractCrdt {
10152
10155
  #applyLocal(op, source) {
10153
10156
  const mutableOp = op;
10154
10157
  if (op.opId !== void 0 && op.opId === this.#inFlightOpId) {
10155
- this.#inFlightOps = [...this.#inFlightOps, ...this.#queuedOps];
10156
- this.#queuedOps = [];
10157
10158
  mutableOp.baseVersion = this.#version;
10158
10159
  mutableOp.ops = [...this.#inFlightOps];
10159
10160
  return { modified: false };
@@ -10192,6 +10193,9 @@ var LiveText = class _LiveText extends AbstractCrdt {
10192
10193
  }
10193
10194
  /** Server acknowledgement of our in-flight op. */
10194
10195
  #applyAck(op, source) {
10196
+ if (this.#reconnecting) {
10197
+ return this.#reconcileAck(op, source);
10198
+ }
10195
10199
  const ackedVersion = _nullishCoalesce(op.version, () => ( Math.max(this.#version, op.baseVersion + 1)));
10196
10200
  const predicted = this.#inFlightOps;
10197
10201
  const opId = this.#inFlightOpId;
@@ -10214,7 +10218,9 @@ var LiveText = class _LiveText extends AbstractCrdt {
10214
10218
  node: this,
10215
10219
  version: ackedVersion,
10216
10220
  updates: rebuilt.changes,
10217
- source
10221
+ // A correction was not applied by the originating editor. It
10222
+ // must not be filtered out as an echo of a local edit.
10223
+ source: REMOTE
10218
10224
  }
10219
10225
  };
10220
10226
  }
@@ -10224,9 +10230,67 @@ var LiveText = class _LiveText extends AbstractCrdt {
10224
10230
  this.#flushQueued();
10225
10231
  return result;
10226
10232
  }
10233
+ /** Recover the missing server timeline, preserving the normal OT invariants. */
10234
+ #reconcileAck(op, source) {
10235
+ const history = nn(
10236
+ op.history,
10237
+ "LiveText replay acknowledgement requires history"
10238
+ );
10239
+ this.#reconnecting = false;
10240
+ const buffered = this.#bufferedRemoteOps;
10241
+ this.#bufferedRemoteOps = [];
10242
+ const changes = [];
10243
+ const collect = (result) => {
10244
+ if (result.modified && result.modified.type === "LiveText") {
10245
+ changes.push(...result.modified.updates);
10246
+ }
10247
+ };
10248
+ for (const entry of history) {
10249
+ if (entry.version <= this.#version) continue;
10250
+ if (entry.version === op.version) {
10251
+ collect(this.#applyAck(op, source));
10252
+ } else {
10253
+ collect(
10254
+ this.#applyRemote(
10255
+ {
10256
+ type: OpCode.UPDATE_TEXT,
10257
+ id: op.id,
10258
+ baseVersion: entry.version - 1,
10259
+ version: entry.version,
10260
+ ops: entry.ops
10261
+ },
10262
+ REMOTE
10263
+ )
10264
+ );
10265
+ }
10266
+ }
10267
+ if (this.#inFlightOpId === op.opId) {
10268
+ collect(this.#applyAck(op, source));
10269
+ }
10270
+ for (const remote of buffered) {
10271
+ collect(this.#applyRemote(remote, REMOTE));
10272
+ }
10273
+ return changes.length === 0 ? { modified: false } : {
10274
+ reverse: [],
10275
+ modified: {
10276
+ type: "LiveText",
10277
+ node: this,
10278
+ version: this.#version,
10279
+ updates: changes,
10280
+ source: REMOTE
10281
+ }
10282
+ };
10283
+ }
10227
10284
  /** An accepted op from another client (or a server-fabricated fix op). */
10228
10285
  #applyRemote(op, source) {
10229
10286
  const version = _nullishCoalesce(op.version, () => ( this.#version + 1));
10287
+ if (op.version !== void 0 && op.version <= this.#version) {
10288
+ return { modified: false };
10289
+ }
10290
+ if (this.#reconnecting) {
10291
+ this.#bufferedRemoteOps.push(op);
10292
+ return { modified: false };
10293
+ }
10230
10294
  this.#confirmed = applyTextOperationsToSegments(this.#confirmed, op.ops);
10231
10295
  const [overInFlight, inFlight] = transformTextOperationsX(
10232
10296
  op.ops,
@@ -10332,14 +10396,17 @@ var LiveText = class _LiveText extends AbstractCrdt {
10332
10396
  return { appliedOps, changes };
10333
10397
  }
10334
10398
  /**
10335
- * Reconcile this node against an authoritative storage snapshot (e.g.
10336
- * after a reconnect). The confirmed state and version are replaced by the
10337
- * snapshot's; pending (in-flight + queued) ops are preserved on top and
10338
- * will be re-sent by the offline-ops replay.
10399
+ * Reconcile against an authoritative snapshot. With pending edits, retain
10400
+ * the current document and confirmed version until replay history arrives:
10401
+ * a snapshot alone cannot re-express the queue over unseen operations.
10339
10402
  *
10340
10403
  * @internal
10341
10404
  */
10342
10405
  _resyncText(data, version, source) {
10406
+ if (this.#inFlightOpId !== void 0) {
10407
+ this.#reconnecting = true;
10408
+ return void 0;
10409
+ }
10343
10410
  this.#confirmed = dataToSegments(data);
10344
10411
  this.#version = version;
10345
10412
  this.#acceptedOps = [];
@@ -10369,6 +10436,8 @@ var LiveText = class _LiveText extends AbstractCrdt {
10369
10436
  this.#inFlightOpId = void 0;
10370
10437
  this.#inFlightOps = [];
10371
10438
  this.#queuedOps = [];
10439
+ this.#bufferedRemoteOps = [];
10440
+ this.#reconnecting = false;
10372
10441
  }
10373
10442
  #recordAccepted(version, ops, opId) {
10374
10443
  if (this.#acceptedOps.some((entry) => entry.version === version)) {
@@ -12537,7 +12606,8 @@ function createRoom(options, config) {
12537
12606
  const result = applyLocalOps(unackedOps);
12538
12607
  messages.push({
12539
12608
  type: ClientMsgCode.UPDATE_STORAGE,
12540
- ops: result.opsToEmit
12609
+ ops: result.opsToEmit,
12610
+ includeTextHistory: true
12541
12611
  });
12542
12612
  notify(result.updates);
12543
12613
  sendMessages(messages);