@liveblocks/core 3.20.0-perm1 → 3.20.0-perm2
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 +450 -148
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +61 -9
- package/dist/index.d.ts +61 -9
- package/dist/index.js +351 -49
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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.20.0-
|
|
9
|
+
var PKG_VERSION = "3.20.0-perm2";
|
|
10
10
|
var PKG_FORMAT = "esm";
|
|
11
11
|
|
|
12
12
|
// src/dupe-detection.ts
|
|
@@ -701,6 +701,20 @@ var SortedList = class _SortedList {
|
|
|
701
701
|
get length() {
|
|
702
702
|
return this.#data.length;
|
|
703
703
|
}
|
|
704
|
+
/**
|
|
705
|
+
* Whether the given value is present, by identity. O(log n) plus the length
|
|
706
|
+
* of any run of items that share its sort key (normally 1). Bisects on the
|
|
707
|
+
* value's own key, so it only finds values sitting at their sorted position,
|
|
708
|
+
* which is true for any item currently in the list.
|
|
709
|
+
*/
|
|
710
|
+
includes(value) {
|
|
711
|
+
for (let i = bisectRight(this.#data, value, this.#lt) - 1; i >= 0 && !this.#lt(this.#data[i], value); i--) {
|
|
712
|
+
if (this.#data[i] === value) {
|
|
713
|
+
return true;
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
return false;
|
|
717
|
+
}
|
|
704
718
|
*filter(predicate) {
|
|
705
719
|
for (const item of this.#data) {
|
|
706
720
|
if (predicate(item)) {
|
|
@@ -5723,6 +5737,9 @@ var OpCode = Object.freeze({
|
|
|
5723
5737
|
function isIgnoredOp(op) {
|
|
5724
5738
|
return op.type === OpCode.DELETE_CRDT && op.id === "ACK";
|
|
5725
5739
|
}
|
|
5740
|
+
function isCreateOp(op) {
|
|
5741
|
+
return op.type === OpCode.CREATE_OBJECT || op.type === OpCode.CREATE_REGISTER || op.type === OpCode.CREATE_MAP || op.type === OpCode.CREATE_LIST;
|
|
5742
|
+
}
|
|
5726
5743
|
|
|
5727
5744
|
// src/protocol/StorageNode.ts
|
|
5728
5745
|
var CrdtType = Object.freeze({
|
|
@@ -5980,12 +5997,112 @@ function asPos(str) {
|
|
|
5980
5997
|
return isPos(str) ? str : convertToPos(str);
|
|
5981
5998
|
}
|
|
5982
5999
|
|
|
6000
|
+
// src/crdts/UnacknowledgedOps.ts
|
|
6001
|
+
var UnacknowledgedOps = class {
|
|
6002
|
+
// opId -> op
|
|
6003
|
+
#byOpId = /* @__PURE__ */ new Map();
|
|
6004
|
+
// position -> (opId -> Create op)
|
|
6005
|
+
#createOpsByPosition = /* @__PURE__ */ new Map();
|
|
6006
|
+
// parentId -> (opId -> Create op)
|
|
6007
|
+
#createOpsByParent = /* @__PURE__ */ new Map();
|
|
6008
|
+
// opIds of pending ops that were in flight when a connection died, so the
|
|
6009
|
+
// server may already have processed them. See isPossiblyStored().
|
|
6010
|
+
#possiblyStoredOpIds = /* @__PURE__ */ new Set();
|
|
6011
|
+
#posKey(parentId, parentKey) {
|
|
6012
|
+
return `${parentId}
|
|
6013
|
+
${parentKey}`;
|
|
6014
|
+
}
|
|
6015
|
+
get size() {
|
|
6016
|
+
return this.#byOpId.size;
|
|
6017
|
+
}
|
|
6018
|
+
/**
|
|
6019
|
+
* Mark the given Op as still unacknowledged.
|
|
6020
|
+
*/
|
|
6021
|
+
add(op) {
|
|
6022
|
+
this.#byOpId.set(op.opId, op);
|
|
6023
|
+
if (isCreateOp(op)) {
|
|
6024
|
+
const posKey = this.#posKey(op.parentId, op.parentKey);
|
|
6025
|
+
let atPosition = this.#createOpsByPosition.get(posKey);
|
|
6026
|
+
if (atPosition === void 0) {
|
|
6027
|
+
atPosition = /* @__PURE__ */ new Map();
|
|
6028
|
+
this.#createOpsByPosition.set(posKey, atPosition);
|
|
6029
|
+
}
|
|
6030
|
+
atPosition.set(op.opId, op);
|
|
6031
|
+
let inParent = this.#createOpsByParent.get(op.parentId);
|
|
6032
|
+
if (inParent === void 0) {
|
|
6033
|
+
inParent = /* @__PURE__ */ new Map();
|
|
6034
|
+
this.#createOpsByParent.set(op.parentId, inParent);
|
|
6035
|
+
}
|
|
6036
|
+
inParent.set(op.opId, op);
|
|
6037
|
+
}
|
|
6038
|
+
}
|
|
6039
|
+
/**
|
|
6040
|
+
* Drop the op with the given opId from the set, because the server has
|
|
6041
|
+
* acknowledged it (confirmed our own op, or signalled it was seen but
|
|
6042
|
+
* ignored).
|
|
6043
|
+
*/
|
|
6044
|
+
delete(opId) {
|
|
6045
|
+
const op = this.#byOpId.get(opId);
|
|
6046
|
+
if (op === void 0) {
|
|
6047
|
+
return;
|
|
6048
|
+
}
|
|
6049
|
+
this.#byOpId.delete(opId);
|
|
6050
|
+
this.#possiblyStoredOpIds.delete(opId);
|
|
6051
|
+
if (isCreateOp(op)) {
|
|
6052
|
+
const posKey = this.#posKey(op.parentId, op.parentKey);
|
|
6053
|
+
const atPosition = this.#createOpsByPosition.get(posKey);
|
|
6054
|
+
atPosition?.delete(opId);
|
|
6055
|
+
if (atPosition !== void 0 && atPosition.size === 0) {
|
|
6056
|
+
this.#createOpsByPosition.delete(posKey);
|
|
6057
|
+
}
|
|
6058
|
+
const inParent = this.#createOpsByParent.get(op.parentId);
|
|
6059
|
+
inParent?.delete(opId);
|
|
6060
|
+
if (inParent !== void 0 && inParent.size === 0) {
|
|
6061
|
+
this.#createOpsByParent.delete(op.parentId);
|
|
6062
|
+
}
|
|
6063
|
+
}
|
|
6064
|
+
}
|
|
6065
|
+
/**
|
|
6066
|
+
* The still-unacknowledged Create ops with the given `parentId` and
|
|
6067
|
+
* `parentKey` (targeting one exact position), in dispatch order. O(1) lookup.
|
|
6068
|
+
* Empty if none.
|
|
6069
|
+
*/
|
|
6070
|
+
getByParentIdAndKey(parentId, parentKey) {
|
|
6071
|
+
return this.#createOpsByPosition.get(this.#posKey(parentId, parentKey))?.values() ?? [];
|
|
6072
|
+
}
|
|
6073
|
+
/**
|
|
6074
|
+
* The still-unacknowledged Create ops with the given `parentId` (across all
|
|
6075
|
+
* positions), in dispatch order. O(1) lookup. Empty if none.
|
|
6076
|
+
*/
|
|
6077
|
+
getByParentId(parentId) {
|
|
6078
|
+
return this.#createOpsByParent.get(parentId)?.values() ?? [];
|
|
6079
|
+
}
|
|
6080
|
+
/** All still-unacknowledged ops, in dispatch order. */
|
|
6081
|
+
values() {
|
|
6082
|
+
return this.#byOpId.values();
|
|
6083
|
+
}
|
|
6084
|
+
isPossiblyStored(opId) {
|
|
6085
|
+
return this.#possiblyStoredOpIds.has(opId);
|
|
6086
|
+
}
|
|
6087
|
+
/**
|
|
6088
|
+
* Mark every currently pending op as possibly stored on the server. Called
|
|
6089
|
+
* when the connection dies: all of these ops were in flight, and their
|
|
6090
|
+
* (possibly lost) acks would have been the only way to know their fate.
|
|
6091
|
+
*/
|
|
6092
|
+
markAllAsPossiblyStored() {
|
|
6093
|
+
for (const opId of this.#byOpId.keys()) {
|
|
6094
|
+
this.#possiblyStoredOpIds.add(opId);
|
|
6095
|
+
}
|
|
6096
|
+
}
|
|
6097
|
+
};
|
|
6098
|
+
|
|
5983
6099
|
// src/crdts/AbstractCrdt.ts
|
|
5984
6100
|
function createManagedPool(roomId, options) {
|
|
5985
6101
|
const {
|
|
5986
6102
|
getCurrentConnectionId,
|
|
5987
6103
|
onDispatch,
|
|
5988
|
-
isStorageWritable = () => true
|
|
6104
|
+
isStorageWritable = () => true,
|
|
6105
|
+
unacknowledgedOps = new UnacknowledgedOps()
|
|
5989
6106
|
} = options;
|
|
5990
6107
|
let clock = 0;
|
|
5991
6108
|
let opClock = 0;
|
|
@@ -6007,7 +6124,8 @@ function createManagedPool(roomId, options) {
|
|
|
6007
6124
|
"Cannot write to storage with a read only user, please ensure the user has write permissions"
|
|
6008
6125
|
);
|
|
6009
6126
|
}
|
|
6010
|
-
}
|
|
6127
|
+
},
|
|
6128
|
+
unacknowledgedOps
|
|
6011
6129
|
};
|
|
6012
6130
|
}
|
|
6013
6131
|
function crdtAsLiveNode(value) {
|
|
@@ -6289,11 +6407,9 @@ function childNodeLt(a, b) {
|
|
|
6289
6407
|
var LiveList = class _LiveList extends AbstractCrdt {
|
|
6290
6408
|
#items;
|
|
6291
6409
|
#implicitlyDeletedItems;
|
|
6292
|
-
#unacknowledgedSets;
|
|
6293
6410
|
constructor(items) {
|
|
6294
6411
|
super();
|
|
6295
6412
|
this.#implicitlyDeletedItems = /* @__PURE__ */ new WeakSet();
|
|
6296
|
-
this.#unacknowledgedSets = /* @__PURE__ */ new Map();
|
|
6297
6413
|
const nodes = [];
|
|
6298
6414
|
let lastPos;
|
|
6299
6415
|
for (const item of items) {
|
|
@@ -6323,12 +6439,13 @@ var LiveList = class _LiveList extends AbstractCrdt {
|
|
|
6323
6439
|
}
|
|
6324
6440
|
/**
|
|
6325
6441
|
* @internal
|
|
6326
|
-
*
|
|
6327
|
-
*
|
|
6328
|
-
*
|
|
6442
|
+
* Serializes this list (and its children) into Create ops. Each child's
|
|
6443
|
+
* create is tagged with the "set" intent (in the loop below) so that a list
|
|
6444
|
+
* created and immediately mutated doesn't transiently re-show its initial
|
|
6445
|
+
* items (flicker, https://github.com/liveblocks/liveblocks/pull/1177).
|
|
6329
6446
|
*
|
|
6330
|
-
* This is quite unintuitive and should disappear as soon as
|
|
6331
|
-
*
|
|
6447
|
+
* This is quite unintuitive and should disappear as soon as we introduce an
|
|
6448
|
+
* explicit LiveList.Set operation.
|
|
6332
6449
|
*/
|
|
6333
6450
|
_toOps(parentId, parentKey) {
|
|
6334
6451
|
if (this._id === void 0) {
|
|
@@ -6344,9 +6461,9 @@ var LiveList = class _LiveList extends AbstractCrdt {
|
|
|
6344
6461
|
ops.push(op);
|
|
6345
6462
|
for (const item of this.#items) {
|
|
6346
6463
|
const parentKey2 = item._getParentKeyOrThrow();
|
|
6347
|
-
const childOps =
|
|
6464
|
+
const childOps = addIntentToRootOp(
|
|
6348
6465
|
item._toOps(this._id, parentKey2),
|
|
6349
|
-
|
|
6466
|
+
"set"
|
|
6350
6467
|
);
|
|
6351
6468
|
for (const childOp of childOps) {
|
|
6352
6469
|
ops.push(childOp);
|
|
@@ -6388,6 +6505,28 @@ var LiveList = class _LiveList extends AbstractCrdt {
|
|
|
6388
6505
|
(item) => item._getParentKeyOrThrow() === position
|
|
6389
6506
|
);
|
|
6390
6507
|
}
|
|
6508
|
+
/**
|
|
6509
|
+
* The opId of this list's still-unacknowledged "set" op at the given position,
|
|
6510
|
+
* or undefined if none. Derived from the room's unacknowledgedOps (the single
|
|
6511
|
+
* source of truth) rather than tracked in a per-instance map. The pool's
|
|
6512
|
+
* position index already scopes to this list's (parentId, position); the last
|
|
6513
|
+
* match wins, matching the original last-write-wins map semantics.
|
|
6514
|
+
*/
|
|
6515
|
+
#unacknowledgedSetOpIdAt(position) {
|
|
6516
|
+
if (this._pool === void 0 || this._id === void 0) {
|
|
6517
|
+
return void 0;
|
|
6518
|
+
}
|
|
6519
|
+
let opId;
|
|
6520
|
+
for (const op of this._pool.unacknowledgedOps.getByParentIdAndKey(
|
|
6521
|
+
this._id,
|
|
6522
|
+
position
|
|
6523
|
+
)) {
|
|
6524
|
+
if (op.intent === "set") {
|
|
6525
|
+
opId = op.opId;
|
|
6526
|
+
}
|
|
6527
|
+
}
|
|
6528
|
+
return opId;
|
|
6529
|
+
}
|
|
6391
6530
|
/** @internal */
|
|
6392
6531
|
_attach(id, pool) {
|
|
6393
6532
|
super._attach(id, pool);
|
|
@@ -6468,13 +6607,9 @@ var LiveList = class _LiveList extends AbstractCrdt {
|
|
|
6468
6607
|
if (deletedDelta) {
|
|
6469
6608
|
delta.push(deletedDelta);
|
|
6470
6609
|
}
|
|
6471
|
-
const unacknowledgedOpId = this.#
|
|
6472
|
-
if (unacknowledgedOpId !== void 0) {
|
|
6473
|
-
|
|
6474
|
-
return delta.length === 0 ? { modified: false } : { modified: makeUpdate(this, delta), reverse: [] };
|
|
6475
|
-
} else {
|
|
6476
|
-
this.#unacknowledgedSets.delete(op.parentKey);
|
|
6477
|
-
}
|
|
6610
|
+
const unacknowledgedOpId = this.#unacknowledgedSetOpIdAt(op.parentKey);
|
|
6611
|
+
if (unacknowledgedOpId !== void 0 && unacknowledgedOpId !== op.opId) {
|
|
6612
|
+
return delta.length === 0 ? { modified: false } : { modified: makeUpdate(this, delta), reverse: [] };
|
|
6478
6613
|
}
|
|
6479
6614
|
const indexOfItemWithSamePosition = this._indexOfPosition(op.parentKey);
|
|
6480
6615
|
const existingItem = this.#items.find((item) => item._id === op.id);
|
|
@@ -6565,11 +6700,92 @@ var LiveList = class _LiveList extends AbstractCrdt {
|
|
|
6565
6700
|
this.#shiftItemPosition(existingItemIndex, key);
|
|
6566
6701
|
}
|
|
6567
6702
|
const { newItem, newIndex } = this.#createAttachItemAndSort(op, key);
|
|
6703
|
+
const bumpDeltas = this.#bumpUnackedPushesAbove(key);
|
|
6568
6704
|
return {
|
|
6569
|
-
modified: makeUpdate(this, [
|
|
6705
|
+
modified: makeUpdate(this, [
|
|
6706
|
+
insertDelta(newIndex, newItem),
|
|
6707
|
+
...bumpDeltas
|
|
6708
|
+
]),
|
|
6570
6709
|
reverse: []
|
|
6571
6710
|
};
|
|
6572
6711
|
}
|
|
6712
|
+
/**
|
|
6713
|
+
* This list's own still-unacknowledged pushed items (their `intent: "push"`
|
|
6714
|
+
* Create op is still pending in the room's unacknowledgedOps). Derived from
|
|
6715
|
+
* the single source of truth, so an item drops out the instant its op is
|
|
6716
|
+
* acked, with no per-instance membership to leak. Yielded in push order.
|
|
6717
|
+
*
|
|
6718
|
+
* Excludes ops that may already be stored on the server (they were in
|
|
6719
|
+
* flight when a connection died, so their fate is unknown): the bump
|
|
6720
|
+
* prediction assumes the server has not processed the op yet, which is only
|
|
6721
|
+
* guaranteed for ops sent on the current connection. For these excluded
|
|
6722
|
+
* ops, the server's (re-)ack states the authoritative position; predicting
|
|
6723
|
+
* locally could produce a wrong position that no ack would correct.
|
|
6724
|
+
*
|
|
6725
|
+
* Restricted to items currently in `#items`: a pushed node whose op is still
|
|
6726
|
+
* pending may have been pulled out of the list (e.g. implicitly deleted by a
|
|
6727
|
+
* remote set, or removed by an undo) while still living in the pool, and such
|
|
6728
|
+
* a node must not be repositioned.
|
|
6729
|
+
*/
|
|
6730
|
+
*#unackedPushNodes() {
|
|
6731
|
+
if (this._pool === void 0 || this._id === void 0) {
|
|
6732
|
+
return;
|
|
6733
|
+
}
|
|
6734
|
+
for (const op of this._pool.unacknowledgedOps.getByParentId(this._id)) {
|
|
6735
|
+
if (op.intent !== "push") {
|
|
6736
|
+
continue;
|
|
6737
|
+
}
|
|
6738
|
+
if (this._pool.unacknowledgedOps.isPossiblyStored(op.opId)) {
|
|
6739
|
+
continue;
|
|
6740
|
+
}
|
|
6741
|
+
const node = this._pool.getNode(op.id);
|
|
6742
|
+
if (node !== void 0 && this.#items.includes(node)) {
|
|
6743
|
+
yield node;
|
|
6744
|
+
}
|
|
6745
|
+
}
|
|
6746
|
+
}
|
|
6747
|
+
/**
|
|
6748
|
+
* Optimistic no-flip for pushed items. When a remote op lands at or below my
|
|
6749
|
+
* still-unacked pushed items, those items must end up *after* it: FIFO plus
|
|
6750
|
+
* the room's serial processing guarantee the remote was processed first, so
|
|
6751
|
+
* my unacked pushes belong behind it. Re-chain the whole unacked-push block,
|
|
6752
|
+
* in push order, to sit after the highest confirmed sibling, so it keeps
|
|
6753
|
+
* rendering as a contiguous tail instead of getting interleaved. Local-only;
|
|
6754
|
+
* the real acks overwrite these keys with the (identical) server keys.
|
|
6755
|
+
*/
|
|
6756
|
+
#bumpUnackedPushesAbove(remoteKey) {
|
|
6757
|
+
const pending = new Set(this.#unackedPushNodes());
|
|
6758
|
+
if (pending.size === 0) {
|
|
6759
|
+
return [];
|
|
6760
|
+
}
|
|
6761
|
+
let minPending;
|
|
6762
|
+
for (const node of pending) {
|
|
6763
|
+
const pos = node._parentPos;
|
|
6764
|
+
if (minPending === void 0 || pos < minPending) {
|
|
6765
|
+
minPending = pos;
|
|
6766
|
+
}
|
|
6767
|
+
}
|
|
6768
|
+
if (remoteKey < nn(minPending)) {
|
|
6769
|
+
return [];
|
|
6770
|
+
}
|
|
6771
|
+
let base;
|
|
6772
|
+
for (const item of this.#items) {
|
|
6773
|
+
if (!pending.has(item)) {
|
|
6774
|
+
base = item._parentPos;
|
|
6775
|
+
}
|
|
6776
|
+
}
|
|
6777
|
+
const deltas = [];
|
|
6778
|
+
for (const node of pending) {
|
|
6779
|
+
const previousIndex = this.#items.findIndex((item) => item === node);
|
|
6780
|
+
base = makePosition(base);
|
|
6781
|
+
this.#updateItemPosition(node, base);
|
|
6782
|
+
const index = this.#items.findIndex((item) => item === node);
|
|
6783
|
+
if (index !== previousIndex) {
|
|
6784
|
+
deltas.push(moveDelta(previousIndex, index, node));
|
|
6785
|
+
}
|
|
6786
|
+
}
|
|
6787
|
+
return deltas;
|
|
6788
|
+
}
|
|
6573
6789
|
#applyInsertAck(op) {
|
|
6574
6790
|
const existingItem = this.#items.find((item) => item._id === op.id);
|
|
6575
6791
|
const key = asPos(op.parentKey);
|
|
@@ -6650,7 +6866,6 @@ var LiveList = class _LiveList extends AbstractCrdt {
|
|
|
6650
6866
|
if (this._pool?.getNode(id) !== void 0) {
|
|
6651
6867
|
return { modified: false };
|
|
6652
6868
|
}
|
|
6653
|
-
this.#unacknowledgedSets.set(key, nn(op.opId));
|
|
6654
6869
|
const indexOfItemWithSameKey = this._indexOfPosition(key);
|
|
6655
6870
|
child._attach(id, nn(this._pool));
|
|
6656
6871
|
child._setParentLink(this, key);
|
|
@@ -6660,8 +6875,9 @@ var LiveList = class _LiveList extends AbstractCrdt {
|
|
|
6660
6875
|
existingItem._detach();
|
|
6661
6876
|
this.#items.remove(existingItem);
|
|
6662
6877
|
this.#items.add(child);
|
|
6663
|
-
const reverse =
|
|
6878
|
+
const reverse = addIntentToRootOp(
|
|
6664
6879
|
existingItem._toOps(nn(this._id), key),
|
|
6880
|
+
"set",
|
|
6665
6881
|
op.id
|
|
6666
6882
|
);
|
|
6667
6883
|
const delta = [setDelta(indexOfItemWithSameKey, child)];
|
|
@@ -6904,8 +7120,7 @@ var LiveList = class _LiveList extends AbstractCrdt {
|
|
|
6904
7120
|
* @param element The element to add to the end of the LiveList.
|
|
6905
7121
|
*/
|
|
6906
7122
|
push(element) {
|
|
6907
|
-
this.
|
|
6908
|
-
return this.insert(element, this.length);
|
|
7123
|
+
return this.#injectAt(element, this.length, "push");
|
|
6909
7124
|
}
|
|
6910
7125
|
/**
|
|
6911
7126
|
* Inserts one element at a specified index.
|
|
@@ -6913,6 +7128,15 @@ var LiveList = class _LiveList extends AbstractCrdt {
|
|
|
6913
7128
|
* @param index The index at which you want to insert the element.
|
|
6914
7129
|
*/
|
|
6915
7130
|
insert(element, index) {
|
|
7131
|
+
return this.#injectAt(element, index, "insert");
|
|
7132
|
+
}
|
|
7133
|
+
/**
|
|
7134
|
+
* Shared implementation of `insert` and `push`. A `"push"` intent leaves the
|
|
7135
|
+
* client-computed position untouched (so optimistic rendering is unchanged),
|
|
7136
|
+
* but tags the Op so the server appends it to the true end of the list
|
|
7137
|
+
* instead of resolving its position against the client's stale view.
|
|
7138
|
+
*/
|
|
7139
|
+
#injectAt(element, index, intent) {
|
|
6916
7140
|
this._pool?.assertStorageIsWritable();
|
|
6917
7141
|
if (index < 0 || index > this.#items.length) {
|
|
6918
7142
|
throw new Error(
|
|
@@ -6928,8 +7152,9 @@ var LiveList = class _LiveList extends AbstractCrdt {
|
|
|
6928
7152
|
if (this._pool && this._id) {
|
|
6929
7153
|
const id = this._pool.generateId();
|
|
6930
7154
|
value._attach(id, this._pool);
|
|
7155
|
+
const ops = value._toOpsWithOpId(this._id, position, this._pool);
|
|
6931
7156
|
this._pool.dispatch(
|
|
6932
|
-
|
|
7157
|
+
intent === "push" ? addIntentToRootOp(ops, "push") : ops,
|
|
6933
7158
|
[{ type: OpCode.DELETE_CRDT, id }],
|
|
6934
7159
|
/* @__PURE__ */ new Map([
|
|
6935
7160
|
[this._id, makeUpdate(this, [insertDelta(index, value)])]
|
|
@@ -7087,13 +7312,14 @@ var LiveList = class _LiveList extends AbstractCrdt {
|
|
|
7087
7312
|
value._attach(id, this._pool);
|
|
7088
7313
|
const storageUpdates = /* @__PURE__ */ new Map();
|
|
7089
7314
|
storageUpdates.set(this._id, makeUpdate(this, [setDelta(index, value)]));
|
|
7090
|
-
const ops =
|
|
7315
|
+
const ops = addIntentToRootOp(
|
|
7091
7316
|
value._toOpsWithOpId(this._id, position, this._pool),
|
|
7317
|
+
"set",
|
|
7092
7318
|
existingId
|
|
7093
7319
|
);
|
|
7094
|
-
|
|
7095
|
-
const reverseOps = HACK_addIntentAndDeletedIdToOperation(
|
|
7320
|
+
const reverseOps = addIntentToRootOp(
|
|
7096
7321
|
existingItem._toOps(this._id, position),
|
|
7322
|
+
"set",
|
|
7097
7323
|
id
|
|
7098
7324
|
);
|
|
7099
7325
|
this._pool.dispatch(ops, reverseOps, storageUpdates);
|
|
@@ -7294,15 +7520,11 @@ function moveDelta(previousIndex, index, item) {
|
|
|
7294
7520
|
previousIndex
|
|
7295
7521
|
};
|
|
7296
7522
|
}
|
|
7297
|
-
function
|
|
7523
|
+
function addIntentToRootOp(ops, intent, deletedId) {
|
|
7298
7524
|
return ops.map((op, index) => {
|
|
7299
7525
|
if (index === 0) {
|
|
7300
7526
|
const firstOp = op;
|
|
7301
|
-
return {
|
|
7302
|
-
...firstOp,
|
|
7303
|
-
intent: "set",
|
|
7304
|
-
deletedId
|
|
7305
|
-
};
|
|
7527
|
+
return { ...firstOp, intent, deletedId };
|
|
7306
7528
|
} else {
|
|
7307
7529
|
return op;
|
|
7308
7530
|
}
|
|
@@ -7954,6 +8176,7 @@ var LiveObject = class _LiveObject extends AbstractCrdt {
|
|
|
7954
8176
|
const id = nn(this._id);
|
|
7955
8177
|
const parentKey = nn(child._parentKey);
|
|
7956
8178
|
const reverse = child._toOps(id, parentKey);
|
|
8179
|
+
const deletedItem = liveNodeToLson(child);
|
|
7957
8180
|
for (const [key, value] of this.#synced) {
|
|
7958
8181
|
if (value === child) {
|
|
7959
8182
|
this.#synced.delete(key);
|
|
@@ -7965,7 +8188,7 @@ var LiveObject = class _LiveObject extends AbstractCrdt {
|
|
|
7965
8188
|
node: this,
|
|
7966
8189
|
type: "LiveObject",
|
|
7967
8190
|
updates: {
|
|
7968
|
-
[parentKey]: { type: "delete" }
|
|
8191
|
+
[parentKey]: { type: "delete", deletedItem }
|
|
7969
8192
|
}
|
|
7970
8193
|
};
|
|
7971
8194
|
return { modified: storageUpdate, reverse };
|
|
@@ -8542,6 +8765,60 @@ function lsonToLiveNode(value) {
|
|
|
8542
8765
|
return new LiveRegister(value);
|
|
8543
8766
|
}
|
|
8544
8767
|
}
|
|
8768
|
+
function dumpPool(pool) {
|
|
8769
|
+
const rows = Array.from(pool.nodes.values(), (node) => {
|
|
8770
|
+
const parent = node.parent;
|
|
8771
|
+
const parentId = parent.type === "HasParent" ? parent.node._id ?? "?" : parent.type === "Orphaned" ? "<orphaned>" : "-";
|
|
8772
|
+
let value;
|
|
8773
|
+
if (node instanceof LiveRegister) {
|
|
8774
|
+
value = stringifyOrLog(node.data);
|
|
8775
|
+
} else if (node instanceof LiveList) {
|
|
8776
|
+
value = "<LiveList>";
|
|
8777
|
+
} else if (node instanceof LiveMap) {
|
|
8778
|
+
value = "<LiveMap>";
|
|
8779
|
+
} else {
|
|
8780
|
+
value = "<LiveObject>";
|
|
8781
|
+
}
|
|
8782
|
+
return { id: nn(node._id), parentId, key: node._parentKey ?? "", value };
|
|
8783
|
+
});
|
|
8784
|
+
rows.sort((a, b) => {
|
|
8785
|
+
if (a.parentId !== b.parentId) return a.parentId < b.parentId ? -1 : 1;
|
|
8786
|
+
if (a.key !== b.key) return a.key < b.key ? -1 : 1;
|
|
8787
|
+
return 0;
|
|
8788
|
+
});
|
|
8789
|
+
return rows.map(
|
|
8790
|
+
(r) => ` ${r.id} parent=${r.parentId} key=${r.key || "\u2014"} ${r.value}`
|
|
8791
|
+
).join("\n");
|
|
8792
|
+
}
|
|
8793
|
+
function isJsonEq(a, b) {
|
|
8794
|
+
if (a === b) {
|
|
8795
|
+
return true;
|
|
8796
|
+
}
|
|
8797
|
+
if (typeof a !== "object" || a === null || typeof b !== "object" || b === null) {
|
|
8798
|
+
return false;
|
|
8799
|
+
}
|
|
8800
|
+
if (Array.isArray(a) || Array.isArray(b)) {
|
|
8801
|
+
if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) {
|
|
8802
|
+
return false;
|
|
8803
|
+
}
|
|
8804
|
+
for (let i = 0; i < a.length; i++) {
|
|
8805
|
+
if (!isJsonEq(a[i], b[i])) {
|
|
8806
|
+
return false;
|
|
8807
|
+
}
|
|
8808
|
+
}
|
|
8809
|
+
return true;
|
|
8810
|
+
}
|
|
8811
|
+
const aKeys = Object.keys(a);
|
|
8812
|
+
if (aKeys.length !== Object.keys(b).length) {
|
|
8813
|
+
return false;
|
|
8814
|
+
}
|
|
8815
|
+
for (const key of aKeys) {
|
|
8816
|
+
if (!isJsonEq(a[key], b[key])) {
|
|
8817
|
+
return false;
|
|
8818
|
+
}
|
|
8819
|
+
}
|
|
8820
|
+
return true;
|
|
8821
|
+
}
|
|
8545
8822
|
function getTreesDiffOperations(currentItems, newItems) {
|
|
8546
8823
|
const ops = [];
|
|
8547
8824
|
currentItems.forEach((_, id) => {
|
|
@@ -8553,12 +8830,28 @@ function getTreesDiffOperations(currentItems, newItems) {
|
|
|
8553
8830
|
const currentCrdt = currentItems.get(id);
|
|
8554
8831
|
if (currentCrdt) {
|
|
8555
8832
|
if (crdt.type === CrdtType.OBJECT) {
|
|
8556
|
-
if (currentCrdt.type !== CrdtType.OBJECT
|
|
8557
|
-
ops.push({
|
|
8558
|
-
|
|
8559
|
-
|
|
8560
|
-
|
|
8561
|
-
|
|
8833
|
+
if (currentCrdt.type !== CrdtType.OBJECT) {
|
|
8834
|
+
ops.push({ type: OpCode.UPDATE_OBJECT, id, data: crdt.data });
|
|
8835
|
+
} else {
|
|
8836
|
+
const changed = /* @__PURE__ */ new Map();
|
|
8837
|
+
for (const key of Object.keys(crdt.data)) {
|
|
8838
|
+
const value = crdt.data[key];
|
|
8839
|
+
if (value !== void 0 && !isJsonEq(value, currentCrdt.data[key])) {
|
|
8840
|
+
changed.set(key, value);
|
|
8841
|
+
}
|
|
8842
|
+
}
|
|
8843
|
+
if (changed.size > 0) {
|
|
8844
|
+
ops.push({
|
|
8845
|
+
type: OpCode.UPDATE_OBJECT,
|
|
8846
|
+
id,
|
|
8847
|
+
data: Object.fromEntries(changed)
|
|
8848
|
+
});
|
|
8849
|
+
}
|
|
8850
|
+
for (const key of Object.keys(currentCrdt.data)) {
|
|
8851
|
+
if (!(key in crdt.data)) {
|
|
8852
|
+
ops.push({ type: OpCode.DELETE_OBJECT_KEY, id, key });
|
|
8853
|
+
}
|
|
8854
|
+
}
|
|
8562
8855
|
}
|
|
8563
8856
|
}
|
|
8564
8857
|
if (crdt.parentKey !== currentCrdt.parentKey) {
|
|
@@ -9551,6 +9844,7 @@ function createRoom(options, config) {
|
|
|
9551
9844
|
delegates,
|
|
9552
9845
|
config.enableDebugLogging
|
|
9553
9846
|
);
|
|
9847
|
+
const unacknowledgedOps = new UnacknowledgedOps();
|
|
9554
9848
|
const context = {
|
|
9555
9849
|
buffer: {
|
|
9556
9850
|
flushTimerID: void 0,
|
|
@@ -9578,14 +9872,15 @@ function createRoom(options, config) {
|
|
|
9578
9872
|
pool: createManagedPool(roomId, {
|
|
9579
9873
|
getCurrentConnectionId,
|
|
9580
9874
|
onDispatch,
|
|
9581
|
-
isStorageWritable
|
|
9875
|
+
isStorageWritable,
|
|
9876
|
+
unacknowledgedOps
|
|
9582
9877
|
}),
|
|
9583
9878
|
root: void 0,
|
|
9584
9879
|
undoStack: [],
|
|
9585
9880
|
redoStack: [],
|
|
9586
9881
|
pausedHistory: null,
|
|
9587
9882
|
activeBatch: null,
|
|
9588
|
-
unacknowledgedOps
|
|
9883
|
+
unacknowledgedOps
|
|
9589
9884
|
};
|
|
9590
9885
|
const nodeMapBuffer = makeNodeMapBuffer();
|
|
9591
9886
|
const stopwatch = config.enableDebugLogging ? makeStopWatch() : void 0;
|
|
@@ -9652,6 +9947,7 @@ function createRoom(options, config) {
|
|
|
9652
9947
|
}
|
|
9653
9948
|
function onDidDisconnect() {
|
|
9654
9949
|
clearTimeout(context.buffer.flushTimerID);
|
|
9950
|
+
context.unacknowledgedOps.markAllAsPossiblyStored();
|
|
9655
9951
|
}
|
|
9656
9952
|
managedSocket.events.onMessage.subscribe(handleServerMessage);
|
|
9657
9953
|
managedSocket.events.statusDidChange.subscribe(onStatusDidChange);
|
|
@@ -10132,12 +10428,11 @@ function createRoom(options, config) {
|
|
|
10132
10428
|
}
|
|
10133
10429
|
}
|
|
10134
10430
|
function applyAndSendOfflineOps(unackedOps) {
|
|
10135
|
-
if (unackedOps.
|
|
10431
|
+
if (unackedOps.length === 0) {
|
|
10136
10432
|
return;
|
|
10137
10433
|
}
|
|
10138
10434
|
const messages = [];
|
|
10139
|
-
const
|
|
10140
|
-
const result = applyLocalOps(inOps);
|
|
10435
|
+
const result = applyLocalOps(unackedOps);
|
|
10141
10436
|
messages.push({
|
|
10142
10437
|
type: ClientMsgCode.UPDATE_STORAGE,
|
|
10143
10438
|
ops: result.opsToEmit
|
|
@@ -10361,7 +10656,7 @@ function createRoom(options, config) {
|
|
|
10361
10656
|
const storageOps = context.buffer.storageOperations;
|
|
10362
10657
|
if (storageOps.length > 0) {
|
|
10363
10658
|
for (const op of storageOps) {
|
|
10364
|
-
context.unacknowledgedOps.
|
|
10659
|
+
context.unacknowledgedOps.add(op);
|
|
10365
10660
|
}
|
|
10366
10661
|
notifyStorageStatus();
|
|
10367
10662
|
}
|
|
@@ -10588,9 +10883,9 @@ function createRoom(options, config) {
|
|
|
10588
10883
|
}
|
|
10589
10884
|
}
|
|
10590
10885
|
function processInitialStorage(nodes) {
|
|
10591
|
-
const
|
|
10886
|
+
const unacknowledgedOps2 = [...context.unacknowledgedOps.values()];
|
|
10592
10887
|
createOrUpdateRootFromMessage(nodes);
|
|
10593
|
-
applyAndSendOfflineOps(
|
|
10888
|
+
applyAndSendOfflineOps(unacknowledgedOps2);
|
|
10594
10889
|
_resolveStoragePromise?.();
|
|
10595
10890
|
notifyStorageStatus();
|
|
10596
10891
|
eventHub.storageDidLoad.notify();
|
|
@@ -11179,6 +11474,11 @@ function createRoom(options, config) {
|
|
|
11179
11474
|
connect: () => managedSocket.connect(),
|
|
11180
11475
|
reconnect: () => managedSocket.reconnect(),
|
|
11181
11476
|
disconnect: () => managedSocket.disconnect(),
|
|
11477
|
+
_dump: () => {
|
|
11478
|
+
const n = context.pool.nodes.size;
|
|
11479
|
+
return `Room "${roomId}" (${n} node${n === 1 ? "" : "s"}):
|
|
11480
|
+
${dumpPool(context.pool)}`;
|
|
11481
|
+
},
|
|
11182
11482
|
destroy: () => {
|
|
11183
11483
|
pendingFeedsRequests.forEach(
|
|
11184
11484
|
(request) => request.reject(new Error("Room destroyed"))
|
|
@@ -11692,6 +11992,7 @@ function createClient(options) {
|
|
|
11692
11992
|
{
|
|
11693
11993
|
enterRoom,
|
|
11694
11994
|
getRoom,
|
|
11995
|
+
_dump: () => Array.from(roomsById.values(), ({ room }) => room._dump()).join("\n\n"),
|
|
11695
11996
|
logout,
|
|
11696
11997
|
// Public inbox notifications API
|
|
11697
11998
|
getInboxNotifications: httpClient.getInboxNotifications,
|
|
@@ -12362,6 +12663,7 @@ export {
|
|
|
12362
12663
|
createManagedPool,
|
|
12363
12664
|
createNotificationSettings,
|
|
12364
12665
|
createThreadId,
|
|
12666
|
+
deepLiveify,
|
|
12365
12667
|
defineAiTool,
|
|
12366
12668
|
deprecate,
|
|
12367
12669
|
deprecateIf,
|