@liveblocks/core 3.19.5-rc1 → 3.19.5

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.19.5-rc1";
9
+ var PKG_VERSION = "3.19.5";
10
10
  var PKG_FORMAT = "cjs";
11
11
 
12
12
  // src/dupe-detection.ts
@@ -5784,6 +5784,9 @@ var UnacknowledgedOps = class {
5784
5784
  #createOpsByPosition = /* @__PURE__ */ new Map();
5785
5785
  // parentId -> (opId -> Create op)
5786
5786
  #createOpsByParent = /* @__PURE__ */ new Map();
5787
+ // opIds of pending ops that were in flight when a connection died, so the
5788
+ // server may already have processed them. See isPossiblyStored().
5789
+ #possiblyStoredOpIds = /* @__PURE__ */ new Set();
5787
5790
  #posKey(parentId, parentKey) {
5788
5791
  return `${parentId}
5789
5792
  ${parentKey}`;
@@ -5823,6 +5826,7 @@ ${parentKey}`;
5823
5826
  return;
5824
5827
  }
5825
5828
  this.#byOpId.delete(opId);
5829
+ this.#possiblyStoredOpIds.delete(opId);
5826
5830
  if (isCreateOp(op)) {
5827
5831
  const posKey = this.#posKey(op.parentId, op.parentKey);
5828
5832
  const atPosition = this.#createOpsByPosition.get(posKey);
@@ -5856,6 +5860,19 @@ ${parentKey}`;
5856
5860
  values() {
5857
5861
  return this.#byOpId.values();
5858
5862
  }
5863
+ isPossiblyStored(opId) {
5864
+ return this.#possiblyStoredOpIds.has(opId);
5865
+ }
5866
+ /**
5867
+ * Mark every currently pending op as possibly stored on the server. Called
5868
+ * when the connection dies: all of these ops were in flight, and their
5869
+ * (possibly lost) acks would have been the only way to know their fate.
5870
+ */
5871
+ markAllAsPossiblyStored() {
5872
+ for (const opId of this.#byOpId.keys()) {
5873
+ this.#possiblyStoredOpIds.add(opId);
5874
+ }
5875
+ }
5859
5876
  };
5860
5877
 
5861
5878
  // src/crdts/AbstractCrdt.ts
@@ -6452,7 +6469,7 @@ var LiveList = class _LiveList extends AbstractCrdt {
6452
6469
  }
6453
6470
  return result.modified.updates[0];
6454
6471
  }
6455
- #applyRemoteInsert(op, fromSnapshot) {
6472
+ #applyRemoteInsert(op) {
6456
6473
  if (this._pool === void 0) {
6457
6474
  throw new Error("Can't attach child if managed pool is not present");
6458
6475
  }
@@ -6462,7 +6479,7 @@ var LiveList = class _LiveList extends AbstractCrdt {
6462
6479
  this.#shiftItemPosition(existingItemIndex, key);
6463
6480
  }
6464
6481
  const { newItem, newIndex } = this.#createAttachItemAndSort(op, key);
6465
- const bumpDeltas = fromSnapshot ? [] : this.#bumpUnackedPushesAbove(key);
6482
+ const bumpDeltas = this.#bumpUnackedPushesAbove(key);
6466
6483
  return {
6467
6484
  modified: makeUpdate(this, [
6468
6485
  insertDelta(newIndex, newItem),
@@ -6477,6 +6494,13 @@ var LiveList = class _LiveList extends AbstractCrdt {
6477
6494
  * the single source of truth, so an item drops out the instant its op is
6478
6495
  * acked, with no per-instance membership to leak. Yielded in push order.
6479
6496
  *
6497
+ * Excludes ops that may already be stored on the server (they were in
6498
+ * flight when a connection died, so their fate is unknown): the bump
6499
+ * prediction assumes the server has not processed the op yet, which is only
6500
+ * guaranteed for ops sent on the current connection. For these excluded
6501
+ * ops, the server's (re-)ack states the authoritative position; predicting
6502
+ * locally could produce a wrong position that no ack would correct.
6503
+ *
6480
6504
  * Restricted to items currently in `#items`: a pushed node whose op is still
6481
6505
  * pending may have been pulled out of the list (e.g. implicitly deleted by a
6482
6506
  * remote set, or removed by an undo) while still living in the pool, and such
@@ -6490,6 +6514,9 @@ var LiveList = class _LiveList extends AbstractCrdt {
6490
6514
  if (op.intent !== "push") {
6491
6515
  continue;
6492
6516
  }
6517
+ if (this._pool.unacknowledgedOps.isPossiblyStored(op.opId)) {
6518
+ continue;
6519
+ }
6493
6520
  const node = this._pool.getNode(op.id);
6494
6521
  if (node !== void 0 && this.#items.includes(node)) {
6495
6522
  yield node;
@@ -6654,7 +6681,7 @@ var LiveList = class _LiveList extends AbstractCrdt {
6654
6681
  }
6655
6682
  }
6656
6683
  /** @internal */
6657
- _attachChild(op, source, fromSnapshot = false) {
6684
+ _attachChild(op, source) {
6658
6685
  if (this._pool === void 0) {
6659
6686
  throw new Error("Can't attach child if managed pool is not present");
6660
6687
  }
@@ -6669,7 +6696,7 @@ var LiveList = class _LiveList extends AbstractCrdt {
6669
6696
  }
6670
6697
  } else {
6671
6698
  if (source === 1 /* THEIRS */) {
6672
- result = this.#applyRemoteInsert(op, fromSnapshot);
6699
+ result = this.#applyRemoteInsert(op);
6673
6700
  } else if (source === 2 /* OURS */) {
6674
6701
  result = this.#applyInsertAck(op);
6675
6702
  } else {
@@ -7928,6 +7955,7 @@ var LiveObject = (_class2 = class _LiveObject extends AbstractCrdt {
7928
7955
  const id = nn(this._id);
7929
7956
  const parentKey = nn(child._parentKey);
7930
7957
  const reverse = child._toOps(id, parentKey);
7958
+ const deletedItem = liveNodeToLson(child);
7931
7959
  for (const [key, value] of this.#synced) {
7932
7960
  if (value === child) {
7933
7961
  this.#synced.delete(key);
@@ -7939,7 +7967,7 @@ var LiveObject = (_class2 = class _LiveObject extends AbstractCrdt {
7939
7967
  node: this,
7940
7968
  type: "LiveObject",
7941
7969
  updates: {
7942
- [parentKey]: { type: "delete" }
7970
+ [parentKey]: { type: "delete", deletedItem }
7943
7971
  }
7944
7972
  };
7945
7973
  return { modified: storageUpdate, reverse };
@@ -8541,6 +8569,35 @@ function dumpPool(pool) {
8541
8569
  (r) => ` ${r.id} parent=${r.parentId} key=${r.key || "\u2014"} ${r.value}`
8542
8570
  ).join("\n");
8543
8571
  }
8572
+ function isJsonEq(a, b) {
8573
+ if (a === b) {
8574
+ return true;
8575
+ }
8576
+ if (typeof a !== "object" || a === null || typeof b !== "object" || b === null) {
8577
+ return false;
8578
+ }
8579
+ if (Array.isArray(a) || Array.isArray(b)) {
8580
+ if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) {
8581
+ return false;
8582
+ }
8583
+ for (let i = 0; i < a.length; i++) {
8584
+ if (!isJsonEq(a[i], b[i])) {
8585
+ return false;
8586
+ }
8587
+ }
8588
+ return true;
8589
+ }
8590
+ const aKeys = Object.keys(a);
8591
+ if (aKeys.length !== Object.keys(b).length) {
8592
+ return false;
8593
+ }
8594
+ for (const key of aKeys) {
8595
+ if (!isJsonEq(a[key], b[key])) {
8596
+ return false;
8597
+ }
8598
+ }
8599
+ return true;
8600
+ }
8544
8601
  function getTreesDiffOperations(currentItems, newItems) {
8545
8602
  const ops = [];
8546
8603
  currentItems.forEach((_, id) => {
@@ -8552,12 +8609,28 @@ function getTreesDiffOperations(currentItems, newItems) {
8552
8609
  const currentCrdt = currentItems.get(id);
8553
8610
  if (currentCrdt) {
8554
8611
  if (crdt.type === CrdtType.OBJECT) {
8555
- if (currentCrdt.type !== CrdtType.OBJECT || stringifyOrLog(crdt.data) !== stringifyOrLog(currentCrdt.data)) {
8556
- ops.push({
8557
- type: OpCode.UPDATE_OBJECT,
8558
- id,
8559
- data: crdt.data
8560
- });
8612
+ if (currentCrdt.type !== CrdtType.OBJECT) {
8613
+ ops.push({ type: OpCode.UPDATE_OBJECT, id, data: crdt.data });
8614
+ } else {
8615
+ const changed = /* @__PURE__ */ new Map();
8616
+ for (const key of Object.keys(crdt.data)) {
8617
+ const value = crdt.data[key];
8618
+ if (value !== void 0 && !isJsonEq(value, currentCrdt.data[key])) {
8619
+ changed.set(key, value);
8620
+ }
8621
+ }
8622
+ if (changed.size > 0) {
8623
+ ops.push({
8624
+ type: OpCode.UPDATE_OBJECT,
8625
+ id,
8626
+ data: Object.fromEntries(changed)
8627
+ });
8628
+ }
8629
+ for (const key of Object.keys(currentCrdt.data)) {
8630
+ if (!(key in crdt.data)) {
8631
+ ops.push({ type: OpCode.DELETE_OBJECT_KEY, id, key });
8632
+ }
8633
+ }
8561
8634
  }
8562
8635
  }
8563
8636
  if (crdt.parentKey !== currentCrdt.parentKey) {
@@ -9653,6 +9726,7 @@ function createRoom(options, config) {
9653
9726
  }
9654
9727
  function onDidDisconnect() {
9655
9728
  clearTimeout(context.buffer.flushTimerID);
9729
+ context.unacknowledgedOps.markAllAsPossiblyStored();
9656
9730
  }
9657
9731
  managedSocket.events.onMessage.subscribe(handleServerMessage);
9658
9732
  managedSocket.events.statusDidChange.subscribe(onStatusDidChange);
@@ -9794,11 +9868,7 @@ function createRoom(options, config) {
9794
9868
  currentItems.set(id, crdt._serialize());
9795
9869
  }
9796
9870
  const ops = getTreesDiffOperations(currentItems, nodes);
9797
- const result = applyRemoteOps(
9798
- ops,
9799
- /* fromSnapshot */
9800
- true
9801
- );
9871
+ const result = applyRemoteOps(ops);
9802
9872
  notify(result.updates);
9803
9873
  } else {
9804
9874
  context.root = LiveObject._fromItems(
@@ -9880,16 +9950,15 @@ function createRoom(options, config) {
9880
9950
  );
9881
9951
  return { opsToEmit: opsWithOpIds, reverse, updates };
9882
9952
  }
9883
- function applyRemoteOps(ops, fromSnapshot = false) {
9953
+ function applyRemoteOps(ops) {
9884
9954
  return applyOps(
9885
9955
  [],
9886
9956
  ops,
9887
9957
  /* isLocal */
9888
- false,
9889
- fromSnapshot
9958
+ false
9890
9959
  );
9891
9960
  }
9892
- function applyOps(pframes, ops, isLocal, fromSnapshot = false) {
9961
+ function applyOps(pframes, ops, isLocal) {
9893
9962
  const output = {
9894
9963
  reverse: new Deque(),
9895
9964
  storageUpdates: /* @__PURE__ */ new Map(),
@@ -9925,7 +9994,7 @@ function createRoom(options, config) {
9925
9994
  } else {
9926
9995
  source = 1 /* THEIRS */;
9927
9996
  }
9928
- const applyOpResult = applyOp(op, source, fromSnapshot);
9997
+ const applyOpResult = applyOp(op, source);
9929
9998
  if (applyOpResult.modified) {
9930
9999
  const nodeId = applyOpResult.modified.node._id;
9931
10000
  if (!(nodeId && createdNodeIds.has(nodeId))) {
@@ -9951,7 +10020,7 @@ function createRoom(options, config) {
9951
10020
  }
9952
10021
  };
9953
10022
  }
9954
- function applyOp(op, source, fromSnapshot = false) {
10023
+ function applyOp(op, source) {
9955
10024
  if (isIgnoredOp(op)) {
9956
10025
  return { modified: false };
9957
10026
  }
@@ -9990,7 +10059,7 @@ function createRoom(options, config) {
9990
10059
  if (parentNode === void 0) {
9991
10060
  return { modified: false };
9992
10061
  }
9993
- return parentNode._attachChild(op, source, fromSnapshot);
10062
+ return parentNode._attachChild(op, source);
9994
10063
  }
9995
10064
  }
9996
10065
  }
@@ -12418,5 +12487,6 @@ detectDupes(PKG_NAME, PKG_VERSION, PKG_FORMAT);
12418
12487
 
12419
12488
 
12420
12489
 
12421
- exports.ClientMsgCode = ClientMsgCode; exports.CrdtType = CrdtType; exports.DefaultMap = DefaultMap; exports.Deque = Deque; exports.DerivedSignal = DerivedSignal; exports.FeedRequestErrorCode = FeedRequestErrorCode; exports.HttpError = HttpError; exports.LiveList = LiveList; exports.LiveMap = LiveMap; exports.LiveObject = LiveObject; exports.LiveblocksError = LiveblocksError; exports.MENTION_CHARACTER = MENTION_CHARACTER; exports.MutableSignal = MutableSignal; exports.OpCode = OpCode; exports.Permission = Permission; exports.Promise_withResolvers = Promise_withResolvers; exports.ServerMsgCode = ServerMsgCode; exports.Signal = Signal; exports.SortedList = SortedList; exports.TextEditorType = TextEditorType; exports.WebsocketCloseCodes = WebsocketCloseCodes; exports.asPos = asPos; exports.assert = assert; exports.assertNever = assertNever; exports.autoRetry = autoRetry; exports.b64decode = b64decode; exports.batch = batch; exports.checkBounds = checkBounds; exports.chunk = chunk; exports.cloneLson = cloneLson; exports.compactNodesToNodeStream = compactNodesToNodeStream; exports.compactObject = compactObject; exports.console = fancy_console_exports; exports.convertToCommentData = convertToCommentData; exports.convertToCommentUserReaction = convertToCommentUserReaction; exports.convertToGroupData = convertToGroupData; exports.convertToInboxNotificationData = convertToInboxNotificationData; exports.convertToSubscriptionData = convertToSubscriptionData; exports.convertToThreadData = convertToThreadData; exports.convertToUserSubscriptionData = convertToUserSubscriptionData; exports.createClient = createClient; exports.createCommentAttachmentId = createCommentAttachmentId; exports.createCommentId = createCommentId; exports.createInboxNotificationId = createInboxNotificationId; exports.createManagedPool = createManagedPool; exports.createNotificationSettings = createNotificationSettings; exports.createThreadId = createThreadId; exports.defineAiTool = defineAiTool; exports.deprecate = deprecate; exports.deprecateIf = deprecateIf; exports.detectDupes = detectDupes; exports.entries = entries; exports.errorIf = errorIf; exports.findLastIndex = findLastIndex; exports.freeze = freeze; exports.generateUrl = generateUrl; exports.getMentionsFromCommentBody = getMentionsFromCommentBody; exports.getSubscriptionKey = getSubscriptionKey; exports.html = html; exports.htmlSafe = htmlSafe; exports.isCommentBodyLink = isCommentBodyLink; exports.isCommentBodyMention = isCommentBodyMention; exports.isCommentBodyText = isCommentBodyText; exports.isJsonArray = isJsonArray; exports.isJsonObject = isJsonObject; exports.isJsonScalar = isJsonScalar; exports.isListStorageNode = isListStorageNode; exports.isLiveNode = isLiveNode; exports.isMapStorageNode = isMapStorageNode; exports.isNotificationChannelEnabled = isNotificationChannelEnabled; exports.isNumberOperator = isNumberOperator; exports.isObjectStorageNode = isObjectStorageNode; exports.isPlainObject = isPlainObject; exports.isRegisterStorageNode = isRegisterStorageNode; exports.isRootStorageNode = isRootStorageNode; exports.isStartsWithOperator = isStartsWithOperator; exports.isUrl = isUrl; exports.kInternal = kInternal; exports.keys = keys; exports.makeAbortController = makeAbortController; exports.makeEventSource = makeEventSource; exports.makePoller = makePoller; exports.makePosition = makePosition; exports.mapValues = mapValues; exports.memoizeOnSuccess = memoizeOnSuccess; exports.nanoid = nanoid; exports.nn = nn; exports.nodeStreamToCompactNodes = nodeStreamToCompactNodes; exports.objectToQuery = objectToQuery; exports.patchNotificationSettings = patchNotificationSettings; exports.raise = raise; exports.resolveMentionsInCommentBody = resolveMentionsInCommentBody; exports.sanitizeUrl = sanitizeUrl; exports.shallow = shallow; exports.shallow2 = shallow2; exports.stableStringify = stableStringify; exports.stringifyCommentBody = stringifyCommentBody; exports.throwUsageError = throwUsageError; exports.toPlainLson = toPlainLson; exports.tryParseJson = tryParseJson; exports.url = url; exports.urljoin = urljoin; exports.wait = wait; exports.warnOnce = warnOnce; exports.warnOnceIf = warnOnceIf; exports.withTimeout = withTimeout;
12490
+
12491
+ exports.ClientMsgCode = ClientMsgCode; exports.CrdtType = CrdtType; exports.DefaultMap = DefaultMap; exports.Deque = Deque; exports.DerivedSignal = DerivedSignal; exports.FeedRequestErrorCode = FeedRequestErrorCode; exports.HttpError = HttpError; exports.LiveList = LiveList; exports.LiveMap = LiveMap; exports.LiveObject = LiveObject; exports.LiveblocksError = LiveblocksError; exports.MENTION_CHARACTER = MENTION_CHARACTER; exports.MutableSignal = MutableSignal; exports.OpCode = OpCode; exports.Permission = Permission; exports.Promise_withResolvers = Promise_withResolvers; exports.ServerMsgCode = ServerMsgCode; exports.Signal = Signal; exports.SortedList = SortedList; exports.TextEditorType = TextEditorType; exports.WebsocketCloseCodes = WebsocketCloseCodes; exports.asPos = asPos; exports.assert = assert; exports.assertNever = assertNever; exports.autoRetry = autoRetry; exports.b64decode = b64decode; exports.batch = batch; exports.checkBounds = checkBounds; exports.chunk = chunk; exports.cloneLson = cloneLson; exports.compactNodesToNodeStream = compactNodesToNodeStream; exports.compactObject = compactObject; exports.console = fancy_console_exports; exports.convertToCommentData = convertToCommentData; exports.convertToCommentUserReaction = convertToCommentUserReaction; exports.convertToGroupData = convertToGroupData; exports.convertToInboxNotificationData = convertToInboxNotificationData; exports.convertToSubscriptionData = convertToSubscriptionData; exports.convertToThreadData = convertToThreadData; exports.convertToUserSubscriptionData = convertToUserSubscriptionData; exports.createClient = createClient; exports.createCommentAttachmentId = createCommentAttachmentId; exports.createCommentId = createCommentId; exports.createInboxNotificationId = createInboxNotificationId; exports.createManagedPool = createManagedPool; exports.createNotificationSettings = createNotificationSettings; exports.createThreadId = createThreadId; exports.deepLiveify = deepLiveify; exports.defineAiTool = defineAiTool; exports.deprecate = deprecate; exports.deprecateIf = deprecateIf; exports.detectDupes = detectDupes; exports.entries = entries; exports.errorIf = errorIf; exports.findLastIndex = findLastIndex; exports.freeze = freeze; exports.generateUrl = generateUrl; exports.getMentionsFromCommentBody = getMentionsFromCommentBody; exports.getSubscriptionKey = getSubscriptionKey; exports.html = html; exports.htmlSafe = htmlSafe; exports.isCommentBodyLink = isCommentBodyLink; exports.isCommentBodyMention = isCommentBodyMention; exports.isCommentBodyText = isCommentBodyText; exports.isJsonArray = isJsonArray; exports.isJsonObject = isJsonObject; exports.isJsonScalar = isJsonScalar; exports.isListStorageNode = isListStorageNode; exports.isLiveNode = isLiveNode; exports.isMapStorageNode = isMapStorageNode; exports.isNotificationChannelEnabled = isNotificationChannelEnabled; exports.isNumberOperator = isNumberOperator; exports.isObjectStorageNode = isObjectStorageNode; exports.isPlainObject = isPlainObject; exports.isRegisterStorageNode = isRegisterStorageNode; exports.isRootStorageNode = isRootStorageNode; exports.isStartsWithOperator = isStartsWithOperator; exports.isUrl = isUrl; exports.kInternal = kInternal; exports.keys = keys; exports.makeAbortController = makeAbortController; exports.makeEventSource = makeEventSource; exports.makePoller = makePoller; exports.makePosition = makePosition; exports.mapValues = mapValues; exports.memoizeOnSuccess = memoizeOnSuccess; exports.nanoid = nanoid; exports.nn = nn; exports.nodeStreamToCompactNodes = nodeStreamToCompactNodes; exports.objectToQuery = objectToQuery; exports.patchNotificationSettings = patchNotificationSettings; exports.raise = raise; exports.resolveMentionsInCommentBody = resolveMentionsInCommentBody; exports.sanitizeUrl = sanitizeUrl; exports.shallow = shallow; exports.shallow2 = shallow2; exports.stableStringify = stableStringify; exports.stringifyCommentBody = stringifyCommentBody; exports.throwUsageError = throwUsageError; exports.toPlainLson = toPlainLson; exports.tryParseJson = tryParseJson; exports.url = url; exports.urljoin = urljoin; exports.wait = wait; exports.warnOnce = warnOnce; exports.warnOnceIf = warnOnceIf; exports.withTimeout = withTimeout;
12422
12492
  //# sourceMappingURL=index.cjs.map