@liveblocks/core 3.19.4-test1 → 3.20.0-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 +433 -842
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +52 -129
- package/dist/index.d.ts +52 -129
- package/dist/index.js +334 -743
- 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.
|
|
9
|
+
var PKG_VERSION = "3.20.0-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)) {
|
|
@@ -2715,6 +2729,7 @@ var HttpClient = class {
|
|
|
2715
2729
|
};
|
|
2716
2730
|
|
|
2717
2731
|
// src/lib/fsm.ts
|
|
2732
|
+
var IGNORE = /* @__PURE__ */ Symbol("fsm.ignore");
|
|
2718
2733
|
function distance(state1, state2) {
|
|
2719
2734
|
if (state1 === state2) {
|
|
2720
2735
|
return [0, 0];
|
|
@@ -2887,7 +2902,7 @@ var FSM = class {
|
|
|
2887
2902
|
this.#eventHub = {
|
|
2888
2903
|
didReceiveEvent: makeEventSource(),
|
|
2889
2904
|
willTransition: makeEventSource(),
|
|
2890
|
-
|
|
2905
|
+
didIgnoreUnexpectedEvent: makeEventSource(),
|
|
2891
2906
|
willExitState: makeEventSource(),
|
|
2892
2907
|
didEnterState: makeEventSource(),
|
|
2893
2908
|
didExitState: makeEventSource()
|
|
@@ -2895,7 +2910,7 @@ var FSM = class {
|
|
|
2895
2910
|
this.events = {
|
|
2896
2911
|
didReceiveEvent: this.#eventHub.didReceiveEvent.observable,
|
|
2897
2912
|
willTransition: this.#eventHub.willTransition.observable,
|
|
2898
|
-
|
|
2913
|
+
didIgnoreUnexpectedEvent: this.#eventHub.didIgnoreUnexpectedEvent.observable,
|
|
2899
2914
|
willExitState: this.#eventHub.willExitState.observable,
|
|
2900
2915
|
didEnterState: this.#eventHub.didEnterState.observable,
|
|
2901
2916
|
didExitState: this.#eventHub.didExitState.observable
|
|
@@ -3018,9 +3033,14 @@ var FSM = class {
|
|
|
3018
3033
|
* `context` params to conditionally decide which next state to transition
|
|
3019
3034
|
* to.
|
|
3020
3035
|
*
|
|
3021
|
-
* If you
|
|
3022
|
-
*
|
|
3023
|
-
*
|
|
3036
|
+
* If you don't define a target for a transition, the event is treated
|
|
3037
|
+
* as unhandled in this state: `didIgnoreUnexpectedEvent` fires and the
|
|
3038
|
+
* state does not change.
|
|
3039
|
+
*
|
|
3040
|
+
* To declare an event as an intentional silent no-op in this state, use
|
|
3041
|
+
* the {@link IGNORE} sentinel — either statically (`{ EVENT: IGNORE }`)
|
|
3042
|
+
* or as a return value from a target function. IGNORE'd events do not
|
|
3043
|
+
* fire `didIgnoreUnexpectedEvent`.
|
|
3024
3044
|
*/
|
|
3025
3045
|
addTransitions(nameOrPattern, mapping) {
|
|
3026
3046
|
if (this.#runningState !== 0 /* NOT_STARTED_YET */) {
|
|
@@ -3040,7 +3060,12 @@ var FSM = class {
|
|
|
3040
3060
|
}
|
|
3041
3061
|
const target = target_;
|
|
3042
3062
|
this.#knownEventTypes.add(type);
|
|
3043
|
-
if (target
|
|
3063
|
+
if (target === void 0) {
|
|
3064
|
+
continue;
|
|
3065
|
+
}
|
|
3066
|
+
if (target === IGNORE) {
|
|
3067
|
+
map.set(type, IGNORE);
|
|
3068
|
+
} else {
|
|
3044
3069
|
const targetFn = typeof target === "function" ? target : () => target;
|
|
3045
3070
|
map.set(type, targetFn);
|
|
3046
3071
|
}
|
|
@@ -3139,12 +3164,14 @@ var FSM = class {
|
|
|
3139
3164
|
if (this.#runningState === 2 /* STOPPED */) {
|
|
3140
3165
|
return;
|
|
3141
3166
|
}
|
|
3142
|
-
const
|
|
3143
|
-
if (
|
|
3144
|
-
return
|
|
3145
|
-
}
|
|
3146
|
-
|
|
3167
|
+
const entry = this.#getTargetFn(event.type);
|
|
3168
|
+
if (entry === IGNORE) {
|
|
3169
|
+
return;
|
|
3170
|
+
}
|
|
3171
|
+
if (entry !== void 0) {
|
|
3172
|
+
return this.#transition(event, entry);
|
|
3147
3173
|
}
|
|
3174
|
+
this.#eventHub.didIgnoreUnexpectedEvent.notify(event);
|
|
3148
3175
|
}
|
|
3149
3176
|
#transition(event, target) {
|
|
3150
3177
|
this.#eventHub.didReceiveEvent.notify(event);
|
|
@@ -3153,8 +3180,7 @@ var FSM = class {
|
|
|
3153
3180
|
const nextTarget = targetFn(event, this.#currentContext.current);
|
|
3154
3181
|
let nextState;
|
|
3155
3182
|
let effects = void 0;
|
|
3156
|
-
if (nextTarget ===
|
|
3157
|
-
this.#eventHub.didIgnoreEvent.notify(event);
|
|
3183
|
+
if (nextTarget === IGNORE) {
|
|
3158
3184
|
return;
|
|
3159
3185
|
}
|
|
3160
3186
|
if (typeof nextTarget === "string") {
|
|
@@ -3373,8 +3399,8 @@ function enableTracing(machine) {
|
|
|
3373
3399
|
machine.events.didExitState.subscribe(
|
|
3374
3400
|
({ state, durationMs }) => log2(`Exited ${state} after ${durationMs.toFixed(0)}ms`)
|
|
3375
3401
|
),
|
|
3376
|
-
machine.events.
|
|
3377
|
-
(e) => log2("Ignored event", e.type, e, "(
|
|
3402
|
+
machine.events.didIgnoreUnexpectedEvent.subscribe(
|
|
3403
|
+
(e) => log2("Ignored unexpected event", e.type, e, "(no transition declared)")
|
|
3378
3404
|
)
|
|
3379
3405
|
];
|
|
3380
3406
|
return () => {
|
|
@@ -3486,7 +3512,12 @@ function createConnectionStateMachine(delegates, options) {
|
|
|
3486
3512
|
);
|
|
3487
3513
|
const onSocketError = (event) => machine.send({ type: "EXPLICIT_SOCKET_ERROR", event });
|
|
3488
3514
|
const onSocketClose = (event) => machine.send({ type: "EXPLICIT_SOCKET_CLOSE", event });
|
|
3489
|
-
const onSocketMessage = (event) =>
|
|
3515
|
+
const onSocketMessage = (event) => {
|
|
3516
|
+
machine.send({ type: "ALIVE" });
|
|
3517
|
+
if (event.data !== "pong") {
|
|
3518
|
+
onMessage.notify(event);
|
|
3519
|
+
}
|
|
3520
|
+
};
|
|
3490
3521
|
function teardownSocket(socket) {
|
|
3491
3522
|
if (socket) {
|
|
3492
3523
|
socket.removeEventListener("error", onSocketError);
|
|
@@ -3656,7 +3687,13 @@ function createConnectionStateMachine(delegates, options) {
|
|
|
3656
3687
|
effect: [increaseBackoffDelay, logPrematureErrorOrCloseEvent(err)]
|
|
3657
3688
|
};
|
|
3658
3689
|
}
|
|
3659
|
-
)
|
|
3690
|
+
).addTransitions("@connecting.busy", {
|
|
3691
|
+
// The socket message listener is attached during @connecting.busy (see
|
|
3692
|
+
// onEnterAsync above), so server frames (most notably the actor-id
|
|
3693
|
+
// handshake) can fire onSocketMessage and emit a ALIVE before we
|
|
3694
|
+
// reach @ok.*. That's fine. Heartbeat only matters in @ok.*.
|
|
3695
|
+
ALIVE: IGNORE
|
|
3696
|
+
});
|
|
3660
3697
|
const sendHeartbeat = {
|
|
3661
3698
|
target: "@ok.awaiting-pong",
|
|
3662
3699
|
effect: (ctx) => {
|
|
@@ -3671,7 +3708,8 @@ function createConnectionStateMachine(delegates, options) {
|
|
|
3671
3708
|
machine.addTimedTransition("@ok.connected", HEARTBEAT_INTERVAL, maybeHeartbeat).addTransitions("@ok.connected", {
|
|
3672
3709
|
NAVIGATOR_OFFLINE: maybeHeartbeat,
|
|
3673
3710
|
// Don't take the browser's word for it when it says it's offline. Do a ping/pong to make sure.
|
|
3674
|
-
WINDOW_GOT_FOCUS: sendHeartbeat
|
|
3711
|
+
WINDOW_GOT_FOCUS: sendHeartbeat,
|
|
3712
|
+
ALIVE: IGNORE
|
|
3675
3713
|
});
|
|
3676
3714
|
machine.addTransitions("@idle.zombie", {
|
|
3677
3715
|
WINDOW_GOT_FOCUS: "@connecting.backoff"
|
|
@@ -3692,7 +3730,7 @@ function createConnectionStateMachine(delegates, options) {
|
|
|
3692
3730
|
clearTimeout(timerID);
|
|
3693
3731
|
onMessage.pause();
|
|
3694
3732
|
};
|
|
3695
|
-
}).addTransitions("@ok.awaiting-pong", {
|
|
3733
|
+
}).addTransitions("@ok.awaiting-pong", { ALIVE: "@ok.connected" }).addTimedTransition("@ok.awaiting-pong", PONG_TIMEOUT, {
|
|
3696
3734
|
target: "@connecting.busy",
|
|
3697
3735
|
// Log implicit connection loss and drop the current open socket
|
|
3698
3736
|
effect: log(
|
|
@@ -3705,7 +3743,7 @@ function createConnectionStateMachine(delegates, options) {
|
|
|
3705
3743
|
// not. When still OPEN, don't transition.
|
|
3706
3744
|
EXPLICIT_SOCKET_ERROR: (_, context) => {
|
|
3707
3745
|
if (context.socket?.readyState === 1) {
|
|
3708
|
-
return
|
|
3746
|
+
return IGNORE;
|
|
3709
3747
|
}
|
|
3710
3748
|
return {
|
|
3711
3749
|
target: "@connecting.backoff",
|
|
@@ -5473,21 +5511,21 @@ var OpCode = Object.freeze({
|
|
|
5473
5511
|
DELETE_CRDT: 5,
|
|
5474
5512
|
DELETE_OBJECT_KEY: 6,
|
|
5475
5513
|
CREATE_MAP: 7,
|
|
5476
|
-
CREATE_REGISTER: 8
|
|
5477
|
-
CREATE_TEXT: 9,
|
|
5478
|
-
UPDATE_TEXT: 10
|
|
5514
|
+
CREATE_REGISTER: 8
|
|
5479
5515
|
});
|
|
5480
5516
|
function isIgnoredOp(op) {
|
|
5481
5517
|
return op.type === OpCode.DELETE_CRDT && op.id === "ACK";
|
|
5482
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
|
+
}
|
|
5483
5522
|
|
|
5484
5523
|
// src/protocol/StorageNode.ts
|
|
5485
5524
|
var CrdtType = Object.freeze({
|
|
5486
5525
|
OBJECT: 0,
|
|
5487
5526
|
LIST: 1,
|
|
5488
5527
|
MAP: 2,
|
|
5489
|
-
REGISTER: 3
|
|
5490
|
-
TEXT: 4
|
|
5528
|
+
REGISTER: 3
|
|
5491
5529
|
});
|
|
5492
5530
|
function isRootStorageNode(node) {
|
|
5493
5531
|
return node[0] === "root";
|
|
@@ -5504,9 +5542,6 @@ function isMapStorageNode(node) {
|
|
|
5504
5542
|
function isRegisterStorageNode(node) {
|
|
5505
5543
|
return node[1].type === CrdtType.REGISTER;
|
|
5506
5544
|
}
|
|
5507
|
-
function isTextStorageNode(node) {
|
|
5508
|
-
return node[1].type === CrdtType.TEXT;
|
|
5509
|
-
}
|
|
5510
5545
|
function isCompactRootNode(node) {
|
|
5511
5546
|
return node[0] === "root";
|
|
5512
5547
|
}
|
|
@@ -5529,9 +5564,6 @@ function* compactNodesToNodeStream(compactNodes) {
|
|
|
5529
5564
|
case CrdtType.REGISTER:
|
|
5530
5565
|
yield [cnode[0], { type: CrdtType.REGISTER, parentId: cnode[2], parentKey: cnode[3], data: cnode[4] }];
|
|
5531
5566
|
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;
|
|
5535
5567
|
default:
|
|
5536
5568
|
}
|
|
5537
5569
|
}
|
|
@@ -5560,17 +5592,6 @@ function* nodeStreamToCompactNodes(nodes) {
|
|
|
5560
5592
|
const id = node[0];
|
|
5561
5593
|
const crdt = node[1];
|
|
5562
5594
|
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
|
-
];
|
|
5574
5595
|
} else {
|
|
5575
5596
|
}
|
|
5576
5597
|
}
|
|
@@ -5755,12 +5776,95 @@ function asPos(str) {
|
|
|
5755
5776
|
return isPos(str) ? str : convertToPos(str);
|
|
5756
5777
|
}
|
|
5757
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
|
+
|
|
5758
5861
|
// src/crdts/AbstractCrdt.ts
|
|
5759
5862
|
function createManagedPool(roomId, options) {
|
|
5760
5863
|
const {
|
|
5761
5864
|
getCurrentConnectionId,
|
|
5762
5865
|
onDispatch,
|
|
5763
|
-
isStorageWritable = () => true
|
|
5866
|
+
isStorageWritable = () => true,
|
|
5867
|
+
unacknowledgedOps = new UnacknowledgedOps()
|
|
5764
5868
|
} = options;
|
|
5765
5869
|
let clock = 0;
|
|
5766
5870
|
let opClock = 0;
|
|
@@ -5782,7 +5886,8 @@ function createManagedPool(roomId, options) {
|
|
|
5782
5886
|
"Cannot write to storage with a read only user, please ensure the user has write permissions"
|
|
5783
5887
|
);
|
|
5784
5888
|
}
|
|
5785
|
-
}
|
|
5889
|
+
},
|
|
5890
|
+
unacknowledgedOps
|
|
5786
5891
|
};
|
|
5787
5892
|
}
|
|
5788
5893
|
function crdtAsLiveNode(value) {
|
|
@@ -6064,11 +6169,9 @@ function childNodeLt(a, b) {
|
|
|
6064
6169
|
var LiveList = class _LiveList extends AbstractCrdt {
|
|
6065
6170
|
#items;
|
|
6066
6171
|
#implicitlyDeletedItems;
|
|
6067
|
-
#unacknowledgedSets;
|
|
6068
6172
|
constructor(items) {
|
|
6069
6173
|
super();
|
|
6070
6174
|
this.#implicitlyDeletedItems = /* @__PURE__ */ new WeakSet();
|
|
6071
|
-
this.#unacknowledgedSets = /* @__PURE__ */ new Map();
|
|
6072
6175
|
const nodes = [];
|
|
6073
6176
|
let lastPos;
|
|
6074
6177
|
for (const item of items) {
|
|
@@ -6098,12 +6201,13 @@ var LiveList = class _LiveList extends AbstractCrdt {
|
|
|
6098
6201
|
}
|
|
6099
6202
|
/**
|
|
6100
6203
|
* @internal
|
|
6101
|
-
*
|
|
6102
|
-
*
|
|
6103
|
-
*
|
|
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).
|
|
6104
6208
|
*
|
|
6105
|
-
* This is quite unintuitive and should disappear as soon as
|
|
6106
|
-
*
|
|
6209
|
+
* This is quite unintuitive and should disappear as soon as we introduce an
|
|
6210
|
+
* explicit LiveList.Set operation.
|
|
6107
6211
|
*/
|
|
6108
6212
|
_toOps(parentId, parentKey) {
|
|
6109
6213
|
if (this._id === void 0) {
|
|
@@ -6119,9 +6223,9 @@ var LiveList = class _LiveList extends AbstractCrdt {
|
|
|
6119
6223
|
ops.push(op);
|
|
6120
6224
|
for (const item of this.#items) {
|
|
6121
6225
|
const parentKey2 = item._getParentKeyOrThrow();
|
|
6122
|
-
const childOps =
|
|
6226
|
+
const childOps = addIntentToRootOp(
|
|
6123
6227
|
item._toOps(this._id, parentKey2),
|
|
6124
|
-
|
|
6228
|
+
"set"
|
|
6125
6229
|
);
|
|
6126
6230
|
for (const childOp of childOps) {
|
|
6127
6231
|
ops.push(childOp);
|
|
@@ -6163,6 +6267,28 @@ var LiveList = class _LiveList extends AbstractCrdt {
|
|
|
6163
6267
|
(item) => item._getParentKeyOrThrow() === position
|
|
6164
6268
|
);
|
|
6165
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
|
+
}
|
|
6166
6292
|
/** @internal */
|
|
6167
6293
|
_attach(id, pool) {
|
|
6168
6294
|
super._attach(id, pool);
|
|
@@ -6243,13 +6369,9 @@ var LiveList = class _LiveList extends AbstractCrdt {
|
|
|
6243
6369
|
if (deletedDelta) {
|
|
6244
6370
|
delta.push(deletedDelta);
|
|
6245
6371
|
}
|
|
6246
|
-
const unacknowledgedOpId = this.#
|
|
6247
|
-
if (unacknowledgedOpId !== void 0) {
|
|
6248
|
-
|
|
6249
|
-
return delta.length === 0 ? { modified: false } : { modified: makeUpdate(this, delta), reverse: [] };
|
|
6250
|
-
} else {
|
|
6251
|
-
this.#unacknowledgedSets.delete(op.parentKey);
|
|
6252
|
-
}
|
|
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: [] };
|
|
6253
6375
|
}
|
|
6254
6376
|
const indexOfItemWithSamePosition = this._indexOfPosition(op.parentKey);
|
|
6255
6377
|
const existingItem = this.#items.find((item) => item._id === op.id);
|
|
@@ -6330,7 +6452,7 @@ var LiveList = class _LiveList extends AbstractCrdt {
|
|
|
6330
6452
|
}
|
|
6331
6453
|
return result.modified.updates[0];
|
|
6332
6454
|
}
|
|
6333
|
-
#applyRemoteInsert(op) {
|
|
6455
|
+
#applyRemoteInsert(op, fromSnapshot) {
|
|
6334
6456
|
if (this._pool === void 0) {
|
|
6335
6457
|
throw new Error("Can't attach child if managed pool is not present");
|
|
6336
6458
|
}
|
|
@@ -6340,11 +6462,82 @@ var LiveList = class _LiveList extends AbstractCrdt {
|
|
|
6340
6462
|
this.#shiftItemPosition(existingItemIndex, key);
|
|
6341
6463
|
}
|
|
6342
6464
|
const { newItem, newIndex } = this.#createAttachItemAndSort(op, key);
|
|
6465
|
+
const bumpDeltas = fromSnapshot ? [] : this.#bumpUnackedPushesAbove(key);
|
|
6343
6466
|
return {
|
|
6344
|
-
modified: makeUpdate(this, [
|
|
6467
|
+
modified: makeUpdate(this, [
|
|
6468
|
+
insertDelta(newIndex, newItem),
|
|
6469
|
+
...bumpDeltas
|
|
6470
|
+
]),
|
|
6345
6471
|
reverse: []
|
|
6346
6472
|
};
|
|
6347
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
|
+
}
|
|
6348
6541
|
#applyInsertAck(op) {
|
|
6349
6542
|
const existingItem = this.#items.find((item) => item._id === op.id);
|
|
6350
6543
|
const key = asPos(op.parentKey);
|
|
@@ -6425,7 +6618,6 @@ var LiveList = class _LiveList extends AbstractCrdt {
|
|
|
6425
6618
|
if (this._pool?.getNode(id) !== void 0) {
|
|
6426
6619
|
return { modified: false };
|
|
6427
6620
|
}
|
|
6428
|
-
this.#unacknowledgedSets.set(key, nn(op.opId));
|
|
6429
6621
|
const indexOfItemWithSameKey = this._indexOfPosition(key);
|
|
6430
6622
|
child._attach(id, nn(this._pool));
|
|
6431
6623
|
child._setParentLink(this, key);
|
|
@@ -6435,8 +6627,9 @@ var LiveList = class _LiveList extends AbstractCrdt {
|
|
|
6435
6627
|
existingItem._detach();
|
|
6436
6628
|
this.#items.remove(existingItem);
|
|
6437
6629
|
this.#items.add(child);
|
|
6438
|
-
const reverse =
|
|
6630
|
+
const reverse = addIntentToRootOp(
|
|
6439
6631
|
existingItem._toOps(nn(this._id), key),
|
|
6632
|
+
"set",
|
|
6440
6633
|
op.id
|
|
6441
6634
|
);
|
|
6442
6635
|
const delta = [setDelta(indexOfItemWithSameKey, child)];
|
|
@@ -6461,7 +6654,7 @@ var LiveList = class _LiveList extends AbstractCrdt {
|
|
|
6461
6654
|
}
|
|
6462
6655
|
}
|
|
6463
6656
|
/** @internal */
|
|
6464
|
-
_attachChild(op, source) {
|
|
6657
|
+
_attachChild(op, source, fromSnapshot = false) {
|
|
6465
6658
|
if (this._pool === void 0) {
|
|
6466
6659
|
throw new Error("Can't attach child if managed pool is not present");
|
|
6467
6660
|
}
|
|
@@ -6476,7 +6669,7 @@ var LiveList = class _LiveList extends AbstractCrdt {
|
|
|
6476
6669
|
}
|
|
6477
6670
|
} else {
|
|
6478
6671
|
if (source === 1 /* THEIRS */) {
|
|
6479
|
-
result = this.#applyRemoteInsert(op);
|
|
6672
|
+
result = this.#applyRemoteInsert(op, fromSnapshot);
|
|
6480
6673
|
} else if (source === 2 /* OURS */) {
|
|
6481
6674
|
result = this.#applyInsertAck(op);
|
|
6482
6675
|
} else {
|
|
@@ -6679,8 +6872,7 @@ var LiveList = class _LiveList extends AbstractCrdt {
|
|
|
6679
6872
|
* @param element The element to add to the end of the LiveList.
|
|
6680
6873
|
*/
|
|
6681
6874
|
push(element) {
|
|
6682
|
-
this.
|
|
6683
|
-
return this.insert(element, this.length);
|
|
6875
|
+
return this.#injectAt(element, this.length, "push");
|
|
6684
6876
|
}
|
|
6685
6877
|
/**
|
|
6686
6878
|
* Inserts one element at a specified index.
|
|
@@ -6688,6 +6880,15 @@ var LiveList = class _LiveList extends AbstractCrdt {
|
|
|
6688
6880
|
* @param index The index at which you want to insert the element.
|
|
6689
6881
|
*/
|
|
6690
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) {
|
|
6691
6892
|
this._pool?.assertStorageIsWritable();
|
|
6692
6893
|
if (index < 0 || index > this.#items.length) {
|
|
6693
6894
|
throw new Error(
|
|
@@ -6703,8 +6904,9 @@ var LiveList = class _LiveList extends AbstractCrdt {
|
|
|
6703
6904
|
if (this._pool && this._id) {
|
|
6704
6905
|
const id = this._pool.generateId();
|
|
6705
6906
|
value._attach(id, this._pool);
|
|
6907
|
+
const ops = value._toOpsWithOpId(this._id, position, this._pool);
|
|
6706
6908
|
this._pool.dispatch(
|
|
6707
|
-
|
|
6909
|
+
intent === "push" ? addIntentToRootOp(ops, "push") : ops,
|
|
6708
6910
|
[{ type: OpCode.DELETE_CRDT, id }],
|
|
6709
6911
|
/* @__PURE__ */ new Map([
|
|
6710
6912
|
[this._id, makeUpdate(this, [insertDelta(index, value)])]
|
|
@@ -6862,13 +7064,14 @@ var LiveList = class _LiveList extends AbstractCrdt {
|
|
|
6862
7064
|
value._attach(id, this._pool);
|
|
6863
7065
|
const storageUpdates = /* @__PURE__ */ new Map();
|
|
6864
7066
|
storageUpdates.set(this._id, makeUpdate(this, [setDelta(index, value)]));
|
|
6865
|
-
const ops =
|
|
7067
|
+
const ops = addIntentToRootOp(
|
|
6866
7068
|
value._toOpsWithOpId(this._id, position, this._pool),
|
|
7069
|
+
"set",
|
|
6867
7070
|
existingId
|
|
6868
7071
|
);
|
|
6869
|
-
|
|
6870
|
-
const reverseOps = HACK_addIntentAndDeletedIdToOperation(
|
|
7072
|
+
const reverseOps = addIntentToRootOp(
|
|
6871
7073
|
existingItem._toOps(this._id, position),
|
|
7074
|
+
"set",
|
|
6872
7075
|
id
|
|
6873
7076
|
);
|
|
6874
7077
|
this._pool.dispatch(ops, reverseOps, storageUpdates);
|
|
@@ -7069,15 +7272,11 @@ function moveDelta(previousIndex, index, item) {
|
|
|
7069
7272
|
previousIndex
|
|
7070
7273
|
};
|
|
7071
7274
|
}
|
|
7072
|
-
function
|
|
7275
|
+
function addIntentToRootOp(ops, intent, deletedId) {
|
|
7073
7276
|
return ops.map((op, index) => {
|
|
7074
7277
|
if (index === 0) {
|
|
7075
7278
|
const firstOp = op;
|
|
7076
|
-
return {
|
|
7077
|
-
...firstOp,
|
|
7078
|
-
intent: "set",
|
|
7079
|
-
deletedId
|
|
7080
|
-
};
|
|
7279
|
+
return { ...firstOp, intent, deletedId };
|
|
7081
7280
|
} else {
|
|
7082
7281
|
return op;
|
|
7083
7282
|
}
|
|
@@ -8227,581 +8426,6 @@ var LiveObject = class _LiveObject extends AbstractCrdt {
|
|
|
8227
8426
|
}
|
|
8228
8427
|
};
|
|
8229
8428
|
|
|
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
|
-
|
|
8805
8429
|
// src/crdts/liveblocks-helpers.ts
|
|
8806
8430
|
function creationOpToLiveNode(op) {
|
|
8807
8431
|
return lsonToLiveNode(creationOpToLson(op));
|
|
@@ -8816,8 +8440,6 @@ function creationOpToLson(op) {
|
|
|
8816
8440
|
return new LiveMap();
|
|
8817
8441
|
case OpCode.CREATE_LIST:
|
|
8818
8442
|
return new LiveList([]);
|
|
8819
|
-
case OpCode.CREATE_TEXT:
|
|
8820
|
-
return new LiveText(op.data, op.version);
|
|
8821
8443
|
default:
|
|
8822
8444
|
return assertNever(op, "Unknown creation Op");
|
|
8823
8445
|
}
|
|
@@ -8840,8 +8462,6 @@ function deserialize(node, parentToChildren, pool) {
|
|
|
8840
8462
|
return LiveMap._deserialize(node, parentToChildren, pool);
|
|
8841
8463
|
} else if (isRegisterStorageNode(node)) {
|
|
8842
8464
|
return LiveRegister._deserialize(node, parentToChildren, pool);
|
|
8843
|
-
} else if (isTextStorageNode(node)) {
|
|
8844
|
-
return LiveText._deserialize(node, parentToChildren, pool);
|
|
8845
8465
|
} else {
|
|
8846
8466
|
throw new Error("Unexpected CRDT type");
|
|
8847
8467
|
}
|
|
@@ -8855,14 +8475,12 @@ function deserializeToLson(node, parentToChildren, pool) {
|
|
|
8855
8475
|
return LiveMap._deserialize(node, parentToChildren, pool);
|
|
8856
8476
|
} else if (isRegisterStorageNode(node)) {
|
|
8857
8477
|
return node[1].data;
|
|
8858
|
-
} else if (isTextStorageNode(node)) {
|
|
8859
|
-
return LiveText._deserialize(node, parentToChildren, pool);
|
|
8860
8478
|
} else {
|
|
8861
8479
|
throw new Error("Unexpected CRDT type");
|
|
8862
8480
|
}
|
|
8863
8481
|
}
|
|
8864
8482
|
function isLiveStructure(value) {
|
|
8865
|
-
return isLiveList(value) || isLiveMap(value) || isLiveObject(value)
|
|
8483
|
+
return isLiveList(value) || isLiveMap(value) || isLiveObject(value);
|
|
8866
8484
|
}
|
|
8867
8485
|
function isLiveNode(value) {
|
|
8868
8486
|
return isLiveStructure(value) || isLiveRegister(value);
|
|
@@ -8876,9 +8494,6 @@ function isLiveMap(value) {
|
|
|
8876
8494
|
function isLiveObject(value) {
|
|
8877
8495
|
return value instanceof LiveObject;
|
|
8878
8496
|
}
|
|
8879
|
-
function isLiveText(value) {
|
|
8880
|
-
return value instanceof LiveText;
|
|
8881
|
-
}
|
|
8882
8497
|
function isLiveRegister(value) {
|
|
8883
8498
|
return value instanceof LiveRegister;
|
|
8884
8499
|
}
|
|
@@ -8888,19 +8503,44 @@ function cloneLson(value) {
|
|
|
8888
8503
|
function liveNodeToLson(obj) {
|
|
8889
8504
|
if (obj instanceof LiveRegister) {
|
|
8890
8505
|
return obj.data;
|
|
8891
|
-
} else if (obj instanceof LiveList || obj instanceof LiveMap || obj instanceof LiveObject
|
|
8506
|
+
} else if (obj instanceof LiveList || obj instanceof LiveMap || obj instanceof LiveObject) {
|
|
8892
8507
|
return obj;
|
|
8893
8508
|
} else {
|
|
8894
8509
|
return assertNever(obj, "Unknown AbstractCrdt");
|
|
8895
8510
|
}
|
|
8896
8511
|
}
|
|
8897
8512
|
function lsonToLiveNode(value) {
|
|
8898
|
-
if (value instanceof LiveObject || value instanceof LiveMap || value instanceof LiveList
|
|
8513
|
+
if (value instanceof LiveObject || value instanceof LiveMap || value instanceof LiveList) {
|
|
8899
8514
|
return value;
|
|
8900
8515
|
} else {
|
|
8901
8516
|
return new LiveRegister(value);
|
|
8902
8517
|
}
|
|
8903
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
|
+
}
|
|
8904
8544
|
function getTreesDiffOperations(currentItems, newItems) {
|
|
8905
8545
|
const ops = [];
|
|
8906
8546
|
currentItems.forEach((_, id) => {
|
|
@@ -8920,38 +8560,6 @@ function getTreesDiffOperations(currentItems, newItems) {
|
|
|
8920
8560
|
});
|
|
8921
8561
|
}
|
|
8922
8562
|
}
|
|
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
|
-
}
|
|
8955
8563
|
if (crdt.parentKey !== currentCrdt.parentKey) {
|
|
8956
8564
|
ops.push({
|
|
8957
8565
|
type: OpCode.SET_PARENT_KEY,
|
|
@@ -9000,16 +8608,6 @@ function getTreesDiffOperations(currentItems, newItems) {
|
|
|
9000
8608
|
parentKey: crdt.parentKey
|
|
9001
8609
|
});
|
|
9002
8610
|
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;
|
|
9013
8611
|
}
|
|
9014
8612
|
}
|
|
9015
8613
|
});
|
|
@@ -9042,12 +8640,6 @@ function mergeListStorageUpdates(first, second) {
|
|
|
9042
8640
|
updates: updates.concat(second.updates)
|
|
9043
8641
|
};
|
|
9044
8642
|
}
|
|
9045
|
-
function mergeTextStorageUpdates(first, second) {
|
|
9046
|
-
return {
|
|
9047
|
-
...second,
|
|
9048
|
-
updates: first.updates.concat(second.updates)
|
|
9049
|
-
};
|
|
9050
|
-
}
|
|
9051
8643
|
function mergeStorageUpdates(first, second) {
|
|
9052
8644
|
if (first === void 0) {
|
|
9053
8645
|
return second;
|
|
@@ -9058,8 +8650,6 @@ function mergeStorageUpdates(first, second) {
|
|
|
9058
8650
|
return mergeMapStorageUpdates(first, second);
|
|
9059
8651
|
} else if (first.type === "LiveList" && second.type === "LiveList") {
|
|
9060
8652
|
return mergeListStorageUpdates(first, second);
|
|
9061
|
-
} else if (first.type === "LiveText" && second.type === "LiveText") {
|
|
9062
|
-
return mergeTextStorageUpdates(first, second);
|
|
9063
8653
|
} else {
|
|
9064
8654
|
}
|
|
9065
8655
|
return second;
|
|
@@ -9960,6 +9550,7 @@ function createRoom(options, config) {
|
|
|
9960
9550
|
delegates,
|
|
9961
9551
|
config.enableDebugLogging
|
|
9962
9552
|
);
|
|
9553
|
+
const unacknowledgedOps = new UnacknowledgedOps();
|
|
9963
9554
|
const context = {
|
|
9964
9555
|
buffer: {
|
|
9965
9556
|
flushTimerID: void 0,
|
|
@@ -9987,14 +9578,15 @@ function createRoom(options, config) {
|
|
|
9987
9578
|
pool: createManagedPool(roomId, {
|
|
9988
9579
|
getCurrentConnectionId,
|
|
9989
9580
|
onDispatch,
|
|
9990
|
-
isStorageWritable
|
|
9581
|
+
isStorageWritable,
|
|
9582
|
+
unacknowledgedOps
|
|
9991
9583
|
}),
|
|
9992
9584
|
root: void 0,
|
|
9993
9585
|
undoStack: [],
|
|
9994
9586
|
redoStack: [],
|
|
9995
9587
|
pausedHistory: null,
|
|
9996
9588
|
activeBatch: null,
|
|
9997
|
-
unacknowledgedOps
|
|
9589
|
+
unacknowledgedOps
|
|
9998
9590
|
};
|
|
9999
9591
|
const nodeMapBuffer = makeNodeMapBuffer();
|
|
10000
9592
|
const stopwatch = config.enableDebugLogging ? makeStopWatch() : void 0;
|
|
@@ -10202,7 +9794,11 @@ function createRoom(options, config) {
|
|
|
10202
9794
|
currentItems.set(id, crdt._serialize());
|
|
10203
9795
|
}
|
|
10204
9796
|
const ops = getTreesDiffOperations(currentItems, nodes);
|
|
10205
|
-
const result = applyRemoteOps(
|
|
9797
|
+
const result = applyRemoteOps(
|
|
9798
|
+
ops,
|
|
9799
|
+
/* fromSnapshot */
|
|
9800
|
+
true
|
|
9801
|
+
);
|
|
10206
9802
|
notify(result.updates);
|
|
10207
9803
|
} else {
|
|
10208
9804
|
context.root = LiveObject._fromItems(
|
|
@@ -10284,15 +9880,16 @@ function createRoom(options, config) {
|
|
|
10284
9880
|
);
|
|
10285
9881
|
return { opsToEmit: opsWithOpIds, reverse, updates };
|
|
10286
9882
|
}
|
|
10287
|
-
function applyRemoteOps(ops) {
|
|
9883
|
+
function applyRemoteOps(ops, fromSnapshot = false) {
|
|
10288
9884
|
return applyOps(
|
|
10289
9885
|
[],
|
|
10290
9886
|
ops,
|
|
10291
9887
|
/* isLocal */
|
|
10292
|
-
false
|
|
9888
|
+
false,
|
|
9889
|
+
fromSnapshot
|
|
10293
9890
|
);
|
|
10294
9891
|
}
|
|
10295
|
-
function applyOps(pframes, ops, isLocal) {
|
|
9892
|
+
function applyOps(pframes, ops, isLocal, fromSnapshot = false) {
|
|
10296
9893
|
const output = {
|
|
10297
9894
|
reverse: new Deque(),
|
|
10298
9895
|
storageUpdates: /* @__PURE__ */ new Map(),
|
|
@@ -10328,7 +9925,7 @@ function createRoom(options, config) {
|
|
|
10328
9925
|
} else {
|
|
10329
9926
|
source = 1 /* THEIRS */;
|
|
10330
9927
|
}
|
|
10331
|
-
const applyOpResult = applyOp(op, source);
|
|
9928
|
+
const applyOpResult = applyOp(op, source, fromSnapshot);
|
|
10332
9929
|
if (applyOpResult.modified) {
|
|
10333
9930
|
const nodeId = applyOpResult.modified.node._id;
|
|
10334
9931
|
if (!(nodeId && createdNodeIds.has(nodeId))) {
|
|
@@ -10341,7 +9938,7 @@ function createRoom(options, config) {
|
|
|
10341
9938
|
);
|
|
10342
9939
|
output.reverse.pushLeft(applyOpResult.reverse);
|
|
10343
9940
|
}
|
|
10344
|
-
if (op.type === OpCode.CREATE_LIST || op.type === OpCode.CREATE_MAP || op.type === OpCode.CREATE_OBJECT
|
|
9941
|
+
if (op.type === OpCode.CREATE_LIST || op.type === OpCode.CREATE_MAP || op.type === OpCode.CREATE_OBJECT) {
|
|
10345
9942
|
createdNodeIds.add(op.id);
|
|
10346
9943
|
}
|
|
10347
9944
|
}
|
|
@@ -10354,14 +9951,13 @@ function createRoom(options, config) {
|
|
|
10354
9951
|
}
|
|
10355
9952
|
};
|
|
10356
9953
|
}
|
|
10357
|
-
function applyOp(op, source) {
|
|
9954
|
+
function applyOp(op, source, fromSnapshot = false) {
|
|
10358
9955
|
if (isIgnoredOp(op)) {
|
|
10359
9956
|
return { modified: false };
|
|
10360
9957
|
}
|
|
10361
9958
|
switch (op.type) {
|
|
10362
9959
|
case OpCode.DELETE_OBJECT_KEY:
|
|
10363
9960
|
case OpCode.UPDATE_OBJECT:
|
|
10364
|
-
case OpCode.UPDATE_TEXT:
|
|
10365
9961
|
case OpCode.DELETE_CRDT: {
|
|
10366
9962
|
const node = context.pool.nodes.get(op.id);
|
|
10367
9963
|
if (node === void 0) {
|
|
@@ -10386,7 +9982,6 @@ function createRoom(options, config) {
|
|
|
10386
9982
|
case OpCode.CREATE_OBJECT:
|
|
10387
9983
|
case OpCode.CREATE_LIST:
|
|
10388
9984
|
case OpCode.CREATE_MAP:
|
|
10389
|
-
case OpCode.CREATE_TEXT:
|
|
10390
9985
|
case OpCode.CREATE_REGISTER: {
|
|
10391
9986
|
if (op.parentId === void 0) {
|
|
10392
9987
|
return { modified: false };
|
|
@@ -10395,7 +9990,7 @@ function createRoom(options, config) {
|
|
|
10395
9990
|
if (parentNode === void 0) {
|
|
10396
9991
|
return { modified: false };
|
|
10397
9992
|
}
|
|
10398
|
-
return parentNode._attachChild(op, source);
|
|
9993
|
+
return parentNode._attachChild(op, source, fromSnapshot);
|
|
10399
9994
|
}
|
|
10400
9995
|
}
|
|
10401
9996
|
}
|
|
@@ -10535,12 +10130,11 @@ function createRoom(options, config) {
|
|
|
10535
10130
|
}
|
|
10536
10131
|
}
|
|
10537
10132
|
function applyAndSendOfflineOps(unackedOps) {
|
|
10538
|
-
if (unackedOps.
|
|
10133
|
+
if (unackedOps.length === 0) {
|
|
10539
10134
|
return;
|
|
10540
10135
|
}
|
|
10541
10136
|
const messages = [];
|
|
10542
|
-
const
|
|
10543
|
-
const result = applyLocalOps(inOps);
|
|
10137
|
+
const result = applyLocalOps(unackedOps);
|
|
10544
10138
|
messages.push({
|
|
10545
10139
|
type: ClientMsgCode.UPDATE_STORAGE,
|
|
10546
10140
|
ops: result.opsToEmit
|
|
@@ -10764,7 +10358,7 @@ function createRoom(options, config) {
|
|
|
10764
10358
|
const storageOps = context.buffer.storageOperations;
|
|
10765
10359
|
if (storageOps.length > 0) {
|
|
10766
10360
|
for (const op of storageOps) {
|
|
10767
|
-
context.unacknowledgedOps.
|
|
10361
|
+
context.unacknowledgedOps.add(op);
|
|
10768
10362
|
}
|
|
10769
10363
|
notifyStorageStatus();
|
|
10770
10364
|
}
|
|
@@ -10991,9 +10585,9 @@ function createRoom(options, config) {
|
|
|
10991
10585
|
}
|
|
10992
10586
|
}
|
|
10993
10587
|
function processInitialStorage(nodes) {
|
|
10994
|
-
const
|
|
10588
|
+
const unacknowledgedOps2 = [...context.unacknowledgedOps.values()];
|
|
10995
10589
|
createOrUpdateRootFromMessage(nodes);
|
|
10996
|
-
applyAndSendOfflineOps(
|
|
10590
|
+
applyAndSendOfflineOps(unacknowledgedOps2);
|
|
10997
10591
|
_resolveStoragePromise?.();
|
|
10998
10592
|
notifyStorageStatus();
|
|
10999
10593
|
eventHub.storageDidLoad.notify();
|
|
@@ -11582,6 +11176,11 @@ function createRoom(options, config) {
|
|
|
11582
11176
|
connect: () => managedSocket.connect(),
|
|
11583
11177
|
reconnect: () => managedSocket.reconnect(),
|
|
11584
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
|
+
},
|
|
11585
11184
|
destroy: () => {
|
|
11586
11185
|
pendingFeedsRequests.forEach(
|
|
11587
11186
|
(request) => request.reject(new Error("Room destroyed"))
|
|
@@ -12089,6 +11688,7 @@ function createClient(options) {
|
|
|
12089
11688
|
{
|
|
12090
11689
|
enterRoom,
|
|
12091
11690
|
getRoom,
|
|
11691
|
+
_dump: () => Array.from(roomsById.values(), ({ room }) => room._dump()).join("\n\n"),
|
|
12092
11692
|
logout,
|
|
12093
11693
|
// Public inbox notifications API
|
|
12094
11694
|
getInboxNotifications: httpClient.getInboxNotifications,
|
|
@@ -12541,12 +12141,6 @@ function toPlainLson(lson) {
|
|
|
12541
12141
|
liveblocksType: "LiveList",
|
|
12542
12142
|
data: [...lson].map((item) => toPlainLson(item))
|
|
12543
12143
|
};
|
|
12544
|
-
} else if (lson instanceof LiveText) {
|
|
12545
|
-
return {
|
|
12546
|
-
liveblocksType: "LiveText",
|
|
12547
|
-
data: lson.toDelta(),
|
|
12548
|
-
version: lson.version
|
|
12549
|
-
};
|
|
12550
12144
|
} else {
|
|
12551
12145
|
return lson;
|
|
12552
12146
|
}
|
|
@@ -12728,7 +12322,6 @@ export {
|
|
|
12728
12322
|
LiveList,
|
|
12729
12323
|
LiveMap,
|
|
12730
12324
|
LiveObject,
|
|
12731
|
-
LiveText,
|
|
12732
12325
|
LiveblocksError,
|
|
12733
12326
|
MENTION_CHARACTER,
|
|
12734
12327
|
MutableSignal,
|
|
@@ -12740,7 +12333,6 @@ export {
|
|
|
12740
12333
|
SortedList,
|
|
12741
12334
|
TextEditorType,
|
|
12742
12335
|
WebsocketCloseCodes,
|
|
12743
|
-
applyLiveTextOperations,
|
|
12744
12336
|
asPos,
|
|
12745
12337
|
assert,
|
|
12746
12338
|
assertNever,
|
|
@@ -12796,7 +12388,6 @@ export {
|
|
|
12796
12388
|
isRegisterStorageNode,
|
|
12797
12389
|
isRootStorageNode,
|
|
12798
12390
|
isStartsWithOperator,
|
|
12799
|
-
isTextStorageNode,
|
|
12800
12391
|
isUrl,
|
|
12801
12392
|
kInternal,
|
|
12802
12393
|
keys,
|