@liveblocks/core 3.23.1 → 3.23.2-exp1
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 +1878 -376
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +620 -273
- package/dist/index.d.ts +620 -273
- package/dist/index.js +1806 -304
- 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.23.
|
|
9
|
+
var PKG_VERSION = "3.23.2-exp1";
|
|
10
10
|
var PKG_FORMAT = "esm";
|
|
11
11
|
|
|
12
12
|
// src/dupe-detection.ts
|
|
@@ -988,14 +988,15 @@ var OpCode = Object.freeze({
|
|
|
988
988
|
DELETE_OBJECT_KEY: 6,
|
|
989
989
|
CREATE_MAP: 7,
|
|
990
990
|
CREATE_REGISTER: 8,
|
|
991
|
-
|
|
991
|
+
CREATE_TEXT: 9,
|
|
992
|
+
UPDATE_TEXT: 10,
|
|
992
993
|
CREATE_FILE: 11
|
|
993
994
|
});
|
|
994
995
|
function isIgnoredOp(op) {
|
|
995
996
|
return op.type === OpCode.DELETE_CRDT && op.id === "ACK";
|
|
996
997
|
}
|
|
997
998
|
function isCreateOp(op) {
|
|
998
|
-
return op.type === OpCode.CREATE_OBJECT || op.type === OpCode.CREATE_REGISTER || op.type === OpCode.CREATE_FILE || op.type === OpCode.CREATE_MAP || op.type === OpCode.CREATE_LIST;
|
|
999
|
+
return op.type === OpCode.CREATE_OBJECT || op.type === OpCode.CREATE_REGISTER || op.type === OpCode.CREATE_FILE || op.type === OpCode.CREATE_MAP || op.type === OpCode.CREATE_LIST || op.type === OpCode.CREATE_TEXT;
|
|
999
1000
|
}
|
|
1000
1001
|
|
|
1001
1002
|
// src/protocol/StorageNode.ts
|
|
@@ -1004,7 +1005,7 @@ var CrdtType = Object.freeze({
|
|
|
1004
1005
|
LIST: 1,
|
|
1005
1006
|
MAP: 2,
|
|
1006
1007
|
REGISTER: 3,
|
|
1007
|
-
|
|
1008
|
+
TEXT: 4,
|
|
1008
1009
|
FILE: 5
|
|
1009
1010
|
});
|
|
1010
1011
|
function isRootStorageNode(node) {
|
|
@@ -1022,6 +1023,9 @@ function isMapStorageNode(node) {
|
|
|
1022
1023
|
function isRegisterStorageNode(node) {
|
|
1023
1024
|
return node[1].type === CrdtType.REGISTER;
|
|
1024
1025
|
}
|
|
1026
|
+
function isTextStorageNode(node) {
|
|
1027
|
+
return node[1].type === CrdtType.TEXT;
|
|
1028
|
+
}
|
|
1025
1029
|
function isFileStorageNode(node) {
|
|
1026
1030
|
return node[1].type === CrdtType.FILE;
|
|
1027
1031
|
}
|
|
@@ -1047,6 +1051,9 @@ function* compactNodesToNodeStream(compactNodes) {
|
|
|
1047
1051
|
case CrdtType.REGISTER:
|
|
1048
1052
|
yield [cnode[0], { type: CrdtType.REGISTER, parentId: cnode[2], parentKey: cnode[3], data: cnode[4] }];
|
|
1049
1053
|
break;
|
|
1054
|
+
case CrdtType.TEXT:
|
|
1055
|
+
yield [cnode[0], { type: CrdtType.TEXT, parentId: cnode[2], parentKey: cnode[3], data: cnode[4], version: cnode[5] }];
|
|
1056
|
+
break;
|
|
1050
1057
|
case CrdtType.FILE:
|
|
1051
1058
|
yield [cnode[0], { type: CrdtType.FILE, parentId: cnode[2], parentKey: cnode[3], data: cnode[4] }];
|
|
1052
1059
|
break;
|
|
@@ -1078,6 +1085,17 @@ function* nodeStreamToCompactNodes(nodes) {
|
|
|
1078
1085
|
const id = node[0];
|
|
1079
1086
|
const crdt = node[1];
|
|
1080
1087
|
yield [id, CrdtType.REGISTER, crdt.parentId, crdt.parentKey, crdt.data];
|
|
1088
|
+
} else if (isTextStorageNode(node)) {
|
|
1089
|
+
const id = node[0];
|
|
1090
|
+
const crdt = node[1];
|
|
1091
|
+
yield [
|
|
1092
|
+
id,
|
|
1093
|
+
CrdtType.TEXT,
|
|
1094
|
+
crdt.parentId,
|
|
1095
|
+
crdt.parentKey,
|
|
1096
|
+
crdt.data,
|
|
1097
|
+
crdt.version
|
|
1098
|
+
];
|
|
1081
1099
|
} else if (isFileStorageNode(node)) {
|
|
1082
1100
|
const id = node[0];
|
|
1083
1101
|
const crdt = node[1];
|
|
@@ -1087,6 +1105,9 @@ function* nodeStreamToCompactNodes(nodes) {
|
|
|
1087
1105
|
}
|
|
1088
1106
|
}
|
|
1089
1107
|
|
|
1108
|
+
// src/internal.ts
|
|
1109
|
+
var kInternal = /* @__PURE__ */ Symbol();
|
|
1110
|
+
|
|
1090
1111
|
// src/lib/position.ts
|
|
1091
1112
|
var MIN_CODE = 32;
|
|
1092
1113
|
var MAX_CODE = 126;
|
|
@@ -1266,6 +1287,18 @@ function asPos(str) {
|
|
|
1266
1287
|
return isPos(str) ? str : convertToPos(str);
|
|
1267
1288
|
}
|
|
1268
1289
|
|
|
1290
|
+
// src/crdts/StorageUpdates.ts
|
|
1291
|
+
var REMOTE = freeze({ origin: "remote" });
|
|
1292
|
+
var LOCAL_EDIT = freeze({ origin: "local", via: "edit" });
|
|
1293
|
+
var LOCAL_UNDO = freeze({ origin: "local", via: "undo" });
|
|
1294
|
+
var LOCAL_REDO = freeze({ origin: "local", via: "redo" });
|
|
1295
|
+
function toUpdateSource(source) {
|
|
1296
|
+
return source.origin === "remote" ? source : (
|
|
1297
|
+
// Removes `optimistic` field, which is not public
|
|
1298
|
+
{ origin: "local", via: source.via }
|
|
1299
|
+
);
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1269
1302
|
// src/crdts/UnacknowledgedOps.ts
|
|
1270
1303
|
var UnacknowledgedOps = class {
|
|
1271
1304
|
// opId -> op
|
|
@@ -1284,6 +1317,10 @@ ${parentKey}`;
|
|
|
1284
1317
|
get size() {
|
|
1285
1318
|
return this.#byOpId.size;
|
|
1286
1319
|
}
|
|
1320
|
+
/** The still-unacknowledged op with the given opId, if any. */
|
|
1321
|
+
get(opId) {
|
|
1322
|
+
return this.#byOpId.get(opId);
|
|
1323
|
+
}
|
|
1287
1324
|
/**
|
|
1288
1325
|
* Mark the given Op as still unacknowledged.
|
|
1289
1326
|
*/
|
|
@@ -1383,8 +1420,8 @@ function createManagedPool(options) {
|
|
|
1383
1420
|
deleteNode: (id) => void nodes.delete(id),
|
|
1384
1421
|
generateId: () => `${getCurrentConnectionId()}:${clock++}`,
|
|
1385
1422
|
generateOpId: () => `${getCurrentConnectionId()}:${opClock++}`,
|
|
1386
|
-
dispatch(ops, reverse, storageUpdates) {
|
|
1387
|
-
onDispatch?.(ops, reverse, storageUpdates);
|
|
1423
|
+
dispatch(ops, reverse, storageUpdates, options2) {
|
|
1424
|
+
onDispatch?.(ops, reverse, storageUpdates, options2);
|
|
1388
1425
|
},
|
|
1389
1426
|
assertStorageIsWritable: () => {
|
|
1390
1427
|
if (!isStorageWritable()) {
|
|
@@ -1407,10 +1444,17 @@ function Orphaned(oldKey, oldPos = asPos(oldKey)) {
|
|
|
1407
1444
|
return Object.freeze({ type: "Orphaned", oldKey, oldPos });
|
|
1408
1445
|
}
|
|
1409
1446
|
var AbstractCrdt = class {
|
|
1410
|
-
// ^^^^^^^^^^^^ TODO: Make this an interface
|
|
1411
1447
|
#pool;
|
|
1412
1448
|
#id;
|
|
1413
1449
|
#parent = NoParent;
|
|
1450
|
+
constructor() {
|
|
1451
|
+
Object.defineProperty(this, kInternal, {
|
|
1452
|
+
value: {
|
|
1453
|
+
getId: () => this.#id
|
|
1454
|
+
},
|
|
1455
|
+
enumerable: false
|
|
1456
|
+
});
|
|
1457
|
+
}
|
|
1414
1458
|
/** @internal */
|
|
1415
1459
|
_getParentKeyOrThrow() {
|
|
1416
1460
|
switch (this.parent.type) {
|
|
@@ -1463,11 +1507,14 @@ var AbstractCrdt = class {
|
|
|
1463
1507
|
}
|
|
1464
1508
|
}
|
|
1465
1509
|
/** @internal */
|
|
1466
|
-
_apply(op,
|
|
1510
|
+
_apply(op, source) {
|
|
1467
1511
|
switch (op.type) {
|
|
1468
1512
|
case OpCode.DELETE_CRDT: {
|
|
1469
1513
|
if (this.parent.type === "HasParent") {
|
|
1470
|
-
return this.parent.node._detachChild(
|
|
1514
|
+
return this.parent.node._detachChild(
|
|
1515
|
+
crdtAsLiveNode(this),
|
|
1516
|
+
toUpdateSource(source)
|
|
1517
|
+
);
|
|
1471
1518
|
}
|
|
1472
1519
|
return { modified: false };
|
|
1473
1520
|
}
|
|
@@ -1657,8 +1704,8 @@ var LiveFile = class _LiveFile extends AbstractCrdt {
|
|
|
1657
1704
|
throw new Error("A LiveFile node cannot have children");
|
|
1658
1705
|
}
|
|
1659
1706
|
/** @internal */
|
|
1660
|
-
_apply(op,
|
|
1661
|
-
return super._apply(op,
|
|
1707
|
+
_apply(op, source) {
|
|
1708
|
+
return super._apply(op, source);
|
|
1662
1709
|
}
|
|
1663
1710
|
/** @internal */
|
|
1664
1711
|
_toTreeNode(key) {
|
|
@@ -4877,9 +4924,6 @@ var ManagedSocket = class {
|
|
|
4877
4924
|
}
|
|
4878
4925
|
};
|
|
4879
4926
|
|
|
4880
|
-
// src/internal.ts
|
|
4881
|
-
var kInternal = /* @__PURE__ */ Symbol();
|
|
4882
|
-
|
|
4883
4927
|
// src/lib/IncrementalJsonParser.ts
|
|
4884
4928
|
var EMPTY_OBJECT = Object.freeze({});
|
|
4885
4929
|
var NULL_KEYWORD_CHARS = Array.from(new Set("null"));
|
|
@@ -6881,8 +6925,8 @@ var LiveRegister = class _LiveRegister extends AbstractCrdt {
|
|
|
6881
6925
|
throw new Error("Method not implemented.");
|
|
6882
6926
|
}
|
|
6883
6927
|
/** @internal */
|
|
6884
|
-
_apply(op,
|
|
6885
|
-
return super._apply(op,
|
|
6928
|
+
_apply(op, source) {
|
|
6929
|
+
return super._apply(op, source);
|
|
6886
6930
|
}
|
|
6887
6931
|
/** @internal */
|
|
6888
6932
|
_toTreeNode(key) {
|
|
@@ -7043,7 +7087,7 @@ var LiveList = class _LiveList extends AbstractCrdt {
|
|
|
7043
7087
|
item._detach();
|
|
7044
7088
|
}
|
|
7045
7089
|
}
|
|
7046
|
-
#
|
|
7090
|
+
#applyRemoteSet(op) {
|
|
7047
7091
|
if (this._pool === void 0) {
|
|
7048
7092
|
throw new Error("Can't attach child if managed pool is not present");
|
|
7049
7093
|
}
|
|
@@ -7061,9 +7105,11 @@ var LiveList = class _LiveList extends AbstractCrdt {
|
|
|
7061
7105
|
itemWithSamePosition._detach();
|
|
7062
7106
|
this.#items.add(child);
|
|
7063
7107
|
return {
|
|
7064
|
-
modified: makeUpdate(
|
|
7065
|
-
|
|
7066
|
-
|
|
7108
|
+
modified: makeUpdate(
|
|
7109
|
+
this,
|
|
7110
|
+
[setDelta(indexOfItemWithSamePosition, child)],
|
|
7111
|
+
REMOTE
|
|
7112
|
+
),
|
|
7067
7113
|
reverse: []
|
|
7068
7114
|
};
|
|
7069
7115
|
} else {
|
|
@@ -7074,20 +7120,22 @@ var LiveList = class _LiveList extends AbstractCrdt {
|
|
|
7074
7120
|
setDelta(indexOfItemWithSamePosition, child)
|
|
7075
7121
|
];
|
|
7076
7122
|
const deleteDelta2 = this.#detachItemAssociatedToSetOperation(
|
|
7077
|
-
op.deletedId
|
|
7123
|
+
op.deletedId,
|
|
7124
|
+
REMOTE
|
|
7078
7125
|
);
|
|
7079
7126
|
if (deleteDelta2) {
|
|
7080
7127
|
delta.push(deleteDelta2);
|
|
7081
7128
|
}
|
|
7082
7129
|
return {
|
|
7083
|
-
modified: makeUpdate(this, delta),
|
|
7130
|
+
modified: makeUpdate(this, delta, REMOTE),
|
|
7084
7131
|
reverse: []
|
|
7085
7132
|
};
|
|
7086
7133
|
}
|
|
7087
7134
|
} else {
|
|
7088
7135
|
const updates = [];
|
|
7089
7136
|
const deleteDelta2 = this.#detachItemAssociatedToSetOperation(
|
|
7090
|
-
op.deletedId
|
|
7137
|
+
op.deletedId,
|
|
7138
|
+
REMOTE
|
|
7091
7139
|
);
|
|
7092
7140
|
if (deleteDelta2) {
|
|
7093
7141
|
updates.push(deleteDelta2);
|
|
@@ -7096,29 +7144,32 @@ var LiveList = class _LiveList extends AbstractCrdt {
|
|
|
7096
7144
|
updates.push(insertDelta(this._indexOfPosition(key), child));
|
|
7097
7145
|
return {
|
|
7098
7146
|
reverse: [],
|
|
7099
|
-
modified: makeUpdate(this, updates)
|
|
7147
|
+
modified: makeUpdate(this, updates, REMOTE)
|
|
7100
7148
|
};
|
|
7101
7149
|
}
|
|
7102
7150
|
}
|
|
7103
|
-
#applySetAck(op) {
|
|
7151
|
+
#applySetAck(op, source) {
|
|
7104
7152
|
if (this._pool === void 0) {
|
|
7105
7153
|
throw new Error("Can't attach child if managed pool is not present");
|
|
7106
7154
|
}
|
|
7107
7155
|
const delta = [];
|
|
7108
|
-
const deletedDelta = this.#detachItemAssociatedToSetOperation(
|
|
7156
|
+
const deletedDelta = this.#detachItemAssociatedToSetOperation(
|
|
7157
|
+
op.deletedId,
|
|
7158
|
+
source
|
|
7159
|
+
);
|
|
7109
7160
|
if (deletedDelta) {
|
|
7110
7161
|
delta.push(deletedDelta);
|
|
7111
7162
|
}
|
|
7112
7163
|
const unacknowledgedOpId = this.#unacknowledgedSetOpIdAt(op.parentKey);
|
|
7113
7164
|
if (unacknowledgedOpId !== void 0 && unacknowledgedOpId !== op.opId) {
|
|
7114
|
-
return delta.length === 0 ? { modified: false } : { modified: makeUpdate(this, delta), reverse: [] };
|
|
7165
|
+
return delta.length === 0 ? { modified: false } : { modified: makeUpdate(this, delta, source), reverse: [] };
|
|
7115
7166
|
}
|
|
7116
7167
|
const indexOfItemWithSamePosition = this._indexOfPosition(op.parentKey);
|
|
7117
7168
|
const existingItem = this.#items.find((item) => item._id === op.id);
|
|
7118
7169
|
if (existingItem !== void 0) {
|
|
7119
7170
|
if (existingItem._parentKey === op.parentKey) {
|
|
7120
7171
|
return {
|
|
7121
|
-
modified: delta.length > 0 ? makeUpdate(this, delta) : false,
|
|
7172
|
+
modified: delta.length > 0 ? makeUpdate(this, delta, source) : false,
|
|
7122
7173
|
reverse: []
|
|
7123
7174
|
};
|
|
7124
7175
|
}
|
|
@@ -7136,7 +7187,7 @@ var LiveList = class _LiveList extends AbstractCrdt {
|
|
|
7136
7187
|
delta.push(moveDelta(prevIndex, newIndex, existingItem));
|
|
7137
7188
|
}
|
|
7138
7189
|
return {
|
|
7139
|
-
modified: delta.length > 0 ? makeUpdate(this, delta) : false,
|
|
7190
|
+
modified: delta.length > 0 ? makeUpdate(this, delta, source) : false,
|
|
7140
7191
|
reverse: []
|
|
7141
7192
|
};
|
|
7142
7193
|
} else {
|
|
@@ -7146,11 +7197,15 @@ var LiveList = class _LiveList extends AbstractCrdt {
|
|
|
7146
7197
|
this.#implicitlyDeletedItems.delete(orphan);
|
|
7147
7198
|
const recreatedItemIndex = this.#insert(orphan);
|
|
7148
7199
|
return {
|
|
7149
|
-
modified: makeUpdate(
|
|
7150
|
-
|
|
7151
|
-
|
|
7152
|
-
|
|
7153
|
-
|
|
7200
|
+
modified: makeUpdate(
|
|
7201
|
+
this,
|
|
7202
|
+
[
|
|
7203
|
+
// If there is an item at this position, update is a set, else it's an insert
|
|
7204
|
+
indexOfItemWithSamePosition === -1 ? insertDelta(recreatedItemIndex, orphan) : setDelta(recreatedItemIndex, orphan),
|
|
7205
|
+
...delta
|
|
7206
|
+
],
|
|
7207
|
+
source
|
|
7208
|
+
),
|
|
7154
7209
|
reverse: []
|
|
7155
7210
|
};
|
|
7156
7211
|
} else {
|
|
@@ -7165,11 +7220,15 @@ var LiveList = class _LiveList extends AbstractCrdt {
|
|
|
7165
7220
|
op.parentKey
|
|
7166
7221
|
);
|
|
7167
7222
|
return {
|
|
7168
|
-
modified: makeUpdate(
|
|
7169
|
-
|
|
7170
|
-
|
|
7171
|
-
|
|
7172
|
-
|
|
7223
|
+
modified: makeUpdate(
|
|
7224
|
+
this,
|
|
7225
|
+
[
|
|
7226
|
+
// If there is an item at this position, update is a set, else it's an insert
|
|
7227
|
+
indexOfItemWithSamePosition === -1 ? insertDelta(newIndex, newItem) : setDelta(newIndex, newItem),
|
|
7228
|
+
...delta
|
|
7229
|
+
],
|
|
7230
|
+
source
|
|
7231
|
+
),
|
|
7173
7232
|
reverse: []
|
|
7174
7233
|
};
|
|
7175
7234
|
}
|
|
@@ -7178,7 +7237,7 @@ var LiveList = class _LiveList extends AbstractCrdt {
|
|
|
7178
7237
|
/**
|
|
7179
7238
|
* Returns the update delta of the deletion or null
|
|
7180
7239
|
*/
|
|
7181
|
-
#detachItemAssociatedToSetOperation(deletedId) {
|
|
7240
|
+
#detachItemAssociatedToSetOperation(deletedId, source) {
|
|
7182
7241
|
if (deletedId === void 0 || this._pool === void 0) {
|
|
7183
7242
|
return null;
|
|
7184
7243
|
}
|
|
@@ -7186,7 +7245,7 @@ var LiveList = class _LiveList extends AbstractCrdt {
|
|
|
7186
7245
|
if (deletedItem === void 0) {
|
|
7187
7246
|
return null;
|
|
7188
7247
|
}
|
|
7189
|
-
const result = this._detachChild(deletedItem);
|
|
7248
|
+
const result = this._detachChild(deletedItem, source);
|
|
7190
7249
|
if (result.modified === false) {
|
|
7191
7250
|
return null;
|
|
7192
7251
|
}
|
|
@@ -7204,10 +7263,11 @@ var LiveList = class _LiveList extends AbstractCrdt {
|
|
|
7204
7263
|
const { newItem, newIndex } = this.#createAttachItemAndSort(op, key);
|
|
7205
7264
|
const bumpDeltas = this.#bumpUnackedPushesAbove(key);
|
|
7206
7265
|
return {
|
|
7207
|
-
modified: makeUpdate(
|
|
7208
|
-
|
|
7209
|
-
...bumpDeltas
|
|
7210
|
-
|
|
7266
|
+
modified: makeUpdate(
|
|
7267
|
+
this,
|
|
7268
|
+
[insertDelta(newIndex, newItem), ...bumpDeltas],
|
|
7269
|
+
REMOTE
|
|
7270
|
+
),
|
|
7211
7271
|
reverse: []
|
|
7212
7272
|
};
|
|
7213
7273
|
}
|
|
@@ -7288,7 +7348,7 @@ var LiveList = class _LiveList extends AbstractCrdt {
|
|
|
7288
7348
|
}
|
|
7289
7349
|
return deltas;
|
|
7290
7350
|
}
|
|
7291
|
-
#applyInsertAck(op) {
|
|
7351
|
+
#applyInsertAck(op, source) {
|
|
7292
7352
|
const existingItem = this.#items.find((item) => item._id === op.id);
|
|
7293
7353
|
const key = asPos(op.parentKey);
|
|
7294
7354
|
const itemIndexAtPosition = this._indexOfPosition(key);
|
|
@@ -7310,9 +7370,11 @@ var LiveList = class _LiveList extends AbstractCrdt {
|
|
|
7310
7370
|
return { modified: false };
|
|
7311
7371
|
}
|
|
7312
7372
|
return {
|
|
7313
|
-
modified: makeUpdate(
|
|
7314
|
-
|
|
7315
|
-
|
|
7373
|
+
modified: makeUpdate(
|
|
7374
|
+
this,
|
|
7375
|
+
[moveDelta(oldPositionIndex, newIndex, existingItem)],
|
|
7376
|
+
source
|
|
7377
|
+
),
|
|
7316
7378
|
reverse: []
|
|
7317
7379
|
};
|
|
7318
7380
|
}
|
|
@@ -7324,7 +7386,7 @@ var LiveList = class _LiveList extends AbstractCrdt {
|
|
|
7324
7386
|
this.#insert(orphan);
|
|
7325
7387
|
const newIndex = this._indexOfPosition(key);
|
|
7326
7388
|
return {
|
|
7327
|
-
modified: makeUpdate(this, [insertDelta(newIndex, orphan)]),
|
|
7389
|
+
modified: makeUpdate(this, [insertDelta(newIndex, orphan)], source),
|
|
7328
7390
|
reverse: []
|
|
7329
7391
|
};
|
|
7330
7392
|
} else {
|
|
@@ -7333,13 +7395,13 @@ var LiveList = class _LiveList extends AbstractCrdt {
|
|
|
7333
7395
|
}
|
|
7334
7396
|
const { newItem, newIndex } = this.#createAttachItemAndSort(op, key);
|
|
7335
7397
|
return {
|
|
7336
|
-
modified: makeUpdate(this, [insertDelta(newIndex, newItem)]),
|
|
7398
|
+
modified: makeUpdate(this, [insertDelta(newIndex, newItem)], source),
|
|
7337
7399
|
reverse: []
|
|
7338
7400
|
};
|
|
7339
7401
|
}
|
|
7340
7402
|
}
|
|
7341
7403
|
}
|
|
7342
|
-
#
|
|
7404
|
+
#applyLocalInsert(op, source) {
|
|
7343
7405
|
const { id, parentKey: key } = op;
|
|
7344
7406
|
const child = creationOpToLiveNode(op);
|
|
7345
7407
|
if (this._pool?.getNode(id) !== void 0) {
|
|
@@ -7358,11 +7420,11 @@ var LiveList = class _LiveList extends AbstractCrdt {
|
|
|
7358
7420
|
this.#insert(child);
|
|
7359
7421
|
const newIndex = this._indexOfPosition(newKey);
|
|
7360
7422
|
return {
|
|
7361
|
-
modified: makeUpdate(this, [insertDelta(newIndex, child)]),
|
|
7423
|
+
modified: makeUpdate(this, [insertDelta(newIndex, child)], source),
|
|
7362
7424
|
reverse: [{ type: OpCode.DELETE_CRDT, id }]
|
|
7363
7425
|
};
|
|
7364
7426
|
}
|
|
7365
|
-
#
|
|
7427
|
+
#applyLocalSet(op, source) {
|
|
7366
7428
|
const { id, parentKey: key } = op;
|
|
7367
7429
|
const child = creationOpToLiveNode(op);
|
|
7368
7430
|
if (this._pool?.getNode(id) !== void 0) {
|
|
@@ -7384,46 +7446,48 @@ var LiveList = class _LiveList extends AbstractCrdt {
|
|
|
7384
7446
|
);
|
|
7385
7447
|
const delta = [setDelta(indexOfItemWithSameKey, child)];
|
|
7386
7448
|
const deletedDelta = this.#detachItemAssociatedToSetOperation(
|
|
7387
|
-
op.deletedId
|
|
7449
|
+
op.deletedId,
|
|
7450
|
+
source
|
|
7388
7451
|
);
|
|
7389
7452
|
if (deletedDelta) {
|
|
7390
7453
|
delta.push(deletedDelta);
|
|
7391
7454
|
}
|
|
7392
7455
|
return {
|
|
7393
|
-
modified: makeUpdate(this, delta),
|
|
7456
|
+
modified: makeUpdate(this, delta, source),
|
|
7394
7457
|
reverse
|
|
7395
7458
|
};
|
|
7396
7459
|
} else {
|
|
7397
7460
|
this.#insert(child);
|
|
7398
|
-
this.#detachItemAssociatedToSetOperation(op.deletedId);
|
|
7461
|
+
this.#detachItemAssociatedToSetOperation(op.deletedId, source);
|
|
7399
7462
|
const newIndex = this._indexOfPosition(newKey);
|
|
7400
7463
|
return {
|
|
7401
7464
|
reverse: [{ type: OpCode.DELETE_CRDT, id }],
|
|
7402
|
-
modified: makeUpdate(this, [insertDelta(newIndex, child)])
|
|
7465
|
+
modified: makeUpdate(this, [insertDelta(newIndex, child)], source)
|
|
7403
7466
|
};
|
|
7404
7467
|
}
|
|
7405
7468
|
}
|
|
7406
7469
|
/** @internal */
|
|
7407
|
-
_attachChild(op,
|
|
7470
|
+
_attachChild(op, opSource) {
|
|
7471
|
+
const source = toUpdateSource(opSource);
|
|
7408
7472
|
if (this._pool === void 0) {
|
|
7409
7473
|
throw new Error("Can't attach child if managed pool is not present");
|
|
7410
7474
|
}
|
|
7411
7475
|
let result;
|
|
7412
7476
|
if (op.intent === "set") {
|
|
7413
|
-
if (
|
|
7414
|
-
result = this.#
|
|
7415
|
-
} else if (
|
|
7416
|
-
result = this.#applySetAck(op);
|
|
7477
|
+
if (opSource.origin === "remote") {
|
|
7478
|
+
result = this.#applyRemoteSet(op);
|
|
7479
|
+
} else if (!opSource.optimistic) {
|
|
7480
|
+
result = this.#applySetAck(op, source);
|
|
7417
7481
|
} else {
|
|
7418
|
-
result = this.#
|
|
7482
|
+
result = this.#applyLocalSet(op, source);
|
|
7419
7483
|
}
|
|
7420
7484
|
} else {
|
|
7421
|
-
if (
|
|
7485
|
+
if (opSource.origin === "remote") {
|
|
7422
7486
|
result = this.#applyRemoteInsert(op);
|
|
7423
|
-
} else if (
|
|
7424
|
-
result = this.#applyInsertAck(op);
|
|
7487
|
+
} else if (!opSource.optimistic) {
|
|
7488
|
+
result = this.#applyInsertAck(op, source);
|
|
7425
7489
|
} else {
|
|
7426
|
-
result = this.#
|
|
7490
|
+
result = this.#applyLocalInsert(op, source);
|
|
7427
7491
|
}
|
|
7428
7492
|
}
|
|
7429
7493
|
if (result.modified !== false) {
|
|
@@ -7432,7 +7496,7 @@ var LiveList = class _LiveList extends AbstractCrdt {
|
|
|
7432
7496
|
return result;
|
|
7433
7497
|
}
|
|
7434
7498
|
/** @internal */
|
|
7435
|
-
_detachChild(child) {
|
|
7499
|
+
_detachChild(child, source) {
|
|
7436
7500
|
if (child) {
|
|
7437
7501
|
const parentKey = nn(child._parentKey);
|
|
7438
7502
|
const reverse = child._toOps(nn(this._id), parentKey);
|
|
@@ -7447,19 +7511,23 @@ var LiveList = class _LiveList extends AbstractCrdt {
|
|
|
7447
7511
|
this.invalidate();
|
|
7448
7512
|
child._detach();
|
|
7449
7513
|
return {
|
|
7450
|
-
modified: makeUpdate(
|
|
7514
|
+
modified: makeUpdate(
|
|
7515
|
+
this,
|
|
7516
|
+
[deleteDelta(indexToDelete, previousNode)],
|
|
7517
|
+
source
|
|
7518
|
+
),
|
|
7451
7519
|
reverse
|
|
7452
7520
|
};
|
|
7453
7521
|
}
|
|
7454
7522
|
return { modified: false };
|
|
7455
7523
|
}
|
|
7456
|
-
#
|
|
7524
|
+
#applyRemoteSetChildKey(newKey, child) {
|
|
7457
7525
|
if (this.#implicitlyDeletedItems.has(child)) {
|
|
7458
7526
|
this.#implicitlyDeletedItems.delete(child);
|
|
7459
7527
|
child._setParentLink(this, newKey);
|
|
7460
7528
|
const newIndex = this.#insert(child);
|
|
7461
7529
|
return {
|
|
7462
|
-
modified: makeUpdate(this, [insertDelta(newIndex, child)]),
|
|
7530
|
+
modified: makeUpdate(this, [insertDelta(newIndex, child)], REMOTE),
|
|
7463
7531
|
reverse: []
|
|
7464
7532
|
};
|
|
7465
7533
|
}
|
|
@@ -7480,7 +7548,11 @@ var LiveList = class _LiveList extends AbstractCrdt {
|
|
|
7480
7548
|
};
|
|
7481
7549
|
}
|
|
7482
7550
|
return {
|
|
7483
|
-
modified: makeUpdate(
|
|
7551
|
+
modified: makeUpdate(
|
|
7552
|
+
this,
|
|
7553
|
+
[moveDelta(previousIndex, newIndex, child)],
|
|
7554
|
+
REMOTE
|
|
7555
|
+
),
|
|
7484
7556
|
reverse: []
|
|
7485
7557
|
};
|
|
7486
7558
|
} else {
|
|
@@ -7497,12 +7569,16 @@ var LiveList = class _LiveList extends AbstractCrdt {
|
|
|
7497
7569
|
};
|
|
7498
7570
|
}
|
|
7499
7571
|
return {
|
|
7500
|
-
modified: makeUpdate(
|
|
7572
|
+
modified: makeUpdate(
|
|
7573
|
+
this,
|
|
7574
|
+
[moveDelta(previousIndex, newIndex, child)],
|
|
7575
|
+
REMOTE
|
|
7576
|
+
),
|
|
7501
7577
|
reverse: []
|
|
7502
7578
|
};
|
|
7503
7579
|
}
|
|
7504
7580
|
}
|
|
7505
|
-
#applySetChildKeyAck(newKey, child) {
|
|
7581
|
+
#applySetChildKeyAck(newKey, child, source) {
|
|
7506
7582
|
const previousKey = nn(child._parentKey);
|
|
7507
7583
|
if (this.#implicitlyDeletedItems.has(child)) {
|
|
7508
7584
|
const existingItemIndex = this._indexOfPosition(newKey);
|
|
@@ -7521,7 +7597,7 @@ var LiveList = class _LiveList extends AbstractCrdt {
|
|
|
7521
7597
|
child._setParentLink(this, newKey);
|
|
7522
7598
|
const newIndex = this.#insert(child);
|
|
7523
7599
|
return {
|
|
7524
|
-
modified: makeUpdate(this, [insertDelta(newIndex, child)]),
|
|
7600
|
+
modified: makeUpdate(this, [insertDelta(newIndex, child)], source),
|
|
7525
7601
|
reverse: []
|
|
7526
7602
|
};
|
|
7527
7603
|
} else {
|
|
@@ -7549,15 +7625,17 @@ var LiveList = class _LiveList extends AbstractCrdt {
|
|
|
7549
7625
|
};
|
|
7550
7626
|
} else {
|
|
7551
7627
|
return {
|
|
7552
|
-
modified: makeUpdate(
|
|
7553
|
-
|
|
7554
|
-
|
|
7628
|
+
modified: makeUpdate(
|
|
7629
|
+
this,
|
|
7630
|
+
[moveDelta(previousIndex, newIndex, child)],
|
|
7631
|
+
source
|
|
7632
|
+
),
|
|
7555
7633
|
reverse: []
|
|
7556
7634
|
};
|
|
7557
7635
|
}
|
|
7558
7636
|
}
|
|
7559
7637
|
}
|
|
7560
|
-
#
|
|
7638
|
+
#applyLocalSetChildKey(newKey, child, source) {
|
|
7561
7639
|
const previousKey = nn(child._parentKey);
|
|
7562
7640
|
const previousIndex = this.#items.findIndex((item) => item === child);
|
|
7563
7641
|
const existingItemIndex = this._indexOfPosition(newKey);
|
|
@@ -7576,7 +7654,11 @@ var LiveList = class _LiveList extends AbstractCrdt {
|
|
|
7576
7654
|
};
|
|
7577
7655
|
}
|
|
7578
7656
|
return {
|
|
7579
|
-
modified: makeUpdate(
|
|
7657
|
+
modified: makeUpdate(
|
|
7658
|
+
this,
|
|
7659
|
+
[moveDelta(previousIndex, newIndex, child)],
|
|
7660
|
+
source
|
|
7661
|
+
),
|
|
7580
7662
|
reverse: [
|
|
7581
7663
|
{
|
|
7582
7664
|
type: OpCode.SET_PARENT_KEY,
|
|
@@ -7587,18 +7669,19 @@ var LiveList = class _LiveList extends AbstractCrdt {
|
|
|
7587
7669
|
};
|
|
7588
7670
|
}
|
|
7589
7671
|
/** @internal */
|
|
7590
|
-
_setChildKey(newKey, child,
|
|
7591
|
-
|
|
7592
|
-
|
|
7593
|
-
|
|
7594
|
-
|
|
7672
|
+
_setChildKey(newKey, child, opSource) {
|
|
7673
|
+
const source = toUpdateSource(opSource);
|
|
7674
|
+
if (opSource.origin === "remote") {
|
|
7675
|
+
return this.#applyRemoteSetChildKey(newKey, child);
|
|
7676
|
+
} else if (!opSource.optimistic) {
|
|
7677
|
+
return this.#applySetChildKeyAck(newKey, child, source);
|
|
7595
7678
|
} else {
|
|
7596
|
-
return this.#
|
|
7679
|
+
return this.#applyLocalSetChildKey(newKey, child, source);
|
|
7597
7680
|
}
|
|
7598
7681
|
}
|
|
7599
7682
|
/** @internal */
|
|
7600
|
-
_apply(op,
|
|
7601
|
-
return super._apply(op,
|
|
7683
|
+
_apply(op, source) {
|
|
7684
|
+
return super._apply(op, source);
|
|
7602
7685
|
}
|
|
7603
7686
|
/** @internal */
|
|
7604
7687
|
_serialize() {
|
|
@@ -7659,7 +7742,7 @@ var LiveList = class _LiveList extends AbstractCrdt {
|
|
|
7659
7742
|
intent === "push" ? addIntentToRootOp(ops, "push") : ops,
|
|
7660
7743
|
[{ type: OpCode.DELETE_CRDT, id }],
|
|
7661
7744
|
/* @__PURE__ */ new Map([
|
|
7662
|
-
[this._id, makeUpdate(this, [insertDelta(index, value)])]
|
|
7745
|
+
[this._id, makeUpdate(this, [insertDelta(index, value)], LOCAL_EDIT)]
|
|
7663
7746
|
])
|
|
7664
7747
|
);
|
|
7665
7748
|
}
|
|
@@ -7700,7 +7783,10 @@ var LiveList = class _LiveList extends AbstractCrdt {
|
|
|
7700
7783
|
this.#updateItemPositionAt(index, position);
|
|
7701
7784
|
if (this._pool && this._id) {
|
|
7702
7785
|
const storageUpdates = /* @__PURE__ */ new Map([
|
|
7703
|
-
[
|
|
7786
|
+
[
|
|
7787
|
+
this._id,
|
|
7788
|
+
makeUpdate(this, [moveDelta(index, targetIndex, item)], LOCAL_EDIT)
|
|
7789
|
+
]
|
|
7704
7790
|
]);
|
|
7705
7791
|
this._pool.dispatch(
|
|
7706
7792
|
[
|
|
@@ -7743,7 +7829,7 @@ var LiveList = class _LiveList extends AbstractCrdt {
|
|
|
7743
7829
|
const storageUpdates = /* @__PURE__ */ new Map();
|
|
7744
7830
|
storageUpdates.set(
|
|
7745
7831
|
nn(this._id),
|
|
7746
|
-
makeUpdate(this, [deleteDelta(index, item)])
|
|
7832
|
+
makeUpdate(this, [deleteDelta(index, item)], LOCAL_EDIT)
|
|
7747
7833
|
);
|
|
7748
7834
|
this._pool.dispatch(
|
|
7749
7835
|
[
|
|
@@ -7783,7 +7869,10 @@ var LiveList = class _LiveList extends AbstractCrdt {
|
|
|
7783
7869
|
this.#items.clear();
|
|
7784
7870
|
this.invalidate();
|
|
7785
7871
|
const storageUpdates = /* @__PURE__ */ new Map();
|
|
7786
|
-
storageUpdates.set(
|
|
7872
|
+
storageUpdates.set(
|
|
7873
|
+
nn(this._id),
|
|
7874
|
+
makeUpdate(this, updateDelta, LOCAL_EDIT)
|
|
7875
|
+
);
|
|
7787
7876
|
this._pool.dispatch(ops, reverseOps, storageUpdates);
|
|
7788
7877
|
} else {
|
|
7789
7878
|
for (const item of this.#items) {
|
|
@@ -7813,7 +7902,10 @@ var LiveList = class _LiveList extends AbstractCrdt {
|
|
|
7813
7902
|
const id = this._pool.generateId();
|
|
7814
7903
|
value._attach(id, this._pool);
|
|
7815
7904
|
const storageUpdates = /* @__PURE__ */ new Map();
|
|
7816
|
-
storageUpdates.set(
|
|
7905
|
+
storageUpdates.set(
|
|
7906
|
+
this._id,
|
|
7907
|
+
makeUpdate(this, [setDelta(index, value)], LOCAL_EDIT)
|
|
7908
|
+
);
|
|
7817
7909
|
const ops = addIntentToRootOp(
|
|
7818
7910
|
value._toOpsWithOpId(this._id, position, this._pool),
|
|
7819
7911
|
"set",
|
|
@@ -7986,11 +8078,12 @@ var LiveList = class _LiveList extends AbstractCrdt {
|
|
|
7986
8078
|
);
|
|
7987
8079
|
}
|
|
7988
8080
|
};
|
|
7989
|
-
function makeUpdate(liveList, deltaUpdates) {
|
|
8081
|
+
function makeUpdate(liveList, deltaUpdates, source) {
|
|
7990
8082
|
return {
|
|
7991
8083
|
node: liveList,
|
|
7992
8084
|
type: "LiveList",
|
|
7993
|
-
updates: deltaUpdates
|
|
8085
|
+
updates: deltaUpdates,
|
|
8086
|
+
source
|
|
7994
8087
|
};
|
|
7995
8088
|
}
|
|
7996
8089
|
function setDelta(index, item) {
|
|
@@ -8109,7 +8202,9 @@ var LiveMap = class _LiveMap extends AbstractCrdt {
|
|
|
8109
8202
|
if (this._pool.getNode(id) !== void 0) {
|
|
8110
8203
|
return { modified: false };
|
|
8111
8204
|
}
|
|
8112
|
-
if (source ===
|
|
8205
|
+
if (source.origin === "remote") {
|
|
8206
|
+
this.#unacknowledgedSet.delete(key);
|
|
8207
|
+
} else if (!source.optimistic) {
|
|
8113
8208
|
const lastUpdateOpId = this.#unacknowledgedSet.get(key);
|
|
8114
8209
|
if (lastUpdateOpId === opId) {
|
|
8115
8210
|
this.#unacknowledgedSet.delete(key);
|
|
@@ -8117,8 +8212,6 @@ var LiveMap = class _LiveMap extends AbstractCrdt {
|
|
|
8117
8212
|
} else if (lastUpdateOpId !== void 0) {
|
|
8118
8213
|
return { modified: false };
|
|
8119
8214
|
}
|
|
8120
|
-
} else if (source === 1 /* THEIRS */) {
|
|
8121
|
-
this.#unacknowledgedSet.delete(key);
|
|
8122
8215
|
}
|
|
8123
8216
|
const previousValue = this.#map.get(key);
|
|
8124
8217
|
let reverse;
|
|
@@ -8137,7 +8230,8 @@ var LiveMap = class _LiveMap extends AbstractCrdt {
|
|
|
8137
8230
|
modified: {
|
|
8138
8231
|
node: this,
|
|
8139
8232
|
type: "LiveMap",
|
|
8140
|
-
updates: { [key]: { type: "update" } }
|
|
8233
|
+
updates: { [key]: { type: "update" } },
|
|
8234
|
+
source: toUpdateSource(source)
|
|
8141
8235
|
},
|
|
8142
8236
|
reverse
|
|
8143
8237
|
};
|
|
@@ -8150,7 +8244,7 @@ var LiveMap = class _LiveMap extends AbstractCrdt {
|
|
|
8150
8244
|
}
|
|
8151
8245
|
}
|
|
8152
8246
|
/** @internal */
|
|
8153
|
-
_detachChild(child) {
|
|
8247
|
+
_detachChild(child, source) {
|
|
8154
8248
|
const id = nn(this._id);
|
|
8155
8249
|
const parentKey = nn(child._parentKey);
|
|
8156
8250
|
const reverse = child._toOps(id, parentKey);
|
|
@@ -8169,7 +8263,8 @@ var LiveMap = class _LiveMap extends AbstractCrdt {
|
|
|
8169
8263
|
type: "delete",
|
|
8170
8264
|
deletedItem: liveNodeToLson(child)
|
|
8171
8265
|
}
|
|
8172
|
-
}
|
|
8266
|
+
},
|
|
8267
|
+
source
|
|
8173
8268
|
};
|
|
8174
8269
|
return { modified: storageUpdate, reverse };
|
|
8175
8270
|
}
|
|
@@ -8218,7 +8313,8 @@ var LiveMap = class _LiveMap extends AbstractCrdt {
|
|
|
8218
8313
|
storageUpdates.set(this._id, {
|
|
8219
8314
|
node: this,
|
|
8220
8315
|
type: "LiveMap",
|
|
8221
|
-
updates: { [key]: { type: "update" } }
|
|
8316
|
+
updates: { [key]: { type: "update" } },
|
|
8317
|
+
source: LOCAL_EDIT
|
|
8222
8318
|
});
|
|
8223
8319
|
const ops = item._toOpsWithOpId(this._id, key, this._pool);
|
|
8224
8320
|
this.#unacknowledgedSet.set(key, nn(ops[0].opId));
|
|
@@ -8267,7 +8363,8 @@ var LiveMap = class _LiveMap extends AbstractCrdt {
|
|
|
8267
8363
|
type: "delete",
|
|
8268
8364
|
deletedItem: liveNodeToLson(item)
|
|
8269
8365
|
}
|
|
8270
|
-
}
|
|
8366
|
+
},
|
|
8367
|
+
source: LOCAL_EDIT
|
|
8271
8368
|
});
|
|
8272
8369
|
this._pool.dispatch(
|
|
8273
8370
|
[
|
|
@@ -8632,7 +8729,7 @@ var LiveObject = class _LiveObject extends AbstractCrdt {
|
|
|
8632
8729
|
}
|
|
8633
8730
|
return { modified: false };
|
|
8634
8731
|
}
|
|
8635
|
-
if (source ===
|
|
8732
|
+
if (source.origin === "local" && source.optimistic) {
|
|
8636
8733
|
this.#unackedOpsByKey.set(key, nn(opId));
|
|
8637
8734
|
} else if (this.#unackedOpsByKey.get(key) === void 0) {
|
|
8638
8735
|
} else if (this.#unackedOpsByKey.get(key) === opId) {
|
|
@@ -8670,12 +8767,13 @@ var LiveObject = class _LiveObject extends AbstractCrdt {
|
|
|
8670
8767
|
modified: {
|
|
8671
8768
|
node: this,
|
|
8672
8769
|
type: "LiveObject",
|
|
8673
|
-
updates: { [key]: { type: "update" } }
|
|
8770
|
+
updates: { [key]: { type: "update" } },
|
|
8771
|
+
source: toUpdateSource(source)
|
|
8674
8772
|
}
|
|
8675
8773
|
};
|
|
8676
8774
|
}
|
|
8677
8775
|
/** @internal */
|
|
8678
|
-
_detachChild(child) {
|
|
8776
|
+
_detachChild(child, source) {
|
|
8679
8777
|
if (child) {
|
|
8680
8778
|
const id = nn(this._id);
|
|
8681
8779
|
const parentKey = nn(child._parentKey);
|
|
@@ -8693,7 +8791,8 @@ var LiveObject = class _LiveObject extends AbstractCrdt {
|
|
|
8693
8791
|
type: "LiveObject",
|
|
8694
8792
|
updates: {
|
|
8695
8793
|
[parentKey]: { type: "delete", deletedItem }
|
|
8696
|
-
}
|
|
8794
|
+
},
|
|
8795
|
+
source
|
|
8697
8796
|
};
|
|
8698
8797
|
return { modified: storageUpdate, reverse };
|
|
8699
8798
|
}
|
|
@@ -8709,13 +8808,13 @@ var LiveObject = class _LiveObject extends AbstractCrdt {
|
|
|
8709
8808
|
}
|
|
8710
8809
|
}
|
|
8711
8810
|
/** @internal */
|
|
8712
|
-
_apply(op,
|
|
8811
|
+
_apply(op, source) {
|
|
8713
8812
|
if (op.type === OpCode.UPDATE_OBJECT) {
|
|
8714
|
-
return this.#applyUpdate(op,
|
|
8813
|
+
return this.#applyUpdate(op, source);
|
|
8715
8814
|
} else if (op.type === OpCode.DELETE_OBJECT_KEY) {
|
|
8716
|
-
return this.#applyDeleteObjectKey(op,
|
|
8815
|
+
return this.#applyDeleteObjectKey(op, source);
|
|
8717
8816
|
}
|
|
8718
|
-
return super._apply(op,
|
|
8817
|
+
return super._apply(op, source);
|
|
8719
8818
|
}
|
|
8720
8819
|
/** @internal */
|
|
8721
8820
|
_serialize() {
|
|
@@ -8739,7 +8838,7 @@ var LiveObject = class _LiveObject extends AbstractCrdt {
|
|
|
8739
8838
|
};
|
|
8740
8839
|
}
|
|
8741
8840
|
}
|
|
8742
|
-
#applyUpdate(op,
|
|
8841
|
+
#applyUpdate(op, source) {
|
|
8743
8842
|
let isModified = false;
|
|
8744
8843
|
const id = nn(this._id);
|
|
8745
8844
|
const reverse = [];
|
|
@@ -8767,7 +8866,7 @@ var LiveObject = class _LiveObject extends AbstractCrdt {
|
|
|
8767
8866
|
if (value === void 0) {
|
|
8768
8867
|
continue;
|
|
8769
8868
|
}
|
|
8770
|
-
if (
|
|
8869
|
+
if (source.origin === "local" && source.optimistic) {
|
|
8771
8870
|
this.#unackedOpsByKey.set(key, nn(op.opId));
|
|
8772
8871
|
} else if (this.#unackedOpsByKey.get(key) === void 0) {
|
|
8773
8872
|
isModified = true;
|
|
@@ -8794,18 +8893,19 @@ var LiveObject = class _LiveObject extends AbstractCrdt {
|
|
|
8794
8893
|
modified: {
|
|
8795
8894
|
node: this,
|
|
8796
8895
|
type: "LiveObject",
|
|
8797
|
-
updates: updateDelta
|
|
8896
|
+
updates: updateDelta,
|
|
8897
|
+
source: toUpdateSource(source)
|
|
8798
8898
|
},
|
|
8799
8899
|
reverse
|
|
8800
8900
|
} : { modified: false };
|
|
8801
8901
|
}
|
|
8802
|
-
#applyDeleteObjectKey(op,
|
|
8902
|
+
#applyDeleteObjectKey(op, source) {
|
|
8803
8903
|
const key = op.key;
|
|
8804
8904
|
const oldValue = this.#synced.get(key);
|
|
8805
8905
|
if (oldValue === void 0) {
|
|
8806
8906
|
return { modified: false };
|
|
8807
8907
|
}
|
|
8808
|
-
if (!
|
|
8908
|
+
if (!(source.origin === "local" && source.optimistic) && this.#unackedOpsByKey.get(key) !== void 0) {
|
|
8809
8909
|
return { modified: false };
|
|
8810
8910
|
}
|
|
8811
8911
|
const id = nn(this._id);
|
|
@@ -8831,7 +8931,8 @@ var LiveObject = class _LiveObject extends AbstractCrdt {
|
|
|
8831
8931
|
type: "LiveObject",
|
|
8832
8932
|
updates: {
|
|
8833
8933
|
[op.key]: { type: "delete", deletedItem: oldValue }
|
|
8834
|
-
}
|
|
8934
|
+
},
|
|
8935
|
+
source: toUpdateSource(source)
|
|
8835
8936
|
},
|
|
8836
8937
|
reverse
|
|
8837
8938
|
};
|
|
@@ -8877,7 +8978,8 @@ var LiveObject = class _LiveObject extends AbstractCrdt {
|
|
|
8877
8978
|
updates: {
|
|
8878
8979
|
...existing?.updates,
|
|
8879
8980
|
[key]: { type: "update" }
|
|
8880
|
-
}
|
|
8981
|
+
},
|
|
8982
|
+
source: LOCAL_EDIT
|
|
8881
8983
|
});
|
|
8882
8984
|
this._pool.dispatch(ops, reverse, storageUpdates);
|
|
8883
8985
|
}
|
|
@@ -8911,7 +9013,8 @@ var LiveObject = class _LiveObject extends AbstractCrdt {
|
|
|
8911
9013
|
type: "delete",
|
|
8912
9014
|
deletedItem: oldValue2
|
|
8913
9015
|
}
|
|
8914
|
-
}
|
|
9016
|
+
},
|
|
9017
|
+
source: LOCAL_EDIT
|
|
8915
9018
|
});
|
|
8916
9019
|
return [[], [], storageUpdates2];
|
|
8917
9020
|
}
|
|
@@ -8959,7 +9062,8 @@ var LiveObject = class _LiveObject extends AbstractCrdt {
|
|
|
8959
9062
|
type: "LiveObject",
|
|
8960
9063
|
updates: {
|
|
8961
9064
|
[key]: { type: "delete", deletedItem: oldValue }
|
|
8962
|
-
}
|
|
9065
|
+
},
|
|
9066
|
+
source: LOCAL_EDIT
|
|
8963
9067
|
});
|
|
8964
9068
|
return [ops, reverse, storageUpdates];
|
|
8965
9069
|
}
|
|
@@ -9097,7 +9201,8 @@ var LiveObject = class _LiveObject extends AbstractCrdt {
|
|
|
9097
9201
|
storageUpdates.set(this._id, {
|
|
9098
9202
|
node: this,
|
|
9099
9203
|
type: "LiveObject",
|
|
9100
|
-
updates: updateDelta
|
|
9204
|
+
updates: updateDelta,
|
|
9205
|
+
source: LOCAL_EDIT
|
|
9101
9206
|
});
|
|
9102
9207
|
this._pool.dispatch(ops, reverseOps, storageUpdates);
|
|
9103
9208
|
}
|
|
@@ -9179,181 +9284,1389 @@ var LiveObject = class _LiveObject extends AbstractCrdt {
|
|
|
9179
9284
|
}
|
|
9180
9285
|
};
|
|
9181
9286
|
|
|
9182
|
-
// src/crdts/
|
|
9183
|
-
function
|
|
9184
|
-
|
|
9185
|
-
}
|
|
9186
|
-
function creationOpToLson(op) {
|
|
9187
|
-
switch (op.type) {
|
|
9188
|
-
case OpCode.CREATE_FILE:
|
|
9189
|
-
return new LiveFile(op.data);
|
|
9190
|
-
case OpCode.CREATE_REGISTER:
|
|
9191
|
-
return op.data;
|
|
9192
|
-
case OpCode.CREATE_OBJECT:
|
|
9193
|
-
return new LiveObject(op.data);
|
|
9194
|
-
case OpCode.CREATE_MAP:
|
|
9195
|
-
return new LiveMap();
|
|
9196
|
-
case OpCode.CREATE_LIST:
|
|
9197
|
-
return new LiveList([]);
|
|
9198
|
-
default:
|
|
9199
|
-
return assertNever(op, "Unknown creation Op");
|
|
9200
|
-
}
|
|
9201
|
-
}
|
|
9202
|
-
function isSameNodeOrChildOf(node, parent) {
|
|
9203
|
-
if (node === parent) {
|
|
9287
|
+
// src/crdts/liveTextOps.ts
|
|
9288
|
+
function attributesEqual(left, right) {
|
|
9289
|
+
if (left === right) {
|
|
9204
9290
|
return true;
|
|
9205
9291
|
}
|
|
9206
|
-
if (
|
|
9207
|
-
return
|
|
9292
|
+
if (left === void 0 || right === void 0) {
|
|
9293
|
+
return false;
|
|
9208
9294
|
}
|
|
9209
|
-
|
|
9210
|
-
|
|
9211
|
-
|
|
9212
|
-
|
|
9213
|
-
|
|
9214
|
-
|
|
9215
|
-
|
|
9216
|
-
|
|
9295
|
+
const leftKeys = Object.keys(left);
|
|
9296
|
+
const rightKeys = Object.keys(right);
|
|
9297
|
+
if (leftKeys.length !== rightKeys.length) {
|
|
9298
|
+
return false;
|
|
9299
|
+
}
|
|
9300
|
+
for (const key of leftKeys) {
|
|
9301
|
+
if (left[key] !== right[key]) {
|
|
9302
|
+
return false;
|
|
9217
9303
|
}
|
|
9218
|
-
});
|
|
9219
|
-
return LiveObject._fromItems(nodes, pool);
|
|
9220
|
-
}
|
|
9221
|
-
function deserialize(node, parentToChildren, pool) {
|
|
9222
|
-
if (isObjectStorageNode(node)) {
|
|
9223
|
-
return LiveObject._deserialize(node, parentToChildren, pool);
|
|
9224
|
-
} else if (isListStorageNode(node)) {
|
|
9225
|
-
return LiveList._deserialize(node, parentToChildren, pool);
|
|
9226
|
-
} else if (isMapStorageNode(node)) {
|
|
9227
|
-
return LiveMap._deserialize(node, parentToChildren, pool);
|
|
9228
|
-
} else if (isRegisterStorageNode(node)) {
|
|
9229
|
-
return LiveRegister._deserialize(node, parentToChildren, pool);
|
|
9230
|
-
} else if (isFileStorageNode(node)) {
|
|
9231
|
-
return LiveFile._deserialize(node, parentToChildren, pool);
|
|
9232
|
-
} else {
|
|
9233
|
-
throw new Error("Unexpected CRDT type");
|
|
9234
9304
|
}
|
|
9305
|
+
return true;
|
|
9235
9306
|
}
|
|
9236
|
-
function
|
|
9237
|
-
|
|
9238
|
-
|
|
9239
|
-
|
|
9240
|
-
|
|
9241
|
-
|
|
9242
|
-
|
|
9243
|
-
|
|
9244
|
-
|
|
9245
|
-
|
|
9246
|
-
|
|
9247
|
-
|
|
9248
|
-
|
|
9307
|
+
function cloneAttributes(attributes) {
|
|
9308
|
+
return attributes === void 0 ? void 0 : freeze({ ...attributes });
|
|
9309
|
+
}
|
|
9310
|
+
function normalizeSegments(segments) {
|
|
9311
|
+
const normalized = [];
|
|
9312
|
+
for (const segment of segments) {
|
|
9313
|
+
if (segment.text.length === 0) {
|
|
9314
|
+
continue;
|
|
9315
|
+
}
|
|
9316
|
+
const last = normalized.at(-1);
|
|
9317
|
+
const attributes = cloneAttributes(segment.attributes);
|
|
9318
|
+
if (last !== void 0 && attributesEqual(last.attributes, attributes)) {
|
|
9319
|
+
last.text += segment.text;
|
|
9320
|
+
} else {
|
|
9321
|
+
normalized.push({ text: segment.text, attributes });
|
|
9322
|
+
}
|
|
9249
9323
|
}
|
|
9324
|
+
return normalized;
|
|
9250
9325
|
}
|
|
9251
|
-
function
|
|
9252
|
-
return
|
|
9326
|
+
function dataToSegments(data) {
|
|
9327
|
+
return normalizeSegments(
|
|
9328
|
+
data.map(([text, attributes]) => ({
|
|
9329
|
+
text,
|
|
9330
|
+
attributes
|
|
9331
|
+
}))
|
|
9332
|
+
);
|
|
9253
9333
|
}
|
|
9254
|
-
function
|
|
9255
|
-
return
|
|
9334
|
+
function segmentsToData(segments) {
|
|
9335
|
+
return segments.map(
|
|
9336
|
+
(segment) => segment.attributes === void 0 ? [segment.text] : [segment.text, { ...segment.attributes }]
|
|
9337
|
+
);
|
|
9256
9338
|
}
|
|
9257
|
-
function
|
|
9258
|
-
return
|
|
9339
|
+
function textLength(segments) {
|
|
9340
|
+
return segments.reduce((sum, segment) => sum + segment.text.length, 0);
|
|
9259
9341
|
}
|
|
9260
|
-
function
|
|
9261
|
-
|
|
9342
|
+
function splitSegmentsAt(segments, index) {
|
|
9343
|
+
const result = [];
|
|
9344
|
+
let offset = 0;
|
|
9345
|
+
for (const segment of segments) {
|
|
9346
|
+
const end = offset + segment.text.length;
|
|
9347
|
+
if (index > offset && index < end) {
|
|
9348
|
+
const before2 = segment.text.slice(0, index - offset);
|
|
9349
|
+
const after2 = segment.text.slice(index - offset);
|
|
9350
|
+
result.push({ text: before2, attributes: segment.attributes });
|
|
9351
|
+
result.push({ text: after2, attributes: segment.attributes });
|
|
9352
|
+
} else {
|
|
9353
|
+
result.push({ text: segment.text, attributes: segment.attributes });
|
|
9354
|
+
}
|
|
9355
|
+
offset = end;
|
|
9356
|
+
}
|
|
9357
|
+
return result;
|
|
9262
9358
|
}
|
|
9263
|
-
function
|
|
9264
|
-
|
|
9359
|
+
function clipRange(index, length, contentLength) {
|
|
9360
|
+
const clippedIndex = Math.max(0, Math.min(index, contentLength));
|
|
9361
|
+
const clippedEnd = Math.max(
|
|
9362
|
+
clippedIndex,
|
|
9363
|
+
Math.min(index + length, contentLength)
|
|
9364
|
+
);
|
|
9365
|
+
return { index: clippedIndex, length: clippedEnd - clippedIndex };
|
|
9265
9366
|
}
|
|
9266
|
-
function
|
|
9267
|
-
|
|
9367
|
+
function isInSurrogatePair(text, index) {
|
|
9368
|
+
const previous = text.charCodeAt(index - 1);
|
|
9369
|
+
const next = text.charCodeAt(index);
|
|
9370
|
+
return previous >= 55296 && previous <= 56319 && next >= 56320 && next <= 57343;
|
|
9268
9371
|
}
|
|
9269
|
-
function
|
|
9270
|
-
|
|
9372
|
+
function clipIndexToCodePointBoundary(text, index) {
|
|
9373
|
+
const clippedIndex = Math.max(0, Math.min(index, text.length));
|
|
9374
|
+
return isInSurrogatePair(text, clippedIndex) ? clippedIndex - 1 : clippedIndex;
|
|
9271
9375
|
}
|
|
9272
|
-
function
|
|
9273
|
-
|
|
9376
|
+
function clipRangeToCodePointBoundaries(text, index, length) {
|
|
9377
|
+
const clipped = clipRange(index, length, text.length);
|
|
9378
|
+
if (clipped.length === 0) {
|
|
9379
|
+
return {
|
|
9380
|
+
index: clipIndexToCodePointBoundary(text, clipped.index),
|
|
9381
|
+
length: 0
|
|
9382
|
+
};
|
|
9383
|
+
}
|
|
9384
|
+
const clippedEnd = clipped.index + clipped.length;
|
|
9385
|
+
const normalizedIndex = isInSurrogatePair(text, clipped.index) ? clipped.index - 1 : clipped.index;
|
|
9386
|
+
const normalizedEnd = isInSurrogatePair(text, clippedEnd) ? clippedEnd + 1 : clippedEnd;
|
|
9387
|
+
return {
|
|
9388
|
+
index: normalizedIndex,
|
|
9389
|
+
length: normalizedEnd - normalizedIndex
|
|
9390
|
+
};
|
|
9274
9391
|
}
|
|
9275
|
-
function
|
|
9276
|
-
if (
|
|
9277
|
-
return
|
|
9278
|
-
}
|
|
9279
|
-
|
|
9280
|
-
|
|
9281
|
-
|
|
9392
|
+
function applyInsert(segments, index, text, attributes) {
|
|
9393
|
+
if (text.length === 0) {
|
|
9394
|
+
return normalizeSegments(segments);
|
|
9395
|
+
}
|
|
9396
|
+
const split = splitSegmentsAt(segments, index);
|
|
9397
|
+
const result = [];
|
|
9398
|
+
let offset = 0;
|
|
9399
|
+
let inserted = false;
|
|
9400
|
+
for (const segment of split) {
|
|
9401
|
+
if (!inserted && offset === index) {
|
|
9402
|
+
result.push({ text, attributes });
|
|
9403
|
+
inserted = true;
|
|
9404
|
+
}
|
|
9405
|
+
result.push(segment);
|
|
9406
|
+
offset += segment.text.length;
|
|
9282
9407
|
}
|
|
9408
|
+
if (!inserted) {
|
|
9409
|
+
result.push({ text, attributes });
|
|
9410
|
+
}
|
|
9411
|
+
return normalizeSegments(result);
|
|
9283
9412
|
}
|
|
9284
|
-
function
|
|
9285
|
-
|
|
9286
|
-
|
|
9287
|
-
|
|
9288
|
-
|
|
9413
|
+
function extractDeletedSegments(segments, index, length) {
|
|
9414
|
+
const split = splitSegmentsAt(
|
|
9415
|
+
splitSegmentsAt(segments, index),
|
|
9416
|
+
index + length
|
|
9417
|
+
);
|
|
9418
|
+
const deleted = [];
|
|
9419
|
+
let offset = 0;
|
|
9420
|
+
for (const segment of split) {
|
|
9421
|
+
const end = offset + segment.text.length;
|
|
9422
|
+
if (offset >= index && end <= index + length) {
|
|
9423
|
+
deleted.push({
|
|
9424
|
+
text: segment.text,
|
|
9425
|
+
attributes: segment.attributes
|
|
9426
|
+
});
|
|
9427
|
+
}
|
|
9428
|
+
offset = end;
|
|
9289
9429
|
}
|
|
9430
|
+
return normalizeSegments(deleted);
|
|
9290
9431
|
}
|
|
9291
|
-
function
|
|
9292
|
-
const
|
|
9293
|
-
|
|
9294
|
-
|
|
9295
|
-
|
|
9296
|
-
|
|
9297
|
-
|
|
9298
|
-
|
|
9299
|
-
|
|
9300
|
-
|
|
9301
|
-
|
|
9302
|
-
|
|
9303
|
-
|
|
9432
|
+
function applyDelete(segments, index, length) {
|
|
9433
|
+
const deletedSegments = extractDeletedSegments(segments, index, length);
|
|
9434
|
+
const split = splitSegmentsAt(
|
|
9435
|
+
splitSegmentsAt(segments, index),
|
|
9436
|
+
index + length
|
|
9437
|
+
);
|
|
9438
|
+
const result = [];
|
|
9439
|
+
let offset = 0;
|
|
9440
|
+
let deletedText = "";
|
|
9441
|
+
for (const segment of split) {
|
|
9442
|
+
const end = offset + segment.text.length;
|
|
9443
|
+
if (offset >= index && end <= index + length) {
|
|
9444
|
+
deletedText += segment.text;
|
|
9304
9445
|
} else {
|
|
9305
|
-
|
|
9446
|
+
result.push(segment);
|
|
9306
9447
|
}
|
|
9307
|
-
|
|
9308
|
-
});
|
|
9309
|
-
rows.sort((a, b) => {
|
|
9310
|
-
if (a.parentId !== b.parentId) return a.parentId < b.parentId ? -1 : 1;
|
|
9311
|
-
if (a.key !== b.key) return a.key < b.key ? -1 : 1;
|
|
9312
|
-
return 0;
|
|
9313
|
-
});
|
|
9314
|
-
return rows.map(
|
|
9315
|
-
(r) => ` ${r.id} parent=${r.parentId} key=${r.key || "\u2014"} ${r.value}`
|
|
9316
|
-
).join("\n");
|
|
9317
|
-
}
|
|
9318
|
-
function isJsonEq(a, b) {
|
|
9319
|
-
if (a === b) {
|
|
9320
|
-
return true;
|
|
9448
|
+
offset = end;
|
|
9321
9449
|
}
|
|
9322
|
-
|
|
9323
|
-
|
|
9324
|
-
|
|
9325
|
-
|
|
9326
|
-
|
|
9327
|
-
|
|
9328
|
-
|
|
9329
|
-
|
|
9330
|
-
|
|
9331
|
-
|
|
9450
|
+
return {
|
|
9451
|
+
segments: normalizeSegments(result),
|
|
9452
|
+
deletedText,
|
|
9453
|
+
deletedSegments
|
|
9454
|
+
};
|
|
9455
|
+
}
|
|
9456
|
+
function applyFormat(segments, index, length, attributes) {
|
|
9457
|
+
const split = splitSegmentsAt(
|
|
9458
|
+
splitSegmentsAt(segments, index),
|
|
9459
|
+
index + length
|
|
9460
|
+
);
|
|
9461
|
+
const result = [];
|
|
9462
|
+
let offset = 0;
|
|
9463
|
+
for (const segment of split) {
|
|
9464
|
+
const end = offset + segment.text.length;
|
|
9465
|
+
if (offset >= index && end <= index + length) {
|
|
9466
|
+
const nextAttributes = {
|
|
9467
|
+
...segment.attributes ?? {}
|
|
9468
|
+
};
|
|
9469
|
+
for (const [key, value] of Object.entries(attributes)) {
|
|
9470
|
+
if (value === null) {
|
|
9471
|
+
delete nextAttributes[key];
|
|
9472
|
+
} else {
|
|
9473
|
+
nextAttributes[key] = value;
|
|
9474
|
+
}
|
|
9332
9475
|
}
|
|
9476
|
+
result.push({
|
|
9477
|
+
text: segment.text,
|
|
9478
|
+
attributes: Object.keys(nextAttributes).length === 0 ? void 0 : freeze(nextAttributes)
|
|
9479
|
+
});
|
|
9480
|
+
} else {
|
|
9481
|
+
result.push(segment);
|
|
9333
9482
|
}
|
|
9334
|
-
|
|
9483
|
+
offset = end;
|
|
9335
9484
|
}
|
|
9336
|
-
|
|
9337
|
-
|
|
9338
|
-
|
|
9485
|
+
return normalizeSegments(result);
|
|
9486
|
+
}
|
|
9487
|
+
function formatReverseOperations(segments, index, length, patch) {
|
|
9488
|
+
const split = splitSegmentsAt(
|
|
9489
|
+
splitSegmentsAt(segments, index),
|
|
9490
|
+
index + length
|
|
9491
|
+
);
|
|
9492
|
+
const result = [];
|
|
9493
|
+
let offset = 0;
|
|
9494
|
+
for (const segment of split) {
|
|
9495
|
+
const end = offset + segment.text.length;
|
|
9496
|
+
if (offset >= index && end <= index + length) {
|
|
9497
|
+
const attributes = {};
|
|
9498
|
+
const current = segment.attributes ?? {};
|
|
9499
|
+
for (const key of Object.keys(patch)) {
|
|
9500
|
+
const value = Object.hasOwn(current, key) ? current[key] : void 0;
|
|
9501
|
+
attributes[key] = value ?? null;
|
|
9502
|
+
}
|
|
9503
|
+
result.push({
|
|
9504
|
+
type: "format",
|
|
9505
|
+
index: offset,
|
|
9506
|
+
length: segment.text.length,
|
|
9507
|
+
attributes
|
|
9508
|
+
});
|
|
9509
|
+
}
|
|
9510
|
+
offset = end;
|
|
9339
9511
|
}
|
|
9340
|
-
|
|
9341
|
-
|
|
9342
|
-
|
|
9512
|
+
return result;
|
|
9513
|
+
}
|
|
9514
|
+
function mapIndexThroughOperation(index, op) {
|
|
9515
|
+
if (op.type === "insert") {
|
|
9516
|
+
return op.index <= index ? index + op.text.length : index;
|
|
9517
|
+
} else if (op.type === "delete") {
|
|
9518
|
+
if (op.index >= index) {
|
|
9519
|
+
return index;
|
|
9343
9520
|
}
|
|
9521
|
+
return Math.max(op.index, index - op.length);
|
|
9522
|
+
} else {
|
|
9523
|
+
return index;
|
|
9344
9524
|
}
|
|
9345
|
-
return true;
|
|
9346
9525
|
}
|
|
9347
|
-
function
|
|
9348
|
-
|
|
9349
|
-
const
|
|
9350
|
-
|
|
9351
|
-
|
|
9352
|
-
|
|
9353
|
-
|
|
9526
|
+
function mapTextIndexThroughOperations(index, ops) {
|
|
9527
|
+
let mapped = index;
|
|
9528
|
+
for (const op of ops) {
|
|
9529
|
+
mapped = mapIndexThroughOperation(mapped, op);
|
|
9530
|
+
}
|
|
9531
|
+
return mapped;
|
|
9532
|
+
}
|
|
9533
|
+
function inverseMapIndexThroughOperation(index, op) {
|
|
9534
|
+
if (op.type === "insert") {
|
|
9535
|
+
if (index <= op.index) {
|
|
9536
|
+
return index;
|
|
9354
9537
|
}
|
|
9355
|
-
|
|
9356
|
-
|
|
9538
|
+
return Math.max(op.index, index - op.text.length);
|
|
9539
|
+
} else if (op.type === "delete") {
|
|
9540
|
+
return op.index <= index ? index + op.length : index;
|
|
9541
|
+
} else {
|
|
9542
|
+
return index;
|
|
9543
|
+
}
|
|
9544
|
+
}
|
|
9545
|
+
function inverseMapTextIndexThroughOperations(index, ops) {
|
|
9546
|
+
let mapped = index;
|
|
9547
|
+
for (let i = ops.length - 1; i >= 0; i--) {
|
|
9548
|
+
mapped = inverseMapIndexThroughOperation(mapped, ops[i]);
|
|
9549
|
+
}
|
|
9550
|
+
return mapped;
|
|
9551
|
+
}
|
|
9552
|
+
function oppositeOrder(order) {
|
|
9553
|
+
return order === "before" ? "after" : "before";
|
|
9554
|
+
}
|
|
9555
|
+
function mapIndexOverDelete(index, deleteIndex, deleteLength) {
|
|
9556
|
+
if (deleteIndex >= index) {
|
|
9557
|
+
return index;
|
|
9558
|
+
}
|
|
9559
|
+
return Math.max(deleteIndex, index - deleteLength);
|
|
9560
|
+
}
|
|
9561
|
+
function transformInsert(op, over, order) {
|
|
9562
|
+
if (over.type === "insert") {
|
|
9563
|
+
const shifts = over.index < op.index || over.index === op.index && order === "after";
|
|
9564
|
+
return [shifts ? { ...op, index: op.index + over.text.length } : { ...op }];
|
|
9565
|
+
} else if (over.type === "delete") {
|
|
9566
|
+
return [
|
|
9567
|
+
{ ...op, index: mapIndexOverDelete(op.index, over.index, over.length) }
|
|
9568
|
+
];
|
|
9569
|
+
} else {
|
|
9570
|
+
return [{ ...op }];
|
|
9571
|
+
}
|
|
9572
|
+
}
|
|
9573
|
+
function transformDelete(op, over) {
|
|
9574
|
+
const start = op.index;
|
|
9575
|
+
const end = op.index + op.length;
|
|
9576
|
+
if (over.type === "insert") {
|
|
9577
|
+
const at = over.index;
|
|
9578
|
+
const len = over.text.length;
|
|
9579
|
+
if (at <= start) {
|
|
9580
|
+
return [{ ...op, index: start + len }];
|
|
9581
|
+
}
|
|
9582
|
+
if (at >= end) {
|
|
9583
|
+
return [{ ...op }];
|
|
9584
|
+
}
|
|
9585
|
+
return [
|
|
9586
|
+
{ type: "delete", index: start, length: at - start },
|
|
9587
|
+
{ type: "delete", index: start + len, length: end - at }
|
|
9588
|
+
];
|
|
9589
|
+
} else if (over.type === "delete") {
|
|
9590
|
+
const newStart = mapIndexOverDelete(start, over.index, over.length);
|
|
9591
|
+
const newEnd = mapIndexOverDelete(end, over.index, over.length);
|
|
9592
|
+
return newEnd - newStart > 0 ? [{ type: "delete", index: newStart, length: newEnd - newStart }] : [];
|
|
9593
|
+
} else {
|
|
9594
|
+
return [{ ...op }];
|
|
9595
|
+
}
|
|
9596
|
+
}
|
|
9597
|
+
function transformFormat(op, over, order) {
|
|
9598
|
+
const start = op.index;
|
|
9599
|
+
const end = op.index + op.length;
|
|
9600
|
+
if (over.type === "insert") {
|
|
9601
|
+
const at = over.index;
|
|
9602
|
+
const len = over.text.length;
|
|
9603
|
+
if (at <= start) {
|
|
9604
|
+
return [{ ...op, index: start + len }];
|
|
9605
|
+
}
|
|
9606
|
+
if (at >= end) {
|
|
9607
|
+
return [{ ...op }];
|
|
9608
|
+
}
|
|
9609
|
+
return [
|
|
9610
|
+
{
|
|
9611
|
+
type: "format",
|
|
9612
|
+
index: start,
|
|
9613
|
+
length: at - start,
|
|
9614
|
+
attributes: op.attributes
|
|
9615
|
+
},
|
|
9616
|
+
{
|
|
9617
|
+
type: "format",
|
|
9618
|
+
index: at + len,
|
|
9619
|
+
length: end - at,
|
|
9620
|
+
attributes: op.attributes
|
|
9621
|
+
}
|
|
9622
|
+
];
|
|
9623
|
+
} else if (over.type === "delete") {
|
|
9624
|
+
const newStart = mapIndexOverDelete(start, over.index, over.length);
|
|
9625
|
+
const newEnd = mapIndexOverDelete(end, over.index, over.length);
|
|
9626
|
+
return newEnd - newStart > 0 ? [
|
|
9627
|
+
{
|
|
9628
|
+
type: "format",
|
|
9629
|
+
index: newStart,
|
|
9630
|
+
length: newEnd - newStart,
|
|
9631
|
+
attributes: op.attributes
|
|
9632
|
+
}
|
|
9633
|
+
] : [];
|
|
9634
|
+
} else {
|
|
9635
|
+
if (order === "after") {
|
|
9636
|
+
return [{ ...op }];
|
|
9637
|
+
}
|
|
9638
|
+
const overlapStart = Math.max(start, over.index);
|
|
9639
|
+
const overlapEnd = Math.min(end, over.index + over.length);
|
|
9640
|
+
if (overlapStart >= overlapEnd) {
|
|
9641
|
+
return [{ ...op }];
|
|
9642
|
+
}
|
|
9643
|
+
const hasConflict = Object.keys(op.attributes).some(
|
|
9644
|
+
(key) => Object.hasOwn(over.attributes, key)
|
|
9645
|
+
);
|
|
9646
|
+
if (!hasConflict) {
|
|
9647
|
+
return [{ ...op }];
|
|
9648
|
+
}
|
|
9649
|
+
const reduced = {};
|
|
9650
|
+
for (const [key, value] of Object.entries(op.attributes)) {
|
|
9651
|
+
if (!Object.hasOwn(over.attributes, key)) {
|
|
9652
|
+
reduced[key] = value;
|
|
9653
|
+
}
|
|
9654
|
+
}
|
|
9655
|
+
const pieces = [];
|
|
9656
|
+
if (start < overlapStart) {
|
|
9657
|
+
pieces.push({
|
|
9658
|
+
type: "format",
|
|
9659
|
+
index: start,
|
|
9660
|
+
length: overlapStart - start,
|
|
9661
|
+
attributes: op.attributes
|
|
9662
|
+
});
|
|
9663
|
+
}
|
|
9664
|
+
if (Object.keys(reduced).length > 0) {
|
|
9665
|
+
pieces.push({
|
|
9666
|
+
type: "format",
|
|
9667
|
+
index: overlapStart,
|
|
9668
|
+
length: overlapEnd - overlapStart,
|
|
9669
|
+
attributes: reduced
|
|
9670
|
+
});
|
|
9671
|
+
}
|
|
9672
|
+
if (overlapEnd < end) {
|
|
9673
|
+
pieces.push({
|
|
9674
|
+
type: "format",
|
|
9675
|
+
index: overlapEnd,
|
|
9676
|
+
length: end - overlapEnd,
|
|
9677
|
+
attributes: op.attributes
|
|
9678
|
+
});
|
|
9679
|
+
}
|
|
9680
|
+
return pieces;
|
|
9681
|
+
}
|
|
9682
|
+
}
|
|
9683
|
+
function transformSingle(op, over, order) {
|
|
9684
|
+
switch (op.type) {
|
|
9685
|
+
case "insert":
|
|
9686
|
+
return transformInsert(op, over, order);
|
|
9687
|
+
case "delete":
|
|
9688
|
+
return transformDelete(op, over);
|
|
9689
|
+
case "format":
|
|
9690
|
+
return transformFormat(op, over, order);
|
|
9691
|
+
}
|
|
9692
|
+
}
|
|
9693
|
+
function transformTextOperationsX(a, b, order) {
|
|
9694
|
+
if (a.length === 0 || b.length === 0) {
|
|
9695
|
+
return [[...a], [...b]];
|
|
9696
|
+
}
|
|
9697
|
+
if (a.length === 1 && b.length === 1) {
|
|
9698
|
+
return [
|
|
9699
|
+
transformSingle(a[0], b[0], order),
|
|
9700
|
+
transformSingle(b[0], a[0], oppositeOrder(order))
|
|
9701
|
+
];
|
|
9702
|
+
}
|
|
9703
|
+
if (a.length > 1) {
|
|
9704
|
+
const [headA1, b1] = transformTextOperationsX([a[0]], b, order);
|
|
9705
|
+
const [restA1, b2] = transformTextOperationsX(a.slice(1), b1, order);
|
|
9706
|
+
return [[...headA1, ...restA1], b2];
|
|
9707
|
+
}
|
|
9708
|
+
const [a1, headB1] = transformTextOperationsX(a, [b[0]], order);
|
|
9709
|
+
const [a2, restB1] = transformTextOperationsX(a1, b.slice(1), order);
|
|
9710
|
+
return [a2, [...headB1, ...restB1]];
|
|
9711
|
+
}
|
|
9712
|
+
function transformTextOperations(ops, over, order) {
|
|
9713
|
+
return transformTextOperationsX(ops, over, order)[0];
|
|
9714
|
+
}
|
|
9715
|
+
function textOperationsEqual(a, b) {
|
|
9716
|
+
return a === b || stableStringify(a) === stableStringify(b);
|
|
9717
|
+
}
|
|
9718
|
+
function applyTextOperationsToSegments(segments, ops) {
|
|
9719
|
+
let next = [...segments];
|
|
9720
|
+
for (const op of ops) {
|
|
9721
|
+
if (op.type === "insert") {
|
|
9722
|
+
const index = Math.max(0, Math.min(op.index, textLength(next)));
|
|
9723
|
+
next = applyInsert(next, index, op.text, op.attributes);
|
|
9724
|
+
} else if (op.type === "delete") {
|
|
9725
|
+
const index = Math.max(0, Math.min(op.index, textLength(next)));
|
|
9726
|
+
const clipped = clipRange(index, op.length, textLength(next));
|
|
9727
|
+
next = applyDelete(next, clipped.index, clipped.length).segments;
|
|
9728
|
+
} else {
|
|
9729
|
+
const index = Math.max(0, Math.min(op.index, textLength(next)));
|
|
9730
|
+
const clipped = clipRange(index, op.length, textLength(next));
|
|
9731
|
+
next = applyFormat(next, clipped.index, clipped.length, op.attributes);
|
|
9732
|
+
}
|
|
9733
|
+
}
|
|
9734
|
+
return next;
|
|
9735
|
+
}
|
|
9736
|
+
function applyLiveTextOperations(data, ops) {
|
|
9737
|
+
return segmentsToData(
|
|
9738
|
+
applyTextOperationsToSegments(dataToSegments(data), ops)
|
|
9739
|
+
);
|
|
9740
|
+
}
|
|
9741
|
+
function normalizeLiveTextOperations(data, operations) {
|
|
9742
|
+
let shadow = dataToSegments(data);
|
|
9743
|
+
const normalized = [];
|
|
9744
|
+
for (const operation of operations) {
|
|
9745
|
+
const text = shadow.map((segment) => segment.text).join("");
|
|
9746
|
+
let normalizedOperation;
|
|
9747
|
+
if (operation.type === "insert") {
|
|
9748
|
+
normalizedOperation = {
|
|
9749
|
+
...operation,
|
|
9750
|
+
index: clipIndexToCodePointBoundary(text, operation.index)
|
|
9751
|
+
};
|
|
9752
|
+
} else {
|
|
9753
|
+
const range = clipRangeToCodePointBoundaries(
|
|
9754
|
+
text,
|
|
9755
|
+
operation.index,
|
|
9756
|
+
operation.length
|
|
9757
|
+
);
|
|
9758
|
+
normalizedOperation = {
|
|
9759
|
+
...operation,
|
|
9760
|
+
index: range.index,
|
|
9761
|
+
length: range.length
|
|
9762
|
+
};
|
|
9763
|
+
}
|
|
9764
|
+
normalized.push(normalizedOperation);
|
|
9765
|
+
shadow = applyTextOperationsToSegments(shadow, [normalizedOperation]);
|
|
9766
|
+
}
|
|
9767
|
+
return normalized;
|
|
9768
|
+
}
|
|
9769
|
+
function invertTextOperations(segments, ops) {
|
|
9770
|
+
let shadow = [...segments];
|
|
9771
|
+
const reverse = [];
|
|
9772
|
+
for (const op of ops) {
|
|
9773
|
+
if (op.type === "insert") {
|
|
9774
|
+
shadow = applyInsert(shadow, op.index, op.text, op.attributes);
|
|
9775
|
+
reverse.unshift({
|
|
9776
|
+
type: "delete",
|
|
9777
|
+
index: op.index,
|
|
9778
|
+
length: op.text.length
|
|
9779
|
+
});
|
|
9780
|
+
} else if (op.type === "delete") {
|
|
9781
|
+
const deletedSegments = extractDeletedSegments(
|
|
9782
|
+
shadow,
|
|
9783
|
+
op.index,
|
|
9784
|
+
op.length
|
|
9785
|
+
);
|
|
9786
|
+
shadow = applyDelete(shadow, op.index, op.length).segments;
|
|
9787
|
+
const inserts = [];
|
|
9788
|
+
let insertIndex = op.index;
|
|
9789
|
+
for (const segment of deletedSegments) {
|
|
9790
|
+
inserts.push({
|
|
9791
|
+
type: "insert",
|
|
9792
|
+
index: insertIndex,
|
|
9793
|
+
text: segment.text,
|
|
9794
|
+
attributes: segment.attributes
|
|
9795
|
+
});
|
|
9796
|
+
insertIndex += segment.text.length;
|
|
9797
|
+
}
|
|
9798
|
+
for (let index = inserts.length - 1; index >= 0; index--) {
|
|
9799
|
+
reverse.unshift(inserts[index]);
|
|
9800
|
+
}
|
|
9801
|
+
} else {
|
|
9802
|
+
const inverse = formatReverseOperations(
|
|
9803
|
+
shadow,
|
|
9804
|
+
op.index,
|
|
9805
|
+
op.length,
|
|
9806
|
+
op.attributes
|
|
9807
|
+
);
|
|
9808
|
+
shadow = applyFormat(shadow, op.index, op.length, op.attributes);
|
|
9809
|
+
reverse.unshift(...inverse.reverse());
|
|
9810
|
+
}
|
|
9811
|
+
}
|
|
9812
|
+
return reverse;
|
|
9813
|
+
}
|
|
9814
|
+
|
|
9815
|
+
// src/crdts/LiveText.ts
|
|
9816
|
+
var ACCEPTED_OPS_HISTORY_LIMIT = 1e3;
|
|
9817
|
+
var LiveText = class _LiveText extends AbstractCrdt {
|
|
9818
|
+
/** The local document: #confirmed ⊕ #inFlightOps ⊕ #queuedOps. */
|
|
9819
|
+
#segments;
|
|
9820
|
+
/** The server-confirmed document (only authoritative ops applied). */
|
|
9821
|
+
#confirmed;
|
|
9822
|
+
#version;
|
|
9823
|
+
/** The op currently awaiting server acknowledgement (at most one). */
|
|
9824
|
+
#inFlightOpId;
|
|
9825
|
+
/** Its ops, continuously re-expressed against current server state. */
|
|
9826
|
+
#inFlightOps = [];
|
|
9827
|
+
/** Local edits made while an op is in flight; sent after the ack. */
|
|
9828
|
+
#queuedOps = [];
|
|
9829
|
+
#acceptedOps = [];
|
|
9830
|
+
/**
|
|
9831
|
+
* Creates a new LiveText document.
|
|
9832
|
+
*
|
|
9833
|
+
* @param textOrData Initial plain text, or an array of `[text]` /
|
|
9834
|
+
* `[text, attributes]` segments. Defaults to an empty document.
|
|
9835
|
+
*
|
|
9836
|
+
* @example
|
|
9837
|
+
* new LiveText();
|
|
9838
|
+
* new LiveText("Hello world");
|
|
9839
|
+
* new LiveText([["Hello ", { bold: true }], ["world"]]);
|
|
9840
|
+
*/
|
|
9841
|
+
constructor(textOrData = "", version = 0) {
|
|
9842
|
+
super();
|
|
9843
|
+
this.#segments = typeof textOrData === "string" ? textOrData.length === 0 ? [] : [{ text: textOrData }] : dataToSegments(textOrData);
|
|
9844
|
+
this.#confirmed = [...this.#segments];
|
|
9845
|
+
this.#version = version;
|
|
9846
|
+
Object.assign(this[kInternal], {
|
|
9847
|
+
encodeIndex: (localIndex) => this.#encodeIndex(localIndex),
|
|
9848
|
+
decodeIndex: (index, fromVersion) => this.#decodeIndex(index, fromVersion)
|
|
9849
|
+
});
|
|
9850
|
+
}
|
|
9851
|
+
get version() {
|
|
9852
|
+
return this.#version;
|
|
9853
|
+
}
|
|
9854
|
+
get length() {
|
|
9855
|
+
return textLength(this.#segments);
|
|
9856
|
+
}
|
|
9857
|
+
/** @internal */
|
|
9858
|
+
static _deserialize([id, item], _parentToChildren, pool) {
|
|
9859
|
+
const text = new _LiveText(item.data, item.version);
|
|
9860
|
+
text._attach(id, pool);
|
|
9861
|
+
return text;
|
|
9862
|
+
}
|
|
9863
|
+
/** @internal */
|
|
9864
|
+
_toOps(parentId, parentKey) {
|
|
9865
|
+
if (this._id === void 0) {
|
|
9866
|
+
throw new Error("Cannot serialize LiveText if it is not attached");
|
|
9867
|
+
}
|
|
9868
|
+
return [
|
|
9869
|
+
{
|
|
9870
|
+
type: OpCode.CREATE_TEXT,
|
|
9871
|
+
id: this._id,
|
|
9872
|
+
parentId,
|
|
9873
|
+
parentKey,
|
|
9874
|
+
data: this.toJSON(),
|
|
9875
|
+
version: this.#version
|
|
9876
|
+
}
|
|
9877
|
+
];
|
|
9878
|
+
}
|
|
9879
|
+
/** @internal */
|
|
9880
|
+
_serialize() {
|
|
9881
|
+
if (this.parent.type !== "HasParent") {
|
|
9882
|
+
throw new Error("Cannot serialize LiveText if parent is missing");
|
|
9883
|
+
}
|
|
9884
|
+
return {
|
|
9885
|
+
type: CrdtType.TEXT,
|
|
9886
|
+
parentId: nn(this.parent.node._id, "Parent node expected to have ID"),
|
|
9887
|
+
parentKey: this.parent.key,
|
|
9888
|
+
data: this.toJSON(),
|
|
9889
|
+
version: this.#version
|
|
9890
|
+
};
|
|
9891
|
+
}
|
|
9892
|
+
/** @internal */
|
|
9893
|
+
_attachChild(_op) {
|
|
9894
|
+
throw new Error("LiveText cannot contain child nodes");
|
|
9895
|
+
}
|
|
9896
|
+
/** @internal */
|
|
9897
|
+
_detachChild(_crdt) {
|
|
9898
|
+
throw new Error("LiveText cannot contain child nodes");
|
|
9899
|
+
}
|
|
9900
|
+
/** @internal */
|
|
9901
|
+
_apply(op, source) {
|
|
9902
|
+
if (op.type !== OpCode.UPDATE_TEXT) {
|
|
9903
|
+
return super._apply(op, source);
|
|
9904
|
+
}
|
|
9905
|
+
if (source.origin === "local" && source.optimistic) {
|
|
9906
|
+
return this.#applyLocal(op, toUpdateSource(source));
|
|
9907
|
+
}
|
|
9908
|
+
if (op.opId !== void 0 && op.opId === this.#inFlightOpId) {
|
|
9909
|
+
return this.#applyAck(op, toUpdateSource(source));
|
|
9910
|
+
}
|
|
9911
|
+
if (op.opId !== void 0 && this.#acceptedOps.some((entry) => entry.opId === op.opId)) {
|
|
9912
|
+
this.#version = Math.max(this.#version, op.version ?? op.baseVersion + 1);
|
|
9913
|
+
return { modified: false };
|
|
9914
|
+
}
|
|
9915
|
+
return this.#applyRemote(op, toUpdateSource(source));
|
|
9916
|
+
}
|
|
9917
|
+
/**
|
|
9918
|
+
* Inserts text at the given index.
|
|
9919
|
+
*
|
|
9920
|
+
* @param index Character index at which to insert. Values outside the
|
|
9921
|
+
* document range are clipped.
|
|
9922
|
+
* @param text Text to insert.
|
|
9923
|
+
* @param attributes Optional inline attributes for the inserted text.
|
|
9924
|
+
*
|
|
9925
|
+
* @example
|
|
9926
|
+
* const text = new LiveText("Hello");
|
|
9927
|
+
* text.insert(5, " world");
|
|
9928
|
+
* text.insert(0, "Say: ", { italic: true });
|
|
9929
|
+
*/
|
|
9930
|
+
insert(index, text, attributes) {
|
|
9931
|
+
const clippedIndex = clipIndexToCodePointBoundary(this.toString(), index);
|
|
9932
|
+
this.#dispatch([{ type: "insert", index: clippedIndex, text, attributes }]);
|
|
9933
|
+
}
|
|
9934
|
+
/**
|
|
9935
|
+
* Deletes `length` characters starting at `index`.
|
|
9936
|
+
*
|
|
9937
|
+
* @example
|
|
9938
|
+
* const text = new LiveText("Hello world");
|
|
9939
|
+
* text.delete(5, 6); // "Hello"
|
|
9940
|
+
*/
|
|
9941
|
+
delete(index, length) {
|
|
9942
|
+
const clipped = clipRangeToCodePointBoundaries(
|
|
9943
|
+
this.toString(),
|
|
9944
|
+
index,
|
|
9945
|
+
length
|
|
9946
|
+
);
|
|
9947
|
+
if (clipped.length === 0) {
|
|
9948
|
+
return;
|
|
9949
|
+
}
|
|
9950
|
+
this.#dispatch([
|
|
9951
|
+
{ type: "delete", index: clipped.index, length: clipped.length }
|
|
9952
|
+
]);
|
|
9953
|
+
}
|
|
9954
|
+
/**
|
|
9955
|
+
* Replaces a range of text with new text.
|
|
9956
|
+
*
|
|
9957
|
+
* @example
|
|
9958
|
+
* const text = new LiveText("Hello world");
|
|
9959
|
+
* text.replace(0, 5, "Hi"); // "Hi world"
|
|
9960
|
+
*/
|
|
9961
|
+
replace(index, length, text, attributes) {
|
|
9962
|
+
const clipped = clipRangeToCodePointBoundaries(
|
|
9963
|
+
this.toString(),
|
|
9964
|
+
index,
|
|
9965
|
+
length
|
|
9966
|
+
);
|
|
9967
|
+
const ops = [];
|
|
9968
|
+
if (clipped.length > 0) {
|
|
9969
|
+
ops.push({
|
|
9970
|
+
type: "delete",
|
|
9971
|
+
index: clipped.index,
|
|
9972
|
+
length: clipped.length
|
|
9973
|
+
});
|
|
9974
|
+
}
|
|
9975
|
+
if (text.length > 0) {
|
|
9976
|
+
ops.push({ type: "insert", index: clipped.index, text, attributes });
|
|
9977
|
+
}
|
|
9978
|
+
this.#dispatch(ops);
|
|
9979
|
+
}
|
|
9980
|
+
/**
|
|
9981
|
+
* Encode a local-document index (an offset into this LiveText's current
|
|
9982
|
+
* #segments, which CodeMirror or any consumer mirrors as its document)
|
|
9983
|
+
* into server-confirmed coordinates suitable for broadcasting to peers via
|
|
9984
|
+
* presence or any other side channel.
|
|
9985
|
+
*
|
|
9986
|
+
* The returned index is in this LiveText's current #confirmed coordinates
|
|
9987
|
+
* — that is, with this client's local pending ops inverse-mapped out.
|
|
9988
|
+
* Pair it with the current {@link LiveText.version} when sending so the
|
|
9989
|
+
* receiver can call {@link PrivateLiveTextApi.decodeIndex} to land the
|
|
9990
|
+
* position in their own local document coordinates regardless of their
|
|
9991
|
+
* private pending ops.
|
|
9992
|
+
*
|
|
9993
|
+
* Index ambiguity at boundaries is resolved by an inverse-of-forward
|
|
9994
|
+
* convention: a position at or before a local insertion is reported as
|
|
9995
|
+
* the position right before the insertion in #confirmed; a position past
|
|
9996
|
+
* the insertion shifts left by the insertion's length. Positions inside
|
|
9997
|
+
* an own-pending insertion collapse to the insertion point.
|
|
9998
|
+
*/
|
|
9999
|
+
#encodeIndex(localIndex) {
|
|
10000
|
+
let mapped = Math.max(0, Math.min(localIndex, this.length));
|
|
10001
|
+
mapped = inverseMapTextIndexThroughOperations(mapped, this.#queuedOps);
|
|
10002
|
+
mapped = inverseMapTextIndexThroughOperations(mapped, this.#inFlightOps);
|
|
10003
|
+
return mapped;
|
|
10004
|
+
}
|
|
10005
|
+
/**
|
|
10006
|
+
* Decode an `(index, fromVersion)` pair produced by
|
|
10007
|
+
* {@link PrivateLiveTextApi.encodeIndex} — typically on a peer — into an
|
|
10008
|
+
* offset in this LiveText's current local document (an index suitable for
|
|
10009
|
+
* placing a CodeMirror marker, an annotation anchor, or anything else that
|
|
10010
|
+
* lives over #segments).
|
|
10011
|
+
*
|
|
10012
|
+
* Composes the accepted ops applied since `fromVersion` (drawn from
|
|
10013
|
+
* #acceptedOps in locally-applied form) with this client's own local
|
|
10014
|
+
* pending ops, in that order. The result is in current #segments
|
|
10015
|
+
* coordinates.
|
|
10016
|
+
*
|
|
10017
|
+
* Returns `null` when the position cannot be decoded against the current
|
|
10018
|
+
* state:
|
|
10019
|
+
* - `fromVersion` is greater than this LiveText's current version: the
|
|
10020
|
+
* peer is ahead of us. The caller should park the message and retry
|
|
10021
|
+
* after more accepted ops arrive.
|
|
10022
|
+
* - `fromVersion` falls outside the retained accepted-ops history. This
|
|
10023
|
+
* only happens after very long-lived disconnections; the caller can
|
|
10024
|
+
* fall back to using the raw index and letting subsequent local
|
|
10025
|
+
* transactions map it (with bounded drift).
|
|
10026
|
+
*/
|
|
10027
|
+
#decodeIndex(index, fromVersion) {
|
|
10028
|
+
if (fromVersion > this.#version) {
|
|
10029
|
+
return null;
|
|
10030
|
+
}
|
|
10031
|
+
if (fromVersion < this.#version) {
|
|
10032
|
+
const oldest = this.#acceptedOps[0]?.version;
|
|
10033
|
+
if (oldest === void 0 || oldest > fromVersion + 1) {
|
|
10034
|
+
return null;
|
|
10035
|
+
}
|
|
10036
|
+
}
|
|
10037
|
+
let mapped = index;
|
|
10038
|
+
for (const entry of this.#acceptedOps) {
|
|
10039
|
+
if (entry.version <= fromVersion) continue;
|
|
10040
|
+
if (entry.version > this.#version) break;
|
|
10041
|
+
if (entry.ops.length === 0) continue;
|
|
10042
|
+
mapped = mapTextIndexThroughOperations(mapped, entry.ops);
|
|
10043
|
+
}
|
|
10044
|
+
mapped = mapTextIndexThroughOperations(mapped, this.#inFlightOps);
|
|
10045
|
+
mapped = mapTextIndexThroughOperations(mapped, this.#queuedOps);
|
|
10046
|
+
return Math.max(0, Math.min(mapped, this.length));
|
|
10047
|
+
}
|
|
10048
|
+
/**
|
|
10049
|
+
* Applies or removes inline attributes on a range of text.
|
|
10050
|
+
*
|
|
10051
|
+
* Set an attribute to `null` to remove it from the range.
|
|
10052
|
+
*
|
|
10053
|
+
* @example
|
|
10054
|
+
* const text = new LiveText("Hello world");
|
|
10055
|
+
* text.format(0, 5, { bold: true });
|
|
10056
|
+
* text.format(0, 5, { bold: null });
|
|
10057
|
+
*/
|
|
10058
|
+
format(index, length, attributes) {
|
|
10059
|
+
const clipped = clipRangeToCodePointBoundaries(
|
|
10060
|
+
this.toString(),
|
|
10061
|
+
index,
|
|
10062
|
+
length
|
|
10063
|
+
);
|
|
10064
|
+
if (clipped.length === 0) {
|
|
10065
|
+
return;
|
|
10066
|
+
}
|
|
10067
|
+
this.#dispatch([
|
|
10068
|
+
{
|
|
10069
|
+
type: "format",
|
|
10070
|
+
index: clipped.index,
|
|
10071
|
+
length: clipped.length,
|
|
10072
|
+
attributes
|
|
10073
|
+
}
|
|
10074
|
+
]);
|
|
10075
|
+
}
|
|
10076
|
+
/** Local edits made through the public API. */
|
|
10077
|
+
#dispatch(ops) {
|
|
10078
|
+
if (ops.length === 0) {
|
|
10079
|
+
return;
|
|
10080
|
+
}
|
|
10081
|
+
this._pool?.assertStorageIsWritable();
|
|
10082
|
+
const attached = this._pool !== void 0 && this._id !== void 0;
|
|
10083
|
+
const reverse = attached ? this.#invertOperations(ops) : [];
|
|
10084
|
+
const changes = this.#applyOperationsLocally(ops);
|
|
10085
|
+
if (!attached) {
|
|
10086
|
+
return;
|
|
10087
|
+
}
|
|
10088
|
+
const pool = nn(this._pool);
|
|
10089
|
+
const id = nn(this._id);
|
|
10090
|
+
const updates = /* @__PURE__ */ new Map([
|
|
10091
|
+
[
|
|
10092
|
+
id,
|
|
10093
|
+
{
|
|
10094
|
+
type: "LiveText",
|
|
10095
|
+
node: this,
|
|
10096
|
+
version: this.#version,
|
|
10097
|
+
updates: changes,
|
|
10098
|
+
source: LOCAL_EDIT
|
|
10099
|
+
}
|
|
10100
|
+
]
|
|
10101
|
+
]);
|
|
10102
|
+
if (this.#inFlightOpId === void 0) {
|
|
10103
|
+
const opId = pool.generateOpId();
|
|
10104
|
+
this.#inFlightOpId = opId;
|
|
10105
|
+
this.#inFlightOps = [...ops];
|
|
10106
|
+
pool.dispatch(
|
|
10107
|
+
[
|
|
10108
|
+
{
|
|
10109
|
+
type: OpCode.UPDATE_TEXT,
|
|
10110
|
+
id,
|
|
10111
|
+
opId,
|
|
10112
|
+
baseVersion: this.#version,
|
|
10113
|
+
ops: [...ops]
|
|
10114
|
+
}
|
|
10115
|
+
],
|
|
10116
|
+
reverse,
|
|
10117
|
+
updates
|
|
10118
|
+
);
|
|
10119
|
+
} else {
|
|
10120
|
+
this.#queuedOps.push(...ops);
|
|
10121
|
+
pool.dispatch([], reverse, updates, { clearRedoStack: true });
|
|
10122
|
+
}
|
|
10123
|
+
}
|
|
10124
|
+
/**
|
|
10125
|
+
* A local replay of an existing wire op: an undo/redo frame, or an
|
|
10126
|
+
* unacknowledged op re-sent after a reconnect.
|
|
10127
|
+
*/
|
|
10128
|
+
#applyLocal(op, source) {
|
|
10129
|
+
const mutableOp = op;
|
|
10130
|
+
if (op.opId !== void 0 && op.opId === this.#inFlightOpId) {
|
|
10131
|
+
this.#inFlightOps = [...this.#inFlightOps, ...this.#queuedOps];
|
|
10132
|
+
this.#queuedOps = [];
|
|
10133
|
+
mutableOp.baseVersion = this.#version;
|
|
10134
|
+
mutableOp.ops = [...this.#inFlightOps];
|
|
10135
|
+
return { modified: false };
|
|
10136
|
+
}
|
|
10137
|
+
let ops = op.ops;
|
|
10138
|
+
for (const entry of this.#acceptedOps) {
|
|
10139
|
+
if (entry.version > op.baseVersion && entry.ops.length > 0) {
|
|
10140
|
+
ops = transformTextOperations(ops, entry.ops, "after");
|
|
10141
|
+
}
|
|
10142
|
+
}
|
|
10143
|
+
const reverse = this.#invertOperations(ops);
|
|
10144
|
+
const changes = this.#applyOperationsLocally(ops);
|
|
10145
|
+
if (this.#inFlightOpId === void 0 && ops.length > 0) {
|
|
10146
|
+
this.#inFlightOpId = nn(op.opId, "Local ops must have an opId");
|
|
10147
|
+
this.#inFlightOps = [...ops];
|
|
10148
|
+
mutableOp.baseVersion = this.#version;
|
|
10149
|
+
mutableOp.ops = [...ops];
|
|
10150
|
+
} else {
|
|
10151
|
+
this.#queuedOps.push(...ops);
|
|
10152
|
+
mutableOp.baseVersion = this.#version;
|
|
10153
|
+
mutableOp.ops = [];
|
|
10154
|
+
}
|
|
10155
|
+
if (changes.length === 0) {
|
|
10156
|
+
return { modified: false };
|
|
10157
|
+
}
|
|
10158
|
+
return {
|
|
10159
|
+
reverse,
|
|
10160
|
+
modified: {
|
|
10161
|
+
type: "LiveText",
|
|
10162
|
+
node: this,
|
|
10163
|
+
version: this.#version,
|
|
10164
|
+
updates: changes,
|
|
10165
|
+
source
|
|
10166
|
+
}
|
|
10167
|
+
};
|
|
10168
|
+
}
|
|
10169
|
+
/** Server acknowledgement of our in-flight op. */
|
|
10170
|
+
#applyAck(op, source) {
|
|
10171
|
+
const ackedVersion = op.version ?? Math.max(this.#version, op.baseVersion + 1);
|
|
10172
|
+
const predicted = this.#inFlightOps;
|
|
10173
|
+
const opId = this.#inFlightOpId;
|
|
10174
|
+
this.#confirmed = applyTextOperationsToSegments(this.#confirmed, op.ops);
|
|
10175
|
+
this.#inFlightOpId = void 0;
|
|
10176
|
+
this.#inFlightOps = [];
|
|
10177
|
+
let appliedOps = [];
|
|
10178
|
+
let result = { modified: false };
|
|
10179
|
+
if (!textOperationsEqual(op.ops, predicted)) {
|
|
10180
|
+
error2(
|
|
10181
|
+
"LiveText: acknowledgement did not match the local prediction; resynchronizing"
|
|
10182
|
+
);
|
|
10183
|
+
const rebuilt = this.#rebuildLocalFromConfirmed();
|
|
10184
|
+
appliedOps = rebuilt.appliedOps;
|
|
10185
|
+
if (rebuilt.changes.length > 0) {
|
|
10186
|
+
result = {
|
|
10187
|
+
reverse: [],
|
|
10188
|
+
modified: {
|
|
10189
|
+
type: "LiveText",
|
|
10190
|
+
node: this,
|
|
10191
|
+
version: ackedVersion,
|
|
10192
|
+
updates: rebuilt.changes,
|
|
10193
|
+
source
|
|
10194
|
+
}
|
|
10195
|
+
};
|
|
10196
|
+
}
|
|
10197
|
+
}
|
|
10198
|
+
this.#version = Math.max(this.#version, ackedVersion);
|
|
10199
|
+
this.#recordAccepted(ackedVersion, appliedOps, opId);
|
|
10200
|
+
this.#flushQueued();
|
|
10201
|
+
return result;
|
|
10202
|
+
}
|
|
10203
|
+
/** An accepted op from another client (or a server-fabricated fix op). */
|
|
10204
|
+
#applyRemote(op, source) {
|
|
10205
|
+
const version = op.version ?? this.#version + 1;
|
|
10206
|
+
this.#confirmed = applyTextOperationsToSegments(this.#confirmed, op.ops);
|
|
10207
|
+
const [overInFlight, inFlight] = transformTextOperationsX(
|
|
10208
|
+
op.ops,
|
|
10209
|
+
this.#inFlightOps,
|
|
10210
|
+
"before"
|
|
10211
|
+
);
|
|
10212
|
+
const [applied, queued] = transformTextOperationsX(
|
|
10213
|
+
overInFlight,
|
|
10214
|
+
this.#queuedOps,
|
|
10215
|
+
"before"
|
|
10216
|
+
);
|
|
10217
|
+
this.#inFlightOps = inFlight;
|
|
10218
|
+
this.#queuedOps = queued;
|
|
10219
|
+
this.#recordAccepted(version, applied, op.opId);
|
|
10220
|
+
if (applied.length === 0) {
|
|
10221
|
+
this.#version = Math.max(this.#version, version);
|
|
10222
|
+
return { modified: false };
|
|
10223
|
+
}
|
|
10224
|
+
const reverse = this.#invertOperations(applied);
|
|
10225
|
+
const changes = this.#applyOperationsLocally(applied);
|
|
10226
|
+
this.#version = Math.max(this.#version, version);
|
|
10227
|
+
return {
|
|
10228
|
+
reverse,
|
|
10229
|
+
modified: {
|
|
10230
|
+
type: "LiveText",
|
|
10231
|
+
node: this,
|
|
10232
|
+
version: this.#version,
|
|
10233
|
+
updates: changes,
|
|
10234
|
+
source
|
|
10235
|
+
}
|
|
10236
|
+
};
|
|
10237
|
+
}
|
|
10238
|
+
/** Send the queued ops as the next in-flight op (after an ack). */
|
|
10239
|
+
#flushQueued() {
|
|
10240
|
+
if (this.#queuedOps.length === 0 || this._pool === void 0 || this._id === void 0) {
|
|
10241
|
+
return;
|
|
10242
|
+
}
|
|
10243
|
+
const opId = this._pool.generateOpId();
|
|
10244
|
+
this.#inFlightOpId = opId;
|
|
10245
|
+
this.#inFlightOps = this.#queuedOps;
|
|
10246
|
+
this.#queuedOps = [];
|
|
10247
|
+
this._pool.dispatch(
|
|
10248
|
+
[
|
|
10249
|
+
{
|
|
10250
|
+
type: OpCode.UPDATE_TEXT,
|
|
10251
|
+
id: this._id,
|
|
10252
|
+
opId,
|
|
10253
|
+
baseVersion: this.#version,
|
|
10254
|
+
ops: [...this.#inFlightOps]
|
|
10255
|
+
}
|
|
10256
|
+
],
|
|
10257
|
+
[],
|
|
10258
|
+
/* @__PURE__ */ new Map(),
|
|
10259
|
+
// The local content was already applied (and made undoable) when the
|
|
10260
|
+
// edits happened; this is purely an outbound flush.
|
|
10261
|
+
{ clearRedoStack: false }
|
|
10262
|
+
);
|
|
10263
|
+
}
|
|
10264
|
+
/**
|
|
10265
|
+
* Rebuild the local document as confirmed ⊕ queued ops, returning the
|
|
10266
|
+
* coarse delta that was applied. Only used by defensive recovery paths.
|
|
10267
|
+
*/
|
|
10268
|
+
#rebuildLocalFromConfirmed() {
|
|
10269
|
+
const before2 = this.#segments;
|
|
10270
|
+
const after2 = applyTextOperationsToSegments(this.#confirmed, [
|
|
10271
|
+
...this.#inFlightOps,
|
|
10272
|
+
...this.#queuedOps
|
|
10273
|
+
]);
|
|
10274
|
+
if (stableStringify(segmentsToData(before2)) === stableStringify(segmentsToData(after2))) {
|
|
10275
|
+
this.#segments = after2;
|
|
10276
|
+
return { appliedOps: [], changes: [] };
|
|
10277
|
+
}
|
|
10278
|
+
const beforeText = before2.map((segment) => segment.text).join("");
|
|
10279
|
+
this.#segments = after2;
|
|
10280
|
+
this.invalidate();
|
|
10281
|
+
const appliedOps = [];
|
|
10282
|
+
const changes = [];
|
|
10283
|
+
if (beforeText.length > 0) {
|
|
10284
|
+
appliedOps.push({ type: "delete", index: 0, length: beforeText.length });
|
|
10285
|
+
changes.push({
|
|
10286
|
+
type: "delete",
|
|
10287
|
+
index: 0,
|
|
10288
|
+
length: beforeText.length,
|
|
10289
|
+
deletedText: beforeText
|
|
10290
|
+
});
|
|
10291
|
+
}
|
|
10292
|
+
let index = 0;
|
|
10293
|
+
for (const segment of after2) {
|
|
10294
|
+
appliedOps.push({
|
|
10295
|
+
type: "insert",
|
|
10296
|
+
index,
|
|
10297
|
+
text: segment.text,
|
|
10298
|
+
attributes: segment.attributes
|
|
10299
|
+
});
|
|
10300
|
+
changes.push({
|
|
10301
|
+
type: "insert",
|
|
10302
|
+
index,
|
|
10303
|
+
text: segment.text,
|
|
10304
|
+
attributes: segment.attributes
|
|
10305
|
+
});
|
|
10306
|
+
index += segment.text.length;
|
|
10307
|
+
}
|
|
10308
|
+
return { appliedOps, changes };
|
|
10309
|
+
}
|
|
10310
|
+
/**
|
|
10311
|
+
* Reconcile this node against an authoritative storage snapshot (e.g.
|
|
10312
|
+
* after a reconnect). The confirmed state and version are replaced by the
|
|
10313
|
+
* snapshot's; pending (in-flight + queued) ops are preserved on top and
|
|
10314
|
+
* will be re-sent by the offline-ops replay.
|
|
10315
|
+
*
|
|
10316
|
+
* @internal
|
|
10317
|
+
*/
|
|
10318
|
+
_resyncText(data, version, source) {
|
|
10319
|
+
this.#confirmed = dataToSegments(data);
|
|
10320
|
+
this.#version = version;
|
|
10321
|
+
this.#acceptedOps = [];
|
|
10322
|
+
const rebuilt = this.#rebuildLocalFromConfirmed();
|
|
10323
|
+
if (rebuilt.changes.length === 0) {
|
|
10324
|
+
return void 0;
|
|
10325
|
+
}
|
|
10326
|
+
return {
|
|
10327
|
+
type: "LiveText",
|
|
10328
|
+
node: this,
|
|
10329
|
+
version: this.#version,
|
|
10330
|
+
updates: rebuilt.changes,
|
|
10331
|
+
source
|
|
10332
|
+
};
|
|
10333
|
+
}
|
|
10334
|
+
/**
|
|
10335
|
+
* Called when the server rejected one of our ops. Drops all pending state
|
|
10336
|
+
* for this node (edits queued behind a rejected op cannot be trusted
|
|
10337
|
+
* either); the room follows up with a storage resync.
|
|
10338
|
+
*
|
|
10339
|
+
* @internal
|
|
10340
|
+
*/
|
|
10341
|
+
_rejectPendingOp(opId) {
|
|
10342
|
+
if (opId !== this.#inFlightOpId) {
|
|
10343
|
+
return;
|
|
10344
|
+
}
|
|
10345
|
+
this.#inFlightOpId = void 0;
|
|
10346
|
+
this.#inFlightOps = [];
|
|
10347
|
+
this.#queuedOps = [];
|
|
10348
|
+
}
|
|
10349
|
+
#recordAccepted(version, ops, opId) {
|
|
10350
|
+
if (this.#acceptedOps.some((entry) => entry.version === version)) {
|
|
10351
|
+
return;
|
|
10352
|
+
}
|
|
10353
|
+
this.#acceptedOps.push({ version, opId, ops: [...ops] });
|
|
10354
|
+
this.#acceptedOps.sort((left, right) => left.version - right.version);
|
|
10355
|
+
if (this.#acceptedOps.length > ACCEPTED_OPS_HISTORY_LIMIT) {
|
|
10356
|
+
this.#acceptedOps.splice(
|
|
10357
|
+
0,
|
|
10358
|
+
this.#acceptedOps.length - ACCEPTED_OPS_HISTORY_LIMIT
|
|
10359
|
+
);
|
|
10360
|
+
}
|
|
10361
|
+
}
|
|
10362
|
+
#applyOperationsLocally(ops) {
|
|
10363
|
+
const changes = [];
|
|
10364
|
+
for (const op of ops) {
|
|
10365
|
+
if (op.type === "insert") {
|
|
10366
|
+
this.#segments = applyInsert(
|
|
10367
|
+
this.#segments,
|
|
10368
|
+
op.index,
|
|
10369
|
+
op.text,
|
|
10370
|
+
op.attributes
|
|
10371
|
+
);
|
|
10372
|
+
changes.push({
|
|
10373
|
+
type: "insert",
|
|
10374
|
+
index: op.index,
|
|
10375
|
+
text: op.text,
|
|
10376
|
+
attributes: op.attributes
|
|
10377
|
+
});
|
|
10378
|
+
} else if (op.type === "delete") {
|
|
10379
|
+
const result = applyDelete(this.#segments, op.index, op.length);
|
|
10380
|
+
this.#segments = result.segments;
|
|
10381
|
+
changes.push({
|
|
10382
|
+
type: "delete",
|
|
10383
|
+
index: op.index,
|
|
10384
|
+
length: op.length,
|
|
10385
|
+
deletedText: result.deletedText
|
|
10386
|
+
});
|
|
10387
|
+
} else {
|
|
10388
|
+
this.#segments = applyFormat(
|
|
10389
|
+
this.#segments,
|
|
10390
|
+
op.index,
|
|
10391
|
+
op.length,
|
|
10392
|
+
op.attributes
|
|
10393
|
+
);
|
|
10394
|
+
changes.push({
|
|
10395
|
+
type: "format",
|
|
10396
|
+
index: op.index,
|
|
10397
|
+
length: op.length,
|
|
10398
|
+
attributes: op.attributes
|
|
10399
|
+
});
|
|
10400
|
+
}
|
|
10401
|
+
}
|
|
10402
|
+
this.invalidate();
|
|
10403
|
+
return changes;
|
|
10404
|
+
}
|
|
10405
|
+
#invertOperations(ops) {
|
|
10406
|
+
return [
|
|
10407
|
+
{
|
|
10408
|
+
type: OpCode.UPDATE_TEXT,
|
|
10409
|
+
id: nn(this._id),
|
|
10410
|
+
baseVersion: this.#version,
|
|
10411
|
+
ops: invertTextOperations(this.#segments, ops)
|
|
10412
|
+
}
|
|
10413
|
+
];
|
|
10414
|
+
}
|
|
10415
|
+
/** Returns the plain text content without attributes. Equivalent to joining the text from each segment in {@link LiveText.toJSON}. */
|
|
10416
|
+
toString() {
|
|
10417
|
+
return this.#segments.map((segment) => segment.text).join("");
|
|
10418
|
+
}
|
|
10419
|
+
/**
|
|
10420
|
+
* Returns a JSON-compatible snapshot of the document as a {@link LiveTextData}
|
|
10421
|
+
* array.
|
|
10422
|
+
*
|
|
10423
|
+
* @example
|
|
10424
|
+
* new LiveText([["Hello ", { bold: true }], ["world"]]).toJSON();
|
|
10425
|
+
* // [["Hello ", { bold: true }], ["world"]]
|
|
10426
|
+
*/
|
|
10427
|
+
toJSON() {
|
|
10428
|
+
return super.toJSON();
|
|
10429
|
+
}
|
|
10430
|
+
/** @internal */
|
|
10431
|
+
_toJSON() {
|
|
10432
|
+
return segmentsToData(this.#segments);
|
|
10433
|
+
}
|
|
10434
|
+
/** @internal */
|
|
10435
|
+
toTreeNode(key) {
|
|
10436
|
+
return super.toTreeNode(key);
|
|
10437
|
+
}
|
|
10438
|
+
/** @internal */
|
|
10439
|
+
_toTreeNode(key) {
|
|
10440
|
+
const nodeId = this._id ?? nanoid();
|
|
10441
|
+
const payload = this.toJSON().map(
|
|
10442
|
+
(segment, index) => ({
|
|
10443
|
+
type: "Json",
|
|
10444
|
+
id: `${nodeId}:${index}`,
|
|
10445
|
+
key: String(index),
|
|
10446
|
+
payload: segment
|
|
10447
|
+
})
|
|
10448
|
+
);
|
|
10449
|
+
payload.push({
|
|
10450
|
+
type: "Json",
|
|
10451
|
+
id: `${nodeId}:version`,
|
|
10452
|
+
key: "version",
|
|
10453
|
+
payload: this.version
|
|
10454
|
+
});
|
|
10455
|
+
return {
|
|
10456
|
+
type: "LiveText",
|
|
10457
|
+
id: nodeId,
|
|
10458
|
+
key,
|
|
10459
|
+
payload
|
|
10460
|
+
};
|
|
10461
|
+
}
|
|
10462
|
+
clone() {
|
|
10463
|
+
return new _LiveText(this.toJSON(), this.#version);
|
|
10464
|
+
}
|
|
10465
|
+
};
|
|
10466
|
+
|
|
10467
|
+
// src/crdts/liveblocks-helpers.ts
|
|
10468
|
+
function creationOpToLiveNode(op) {
|
|
10469
|
+
return lsonToLiveNode(creationOpToLson(op));
|
|
10470
|
+
}
|
|
10471
|
+
function creationOpToLson(op) {
|
|
10472
|
+
switch (op.type) {
|
|
10473
|
+
case OpCode.CREATE_FILE:
|
|
10474
|
+
return new LiveFile(op.data);
|
|
10475
|
+
case OpCode.CREATE_REGISTER:
|
|
10476
|
+
return op.data;
|
|
10477
|
+
case OpCode.CREATE_OBJECT:
|
|
10478
|
+
return new LiveObject(op.data);
|
|
10479
|
+
case OpCode.CREATE_MAP:
|
|
10480
|
+
return new LiveMap();
|
|
10481
|
+
case OpCode.CREATE_LIST:
|
|
10482
|
+
return new LiveList([]);
|
|
10483
|
+
case OpCode.CREATE_TEXT:
|
|
10484
|
+
return new LiveText(op.data, op.version);
|
|
10485
|
+
default:
|
|
10486
|
+
return assertNever(op, "Unknown creation Op");
|
|
10487
|
+
}
|
|
10488
|
+
}
|
|
10489
|
+
function isSameNodeOrChildOf(node, parent) {
|
|
10490
|
+
if (node === parent) {
|
|
10491
|
+
return true;
|
|
10492
|
+
}
|
|
10493
|
+
if (node.parent.type === "HasParent") {
|
|
10494
|
+
return isSameNodeOrChildOf(node.parent.node, parent);
|
|
10495
|
+
}
|
|
10496
|
+
return false;
|
|
10497
|
+
}
|
|
10498
|
+
function liveObjectFromNodeStream(nodes) {
|
|
10499
|
+
const pool = createManagedPool({
|
|
10500
|
+
getCurrentConnectionId: () => {
|
|
10501
|
+
throw new Error(
|
|
10502
|
+
"Cannot mutate a historic storage version: it is a read-only snapshot"
|
|
10503
|
+
);
|
|
10504
|
+
}
|
|
10505
|
+
});
|
|
10506
|
+
return LiveObject._fromItems(nodes, pool);
|
|
10507
|
+
}
|
|
10508
|
+
function deserialize(node, parentToChildren, pool) {
|
|
10509
|
+
if (isObjectStorageNode(node)) {
|
|
10510
|
+
return LiveObject._deserialize(node, parentToChildren, pool);
|
|
10511
|
+
} else if (isListStorageNode(node)) {
|
|
10512
|
+
return LiveList._deserialize(node, parentToChildren, pool);
|
|
10513
|
+
} else if (isMapStorageNode(node)) {
|
|
10514
|
+
return LiveMap._deserialize(node, parentToChildren, pool);
|
|
10515
|
+
} else if (isRegisterStorageNode(node)) {
|
|
10516
|
+
return LiveRegister._deserialize(node, parentToChildren, pool);
|
|
10517
|
+
} else if (isTextStorageNode(node)) {
|
|
10518
|
+
return LiveText._deserialize(node, parentToChildren, pool);
|
|
10519
|
+
} else if (isFileStorageNode(node)) {
|
|
10520
|
+
return LiveFile._deserialize(node, parentToChildren, pool);
|
|
10521
|
+
} else {
|
|
10522
|
+
throw new Error("Unexpected CRDT type");
|
|
10523
|
+
}
|
|
10524
|
+
}
|
|
10525
|
+
function deserializeToLson(node, parentToChildren, pool) {
|
|
10526
|
+
if (isObjectStorageNode(node)) {
|
|
10527
|
+
return LiveObject._deserialize(node, parentToChildren, pool);
|
|
10528
|
+
} else if (isListStorageNode(node)) {
|
|
10529
|
+
return LiveList._deserialize(node, parentToChildren, pool);
|
|
10530
|
+
} else if (isMapStorageNode(node)) {
|
|
10531
|
+
return LiveMap._deserialize(node, parentToChildren, pool);
|
|
10532
|
+
} else if (isRegisterStorageNode(node)) {
|
|
10533
|
+
return node[1].data;
|
|
10534
|
+
} else if (isTextStorageNode(node)) {
|
|
10535
|
+
return LiveText._deserialize(node, parentToChildren, pool);
|
|
10536
|
+
} else if (isFileStorageNode(node)) {
|
|
10537
|
+
return LiveFile._deserialize(node, parentToChildren, pool);
|
|
10538
|
+
} else {
|
|
10539
|
+
throw new Error("Unexpected CRDT type");
|
|
10540
|
+
}
|
|
10541
|
+
}
|
|
10542
|
+
function isLiveStructure(value) {
|
|
10543
|
+
return isLiveList(value) || isLiveMap(value) || isLiveObject(value) || isLiveText(value) || isLiveFile(value);
|
|
10544
|
+
}
|
|
10545
|
+
function isLiveNode(value) {
|
|
10546
|
+
return isLiveStructure(value) || isLiveRegister(value);
|
|
10547
|
+
}
|
|
10548
|
+
function isLiveList(value) {
|
|
10549
|
+
return value instanceof LiveList;
|
|
10550
|
+
}
|
|
10551
|
+
function isLiveMap(value) {
|
|
10552
|
+
return value instanceof LiveMap;
|
|
10553
|
+
}
|
|
10554
|
+
function isLiveObject(value) {
|
|
10555
|
+
return value instanceof LiveObject;
|
|
10556
|
+
}
|
|
10557
|
+
function isLiveText(value) {
|
|
10558
|
+
return value instanceof LiveText;
|
|
10559
|
+
}
|
|
10560
|
+
function isLiveFile(value) {
|
|
10561
|
+
return value instanceof LiveFile;
|
|
10562
|
+
}
|
|
10563
|
+
function isLiveRegister(value) {
|
|
10564
|
+
return value instanceof LiveRegister;
|
|
10565
|
+
}
|
|
10566
|
+
function cloneLson(value) {
|
|
10567
|
+
return value === void 0 ? void 0 : isLiveStructure(value) ? value.clone() : deepClone(value);
|
|
10568
|
+
}
|
|
10569
|
+
function liveNodeToLson(obj) {
|
|
10570
|
+
if (obj instanceof LiveRegister) {
|
|
10571
|
+
return obj.data;
|
|
10572
|
+
} else if (obj instanceof LiveList || obj instanceof LiveMap || obj instanceof LiveObject || obj instanceof LiveText || obj instanceof LiveFile) {
|
|
10573
|
+
return obj;
|
|
10574
|
+
} else {
|
|
10575
|
+
return assertNever(obj, "Unknown AbstractCrdt");
|
|
10576
|
+
}
|
|
10577
|
+
}
|
|
10578
|
+
function lsonToLiveNode(value) {
|
|
10579
|
+
if (value instanceof LiveObject || value instanceof LiveMap || value instanceof LiveList || value instanceof LiveText || value instanceof LiveFile) {
|
|
10580
|
+
return value;
|
|
10581
|
+
} else {
|
|
10582
|
+
return new LiveRegister(value);
|
|
10583
|
+
}
|
|
10584
|
+
}
|
|
10585
|
+
function dumpPool(pool) {
|
|
10586
|
+
const rows = Array.from(pool.nodes.values(), (node) => {
|
|
10587
|
+
const parent = node.parent;
|
|
10588
|
+
const parentId = parent.type === "HasParent" ? parent.node._id ?? "?" : parent.type === "Orphaned" ? "<orphaned>" : "-";
|
|
10589
|
+
let value;
|
|
10590
|
+
if (node instanceof LiveRegister) {
|
|
10591
|
+
value = stringifyOrLog(node.data);
|
|
10592
|
+
} else if (node instanceof LiveList) {
|
|
10593
|
+
value = "<LiveList>";
|
|
10594
|
+
} else if (node instanceof LiveMap) {
|
|
10595
|
+
value = "<LiveMap>";
|
|
10596
|
+
} else if (node instanceof LiveFile) {
|
|
10597
|
+
value = stringifyOrLog(node.data);
|
|
10598
|
+
} else {
|
|
10599
|
+
value = "<LiveObject>";
|
|
10600
|
+
}
|
|
10601
|
+
return { id: nn(node._id), parentId, key: node._parentKey ?? "", value };
|
|
10602
|
+
});
|
|
10603
|
+
rows.sort((a, b) => {
|
|
10604
|
+
if (a.parentId !== b.parentId) return a.parentId < b.parentId ? -1 : 1;
|
|
10605
|
+
if (a.key !== b.key) return a.key < b.key ? -1 : 1;
|
|
10606
|
+
return 0;
|
|
10607
|
+
});
|
|
10608
|
+
return rows.map(
|
|
10609
|
+
(r) => ` ${r.id} parent=${r.parentId} key=${r.key || "\u2014"} ${r.value}`
|
|
10610
|
+
).join("\n");
|
|
10611
|
+
}
|
|
10612
|
+
function isJsonEq(a, b) {
|
|
10613
|
+
if (a === b) {
|
|
10614
|
+
return true;
|
|
10615
|
+
}
|
|
10616
|
+
if (typeof a !== "object" || a === null || typeof b !== "object" || b === null) {
|
|
10617
|
+
return false;
|
|
10618
|
+
}
|
|
10619
|
+
if (Array.isArray(a) || Array.isArray(b)) {
|
|
10620
|
+
if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) {
|
|
10621
|
+
return false;
|
|
10622
|
+
}
|
|
10623
|
+
for (let i = 0; i < a.length; i++) {
|
|
10624
|
+
if (!isJsonEq(a[i], b[i])) {
|
|
10625
|
+
return false;
|
|
10626
|
+
}
|
|
10627
|
+
}
|
|
10628
|
+
return true;
|
|
10629
|
+
}
|
|
10630
|
+
const aKeys = Object.keys(a);
|
|
10631
|
+
if (aKeys.length !== Object.keys(b).length) {
|
|
10632
|
+
return false;
|
|
10633
|
+
}
|
|
10634
|
+
for (const key of aKeys) {
|
|
10635
|
+
if (!isJsonEq(a[key], b[key])) {
|
|
10636
|
+
return false;
|
|
10637
|
+
}
|
|
10638
|
+
}
|
|
10639
|
+
return true;
|
|
10640
|
+
}
|
|
10641
|
+
function liveTextDataToReplaceOps(before2, after2) {
|
|
10642
|
+
const ops = [];
|
|
10643
|
+
const beforeLength = before2.reduce(
|
|
10644
|
+
(length, [text]) => length + text.length,
|
|
10645
|
+
0
|
|
10646
|
+
);
|
|
10647
|
+
if (beforeLength > 0) {
|
|
10648
|
+
ops.push({ type: "delete", index: 0, length: beforeLength });
|
|
10649
|
+
}
|
|
10650
|
+
let index = 0;
|
|
10651
|
+
for (const [text, attributes] of after2) {
|
|
10652
|
+
if (text.length === 0) {
|
|
10653
|
+
continue;
|
|
10654
|
+
}
|
|
10655
|
+
ops.push({ type: "insert", index, text, attributes });
|
|
10656
|
+
index += text.length;
|
|
10657
|
+
}
|
|
10658
|
+
return ops;
|
|
10659
|
+
}
|
|
10660
|
+
function diffNodeMap(prev, next, options) {
|
|
10661
|
+
const ops = [];
|
|
10662
|
+
const idsToRecreate = /* @__PURE__ */ new Set();
|
|
10663
|
+
next.forEach((nextCrdt, id) => {
|
|
10664
|
+
const currentCrdt = prev.get(id);
|
|
10665
|
+
if (currentCrdt === void 0) {
|
|
10666
|
+
return;
|
|
10667
|
+
}
|
|
10668
|
+
if (currentCrdt.type !== nextCrdt.type || currentCrdt.type === CrdtType.FILE && nextCrdt.type === CrdtType.FILE && (currentCrdt.data.id !== nextCrdt.data.id || currentCrdt.data.name !== nextCrdt.data.name || currentCrdt.data.size !== nextCrdt.data.size || currentCrdt.data.mimeType !== nextCrdt.data.mimeType)) {
|
|
10669
|
+
idsToRecreate.add(id);
|
|
9357
10670
|
}
|
|
9358
10671
|
});
|
|
9359
10672
|
let foundDescendant = true;
|
|
@@ -9436,6 +10749,16 @@ function diffNodeMap(prev, next) {
|
|
|
9436
10749
|
parentKey: crdt.parentKey
|
|
9437
10750
|
});
|
|
9438
10751
|
break;
|
|
10752
|
+
case CrdtType.TEXT:
|
|
10753
|
+
ops.push({
|
|
10754
|
+
type: OpCode.CREATE_TEXT,
|
|
10755
|
+
id,
|
|
10756
|
+
parentId: crdt.parentId,
|
|
10757
|
+
parentKey: crdt.parentKey,
|
|
10758
|
+
data: crdt.data,
|
|
10759
|
+
version: crdt.version
|
|
10760
|
+
});
|
|
10761
|
+
break;
|
|
9439
10762
|
}
|
|
9440
10763
|
}
|
|
9441
10764
|
next.forEach((crdt, id) => {
|
|
@@ -9462,6 +10785,17 @@ function diffNodeMap(prev, next) {
|
|
|
9462
10785
|
}
|
|
9463
10786
|
}
|
|
9464
10787
|
}
|
|
10788
|
+
if (options?.includeLiveTextUpdates === true && crdt.type === CrdtType.TEXT && currentCrdt.type === CrdtType.TEXT && !isJsonEq(crdt.data, currentCrdt.data)) {
|
|
10789
|
+
ops.push({
|
|
10790
|
+
type: OpCode.UPDATE_TEXT,
|
|
10791
|
+
id,
|
|
10792
|
+
// A restore is a new edit in the current timeline. The version from
|
|
10793
|
+
// the historic snapshot describes its old timeline and must not move
|
|
10794
|
+
// this node's current version backwards.
|
|
10795
|
+
baseVersion: currentCrdt.version,
|
|
10796
|
+
ops: liveTextDataToReplaceOps(currentCrdt.data, crdt.data)
|
|
10797
|
+
});
|
|
10798
|
+
}
|
|
9465
10799
|
if (crdt.parentKey !== currentCrdt.parentKey) {
|
|
9466
10800
|
ops.push({
|
|
9467
10801
|
type: OpCode.SET_PARENT_KEY,
|
|
@@ -9502,19 +10836,36 @@ function mergeListStorageUpdates(first, second) {
|
|
|
9502
10836
|
updates: updates.concat(second.updates)
|
|
9503
10837
|
};
|
|
9504
10838
|
}
|
|
10839
|
+
function mergeTextStorageUpdates(first, second) {
|
|
10840
|
+
return {
|
|
10841
|
+
...second,
|
|
10842
|
+
updates: first.updates.concat(second.updates)
|
|
10843
|
+
};
|
|
10844
|
+
}
|
|
10845
|
+
function mergeUpdateSources(first, second) {
|
|
10846
|
+
if (first.origin === "remote" || second.origin === "remote") {
|
|
10847
|
+
return REMOTE;
|
|
10848
|
+
}
|
|
10849
|
+
if (second.via !== "edit") return second;
|
|
10850
|
+
if (first.via !== "edit") return first;
|
|
10851
|
+
return LOCAL_EDIT;
|
|
10852
|
+
}
|
|
9505
10853
|
function mergeStorageUpdates(first, second) {
|
|
9506
10854
|
if (first === void 0) {
|
|
9507
10855
|
return second;
|
|
9508
10856
|
}
|
|
10857
|
+
const source = mergeUpdateSources(first.source, second.source);
|
|
9509
10858
|
if (first.type === "LiveObject" && second.type === "LiveObject") {
|
|
9510
|
-
return mergeObjectStorageUpdates(first, second);
|
|
10859
|
+
return { ...mergeObjectStorageUpdates(first, second), source };
|
|
9511
10860
|
} else if (first.type === "LiveMap" && second.type === "LiveMap") {
|
|
9512
|
-
return mergeMapStorageUpdates(first, second);
|
|
10861
|
+
return { ...mergeMapStorageUpdates(first, second), source };
|
|
9513
10862
|
} else if (first.type === "LiveList" && second.type === "LiveList") {
|
|
9514
|
-
return mergeListStorageUpdates(first, second);
|
|
10863
|
+
return { ...mergeListStorageUpdates(first, second), source };
|
|
10864
|
+
} else if (first.type === "LiveText" && second.type === "LiveText") {
|
|
10865
|
+
return { ...mergeTextStorageUpdates(first, second), source };
|
|
9515
10866
|
} else {
|
|
10867
|
+
return { ...second, source };
|
|
9516
10868
|
}
|
|
9517
|
-
return second;
|
|
9518
10869
|
}
|
|
9519
10870
|
|
|
9520
10871
|
// src/devtools/bridge.ts
|
|
@@ -10471,6 +11822,8 @@ function createRoom(options, config) {
|
|
|
10471
11822
|
activeBatch: null,
|
|
10472
11823
|
unacknowledgedOps
|
|
10473
11824
|
};
|
|
11825
|
+
let nextHistoryItemId = 0;
|
|
11826
|
+
let historyDisabled = 0;
|
|
10474
11827
|
const nodeMapBuffer = makeNodeMapBuffer();
|
|
10475
11828
|
const stopwatch = config.enableDebugLogging ? makeStopWatch() : void 0;
|
|
10476
11829
|
let lastTokenKey;
|
|
@@ -10555,7 +11908,7 @@ function createRoom(options, config) {
|
|
|
10555
11908
|
}
|
|
10556
11909
|
}
|
|
10557
11910
|
});
|
|
10558
|
-
function onDispatch(ops, reverse, storageUpdates) {
|
|
11911
|
+
function onDispatch(ops, reverse, storageUpdates, options2) {
|
|
10559
11912
|
if (context.activeBatch) {
|
|
10560
11913
|
for (const op of ops) {
|
|
10561
11914
|
context.activeBatch.ops.push(op);
|
|
@@ -10570,12 +11923,17 @@ function createRoom(options, config) {
|
|
|
10570
11923
|
);
|
|
10571
11924
|
}
|
|
10572
11925
|
context.activeBatch.reverseOps.pushLeft(reverse);
|
|
11926
|
+
if (options2?.clearRedoStack) {
|
|
11927
|
+
context.activeBatch.clearRedoStack = true;
|
|
11928
|
+
}
|
|
10573
11929
|
} else {
|
|
10574
11930
|
if (reverse.length > 0) {
|
|
10575
11931
|
addToUndoStack(reverse);
|
|
10576
11932
|
}
|
|
11933
|
+
if (options2?.clearRedoStack ?? ops.length > 0) {
|
|
11934
|
+
clearRedoStack();
|
|
11935
|
+
}
|
|
10577
11936
|
if (ops.length > 0) {
|
|
10578
|
-
context.redoStack.length = 0;
|
|
10579
11937
|
dispatchOps(ops);
|
|
10580
11938
|
}
|
|
10581
11939
|
notify({ storageUpdates });
|
|
@@ -10595,6 +11953,7 @@ function createRoom(options, config) {
|
|
|
10595
11953
|
others: makeEventSource(),
|
|
10596
11954
|
storageBatch: makeEventSource(),
|
|
10597
11955
|
history: makeEventSource(),
|
|
11956
|
+
privateHistory: makeEventSource(),
|
|
10598
11957
|
storageDidLoad: makeEventSource(),
|
|
10599
11958
|
storageStatus: makeEventSource(),
|
|
10600
11959
|
ydoc: makeEventSource(),
|
|
@@ -10682,12 +12041,12 @@ function createRoom(options, config) {
|
|
|
10682
12041
|
self,
|
|
10683
12042
|
(me) => me !== null ? userToTreeNode("Me", me) : null
|
|
10684
12043
|
);
|
|
10685
|
-
function diffCurrentStorageAgainst(target) {
|
|
12044
|
+
function diffCurrentStorageAgainst(target, options2) {
|
|
10686
12045
|
const current = /* @__PURE__ */ new Map();
|
|
10687
12046
|
for (const [id, crdt] of context.pool.nodes) {
|
|
10688
12047
|
current.set(id, crdt._serialize());
|
|
10689
12048
|
}
|
|
10690
|
-
return diffNodeMap(current, target);
|
|
12049
|
+
return diffNodeMap(current, target, options2);
|
|
10691
12050
|
}
|
|
10692
12051
|
function createOrUpdateRootFromMessage(nodes) {
|
|
10693
12052
|
if (nodes.size === 0) {
|
|
@@ -10695,6 +12054,23 @@ function createRoom(options, config) {
|
|
|
10695
12054
|
}
|
|
10696
12055
|
if (context.root !== void 0) {
|
|
10697
12056
|
const result = applyRemoteOps(diffCurrentStorageAgainst(nodes));
|
|
12057
|
+
for (const [id, crdt] of nodes) {
|
|
12058
|
+
if (crdt.type === CrdtType.TEXT) {
|
|
12059
|
+
const node = context.pool.nodes.get(id);
|
|
12060
|
+
if (node !== void 0 && isLiveText(node)) {
|
|
12061
|
+
const update = node._resyncText(crdt.data, crdt.version, REMOTE);
|
|
12062
|
+
if (update !== void 0) {
|
|
12063
|
+
result.updates.storageUpdates.set(
|
|
12064
|
+
id,
|
|
12065
|
+
mergeStorageUpdates(
|
|
12066
|
+
result.updates.storageUpdates.get(id),
|
|
12067
|
+
update
|
|
12068
|
+
)
|
|
12069
|
+
);
|
|
12070
|
+
}
|
|
12071
|
+
}
|
|
12072
|
+
}
|
|
12073
|
+
}
|
|
10698
12074
|
notify(result.updates);
|
|
10699
12075
|
} else {
|
|
10700
12076
|
context.root = LiveObject._fromItems(
|
|
@@ -10719,12 +12095,23 @@ function createRoom(options, config) {
|
|
|
10719
12095
|
}
|
|
10720
12096
|
});
|
|
10721
12097
|
}
|
|
12098
|
+
function notifyPrivateHistory(event) {
|
|
12099
|
+
if (historyDisabled > 0) return;
|
|
12100
|
+
eventHub.privateHistory.notify(event);
|
|
12101
|
+
}
|
|
12102
|
+
function clearRedoStack() {
|
|
12103
|
+
if (context.redoStack.length === 0) return;
|
|
12104
|
+
const ids = context.redoStack.map((item) => item.id);
|
|
12105
|
+
context.redoStack.length = 0;
|
|
12106
|
+
notifyPrivateHistory({ action: "discard", ids });
|
|
12107
|
+
}
|
|
10722
12108
|
function reconcileStorageWithNodes(nodes) {
|
|
10723
12109
|
if (context.root === void 0) {
|
|
10724
12110
|
throw new Error("Cannot reconcile storage before it is loaded");
|
|
10725
12111
|
}
|
|
10726
12112
|
const ops = diffCurrentStorageAgainst(
|
|
10727
|
-
new Map(nodes)
|
|
12113
|
+
new Map(nodes),
|
|
12114
|
+
{ includeLiveTextUpdates: true }
|
|
10728
12115
|
);
|
|
10729
12116
|
if (ops.length === 0) {
|
|
10730
12117
|
return;
|
|
@@ -10743,9 +12130,14 @@ function createRoom(options, config) {
|
|
|
10743
12130
|
}
|
|
10744
12131
|
function _addToRealUndoStack(frames) {
|
|
10745
12132
|
if (context.undoStack.length >= 50) {
|
|
10746
|
-
context.undoStack.shift();
|
|
12133
|
+
const evicted = context.undoStack.shift();
|
|
12134
|
+
if (evicted !== void 0) {
|
|
12135
|
+
notifyPrivateHistory({ action: "discard", ids: [evicted.id] });
|
|
12136
|
+
}
|
|
10747
12137
|
}
|
|
10748
|
-
|
|
12138
|
+
const id = nextHistoryItemId++;
|
|
12139
|
+
context.undoStack.push({ id, frames });
|
|
12140
|
+
notifyPrivateHistory({ action: "push", id });
|
|
10749
12141
|
onHistoryChange();
|
|
10750
12142
|
}
|
|
10751
12143
|
function addToUndoStack(frames) {
|
|
@@ -10769,7 +12161,10 @@ function createRoom(options, config) {
|
|
|
10769
12161
|
eventHub.myPresence.notify(context.myPresence.get());
|
|
10770
12162
|
}
|
|
10771
12163
|
if (storageUpdates !== void 0 && storageUpdates.size > 0) {
|
|
10772
|
-
const updates2 = Array.from(storageUpdates.values())
|
|
12164
|
+
const updates2 = Array.from(storageUpdates.values(), (update) => ({
|
|
12165
|
+
...update,
|
|
12166
|
+
source: toUpdateSource(update.source)
|
|
12167
|
+
}));
|
|
10773
12168
|
eventHub.storageBatch.notify(updates2);
|
|
10774
12169
|
}
|
|
10775
12170
|
notifyStorageStatus();
|
|
@@ -10783,19 +12178,69 @@ function createRoom(options, config) {
|
|
|
10783
12178
|
"Internal. Tried to get connection id but connection was never open"
|
|
10784
12179
|
);
|
|
10785
12180
|
}
|
|
10786
|
-
|
|
12181
|
+
const viaByOpId = /* @__PURE__ */ new Map();
|
|
12182
|
+
function viaOfAckedOp(opId) {
|
|
12183
|
+
const via = viaByOpId.get(opId);
|
|
12184
|
+
if (via === void 0) {
|
|
12185
|
+
return "edit";
|
|
12186
|
+
}
|
|
12187
|
+
viaByOpId.delete(opId);
|
|
12188
|
+
return via;
|
|
12189
|
+
}
|
|
12190
|
+
function applyLocalOps(frames, localSource = LOCAL_EDIT) {
|
|
10787
12191
|
const [pframes, ops] = partition(
|
|
10788
12192
|
frames,
|
|
10789
12193
|
(f) => f.type === "presence"
|
|
10790
12194
|
);
|
|
10791
|
-
const
|
|
12195
|
+
const restoredTextIds = /* @__PURE__ */ new Map();
|
|
12196
|
+
for (const op of ops) {
|
|
12197
|
+
if (op.type === OpCode.CREATE_TEXT && op.opId === void 0 && context.pool.nodes.get(op.id) === void 0 && !restoredTextIds.has(op.id)) {
|
|
12198
|
+
restoredTextIds.set(op.id, context.pool.generateId());
|
|
12199
|
+
}
|
|
12200
|
+
}
|
|
12201
|
+
const remappedOps = restoredTextIds.size === 0 ? ops : ops.map((op) => {
|
|
12202
|
+
if (op.opId !== void 0) {
|
|
12203
|
+
return op;
|
|
12204
|
+
}
|
|
12205
|
+
const id = restoredTextIds.get(op.id);
|
|
12206
|
+
if (isCreateOp(op)) {
|
|
12207
|
+
const parentId = restoredTextIds.get(op.parentId);
|
|
12208
|
+
const deletedId = op.deletedId === void 0 ? void 0 : restoredTextIds.get(op.deletedId);
|
|
12209
|
+
if (id === void 0 && parentId === void 0 && deletedId === void 0) {
|
|
12210
|
+
return op;
|
|
12211
|
+
}
|
|
12212
|
+
if (op.type === OpCode.CREATE_TEXT && id !== void 0) {
|
|
12213
|
+
return {
|
|
12214
|
+
...op,
|
|
12215
|
+
id,
|
|
12216
|
+
version: 0,
|
|
12217
|
+
...parentId === void 0 ? {} : { parentId },
|
|
12218
|
+
...deletedId === void 0 ? {} : { deletedId }
|
|
12219
|
+
};
|
|
12220
|
+
}
|
|
12221
|
+
return {
|
|
12222
|
+
...op,
|
|
12223
|
+
...id === void 0 ? {} : { id },
|
|
12224
|
+
...parentId === void 0 ? {} : { parentId },
|
|
12225
|
+
...deletedId === void 0 ? {} : { deletedId }
|
|
12226
|
+
};
|
|
12227
|
+
}
|
|
12228
|
+
return id === void 0 ? op : { ...op, id };
|
|
12229
|
+
});
|
|
12230
|
+
const opsWithOpIds = remappedOps.map(
|
|
10792
12231
|
(op) => op.opId === void 0 ? { ...op, opId: context.pool.generateOpId() } : op
|
|
10793
12232
|
);
|
|
12233
|
+
if (localSource.via !== "edit") {
|
|
12234
|
+
for (const op of opsWithOpIds) {
|
|
12235
|
+
viaByOpId.set(op.opId, localSource.via);
|
|
12236
|
+
}
|
|
12237
|
+
}
|
|
10794
12238
|
const { reverse, updates } = applyOps(
|
|
10795
12239
|
pframes,
|
|
10796
12240
|
opsWithOpIds,
|
|
10797
12241
|
/* isLocal */
|
|
10798
|
-
true
|
|
12242
|
+
true,
|
|
12243
|
+
localSource
|
|
10799
12244
|
);
|
|
10800
12245
|
return { opsToEmit: opsWithOpIds, reverse, updates };
|
|
10801
12246
|
}
|
|
@@ -10807,7 +12252,7 @@ function createRoom(options, config) {
|
|
|
10807
12252
|
false
|
|
10808
12253
|
);
|
|
10809
12254
|
}
|
|
10810
|
-
function applyOps(pframes, ops, isLocal) {
|
|
12255
|
+
function applyOps(pframes, ops, isLocal, localSource = LOCAL_EDIT) {
|
|
10811
12256
|
const output = {
|
|
10812
12257
|
reverse: new Deque(),
|
|
10813
12258
|
storageUpdates: /* @__PURE__ */ new Map(),
|
|
@@ -10836,12 +12281,16 @@ function createRoom(options, config) {
|
|
|
10836
12281
|
for (const op of ops) {
|
|
10837
12282
|
let source;
|
|
10838
12283
|
if (isLocal) {
|
|
10839
|
-
source =
|
|
12284
|
+
source = { ...localSource, optimistic: true };
|
|
10840
12285
|
} else if (op.opId !== void 0) {
|
|
10841
12286
|
context.unacknowledgedOps.delete(op.opId);
|
|
10842
|
-
source =
|
|
12287
|
+
source = {
|
|
12288
|
+
origin: "local",
|
|
12289
|
+
via: viaOfAckedOp(op.opId),
|
|
12290
|
+
optimistic: false
|
|
12291
|
+
};
|
|
10843
12292
|
} else {
|
|
10844
|
-
source =
|
|
12293
|
+
source = REMOTE;
|
|
10845
12294
|
}
|
|
10846
12295
|
const applyOpResult = applyOp(op, source);
|
|
10847
12296
|
if (applyOpResult.modified) {
|
|
@@ -10856,7 +12305,7 @@ function createRoom(options, config) {
|
|
|
10856
12305
|
);
|
|
10857
12306
|
output.reverse.pushLeft(applyOpResult.reverse);
|
|
10858
12307
|
}
|
|
10859
|
-
if (op.type === OpCode.CREATE_LIST || op.type === OpCode.CREATE_MAP || op.type === OpCode.CREATE_OBJECT || op.type === OpCode.CREATE_FILE) {
|
|
12308
|
+
if (op.type === OpCode.CREATE_LIST || op.type === OpCode.CREATE_MAP || op.type === OpCode.CREATE_OBJECT || op.type === OpCode.CREATE_TEXT || op.type === OpCode.CREATE_FILE) {
|
|
10860
12309
|
createdNodeIds.add(op.id);
|
|
10861
12310
|
}
|
|
10862
12311
|
}
|
|
@@ -10876,12 +12325,13 @@ function createRoom(options, config) {
|
|
|
10876
12325
|
switch (op.type) {
|
|
10877
12326
|
case OpCode.DELETE_OBJECT_KEY:
|
|
10878
12327
|
case OpCode.UPDATE_OBJECT:
|
|
12328
|
+
case OpCode.UPDATE_TEXT:
|
|
10879
12329
|
case OpCode.DELETE_CRDT: {
|
|
10880
12330
|
const node = context.pool.nodes.get(op.id);
|
|
10881
12331
|
if (node === void 0) {
|
|
10882
12332
|
return { modified: false };
|
|
10883
12333
|
}
|
|
10884
|
-
return node._apply(op, source
|
|
12334
|
+
return node._apply(op, source);
|
|
10885
12335
|
}
|
|
10886
12336
|
case OpCode.SET_PARENT_KEY: {
|
|
10887
12337
|
const node = context.pool.nodes.get(op.id);
|
|
@@ -10900,6 +12350,7 @@ function createRoom(options, config) {
|
|
|
10900
12350
|
case OpCode.CREATE_OBJECT:
|
|
10901
12351
|
case OpCode.CREATE_LIST:
|
|
10902
12352
|
case OpCode.CREATE_MAP:
|
|
12353
|
+
case OpCode.CREATE_TEXT:
|
|
10903
12354
|
case OpCode.CREATE_FILE:
|
|
10904
12355
|
case OpCode.CREATE_REGISTER: {
|
|
10905
12356
|
if (op.parentId === void 0) {
|
|
@@ -11154,16 +12605,38 @@ function createRoom(options, config) {
|
|
|
11154
12605
|
}
|
|
11155
12606
|
break;
|
|
11156
12607
|
}
|
|
11157
|
-
// Receiving a RejectedOps message
|
|
11158
|
-
//
|
|
11159
|
-
//
|
|
11160
|
-
//
|
|
12608
|
+
// Receiving a RejectedOps message means the server refused some of
|
|
12609
|
+
// our ops, so our optimistic local state is out of sync with the
|
|
12610
|
+
// server. For LiveText ops this is a normal (if rare) situation —
|
|
12611
|
+
// e.g. a client that was offline long enough to fall outside the
|
|
12612
|
+
// server's retained history window — and we can recover: drop the
|
|
12613
|
+
// rejected pending state and re-fetch the authoritative storage
|
|
12614
|
+
// snapshot. For other ops (e.g. permission rejections), rolling back
|
|
12615
|
+
// particular Ops is hard/impossible, so we keep the old behavior of
|
|
12616
|
+
// accepting the out-of-sync reality and surfacing an error.
|
|
11161
12617
|
case ServerMsgCode.REJECT_STORAGE_OP: {
|
|
11162
12618
|
errorWithTitle(
|
|
11163
12619
|
"Storage mutation rejection error",
|
|
11164
12620
|
message.reason
|
|
11165
12621
|
);
|
|
11166
|
-
|
|
12622
|
+
let needsStorageResync = false;
|
|
12623
|
+
for (const opId of message.opIds) {
|
|
12624
|
+
const rejectedOp = context.unacknowledgedOps.get(opId);
|
|
12625
|
+
context.unacknowledgedOps.delete(opId);
|
|
12626
|
+
context.buffer.storageOperations = context.buffer.storageOperations.filter((op) => op.opId !== opId);
|
|
12627
|
+
viaByOpId.delete(opId);
|
|
12628
|
+
if (rejectedOp !== void 0 && rejectedOp.type === OpCode.UPDATE_TEXT) {
|
|
12629
|
+
const node = context.pool.nodes.get(rejectedOp.id);
|
|
12630
|
+
if (node !== void 0 && isLiveText(node)) {
|
|
12631
|
+
node._rejectPendingOp(opId);
|
|
12632
|
+
needsStorageResync = true;
|
|
12633
|
+
}
|
|
12634
|
+
}
|
|
12635
|
+
}
|
|
12636
|
+
if (needsStorageResync) {
|
|
12637
|
+
refreshStorage();
|
|
12638
|
+
flushNowOrSoon();
|
|
12639
|
+
} else if (process.env.NODE_ENV !== "production") {
|
|
11167
12640
|
throw new Error(
|
|
11168
12641
|
`Storage mutations rejected by server: ${message.reason}`
|
|
11169
12642
|
);
|
|
@@ -11705,14 +13178,15 @@ function createRoom(options, config) {
|
|
|
11705
13178
|
if (context.activeBatch) {
|
|
11706
13179
|
throw new Error("undo is not allowed during a batch");
|
|
11707
13180
|
}
|
|
11708
|
-
const
|
|
11709
|
-
if (
|
|
13181
|
+
const item = context.undoStack.pop();
|
|
13182
|
+
if (item === void 0) {
|
|
11710
13183
|
return;
|
|
11711
13184
|
}
|
|
11712
13185
|
context.pausedHistory = null;
|
|
11713
|
-
const result = applyLocalOps(frames);
|
|
13186
|
+
const result = applyLocalOps(item.frames, LOCAL_UNDO);
|
|
13187
|
+
context.redoStack.push({ id: item.id, frames: result.reverse });
|
|
13188
|
+
notifyPrivateHistory({ action: "undo", id: item.id });
|
|
11714
13189
|
notify(result.updates);
|
|
11715
|
-
context.redoStack.push(result.reverse);
|
|
11716
13190
|
onHistoryChange();
|
|
11717
13191
|
for (const op of result.opsToEmit) {
|
|
11718
13192
|
context.buffer.storageOperations.push(op);
|
|
@@ -11723,14 +13197,15 @@ function createRoom(options, config) {
|
|
|
11723
13197
|
if (context.activeBatch) {
|
|
11724
13198
|
throw new Error("redo is not allowed during a batch");
|
|
11725
13199
|
}
|
|
11726
|
-
const
|
|
11727
|
-
if (
|
|
13200
|
+
const item = context.redoStack.pop();
|
|
13201
|
+
if (item === void 0) {
|
|
11728
13202
|
return;
|
|
11729
13203
|
}
|
|
11730
13204
|
context.pausedHistory = null;
|
|
11731
|
-
const result = applyLocalOps(frames);
|
|
13205
|
+
const result = applyLocalOps(item.frames, LOCAL_REDO);
|
|
13206
|
+
context.undoStack.push({ id: item.id, frames: result.reverse });
|
|
13207
|
+
notifyPrivateHistory({ action: "redo", id: item.id });
|
|
11732
13208
|
notify(result.updates);
|
|
11733
|
-
context.undoStack.push(result.reverse);
|
|
11734
13209
|
onHistoryChange();
|
|
11735
13210
|
for (const op of result.opsToEmit) {
|
|
11736
13211
|
context.buffer.storageOperations.push(op);
|
|
@@ -11740,6 +13215,8 @@ function createRoom(options, config) {
|
|
|
11740
13215
|
function clear() {
|
|
11741
13216
|
context.undoStack.length = 0;
|
|
11742
13217
|
context.redoStack.length = 0;
|
|
13218
|
+
notifyPrivateHistory({ action: "clear" });
|
|
13219
|
+
onHistoryChange();
|
|
11743
13220
|
}
|
|
11744
13221
|
function batch2(callback) {
|
|
11745
13222
|
if (context.activeBatch) {
|
|
@@ -11767,8 +13244,8 @@ function createRoom(options, config) {
|
|
|
11767
13244
|
if (currentBatch.scheduleHistoryResume) {
|
|
11768
13245
|
commitPausedHistoryToUndoStack();
|
|
11769
13246
|
}
|
|
11770
|
-
if (currentBatch.ops.length > 0) {
|
|
11771
|
-
|
|
13247
|
+
if (currentBatch.ops.length > 0 || currentBatch.clearRedoStack) {
|
|
13248
|
+
clearRedoStack();
|
|
11772
13249
|
}
|
|
11773
13250
|
if (currentBatch.ops.length > 0) {
|
|
11774
13251
|
dispatchOps(currentBatch.ops);
|
|
@@ -11797,7 +13274,6 @@ function createRoom(options, config) {
|
|
|
11797
13274
|
}
|
|
11798
13275
|
commitPausedHistoryToUndoStack();
|
|
11799
13276
|
}
|
|
11800
|
-
let historyDisabled = 0;
|
|
11801
13277
|
function disableHistory(fn) {
|
|
11802
13278
|
const origUndo = context.undoStack;
|
|
11803
13279
|
const origRedo = context.redoStack;
|
|
@@ -12047,13 +13523,28 @@ function createRoom(options, config) {
|
|
|
12047
13523
|
},
|
|
12048
13524
|
// prettier-ignore
|
|
12049
13525
|
get undoStack() {
|
|
12050
|
-
return
|
|
13526
|
+
return structuredClone(
|
|
13527
|
+
context.undoStack.map((item) => ({
|
|
13528
|
+
id: item.id,
|
|
13529
|
+
frames: item.frames
|
|
13530
|
+
}))
|
|
13531
|
+
);
|
|
13532
|
+
},
|
|
13533
|
+
// prettier-ignore
|
|
13534
|
+
get redoStack() {
|
|
13535
|
+
return structuredClone(
|
|
13536
|
+
context.redoStack.map((item) => ({
|
|
13537
|
+
id: item.id,
|
|
13538
|
+
frames: item.frames
|
|
13539
|
+
}))
|
|
13540
|
+
);
|
|
12051
13541
|
},
|
|
12052
13542
|
// prettier-ignore
|
|
12053
13543
|
get nodeCount() {
|
|
12054
13544
|
return context.pool.nodes.size;
|
|
12055
13545
|
},
|
|
12056
13546
|
// prettier-ignore
|
|
13547
|
+
history: eventHub.privateHistory.observable,
|
|
12057
13548
|
getYjsProvider() {
|
|
12058
13549
|
return context.yjsProvider;
|
|
12059
13550
|
},
|
|
@@ -13084,6 +14575,12 @@ function toPlainLson(lson) {
|
|
|
13084
14575
|
liveblocksType: "LiveList",
|
|
13085
14576
|
data: [...lson].map((item) => toPlainLson(item))
|
|
13086
14577
|
};
|
|
14578
|
+
} else if (lson instanceof LiveText) {
|
|
14579
|
+
return {
|
|
14580
|
+
liveblocksType: "LiveText",
|
|
14581
|
+
data: lson.toJSON(),
|
|
14582
|
+
version: lson.version
|
|
14583
|
+
};
|
|
13087
14584
|
} else if (lson instanceof LiveFile) {
|
|
13088
14585
|
return {
|
|
13089
14586
|
liveblocksType: "LiveFile",
|
|
@@ -13271,6 +14768,7 @@ export {
|
|
|
13271
14768
|
LiveList,
|
|
13272
14769
|
LiveMap,
|
|
13273
14770
|
LiveObject,
|
|
14771
|
+
LiveText,
|
|
13274
14772
|
LiveblocksError,
|
|
13275
14773
|
MENTION_CHARACTER,
|
|
13276
14774
|
MutableSignal,
|
|
@@ -13282,6 +14780,7 @@ export {
|
|
|
13282
14780
|
SortedList,
|
|
13283
14781
|
TextEditorType,
|
|
13284
14782
|
WebsocketCloseCodes,
|
|
14783
|
+
applyLiveTextOperations,
|
|
13285
14784
|
asPos,
|
|
13286
14785
|
assert,
|
|
13287
14786
|
assertNever,
|
|
@@ -13342,6 +14841,7 @@ export {
|
|
|
13342
14841
|
isRegisterStorageNode,
|
|
13343
14842
|
isRootStorageNode,
|
|
13344
14843
|
isStartsWithOperator,
|
|
14844
|
+
isTextStorageNode,
|
|
13345
14845
|
isUrl,
|
|
13346
14846
|
kInternal,
|
|
13347
14847
|
keys,
|
|
@@ -13355,6 +14855,7 @@ export {
|
|
|
13355
14855
|
nanoid,
|
|
13356
14856
|
nn,
|
|
13357
14857
|
nodeStreamToCompactNodes,
|
|
14858
|
+
normalizeLiveTextOperations,
|
|
13358
14859
|
normalizeRoomAccesses,
|
|
13359
14860
|
normalizeRoomPermissions,
|
|
13360
14861
|
normalizeUpdateRoomAccesses,
|
|
@@ -13370,6 +14871,7 @@ export {
|
|
|
13370
14871
|
stringifyCommentBody,
|
|
13371
14872
|
throwUsageError,
|
|
13372
14873
|
toPlainLson,
|
|
14874
|
+
transformTextOperations,
|
|
13373
14875
|
tryParseJson,
|
|
13374
14876
|
url,
|
|
13375
14877
|
urljoin,
|