@liveblocks/core 3.19.3 → 3.19.5-rc1
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 +382 -150
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +43 -8
- package/dist/index.d.ts +43 -8
- package/dist/index.js +284 -52
- 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.19.
|
|
9
|
+
var PKG_VERSION = "3.19.5-rc1";
|
|
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)) {
|
|
@@ -5502,6 +5516,9 @@ var OpCode = Object.freeze({
|
|
|
5502
5516
|
function isIgnoredOp(op) {
|
|
5503
5517
|
return op.type === OpCode.DELETE_CRDT && op.id === "ACK";
|
|
5504
5518
|
}
|
|
5519
|
+
function isCreateOp(op) {
|
|
5520
|
+
return op.type === OpCode.CREATE_OBJECT || op.type === OpCode.CREATE_REGISTER || op.type === OpCode.CREATE_MAP || op.type === OpCode.CREATE_LIST;
|
|
5521
|
+
}
|
|
5505
5522
|
|
|
5506
5523
|
// src/protocol/StorageNode.ts
|
|
5507
5524
|
var CrdtType = Object.freeze({
|
|
@@ -5759,12 +5776,95 @@ function asPos(str) {
|
|
|
5759
5776
|
return isPos(str) ? str : convertToPos(str);
|
|
5760
5777
|
}
|
|
5761
5778
|
|
|
5779
|
+
// src/crdts/UnacknowledgedOps.ts
|
|
5780
|
+
var UnacknowledgedOps = class {
|
|
5781
|
+
// opId -> op
|
|
5782
|
+
#byOpId = /* @__PURE__ */ new Map();
|
|
5783
|
+
// position -> (opId -> Create op)
|
|
5784
|
+
#createOpsByPosition = /* @__PURE__ */ new Map();
|
|
5785
|
+
// parentId -> (opId -> Create op)
|
|
5786
|
+
#createOpsByParent = /* @__PURE__ */ new Map();
|
|
5787
|
+
#posKey(parentId, parentKey) {
|
|
5788
|
+
return `${parentId}
|
|
5789
|
+
${parentKey}`;
|
|
5790
|
+
}
|
|
5791
|
+
get size() {
|
|
5792
|
+
return this.#byOpId.size;
|
|
5793
|
+
}
|
|
5794
|
+
/**
|
|
5795
|
+
* Mark the given Op as still unacknowledged.
|
|
5796
|
+
*/
|
|
5797
|
+
add(op) {
|
|
5798
|
+
this.#byOpId.set(op.opId, op);
|
|
5799
|
+
if (isCreateOp(op)) {
|
|
5800
|
+
const posKey = this.#posKey(op.parentId, op.parentKey);
|
|
5801
|
+
let atPosition = this.#createOpsByPosition.get(posKey);
|
|
5802
|
+
if (atPosition === void 0) {
|
|
5803
|
+
atPosition = /* @__PURE__ */ new Map();
|
|
5804
|
+
this.#createOpsByPosition.set(posKey, atPosition);
|
|
5805
|
+
}
|
|
5806
|
+
atPosition.set(op.opId, op);
|
|
5807
|
+
let inParent = this.#createOpsByParent.get(op.parentId);
|
|
5808
|
+
if (inParent === void 0) {
|
|
5809
|
+
inParent = /* @__PURE__ */ new Map();
|
|
5810
|
+
this.#createOpsByParent.set(op.parentId, inParent);
|
|
5811
|
+
}
|
|
5812
|
+
inParent.set(op.opId, op);
|
|
5813
|
+
}
|
|
5814
|
+
}
|
|
5815
|
+
/**
|
|
5816
|
+
* Drop the op with the given opId from the set, because the server has
|
|
5817
|
+
* acknowledged it (confirmed our own op, or signalled it was seen but
|
|
5818
|
+
* ignored).
|
|
5819
|
+
*/
|
|
5820
|
+
delete(opId) {
|
|
5821
|
+
const op = this.#byOpId.get(opId);
|
|
5822
|
+
if (op === void 0) {
|
|
5823
|
+
return;
|
|
5824
|
+
}
|
|
5825
|
+
this.#byOpId.delete(opId);
|
|
5826
|
+
if (isCreateOp(op)) {
|
|
5827
|
+
const posKey = this.#posKey(op.parentId, op.parentKey);
|
|
5828
|
+
const atPosition = this.#createOpsByPosition.get(posKey);
|
|
5829
|
+
atPosition?.delete(opId);
|
|
5830
|
+
if (atPosition !== void 0 && atPosition.size === 0) {
|
|
5831
|
+
this.#createOpsByPosition.delete(posKey);
|
|
5832
|
+
}
|
|
5833
|
+
const inParent = this.#createOpsByParent.get(op.parentId);
|
|
5834
|
+
inParent?.delete(opId);
|
|
5835
|
+
if (inParent !== void 0 && inParent.size === 0) {
|
|
5836
|
+
this.#createOpsByParent.delete(op.parentId);
|
|
5837
|
+
}
|
|
5838
|
+
}
|
|
5839
|
+
}
|
|
5840
|
+
/**
|
|
5841
|
+
* The still-unacknowledged Create ops with the given `parentId` and
|
|
5842
|
+
* `parentKey` (targeting one exact position), in dispatch order. O(1) lookup.
|
|
5843
|
+
* Empty if none.
|
|
5844
|
+
*/
|
|
5845
|
+
getByParentIdAndKey(parentId, parentKey) {
|
|
5846
|
+
return this.#createOpsByPosition.get(this.#posKey(parentId, parentKey))?.values() ?? [];
|
|
5847
|
+
}
|
|
5848
|
+
/**
|
|
5849
|
+
* The still-unacknowledged Create ops with the given `parentId` (across all
|
|
5850
|
+
* positions), in dispatch order. O(1) lookup. Empty if none.
|
|
5851
|
+
*/
|
|
5852
|
+
getByParentId(parentId) {
|
|
5853
|
+
return this.#createOpsByParent.get(parentId)?.values() ?? [];
|
|
5854
|
+
}
|
|
5855
|
+
/** All still-unacknowledged ops, in dispatch order. */
|
|
5856
|
+
values() {
|
|
5857
|
+
return this.#byOpId.values();
|
|
5858
|
+
}
|
|
5859
|
+
};
|
|
5860
|
+
|
|
5762
5861
|
// src/crdts/AbstractCrdt.ts
|
|
5763
5862
|
function createManagedPool(roomId, options) {
|
|
5764
5863
|
const {
|
|
5765
5864
|
getCurrentConnectionId,
|
|
5766
5865
|
onDispatch,
|
|
5767
|
-
isStorageWritable = () => true
|
|
5866
|
+
isStorageWritable = () => true,
|
|
5867
|
+
unacknowledgedOps = new UnacknowledgedOps()
|
|
5768
5868
|
} = options;
|
|
5769
5869
|
let clock = 0;
|
|
5770
5870
|
let opClock = 0;
|
|
@@ -5786,7 +5886,8 @@ function createManagedPool(roomId, options) {
|
|
|
5786
5886
|
"Cannot write to storage with a read only user, please ensure the user has write permissions"
|
|
5787
5887
|
);
|
|
5788
5888
|
}
|
|
5789
|
-
}
|
|
5889
|
+
},
|
|
5890
|
+
unacknowledgedOps
|
|
5790
5891
|
};
|
|
5791
5892
|
}
|
|
5792
5893
|
function crdtAsLiveNode(value) {
|
|
@@ -6068,11 +6169,9 @@ function childNodeLt(a, b) {
|
|
|
6068
6169
|
var LiveList = class _LiveList extends AbstractCrdt {
|
|
6069
6170
|
#items;
|
|
6070
6171
|
#implicitlyDeletedItems;
|
|
6071
|
-
#unacknowledgedSets;
|
|
6072
6172
|
constructor(items) {
|
|
6073
6173
|
super();
|
|
6074
6174
|
this.#implicitlyDeletedItems = /* @__PURE__ */ new WeakSet();
|
|
6075
|
-
this.#unacknowledgedSets = /* @__PURE__ */ new Map();
|
|
6076
6175
|
const nodes = [];
|
|
6077
6176
|
let lastPos;
|
|
6078
6177
|
for (const item of items) {
|
|
@@ -6102,12 +6201,13 @@ var LiveList = class _LiveList extends AbstractCrdt {
|
|
|
6102
6201
|
}
|
|
6103
6202
|
/**
|
|
6104
6203
|
* @internal
|
|
6105
|
-
*
|
|
6106
|
-
*
|
|
6107
|
-
*
|
|
6204
|
+
* Serializes this list (and its children) into Create ops. Each child's
|
|
6205
|
+
* create is tagged with the "set" intent (in the loop below) so that a list
|
|
6206
|
+
* created and immediately mutated doesn't transiently re-show its initial
|
|
6207
|
+
* items (flicker, https://github.com/liveblocks/liveblocks/pull/1177).
|
|
6108
6208
|
*
|
|
6109
|
-
* This is quite unintuitive and should disappear as soon as
|
|
6110
|
-
*
|
|
6209
|
+
* This is quite unintuitive and should disappear as soon as we introduce an
|
|
6210
|
+
* explicit LiveList.Set operation.
|
|
6111
6211
|
*/
|
|
6112
6212
|
_toOps(parentId, parentKey) {
|
|
6113
6213
|
if (this._id === void 0) {
|
|
@@ -6123,9 +6223,9 @@ var LiveList = class _LiveList extends AbstractCrdt {
|
|
|
6123
6223
|
ops.push(op);
|
|
6124
6224
|
for (const item of this.#items) {
|
|
6125
6225
|
const parentKey2 = item._getParentKeyOrThrow();
|
|
6126
|
-
const childOps =
|
|
6226
|
+
const childOps = addIntentToRootOp(
|
|
6127
6227
|
item._toOps(this._id, parentKey2),
|
|
6128
|
-
|
|
6228
|
+
"set"
|
|
6129
6229
|
);
|
|
6130
6230
|
for (const childOp of childOps) {
|
|
6131
6231
|
ops.push(childOp);
|
|
@@ -6167,6 +6267,28 @@ var LiveList = class _LiveList extends AbstractCrdt {
|
|
|
6167
6267
|
(item) => item._getParentKeyOrThrow() === position
|
|
6168
6268
|
);
|
|
6169
6269
|
}
|
|
6270
|
+
/**
|
|
6271
|
+
* The opId of this list's still-unacknowledged "set" op at the given position,
|
|
6272
|
+
* or undefined if none. Derived from the room's unacknowledgedOps (the single
|
|
6273
|
+
* source of truth) rather than tracked in a per-instance map. The pool's
|
|
6274
|
+
* position index already scopes to this list's (parentId, position); the last
|
|
6275
|
+
* match wins, matching the original last-write-wins map semantics.
|
|
6276
|
+
*/
|
|
6277
|
+
#unacknowledgedSetOpIdAt(position) {
|
|
6278
|
+
if (this._pool === void 0 || this._id === void 0) {
|
|
6279
|
+
return void 0;
|
|
6280
|
+
}
|
|
6281
|
+
let opId;
|
|
6282
|
+
for (const op of this._pool.unacknowledgedOps.getByParentIdAndKey(
|
|
6283
|
+
this._id,
|
|
6284
|
+
position
|
|
6285
|
+
)) {
|
|
6286
|
+
if (op.intent === "set") {
|
|
6287
|
+
opId = op.opId;
|
|
6288
|
+
}
|
|
6289
|
+
}
|
|
6290
|
+
return opId;
|
|
6291
|
+
}
|
|
6170
6292
|
/** @internal */
|
|
6171
6293
|
_attach(id, pool) {
|
|
6172
6294
|
super._attach(id, pool);
|
|
@@ -6247,13 +6369,9 @@ var LiveList = class _LiveList extends AbstractCrdt {
|
|
|
6247
6369
|
if (deletedDelta) {
|
|
6248
6370
|
delta.push(deletedDelta);
|
|
6249
6371
|
}
|
|
6250
|
-
const unacknowledgedOpId = this.#
|
|
6251
|
-
if (unacknowledgedOpId !== void 0) {
|
|
6252
|
-
|
|
6253
|
-
return delta.length === 0 ? { modified: false } : { modified: makeUpdate(this, delta), reverse: [] };
|
|
6254
|
-
} else {
|
|
6255
|
-
this.#unacknowledgedSets.delete(op.parentKey);
|
|
6256
|
-
}
|
|
6372
|
+
const unacknowledgedOpId = this.#unacknowledgedSetOpIdAt(op.parentKey);
|
|
6373
|
+
if (unacknowledgedOpId !== void 0 && unacknowledgedOpId !== op.opId) {
|
|
6374
|
+
return delta.length === 0 ? { modified: false } : { modified: makeUpdate(this, delta), reverse: [] };
|
|
6257
6375
|
}
|
|
6258
6376
|
const indexOfItemWithSamePosition = this._indexOfPosition(op.parentKey);
|
|
6259
6377
|
const existingItem = this.#items.find((item) => item._id === op.id);
|
|
@@ -6334,7 +6452,7 @@ var LiveList = class _LiveList extends AbstractCrdt {
|
|
|
6334
6452
|
}
|
|
6335
6453
|
return result.modified.updates[0];
|
|
6336
6454
|
}
|
|
6337
|
-
#applyRemoteInsert(op) {
|
|
6455
|
+
#applyRemoteInsert(op, fromSnapshot) {
|
|
6338
6456
|
if (this._pool === void 0) {
|
|
6339
6457
|
throw new Error("Can't attach child if managed pool is not present");
|
|
6340
6458
|
}
|
|
@@ -6344,11 +6462,82 @@ var LiveList = class _LiveList extends AbstractCrdt {
|
|
|
6344
6462
|
this.#shiftItemPosition(existingItemIndex, key);
|
|
6345
6463
|
}
|
|
6346
6464
|
const { newItem, newIndex } = this.#createAttachItemAndSort(op, key);
|
|
6465
|
+
const bumpDeltas = fromSnapshot ? [] : this.#bumpUnackedPushesAbove(key);
|
|
6347
6466
|
return {
|
|
6348
|
-
modified: makeUpdate(this, [
|
|
6467
|
+
modified: makeUpdate(this, [
|
|
6468
|
+
insertDelta(newIndex, newItem),
|
|
6469
|
+
...bumpDeltas
|
|
6470
|
+
]),
|
|
6349
6471
|
reverse: []
|
|
6350
6472
|
};
|
|
6351
6473
|
}
|
|
6474
|
+
/**
|
|
6475
|
+
* This list's own still-unacknowledged pushed items (their `intent: "push"`
|
|
6476
|
+
* Create op is still pending in the room's unacknowledgedOps). Derived from
|
|
6477
|
+
* the single source of truth, so an item drops out the instant its op is
|
|
6478
|
+
* acked, with no per-instance membership to leak. Yielded in push order.
|
|
6479
|
+
*
|
|
6480
|
+
* Restricted to items currently in `#items`: a pushed node whose op is still
|
|
6481
|
+
* pending may have been pulled out of the list (e.g. implicitly deleted by a
|
|
6482
|
+
* remote set, or removed by an undo) while still living in the pool, and such
|
|
6483
|
+
* a node must not be repositioned.
|
|
6484
|
+
*/
|
|
6485
|
+
*#unackedPushNodes() {
|
|
6486
|
+
if (this._pool === void 0 || this._id === void 0) {
|
|
6487
|
+
return;
|
|
6488
|
+
}
|
|
6489
|
+
for (const op of this._pool.unacknowledgedOps.getByParentId(this._id)) {
|
|
6490
|
+
if (op.intent !== "push") {
|
|
6491
|
+
continue;
|
|
6492
|
+
}
|
|
6493
|
+
const node = this._pool.getNode(op.id);
|
|
6494
|
+
if (node !== void 0 && this.#items.includes(node)) {
|
|
6495
|
+
yield node;
|
|
6496
|
+
}
|
|
6497
|
+
}
|
|
6498
|
+
}
|
|
6499
|
+
/**
|
|
6500
|
+
* Optimistic no-flip for pushed items. When a remote op lands at or below my
|
|
6501
|
+
* still-unacked pushed items, those items must end up *after* it: FIFO plus
|
|
6502
|
+
* the room's serial processing guarantee the remote was processed first, so
|
|
6503
|
+
* my unacked pushes belong behind it. Re-chain the whole unacked-push block,
|
|
6504
|
+
* in push order, to sit after the highest confirmed sibling, so it keeps
|
|
6505
|
+
* rendering as a contiguous tail instead of getting interleaved. Local-only;
|
|
6506
|
+
* the real acks overwrite these keys with the (identical) server keys.
|
|
6507
|
+
*/
|
|
6508
|
+
#bumpUnackedPushesAbove(remoteKey) {
|
|
6509
|
+
const pending = new Set(this.#unackedPushNodes());
|
|
6510
|
+
if (pending.size === 0) {
|
|
6511
|
+
return [];
|
|
6512
|
+
}
|
|
6513
|
+
let minPending;
|
|
6514
|
+
for (const node of pending) {
|
|
6515
|
+
const pos = node._parentPos;
|
|
6516
|
+
if (minPending === void 0 || pos < minPending) {
|
|
6517
|
+
minPending = pos;
|
|
6518
|
+
}
|
|
6519
|
+
}
|
|
6520
|
+
if (remoteKey < nn(minPending)) {
|
|
6521
|
+
return [];
|
|
6522
|
+
}
|
|
6523
|
+
let base;
|
|
6524
|
+
for (const item of this.#items) {
|
|
6525
|
+
if (!pending.has(item)) {
|
|
6526
|
+
base = item._parentPos;
|
|
6527
|
+
}
|
|
6528
|
+
}
|
|
6529
|
+
const deltas = [];
|
|
6530
|
+
for (const node of pending) {
|
|
6531
|
+
const previousIndex = this.#items.findIndex((item) => item === node);
|
|
6532
|
+
base = makePosition(base);
|
|
6533
|
+
this.#updateItemPosition(node, base);
|
|
6534
|
+
const index = this.#items.findIndex((item) => item === node);
|
|
6535
|
+
if (index !== previousIndex) {
|
|
6536
|
+
deltas.push(moveDelta(previousIndex, index, node));
|
|
6537
|
+
}
|
|
6538
|
+
}
|
|
6539
|
+
return deltas;
|
|
6540
|
+
}
|
|
6352
6541
|
#applyInsertAck(op) {
|
|
6353
6542
|
const existingItem = this.#items.find((item) => item._id === op.id);
|
|
6354
6543
|
const key = asPos(op.parentKey);
|
|
@@ -6429,7 +6618,6 @@ var LiveList = class _LiveList extends AbstractCrdt {
|
|
|
6429
6618
|
if (this._pool?.getNode(id) !== void 0) {
|
|
6430
6619
|
return { modified: false };
|
|
6431
6620
|
}
|
|
6432
|
-
this.#unacknowledgedSets.set(key, nn(op.opId));
|
|
6433
6621
|
const indexOfItemWithSameKey = this._indexOfPosition(key);
|
|
6434
6622
|
child._attach(id, nn(this._pool));
|
|
6435
6623
|
child._setParentLink(this, key);
|
|
@@ -6439,8 +6627,9 @@ var LiveList = class _LiveList extends AbstractCrdt {
|
|
|
6439
6627
|
existingItem._detach();
|
|
6440
6628
|
this.#items.remove(existingItem);
|
|
6441
6629
|
this.#items.add(child);
|
|
6442
|
-
const reverse =
|
|
6630
|
+
const reverse = addIntentToRootOp(
|
|
6443
6631
|
existingItem._toOps(nn(this._id), key),
|
|
6632
|
+
"set",
|
|
6444
6633
|
op.id
|
|
6445
6634
|
);
|
|
6446
6635
|
const delta = [setDelta(indexOfItemWithSameKey, child)];
|
|
@@ -6465,7 +6654,7 @@ var LiveList = class _LiveList extends AbstractCrdt {
|
|
|
6465
6654
|
}
|
|
6466
6655
|
}
|
|
6467
6656
|
/** @internal */
|
|
6468
|
-
_attachChild(op, source) {
|
|
6657
|
+
_attachChild(op, source, fromSnapshot = false) {
|
|
6469
6658
|
if (this._pool === void 0) {
|
|
6470
6659
|
throw new Error("Can't attach child if managed pool is not present");
|
|
6471
6660
|
}
|
|
@@ -6480,7 +6669,7 @@ var LiveList = class _LiveList extends AbstractCrdt {
|
|
|
6480
6669
|
}
|
|
6481
6670
|
} else {
|
|
6482
6671
|
if (source === 1 /* THEIRS */) {
|
|
6483
|
-
result = this.#applyRemoteInsert(op);
|
|
6672
|
+
result = this.#applyRemoteInsert(op, fromSnapshot);
|
|
6484
6673
|
} else if (source === 2 /* OURS */) {
|
|
6485
6674
|
result = this.#applyInsertAck(op);
|
|
6486
6675
|
} else {
|
|
@@ -6683,8 +6872,7 @@ var LiveList = class _LiveList extends AbstractCrdt {
|
|
|
6683
6872
|
* @param element The element to add to the end of the LiveList.
|
|
6684
6873
|
*/
|
|
6685
6874
|
push(element) {
|
|
6686
|
-
this.
|
|
6687
|
-
return this.insert(element, this.length);
|
|
6875
|
+
return this.#injectAt(element, this.length, "push");
|
|
6688
6876
|
}
|
|
6689
6877
|
/**
|
|
6690
6878
|
* Inserts one element at a specified index.
|
|
@@ -6692,6 +6880,15 @@ var LiveList = class _LiveList extends AbstractCrdt {
|
|
|
6692
6880
|
* @param index The index at which you want to insert the element.
|
|
6693
6881
|
*/
|
|
6694
6882
|
insert(element, index) {
|
|
6883
|
+
return this.#injectAt(element, index, "insert");
|
|
6884
|
+
}
|
|
6885
|
+
/**
|
|
6886
|
+
* Shared implementation of `insert` and `push`. A `"push"` intent leaves the
|
|
6887
|
+
* client-computed position untouched (so optimistic rendering is unchanged),
|
|
6888
|
+
* but tags the Op so the server appends it to the true end of the list
|
|
6889
|
+
* instead of resolving its position against the client's stale view.
|
|
6890
|
+
*/
|
|
6891
|
+
#injectAt(element, index, intent) {
|
|
6695
6892
|
this._pool?.assertStorageIsWritable();
|
|
6696
6893
|
if (index < 0 || index > this.#items.length) {
|
|
6697
6894
|
throw new Error(
|
|
@@ -6707,8 +6904,9 @@ var LiveList = class _LiveList extends AbstractCrdt {
|
|
|
6707
6904
|
if (this._pool && this._id) {
|
|
6708
6905
|
const id = this._pool.generateId();
|
|
6709
6906
|
value._attach(id, this._pool);
|
|
6907
|
+
const ops = value._toOpsWithOpId(this._id, position, this._pool);
|
|
6710
6908
|
this._pool.dispatch(
|
|
6711
|
-
|
|
6909
|
+
intent === "push" ? addIntentToRootOp(ops, "push") : ops,
|
|
6712
6910
|
[{ type: OpCode.DELETE_CRDT, id }],
|
|
6713
6911
|
/* @__PURE__ */ new Map([
|
|
6714
6912
|
[this._id, makeUpdate(this, [insertDelta(index, value)])]
|
|
@@ -6866,13 +7064,14 @@ var LiveList = class _LiveList extends AbstractCrdt {
|
|
|
6866
7064
|
value._attach(id, this._pool);
|
|
6867
7065
|
const storageUpdates = /* @__PURE__ */ new Map();
|
|
6868
7066
|
storageUpdates.set(this._id, makeUpdate(this, [setDelta(index, value)]));
|
|
6869
|
-
const ops =
|
|
7067
|
+
const ops = addIntentToRootOp(
|
|
6870
7068
|
value._toOpsWithOpId(this._id, position, this._pool),
|
|
7069
|
+
"set",
|
|
6871
7070
|
existingId
|
|
6872
7071
|
);
|
|
6873
|
-
|
|
6874
|
-
const reverseOps = HACK_addIntentAndDeletedIdToOperation(
|
|
7072
|
+
const reverseOps = addIntentToRootOp(
|
|
6875
7073
|
existingItem._toOps(this._id, position),
|
|
7074
|
+
"set",
|
|
6876
7075
|
id
|
|
6877
7076
|
);
|
|
6878
7077
|
this._pool.dispatch(ops, reverseOps, storageUpdates);
|
|
@@ -7073,15 +7272,11 @@ function moveDelta(previousIndex, index, item) {
|
|
|
7073
7272
|
previousIndex
|
|
7074
7273
|
};
|
|
7075
7274
|
}
|
|
7076
|
-
function
|
|
7275
|
+
function addIntentToRootOp(ops, intent, deletedId) {
|
|
7077
7276
|
return ops.map((op, index) => {
|
|
7078
7277
|
if (index === 0) {
|
|
7079
7278
|
const firstOp = op;
|
|
7080
|
-
return {
|
|
7081
|
-
...firstOp,
|
|
7082
|
-
intent: "set",
|
|
7083
|
-
deletedId
|
|
7084
|
-
};
|
|
7279
|
+
return { ...firstOp, intent, deletedId };
|
|
7085
7280
|
} else {
|
|
7086
7281
|
return op;
|
|
7087
7282
|
}
|
|
@@ -8321,6 +8516,31 @@ function lsonToLiveNode(value) {
|
|
|
8321
8516
|
return new LiveRegister(value);
|
|
8322
8517
|
}
|
|
8323
8518
|
}
|
|
8519
|
+
function dumpPool(pool) {
|
|
8520
|
+
const rows = Array.from(pool.nodes.values(), (node) => {
|
|
8521
|
+
const parent = node.parent;
|
|
8522
|
+
const parentId = parent.type === "HasParent" ? parent.node._id ?? "?" : parent.type === "Orphaned" ? "<orphaned>" : "-";
|
|
8523
|
+
let value;
|
|
8524
|
+
if (node instanceof LiveRegister) {
|
|
8525
|
+
value = stringifyOrLog(node.data);
|
|
8526
|
+
} else if (node instanceof LiveList) {
|
|
8527
|
+
value = "<LiveList>";
|
|
8528
|
+
} else if (node instanceof LiveMap) {
|
|
8529
|
+
value = "<LiveMap>";
|
|
8530
|
+
} else {
|
|
8531
|
+
value = "<LiveObject>";
|
|
8532
|
+
}
|
|
8533
|
+
return { id: nn(node._id), parentId, key: node._parentKey ?? "", value };
|
|
8534
|
+
});
|
|
8535
|
+
rows.sort((a, b) => {
|
|
8536
|
+
if (a.parentId !== b.parentId) return a.parentId < b.parentId ? -1 : 1;
|
|
8537
|
+
if (a.key !== b.key) return a.key < b.key ? -1 : 1;
|
|
8538
|
+
return 0;
|
|
8539
|
+
});
|
|
8540
|
+
return rows.map(
|
|
8541
|
+
(r) => ` ${r.id} parent=${r.parentId} key=${r.key || "\u2014"} ${r.value}`
|
|
8542
|
+
).join("\n");
|
|
8543
|
+
}
|
|
8324
8544
|
function getTreesDiffOperations(currentItems, newItems) {
|
|
8325
8545
|
const ops = [];
|
|
8326
8546
|
currentItems.forEach((_, id) => {
|
|
@@ -9330,6 +9550,7 @@ function createRoom(options, config) {
|
|
|
9330
9550
|
delegates,
|
|
9331
9551
|
config.enableDebugLogging
|
|
9332
9552
|
);
|
|
9553
|
+
const unacknowledgedOps = new UnacknowledgedOps();
|
|
9333
9554
|
const context = {
|
|
9334
9555
|
buffer: {
|
|
9335
9556
|
flushTimerID: void 0,
|
|
@@ -9357,14 +9578,15 @@ function createRoom(options, config) {
|
|
|
9357
9578
|
pool: createManagedPool(roomId, {
|
|
9358
9579
|
getCurrentConnectionId,
|
|
9359
9580
|
onDispatch,
|
|
9360
|
-
isStorageWritable
|
|
9581
|
+
isStorageWritable,
|
|
9582
|
+
unacknowledgedOps
|
|
9361
9583
|
}),
|
|
9362
9584
|
root: void 0,
|
|
9363
9585
|
undoStack: [],
|
|
9364
9586
|
redoStack: [],
|
|
9365
9587
|
pausedHistory: null,
|
|
9366
9588
|
activeBatch: null,
|
|
9367
|
-
unacknowledgedOps
|
|
9589
|
+
unacknowledgedOps
|
|
9368
9590
|
};
|
|
9369
9591
|
const nodeMapBuffer = makeNodeMapBuffer();
|
|
9370
9592
|
const stopwatch = config.enableDebugLogging ? makeStopWatch() : void 0;
|
|
@@ -9572,7 +9794,11 @@ function createRoom(options, config) {
|
|
|
9572
9794
|
currentItems.set(id, crdt._serialize());
|
|
9573
9795
|
}
|
|
9574
9796
|
const ops = getTreesDiffOperations(currentItems, nodes);
|
|
9575
|
-
const result = applyRemoteOps(
|
|
9797
|
+
const result = applyRemoteOps(
|
|
9798
|
+
ops,
|
|
9799
|
+
/* fromSnapshot */
|
|
9800
|
+
true
|
|
9801
|
+
);
|
|
9576
9802
|
notify(result.updates);
|
|
9577
9803
|
} else {
|
|
9578
9804
|
context.root = LiveObject._fromItems(
|
|
@@ -9654,15 +9880,16 @@ function createRoom(options, config) {
|
|
|
9654
9880
|
);
|
|
9655
9881
|
return { opsToEmit: opsWithOpIds, reverse, updates };
|
|
9656
9882
|
}
|
|
9657
|
-
function applyRemoteOps(ops) {
|
|
9883
|
+
function applyRemoteOps(ops, fromSnapshot = false) {
|
|
9658
9884
|
return applyOps(
|
|
9659
9885
|
[],
|
|
9660
9886
|
ops,
|
|
9661
9887
|
/* isLocal */
|
|
9662
|
-
false
|
|
9888
|
+
false,
|
|
9889
|
+
fromSnapshot
|
|
9663
9890
|
);
|
|
9664
9891
|
}
|
|
9665
|
-
function applyOps(pframes, ops, isLocal) {
|
|
9892
|
+
function applyOps(pframes, ops, isLocal, fromSnapshot = false) {
|
|
9666
9893
|
const output = {
|
|
9667
9894
|
reverse: new Deque(),
|
|
9668
9895
|
storageUpdates: /* @__PURE__ */ new Map(),
|
|
@@ -9698,7 +9925,7 @@ function createRoom(options, config) {
|
|
|
9698
9925
|
} else {
|
|
9699
9926
|
source = 1 /* THEIRS */;
|
|
9700
9927
|
}
|
|
9701
|
-
const applyOpResult = applyOp(op, source);
|
|
9928
|
+
const applyOpResult = applyOp(op, source, fromSnapshot);
|
|
9702
9929
|
if (applyOpResult.modified) {
|
|
9703
9930
|
const nodeId = applyOpResult.modified.node._id;
|
|
9704
9931
|
if (!(nodeId && createdNodeIds.has(nodeId))) {
|
|
@@ -9724,7 +9951,7 @@ function createRoom(options, config) {
|
|
|
9724
9951
|
}
|
|
9725
9952
|
};
|
|
9726
9953
|
}
|
|
9727
|
-
function applyOp(op, source) {
|
|
9954
|
+
function applyOp(op, source, fromSnapshot = false) {
|
|
9728
9955
|
if (isIgnoredOp(op)) {
|
|
9729
9956
|
return { modified: false };
|
|
9730
9957
|
}
|
|
@@ -9763,7 +9990,7 @@ function createRoom(options, config) {
|
|
|
9763
9990
|
if (parentNode === void 0) {
|
|
9764
9991
|
return { modified: false };
|
|
9765
9992
|
}
|
|
9766
|
-
return parentNode._attachChild(op, source);
|
|
9993
|
+
return parentNode._attachChild(op, source, fromSnapshot);
|
|
9767
9994
|
}
|
|
9768
9995
|
}
|
|
9769
9996
|
}
|
|
@@ -9903,12 +10130,11 @@ function createRoom(options, config) {
|
|
|
9903
10130
|
}
|
|
9904
10131
|
}
|
|
9905
10132
|
function applyAndSendOfflineOps(unackedOps) {
|
|
9906
|
-
if (unackedOps.
|
|
10133
|
+
if (unackedOps.length === 0) {
|
|
9907
10134
|
return;
|
|
9908
10135
|
}
|
|
9909
10136
|
const messages = [];
|
|
9910
|
-
const
|
|
9911
|
-
const result = applyLocalOps(inOps);
|
|
10137
|
+
const result = applyLocalOps(unackedOps);
|
|
9912
10138
|
messages.push({
|
|
9913
10139
|
type: ClientMsgCode.UPDATE_STORAGE,
|
|
9914
10140
|
ops: result.opsToEmit
|
|
@@ -10132,7 +10358,7 @@ function createRoom(options, config) {
|
|
|
10132
10358
|
const storageOps = context.buffer.storageOperations;
|
|
10133
10359
|
if (storageOps.length > 0) {
|
|
10134
10360
|
for (const op of storageOps) {
|
|
10135
|
-
context.unacknowledgedOps.
|
|
10361
|
+
context.unacknowledgedOps.add(op);
|
|
10136
10362
|
}
|
|
10137
10363
|
notifyStorageStatus();
|
|
10138
10364
|
}
|
|
@@ -10359,9 +10585,9 @@ function createRoom(options, config) {
|
|
|
10359
10585
|
}
|
|
10360
10586
|
}
|
|
10361
10587
|
function processInitialStorage(nodes) {
|
|
10362
|
-
const
|
|
10588
|
+
const unacknowledgedOps2 = [...context.unacknowledgedOps.values()];
|
|
10363
10589
|
createOrUpdateRootFromMessage(nodes);
|
|
10364
|
-
applyAndSendOfflineOps(
|
|
10590
|
+
applyAndSendOfflineOps(unacknowledgedOps2);
|
|
10365
10591
|
_resolveStoragePromise?.();
|
|
10366
10592
|
notifyStorageStatus();
|
|
10367
10593
|
eventHub.storageDidLoad.notify();
|
|
@@ -10950,6 +11176,11 @@ function createRoom(options, config) {
|
|
|
10950
11176
|
connect: () => managedSocket.connect(),
|
|
10951
11177
|
reconnect: () => managedSocket.reconnect(),
|
|
10952
11178
|
disconnect: () => managedSocket.disconnect(),
|
|
11179
|
+
_dump: () => {
|
|
11180
|
+
const n = context.pool.nodes.size;
|
|
11181
|
+
return `Room "${roomId}" (${n} node${n === 1 ? "" : "s"}):
|
|
11182
|
+
${dumpPool(context.pool)}`;
|
|
11183
|
+
},
|
|
10953
11184
|
destroy: () => {
|
|
10954
11185
|
pendingFeedsRequests.forEach(
|
|
10955
11186
|
(request) => request.reject(new Error("Room destroyed"))
|
|
@@ -11457,6 +11688,7 @@ function createClient(options) {
|
|
|
11457
11688
|
{
|
|
11458
11689
|
enterRoom,
|
|
11459
11690
|
getRoom,
|
|
11691
|
+
_dump: () => Array.from(roomsById.values(), ({ room }) => room._dump()).join("\n\n"),
|
|
11460
11692
|
logout,
|
|
11461
11693
|
// Public inbox notifications API
|
|
11462
11694
|
getInboxNotifications: httpClient.getInboxNotifications,
|