reptree 0.9.0 → 1.0.0
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/README.md +8 -8
- package/dist/index.cjs +444 -404
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +190 -173
- package/dist/index.d.ts +190 -173
- package/dist/index.js +437 -397
- package/dist/index.js.map +1 -1
- package/package.json +1 -3
package/dist/index.js
CHANGED
|
@@ -46,24 +46,24 @@ function opIdToString(opId) {
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
// src/operations.ts
|
|
49
|
-
function
|
|
49
|
+
function isMoveNodeOp(op) {
|
|
50
50
|
return "parentId" in op;
|
|
51
51
|
}
|
|
52
52
|
function isAnyPropertyOp(op) {
|
|
53
53
|
return "key" in op;
|
|
54
54
|
}
|
|
55
|
-
function
|
|
55
|
+
function newMoveNodeOp(clock, peerId, targetId, parentId) {
|
|
56
56
|
return { id: createOpId(clock, peerId), targetId, parentId };
|
|
57
57
|
}
|
|
58
|
-
function
|
|
58
|
+
function newSetNodePropertyOp(clock, peerId, targetId, key, value) {
|
|
59
59
|
return { id: createOpId(clock, peerId), targetId, key, value, transient: false };
|
|
60
60
|
}
|
|
61
|
-
function
|
|
61
|
+
function newSetTransientNodePropertyOp(clock, peerId, targetId, key, value) {
|
|
62
62
|
return { id: createOpId(clock, peerId), targetId, key, value, transient: true };
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
// src/
|
|
66
|
-
var
|
|
65
|
+
// src/NodeState.ts
|
|
66
|
+
var NodeState = class {
|
|
67
67
|
constructor(id, parentId) {
|
|
68
68
|
this.id = id;
|
|
69
69
|
this.parentId = parentId;
|
|
@@ -140,21 +140,31 @@ var VertexState = class {
|
|
|
140
140
|
};
|
|
141
141
|
|
|
142
142
|
// src/TreeState.ts
|
|
143
|
-
var
|
|
143
|
+
var _TreeState = class _TreeState {
|
|
144
144
|
constructor() {
|
|
145
145
|
this.changeCallbacks = /* @__PURE__ */ new Map();
|
|
146
146
|
this.globalChangeCallbacks = /* @__PURE__ */ new Set();
|
|
147
147
|
this.batchedEvents = /* @__PURE__ */ new Map();
|
|
148
|
-
this.
|
|
149
|
-
this.batchTickInterval = setInterval(() => {
|
|
150
|
-
this.processBatchedEvents();
|
|
151
|
-
}, 33.3);
|
|
148
|
+
this.nodes = /* @__PURE__ */ new Map();
|
|
152
149
|
}
|
|
153
150
|
dispose() {
|
|
154
|
-
|
|
151
|
+
if (this.batchTickTimeout) {
|
|
152
|
+
clearTimeout(this.batchTickTimeout);
|
|
153
|
+
this.batchTickTimeout = void 0;
|
|
154
|
+
}
|
|
155
|
+
this.batchedEvents.clear();
|
|
156
|
+
}
|
|
157
|
+
scheduleBatchProcessing() {
|
|
158
|
+
if (this.batchTickTimeout) {
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
this.batchTickTimeout = setTimeout(() => {
|
|
162
|
+
this.batchTickTimeout = void 0;
|
|
163
|
+
this.processBatchedEvents();
|
|
164
|
+
}, _TreeState.BATCH_DELAY_MS);
|
|
155
165
|
}
|
|
156
166
|
processBatchedEvents() {
|
|
157
|
-
for (const [
|
|
167
|
+
for (const [nodeId, events] of this.batchedEvents) {
|
|
158
168
|
let lastMoveEvent = null;
|
|
159
169
|
let lastChildrenEvent = null;
|
|
160
170
|
const propertyEventsByKey = /* @__PURE__ */ new Map();
|
|
@@ -175,24 +185,21 @@ var TreeState = class {
|
|
|
175
185
|
...propertyEventsByKey.values()
|
|
176
186
|
];
|
|
177
187
|
this.globalChangeCallbacks.forEach((listener) => listener(filteredEvents));
|
|
178
|
-
this.changeCallbacks.get(
|
|
188
|
+
this.changeCallbacks.get(nodeId)?.forEach((listener) => listener(filteredEvents));
|
|
179
189
|
}
|
|
180
190
|
this.batchedEvents.clear();
|
|
181
191
|
}
|
|
182
|
-
|
|
183
|
-
return Array.from(this.
|
|
192
|
+
getAllNodes() {
|
|
193
|
+
return Array.from(this.nodes.values());
|
|
184
194
|
}
|
|
185
|
-
|
|
186
|
-
return this.
|
|
195
|
+
getNode(id) {
|
|
196
|
+
return this.nodes.get(id);
|
|
187
197
|
}
|
|
188
|
-
getChildrenIds(
|
|
189
|
-
return this.
|
|
198
|
+
getChildrenIds(nodeId) {
|
|
199
|
+
return this.getNode(nodeId)?.children ?? [];
|
|
190
200
|
}
|
|
191
|
-
getChildren(
|
|
192
|
-
return this.getChildrenIds(
|
|
193
|
-
const vertex = this.vertices.get(id);
|
|
194
|
-
return vertex ? vertex : void 0;
|
|
195
|
-
}).filter((vertex) => vertex !== void 0).sort((a, b) => {
|
|
201
|
+
getChildren(nodeId) {
|
|
202
|
+
return this.getChildrenIds(nodeId).map((id) => this.nodes.get(id)).filter((node) => node !== void 0).sort((a, b) => {
|
|
196
203
|
const aDate = a.getProperty("_c");
|
|
197
204
|
const bDate = b.getProperty("_c");
|
|
198
205
|
if (!aDate) return -1;
|
|
@@ -200,100 +207,99 @@ var TreeState = class {
|
|
|
200
207
|
return new Date(aDate).getTime() - new Date(bDate).getTime();
|
|
201
208
|
});
|
|
202
209
|
}
|
|
203
|
-
|
|
204
|
-
let
|
|
205
|
-
const prevParentId =
|
|
206
|
-
if (!
|
|
207
|
-
|
|
208
|
-
this.
|
|
210
|
+
moveNode(nodeId, newParentId) {
|
|
211
|
+
let node = this.getNode(nodeId);
|
|
212
|
+
const prevParentId = node ? node.parentId : void 0;
|
|
213
|
+
if (!node) {
|
|
214
|
+
node = new NodeState(nodeId, newParentId);
|
|
215
|
+
this.nodes.set(nodeId, node);
|
|
209
216
|
}
|
|
210
217
|
if (prevParentId === newParentId) {
|
|
211
|
-
return
|
|
218
|
+
return node;
|
|
212
219
|
}
|
|
213
|
-
|
|
220
|
+
node.parentId = newParentId;
|
|
214
221
|
let childrenInNewParent = null;
|
|
215
222
|
let childrenInOldParent = null;
|
|
216
223
|
if (prevParentId) {
|
|
217
|
-
const
|
|
218
|
-
if (
|
|
219
|
-
|
|
220
|
-
childrenInOldParent =
|
|
224
|
+
const oldParentNode = this.getNode(prevParentId);
|
|
225
|
+
if (oldParentNode) {
|
|
226
|
+
oldParentNode.children = oldParentNode.children.filter((child) => child !== nodeId);
|
|
227
|
+
childrenInOldParent = oldParentNode.children;
|
|
221
228
|
} else {
|
|
222
|
-
console.error(`Old parent
|
|
229
|
+
console.error(`Old parent node not found for ${prevParentId}`);
|
|
223
230
|
}
|
|
224
231
|
}
|
|
225
232
|
if (newParentId !== null) {
|
|
226
|
-
const
|
|
227
|
-
if (
|
|
228
|
-
|
|
229
|
-
childrenInNewParent =
|
|
233
|
+
const newParentNode = this.nodes.get(newParentId);
|
|
234
|
+
if (newParentNode) {
|
|
235
|
+
newParentNode.children.push(nodeId);
|
|
236
|
+
childrenInNewParent = newParentNode.children;
|
|
230
237
|
} else {
|
|
231
|
-
console.error(`New parent
|
|
238
|
+
console.error(`New parent node not found for ${newParentId}`);
|
|
232
239
|
}
|
|
233
240
|
}
|
|
234
241
|
this.notifyChange({
|
|
235
242
|
type: "move",
|
|
236
|
-
|
|
243
|
+
nodeId,
|
|
237
244
|
oldParentId: prevParentId,
|
|
238
245
|
newParentId
|
|
239
246
|
});
|
|
240
247
|
if (childrenInNewParent !== null && newParentId !== null) {
|
|
241
248
|
this.notifyChange({
|
|
242
249
|
type: "children",
|
|
243
|
-
|
|
244
|
-
children: childrenInNewParent.map((id) => this.
|
|
250
|
+
nodeId: newParentId,
|
|
251
|
+
children: childrenInNewParent.map((id) => this.nodes.get(id))
|
|
245
252
|
});
|
|
246
253
|
}
|
|
247
254
|
if (childrenInOldParent !== null && prevParentId) {
|
|
248
255
|
this.notifyChange({
|
|
249
256
|
type: "children",
|
|
250
|
-
|
|
251
|
-
children: childrenInOldParent.map((id) => this.
|
|
257
|
+
nodeId: prevParentId,
|
|
258
|
+
children: childrenInOldParent.map((id) => this.nodes.get(id))
|
|
252
259
|
});
|
|
253
260
|
}
|
|
254
|
-
return
|
|
261
|
+
return node;
|
|
255
262
|
}
|
|
256
|
-
setProperty(
|
|
257
|
-
const
|
|
258
|
-
if (!
|
|
259
|
-
throw new Error(`
|
|
263
|
+
setProperty(nodeId, key, value) {
|
|
264
|
+
const node = this.getNode(nodeId);
|
|
265
|
+
if (!node) {
|
|
266
|
+
throw new Error(`Node ${nodeId} not found`);
|
|
260
267
|
}
|
|
261
|
-
|
|
268
|
+
node.setProperty(key, value);
|
|
262
269
|
this.notifyChange({
|
|
263
270
|
type: "property",
|
|
264
|
-
|
|
271
|
+
nodeId,
|
|
265
272
|
key,
|
|
266
273
|
value
|
|
267
274
|
});
|
|
268
|
-
if (
|
|
275
|
+
if (node.parentId !== null) {
|
|
269
276
|
this.notifyChange({
|
|
270
277
|
type: "children",
|
|
271
|
-
|
|
272
|
-
children:
|
|
273
|
-
// @TODO: shoulld I set all children or rename this property?
|
|
278
|
+
nodeId: node.parentId,
|
|
279
|
+
children: this.getChildren(node.parentId)
|
|
274
280
|
});
|
|
275
281
|
}
|
|
276
282
|
}
|
|
277
|
-
setTransientProperty(
|
|
278
|
-
const
|
|
279
|
-
if (
|
|
280
|
-
|
|
283
|
+
setTransientProperty(nodeId, key, value) {
|
|
284
|
+
const node = this.getNode(nodeId);
|
|
285
|
+
if (node) {
|
|
286
|
+
node.setTransientProperty(key, value);
|
|
281
287
|
}
|
|
282
288
|
this.notifyChange({
|
|
283
289
|
type: "property",
|
|
284
|
-
|
|
290
|
+
nodeId,
|
|
285
291
|
key,
|
|
286
292
|
value
|
|
287
293
|
});
|
|
288
294
|
}
|
|
289
|
-
addChangeCallback(
|
|
290
|
-
if (!this.changeCallbacks.has(
|
|
291
|
-
this.changeCallbacks.set(
|
|
295
|
+
addChangeCallback(nodeId, listener) {
|
|
296
|
+
if (!this.changeCallbacks.has(nodeId)) {
|
|
297
|
+
this.changeCallbacks.set(nodeId, /* @__PURE__ */ new Set());
|
|
292
298
|
}
|
|
293
|
-
this.changeCallbacks.get(
|
|
299
|
+
this.changeCallbacks.get(nodeId).add(listener);
|
|
294
300
|
}
|
|
295
|
-
removeChangeCallback(
|
|
296
|
-
this.changeCallbacks.get(
|
|
301
|
+
removeChangeCallback(nodeId, listener) {
|
|
302
|
+
this.changeCallbacks.get(nodeId)?.delete(listener);
|
|
297
303
|
}
|
|
298
304
|
addGlobalChangeCallback(listener) {
|
|
299
305
|
this.globalChangeCallbacks.add(listener);
|
|
@@ -302,23 +308,24 @@ var TreeState = class {
|
|
|
302
308
|
this.globalChangeCallbacks.delete(listener);
|
|
303
309
|
}
|
|
304
310
|
notifyChange(event) {
|
|
305
|
-
let events = this.batchedEvents.get(event.
|
|
311
|
+
let events = this.batchedEvents.get(event.nodeId);
|
|
306
312
|
if (!events) {
|
|
307
313
|
events = [];
|
|
308
|
-
this.batchedEvents.set(event.
|
|
314
|
+
this.batchedEvents.set(event.nodeId, events);
|
|
309
315
|
}
|
|
310
316
|
events.push(event);
|
|
317
|
+
this.scheduleBatchProcessing();
|
|
311
318
|
}
|
|
312
|
-
printTree(
|
|
319
|
+
printTree(nodeId, indent = "", isLast = true) {
|
|
313
320
|
const prefix = indent + (isLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ");
|
|
314
|
-
let result = prefix +
|
|
315
|
-
let
|
|
316
|
-
if (
|
|
317
|
-
const
|
|
318
|
-
if (
|
|
319
|
-
for (const prop of
|
|
321
|
+
let result = prefix + nodeId + "\n";
|
|
322
|
+
let nodeName = null;
|
|
323
|
+
if (nodeId !== null) {
|
|
324
|
+
const node = this.getNode(nodeId);
|
|
325
|
+
if (node) {
|
|
326
|
+
for (const prop of node.getAllProperties()) {
|
|
320
327
|
if (prop.key === "name") {
|
|
321
|
-
|
|
328
|
+
nodeName = prop.value;
|
|
322
329
|
}
|
|
323
330
|
const propPrefix = indent + (isLast ? " " : "\u2502 ") + "\u2022 ";
|
|
324
331
|
result += `${propPrefix}${prop.key}: ${JSON.stringify(prop.value)}
|
|
@@ -326,12 +333,12 @@ var TreeState = class {
|
|
|
326
333
|
}
|
|
327
334
|
}
|
|
328
335
|
}
|
|
329
|
-
const children = this.getChildrenIds(
|
|
336
|
+
const children = this.getChildrenIds(nodeId);
|
|
330
337
|
const sortedChildren = [...children].sort((a, b) => {
|
|
331
|
-
const
|
|
332
|
-
const
|
|
333
|
-
const nameA =
|
|
334
|
-
const nameB =
|
|
338
|
+
const nodeA = this.getNode(a);
|
|
339
|
+
const nodeB = this.getNode(b);
|
|
340
|
+
const nameA = nodeA?.getProperty("name");
|
|
341
|
+
const nameB = nodeB?.getProperty("name");
|
|
335
342
|
if (nameA && nameB) {
|
|
336
343
|
return nameA.localeCompare(nameB);
|
|
337
344
|
}
|
|
@@ -347,6 +354,8 @@ var TreeState = class {
|
|
|
347
354
|
return result;
|
|
348
355
|
}
|
|
349
356
|
};
|
|
357
|
+
_TreeState.BATCH_DELAY_MS = 33.3;
|
|
358
|
+
var TreeState = _TreeState;
|
|
350
359
|
|
|
351
360
|
// src/utils/uuid.ts
|
|
352
361
|
var removeDashes = (guid) => guid.replace(/-/g, "");
|
|
@@ -355,28 +364,28 @@ function uuid() {
|
|
|
355
364
|
}
|
|
356
365
|
|
|
357
366
|
// src/reactive.ts
|
|
358
|
-
function
|
|
367
|
+
function bindNode(tree, id, schemaOrOptions) {
|
|
359
368
|
const isOptions = typeof schemaOrOptions === "object" && schemaOrOptions !== null && (Object.prototype.hasOwnProperty.call(schemaOrOptions, "includeInternalKeys") || Object.prototype.hasOwnProperty.call(schemaOrOptions, "schema"));
|
|
360
369
|
const options = isOptions ? schemaOrOptions : { schema: schemaOrOptions };
|
|
361
370
|
const schema = options.schema;
|
|
362
371
|
const obj = {};
|
|
363
372
|
Object.defineProperties(obj, {
|
|
364
|
-
$
|
|
373
|
+
$node: { get: () => tree.getNode(id), enumerable: false, configurable: true },
|
|
365
374
|
$id: { get: () => id, enumerable: false, configurable: true },
|
|
366
|
-
$parentId: { get: () => tree.
|
|
367
|
-
$parent: { get: () => tree.
|
|
375
|
+
$parentId: { get: () => tree.getNode(id)?.parentId ?? null, enumerable: false, configurable: true },
|
|
376
|
+
$parent: { get: () => tree.getNode(id)?.parent, enumerable: false, configurable: true },
|
|
368
377
|
$children: { get: () => tree.getChildren(id), enumerable: false, configurable: true },
|
|
369
378
|
$childrenIds: { get: () => tree.getChildrenIds(id), enumerable: false, configurable: true },
|
|
370
379
|
$moveTo: {
|
|
371
380
|
value: (parent) => {
|
|
372
381
|
const parentId = typeof parent === "object" && parent !== null ? parent.id || parent.$id : parent;
|
|
373
|
-
tree.
|
|
382
|
+
tree.moveNode(id, parentId);
|
|
374
383
|
},
|
|
375
384
|
enumerable: false,
|
|
376
385
|
configurable: true,
|
|
377
386
|
writable: false
|
|
378
387
|
},
|
|
379
|
-
$delete: { value: () => tree.
|
|
388
|
+
$delete: { value: () => tree.deleteNode(id), enumerable: false, configurable: true, writable: false },
|
|
380
389
|
$observe: { value: (listener) => tree.observe(id, listener), enumerable: false, configurable: true, writable: false },
|
|
381
390
|
$observeChildren: {
|
|
382
391
|
value: (listener) => tree.observe(id, (events) => {
|
|
@@ -388,20 +397,20 @@ function bindVertex(tree, id, schemaOrOptions) {
|
|
|
388
397
|
configurable: true,
|
|
389
398
|
writable: false
|
|
390
399
|
},
|
|
391
|
-
$newChild: { value: (props) => tree.
|
|
392
|
-
$newNamedChild: { value: (name, props) => tree.
|
|
400
|
+
$newChild: { value: (props) => tree.getNode(id).newChild(props), enumerable: false, configurable: true, writable: false },
|
|
401
|
+
$newNamedChild: { value: (name, props) => tree.getNode(id).newNamedChild(name, props), enumerable: false, configurable: true, writable: false },
|
|
393
402
|
$useTransients: {
|
|
394
403
|
value: function(fn) {
|
|
395
404
|
const transientProxy = new Proxy({}, {
|
|
396
405
|
set(_, prop, value) {
|
|
397
406
|
if (typeof prop === "string") {
|
|
398
|
-
tree.
|
|
407
|
+
tree.setTransientNodeProperty(id, prop, value);
|
|
399
408
|
}
|
|
400
409
|
return true;
|
|
401
410
|
},
|
|
402
411
|
get(_, prop) {
|
|
403
412
|
if (typeof prop !== "string") return void 0;
|
|
404
|
-
const rawValue = tree.
|
|
413
|
+
const rawValue = tree.getNodeProperty(id, prop, true);
|
|
405
414
|
return rawValue;
|
|
406
415
|
}
|
|
407
416
|
});
|
|
@@ -432,7 +441,7 @@ function bindVertex(tree, id, schemaOrOptions) {
|
|
|
432
441
|
if (prop in target) {
|
|
433
442
|
return Reflect.get(target, prop, receiver);
|
|
434
443
|
}
|
|
435
|
-
const rawValue = tree.
|
|
444
|
+
const rawValue = tree.getNodeProperty(id, prop, true);
|
|
436
445
|
return rawValue;
|
|
437
446
|
},
|
|
438
447
|
set(target, prop, value) {
|
|
@@ -447,46 +456,46 @@ function bindVertex(tree, id, schemaOrOptions) {
|
|
|
447
456
|
value = res.data;
|
|
448
457
|
}
|
|
449
458
|
}
|
|
450
|
-
tree.
|
|
459
|
+
tree.setNodeProperty(id, prop, value);
|
|
451
460
|
return true;
|
|
452
461
|
},
|
|
453
462
|
deleteProperty(_target, prop) {
|
|
454
463
|
if (typeof prop !== "string") {
|
|
455
464
|
return true;
|
|
456
465
|
}
|
|
457
|
-
tree.
|
|
466
|
+
tree.setNodeProperty(id, prop, void 0);
|
|
458
467
|
return true;
|
|
459
468
|
}
|
|
460
469
|
});
|
|
461
470
|
return proxy;
|
|
462
471
|
}
|
|
463
472
|
|
|
464
|
-
// src/
|
|
465
|
-
var
|
|
473
|
+
// src/Node.ts
|
|
474
|
+
var Node = class {
|
|
466
475
|
constructor(tree, state) {
|
|
467
476
|
this.state = state;
|
|
468
477
|
this._tree = tree;
|
|
469
478
|
}
|
|
470
|
-
/** Returns the tree this
|
|
479
|
+
/** Returns the tree this node belongs to. */
|
|
471
480
|
get tree() {
|
|
472
481
|
return this._tree;
|
|
473
482
|
}
|
|
474
483
|
set tree(value) {
|
|
475
484
|
this._tree = value;
|
|
476
485
|
}
|
|
477
|
-
/** Returns the ID of this
|
|
486
|
+
/** Returns the ID of this node. */
|
|
478
487
|
get id() {
|
|
479
488
|
return this.state.id;
|
|
480
489
|
}
|
|
481
|
-
/** Returns the name of this
|
|
490
|
+
/** Returns the name of this node. The name is stored as a property with the key 'name'. */
|
|
482
491
|
get name() {
|
|
483
492
|
return this.getProperty("name");
|
|
484
493
|
}
|
|
485
|
-
/** Sets the name of this
|
|
494
|
+
/** Sets the name of this node. The name is stored as a property with the key 'name'. */
|
|
486
495
|
set name(name) {
|
|
487
|
-
this.tree.
|
|
496
|
+
this.tree.setNodeProperty(this.id, "name", name);
|
|
488
497
|
}
|
|
489
|
-
/** Returns the creation date of this
|
|
498
|
+
/** Returns the creation date of this node. The creation date is stored as a property with the key '_c'. */
|
|
490
499
|
get createdAt() {
|
|
491
500
|
const createdAt = this.getProperty("_c");
|
|
492
501
|
if (!createdAt) {
|
|
@@ -494,41 +503,41 @@ var Vertex = class {
|
|
|
494
503
|
}
|
|
495
504
|
return new Date(createdAt);
|
|
496
505
|
}
|
|
497
|
-
/** Returns the ID of the parent
|
|
506
|
+
/** Returns the ID of the parent node of this node. */
|
|
498
507
|
get parentId() {
|
|
499
508
|
return this.state.parentId;
|
|
500
509
|
}
|
|
501
|
-
/** Returns the parent
|
|
510
|
+
/** Returns the parent node of this node. */
|
|
502
511
|
get parent() {
|
|
503
512
|
if (!this.parentId) {
|
|
504
513
|
return void 0;
|
|
505
514
|
}
|
|
506
|
-
return this.tree.
|
|
515
|
+
return this.tree.getNode(this.parentId);
|
|
507
516
|
}
|
|
508
|
-
/** Returns the children
|
|
517
|
+
/** Returns the children nodes of this node. */
|
|
509
518
|
get children() {
|
|
510
519
|
return this.tree.getChildren(this.id);
|
|
511
520
|
}
|
|
512
|
-
/** Returns the IDs of the children
|
|
521
|
+
/** Returns the IDs of the children nodes of this node. */
|
|
513
522
|
get childrenIds() {
|
|
514
523
|
return this.tree.getChildrenIds(this.id);
|
|
515
524
|
}
|
|
516
|
-
/** Returns the ancestors of this
|
|
525
|
+
/** Returns the ancestors of this node. The first element is the root node.
|
|
517
526
|
* E.g root -> grandparent -> parent.
|
|
518
|
-
* Doesn't include this
|
|
527
|
+
* Doesn't include this node in the array.
|
|
519
528
|
*/
|
|
520
529
|
get ancestors() {
|
|
521
530
|
return this.tree.getAncestors(this.id);
|
|
522
531
|
}
|
|
523
|
-
/** Returns the ID of the root
|
|
532
|
+
/** Returns the ID of the root node of the tree this node belongs to. */
|
|
524
533
|
get treeId() {
|
|
525
534
|
return this.root.id;
|
|
526
535
|
}
|
|
527
|
-
/** Returns the root
|
|
536
|
+
/** Returns the root node of the tree this node belongs to. */
|
|
528
537
|
get root() {
|
|
529
538
|
const root = this.tree.root;
|
|
530
539
|
if (!root) {
|
|
531
|
-
throw new Error("Root
|
|
540
|
+
throw new Error("Root node of the tree is not set");
|
|
532
541
|
}
|
|
533
542
|
return root;
|
|
534
543
|
}
|
|
@@ -538,48 +547,48 @@ var Vertex = class {
|
|
|
538
547
|
getChildrenAsTypedArray() {
|
|
539
548
|
return this.children.map((v) => v.getAsTypedObject());
|
|
540
549
|
}
|
|
541
|
-
/** Creates a new child
|
|
550
|
+
/** Creates a new child node of this node. */
|
|
542
551
|
newChild(props) {
|
|
543
|
-
return this.tree.
|
|
552
|
+
return this.tree.newNode(this.id, props);
|
|
544
553
|
}
|
|
545
|
-
/** Creates a new named child
|
|
554
|
+
/** Creates a new named child node of this node. */
|
|
546
555
|
newNamedChild(name, props) {
|
|
547
|
-
return this.tree.
|
|
556
|
+
return this.tree.newNamedNode(this.id, name, props);
|
|
548
557
|
}
|
|
549
|
-
/** Sets a property on this
|
|
558
|
+
/** Sets a property on this node. */
|
|
550
559
|
setProperty(key, value) {
|
|
551
560
|
const existingValue = this.getProperty(key, false);
|
|
552
561
|
if (existingValue === value) {
|
|
553
562
|
return;
|
|
554
563
|
}
|
|
555
|
-
this.tree.
|
|
564
|
+
this.tree.setNodeProperty(this.id, key, value);
|
|
556
565
|
}
|
|
557
|
-
/** Sets a transient property on this
|
|
566
|
+
/** Sets a transient property on this node. Transient properties are not persisted to the tree and are not included in the state vector. */
|
|
558
567
|
setTransientProperty(key, value) {
|
|
559
568
|
const existingValue = this.getProperty(key);
|
|
560
569
|
if (existingValue === value) {
|
|
561
570
|
return;
|
|
562
571
|
}
|
|
563
|
-
this.tree.
|
|
572
|
+
this.tree.setTransientNodeProperty(this.id, key, value);
|
|
564
573
|
}
|
|
565
574
|
/** Promotes all transient (temporary) properties to persistent properties. */
|
|
566
575
|
commitTransients() {
|
|
567
576
|
this.tree.commitTransients(this.id);
|
|
568
577
|
}
|
|
569
|
-
/** Sets multiple properties on this
|
|
578
|
+
/** Sets multiple properties on this node. */
|
|
570
579
|
setProperties(props) {
|
|
571
580
|
for (const [key, value] of Object.entries(props)) {
|
|
572
581
|
this.setProperty(key, value);
|
|
573
582
|
}
|
|
574
583
|
}
|
|
575
|
-
/** Returns the value of a property on this
|
|
584
|
+
/** Returns the value of a property on this node. */
|
|
576
585
|
getProperty(key, includingTransient = true) {
|
|
577
|
-
return this.tree.
|
|
586
|
+
return this.tree.getNodeProperty(this.id, key, includingTransient);
|
|
578
587
|
}
|
|
579
|
-
/** Returns all properties on this
|
|
588
|
+
/** Returns all properties on this node. */
|
|
580
589
|
getProperties() {
|
|
581
590
|
const props = {};
|
|
582
|
-
this.tree.
|
|
591
|
+
this.tree.getNodeProperties(this.id).forEach((p) => {
|
|
583
592
|
props[p.key] = p.value;
|
|
584
593
|
});
|
|
585
594
|
return props;
|
|
@@ -587,21 +596,21 @@ var Vertex = class {
|
|
|
587
596
|
findAllChildrenWithProperty(key, value) {
|
|
588
597
|
return this.children.filter((c) => c.getProperty(key) === value);
|
|
589
598
|
}
|
|
590
|
-
|
|
599
|
+
findFirstChildNodeWithProperty(key, value) {
|
|
591
600
|
return this.children.find((c) => c.getProperty(key) === value);
|
|
592
601
|
}
|
|
593
602
|
findFirstTypedChildWithProperty(key, value) {
|
|
594
|
-
return this.
|
|
603
|
+
return this.findFirstChildNodeWithProperty(key, value)?.getAsTypedObject();
|
|
595
604
|
}
|
|
596
605
|
findAllTypedChildrenWithProperty(key, value) {
|
|
597
606
|
return this.findAllChildrenWithProperty(key, value).map((c) => c.getAsTypedObject());
|
|
598
607
|
}
|
|
599
|
-
/** Observes changes to this
|
|
608
|
+
/** Observes changes to this node. */
|
|
600
609
|
observe(listener) {
|
|
601
610
|
const unobserve = this.tree.observe(this.id, listener);
|
|
602
611
|
return () => unobserve();
|
|
603
612
|
}
|
|
604
|
-
/** Observes changes to the children of this
|
|
613
|
+
/** Observes changes to the children of this node. */
|
|
605
614
|
observeChildren(listener) {
|
|
606
615
|
const unobserve = this.tree.observe(this.id, (events) => {
|
|
607
616
|
if (events.some((e) => e.type === "children")) {
|
|
@@ -616,14 +625,14 @@ var Vertex = class {
|
|
|
616
625
|
});
|
|
617
626
|
}
|
|
618
627
|
delete() {
|
|
619
|
-
this.tree.
|
|
628
|
+
this.tree.deleteNode(this.id);
|
|
620
629
|
}
|
|
621
630
|
moveTo(parent) {
|
|
622
|
-
this.tree.
|
|
631
|
+
this.tree.moveNode(this.id, parent.id);
|
|
623
632
|
}
|
|
624
|
-
/** Returns a live reactive object bound to this
|
|
633
|
+
/** Returns a live reactive object bound to this node. Accepts schema or options. */
|
|
625
634
|
bind(schemaOrOptions) {
|
|
626
|
-
return
|
|
635
|
+
return bindNode(this.tree, this.id, schemaOrOptions);
|
|
627
636
|
}
|
|
628
637
|
};
|
|
629
638
|
|
|
@@ -674,7 +683,7 @@ var StateVector = class _StateVector {
|
|
|
674
683
|
/**
|
|
675
684
|
* Updates the state vector with a newly applied operation.
|
|
676
685
|
* Assumes ranges are sorted and non-overlapping.
|
|
677
|
-
*
|
|
686
|
+
*
|
|
678
687
|
* @param peerId The peer ID of the operation
|
|
679
688
|
* @param counter The counter value of the operation
|
|
680
689
|
*/
|
|
@@ -735,7 +744,7 @@ var StateVector = class _StateVector {
|
|
|
735
744
|
}
|
|
736
745
|
/**
|
|
737
746
|
* Updates the state vector with a newly applied operation.
|
|
738
|
-
*
|
|
747
|
+
*
|
|
739
748
|
* @param op The operation that was just applied
|
|
740
749
|
*/
|
|
741
750
|
updateFromOp(op) {
|
|
@@ -751,7 +760,7 @@ var StateVector = class _StateVector {
|
|
|
751
760
|
/**
|
|
752
761
|
* Calculates which operation ranges we have that the other state vector is missing
|
|
753
762
|
* by comparing state vectors.
|
|
754
|
-
*
|
|
763
|
+
*
|
|
755
764
|
* @param other The other state vector to compare against
|
|
756
765
|
* @returns Array of operation ID ranges that we have but they don't
|
|
757
766
|
*/
|
|
@@ -775,7 +784,7 @@ var StateVector = class _StateVector {
|
|
|
775
784
|
}
|
|
776
785
|
/**
|
|
777
786
|
* Checks if the state vector contains the given operation ID
|
|
778
|
-
*
|
|
787
|
+
*
|
|
779
788
|
* @param opId The operation ID to check
|
|
780
789
|
* @returns true if the operation is in the state vector, false otherwise
|
|
781
790
|
*/
|
|
@@ -871,50 +880,54 @@ function isJsonValue(v) {
|
|
|
871
880
|
var _RepTree = class _RepTree {
|
|
872
881
|
/**
|
|
873
882
|
* @param peerId - The peer ID of the current client. Should be unique across all peers.
|
|
874
|
-
* @param ops - The operations to replicate an existing tree, if not provided - an empty tree will be created without a root
|
|
883
|
+
* @param ops - The operations to replicate an existing tree, if not provided - an empty tree will be created without a root node
|
|
875
884
|
*/
|
|
876
885
|
constructor(peerId, ops) {
|
|
877
|
-
this.
|
|
886
|
+
this.moveClock = 0;
|
|
887
|
+
this.propClock = 0;
|
|
878
888
|
this.moveOps = [];
|
|
879
|
-
this.
|
|
880
|
-
this.propertiesAndTheirOpIds = /* @__PURE__ */ new Map();
|
|
889
|
+
this.propertyOpsByKey = /* @__PURE__ */ new Map();
|
|
881
890
|
this.transientPropertiesAndTheirOpIds = /* @__PURE__ */ new Map();
|
|
882
891
|
this.localOps = [];
|
|
883
892
|
this.pendingMovesWithMissingParent = /* @__PURE__ */ new Map();
|
|
884
|
-
this.
|
|
893
|
+
this.pendingPropertiesWithMissingNode = /* @__PURE__ */ new Map();
|
|
885
894
|
this.knownOps = /* @__PURE__ */ new Set();
|
|
886
895
|
this.parentIdBeforeMove = /* @__PURE__ */ new Map();
|
|
887
896
|
this.opAppliedCallbacks = [];
|
|
888
897
|
this._stateVectorEnabled = true;
|
|
889
898
|
this.peerId = peerId;
|
|
890
899
|
this.state = new TreeState();
|
|
891
|
-
this.
|
|
900
|
+
this.moveStateVector = new StateVector();
|
|
901
|
+
this.propStateVector = new StateVector();
|
|
892
902
|
if (ops && ops.length > 0) {
|
|
893
903
|
this.applyOps(ops);
|
|
894
904
|
const root = this.root;
|
|
895
905
|
if (!root) {
|
|
896
|
-
throw new Error("There has to be a root
|
|
906
|
+
throw new Error("There has to be a root node in the operations");
|
|
897
907
|
}
|
|
898
908
|
} else {
|
|
899
|
-
this.
|
|
909
|
+
this.ensureNullNode();
|
|
900
910
|
}
|
|
901
911
|
}
|
|
912
|
+
dispose() {
|
|
913
|
+
this.state.dispose();
|
|
914
|
+
}
|
|
902
915
|
get root() {
|
|
903
|
-
if (!this.
|
|
904
|
-
const
|
|
905
|
-
for (const
|
|
906
|
-
if (
|
|
907
|
-
this.
|
|
908
|
-
return new
|
|
916
|
+
if (!this.rootNodeId) {
|
|
917
|
+
const nodes = this.state.getAllNodes();
|
|
918
|
+
for (const node of nodes) {
|
|
919
|
+
if (node.parentId === null && node.id !== _RepTree.NULL_NODE_ID) {
|
|
920
|
+
this.rootNodeId = node.id;
|
|
921
|
+
return new Node(this, node);
|
|
909
922
|
}
|
|
910
923
|
}
|
|
911
924
|
return void 0;
|
|
912
925
|
}
|
|
913
|
-
const
|
|
914
|
-
if (!
|
|
915
|
-
throw new Error("Root
|
|
926
|
+
const rootNode = this.state.getNode(this.rootNodeId);
|
|
927
|
+
if (!rootNode) {
|
|
928
|
+
throw new Error("Root node not found");
|
|
916
929
|
}
|
|
917
|
-
return new
|
|
930
|
+
return new Node(this, rootNode);
|
|
918
931
|
}
|
|
919
932
|
replicate(newPeerId) {
|
|
920
933
|
return new _RepTree(newPeerId, this.getAllOps());
|
|
@@ -923,54 +936,54 @@ var _RepTree = class _RepTree {
|
|
|
923
936
|
return this.moveOps;
|
|
924
937
|
}
|
|
925
938
|
getAllOps() {
|
|
926
|
-
return [...this.moveOps, ...this.
|
|
939
|
+
return [...this.moveOps, ...this.getPropertyOps()];
|
|
927
940
|
}
|
|
928
|
-
|
|
929
|
-
const
|
|
930
|
-
return
|
|
941
|
+
getNode(nodeId) {
|
|
942
|
+
const node = this.state.getNode(nodeId);
|
|
943
|
+
return node ? new Node(this, node) : void 0;
|
|
931
944
|
}
|
|
932
|
-
|
|
933
|
-
return this.state.
|
|
945
|
+
getAllNodes() {
|
|
946
|
+
return this.state.getAllNodes().map((v) => new Node(this, v));
|
|
934
947
|
}
|
|
935
|
-
getParent(
|
|
936
|
-
const parentId = this.state.
|
|
937
|
-
const parent = parentId ? this.state.
|
|
938
|
-
return parent ? new
|
|
948
|
+
getParent(nodeId) {
|
|
949
|
+
const parentId = this.state.getNode(nodeId)?.parentId;
|
|
950
|
+
const parent = parentId ? this.state.getNode(parentId) : void 0;
|
|
951
|
+
return parent ? new Node(this, parent) : void 0;
|
|
939
952
|
}
|
|
940
|
-
getChildren(
|
|
941
|
-
return this.state.getChildren(
|
|
953
|
+
getChildren(nodeId) {
|
|
954
|
+
return this.state.getChildren(nodeId).map((v) => new Node(this, v));
|
|
942
955
|
}
|
|
943
|
-
getChildrenIds(
|
|
944
|
-
return this.state.getChildrenIds(
|
|
956
|
+
getChildrenIds(nodeId) {
|
|
957
|
+
return this.state.getChildrenIds(nodeId);
|
|
945
958
|
}
|
|
946
|
-
/** Returns the ancestors of the given
|
|
947
|
-
getAncestors(
|
|
959
|
+
/** Returns the ancestors of the given node. The first element is the root node. */
|
|
960
|
+
getAncestors(nodeId) {
|
|
948
961
|
const ancestors = [];
|
|
949
|
-
let
|
|
950
|
-
while (
|
|
951
|
-
const
|
|
952
|
-
if (
|
|
953
|
-
ancestors.push(new
|
|
954
|
-
|
|
962
|
+
let currentNode = this.state.getNode(nodeId);
|
|
963
|
+
while (currentNode && currentNode.parentId) {
|
|
964
|
+
const parentNode = this.state.getNode(currentNode.parentId);
|
|
965
|
+
if (parentNode) {
|
|
966
|
+
ancestors.push(new Node(this, parentNode));
|
|
967
|
+
currentNode = parentNode;
|
|
955
968
|
} else {
|
|
956
969
|
break;
|
|
957
970
|
}
|
|
958
971
|
}
|
|
959
972
|
return ancestors;
|
|
960
973
|
}
|
|
961
|
-
|
|
962
|
-
const
|
|
963
|
-
if (!
|
|
974
|
+
getNodeProperty(nodeId, key, includingTransient = true) {
|
|
975
|
+
const node = this.state.getNode(nodeId);
|
|
976
|
+
if (!node) {
|
|
964
977
|
return void 0;
|
|
965
978
|
}
|
|
966
|
-
return
|
|
979
|
+
return node.getProperty(key, includingTransient);
|
|
967
980
|
}
|
|
968
|
-
|
|
969
|
-
const
|
|
970
|
-
if (!
|
|
981
|
+
getNodeProperties(nodeId) {
|
|
982
|
+
const node = this.state.getNode(nodeId);
|
|
983
|
+
if (!node) {
|
|
971
984
|
return [];
|
|
972
985
|
}
|
|
973
|
-
return
|
|
986
|
+
return node.getAllProperties();
|
|
974
987
|
}
|
|
975
988
|
/**
|
|
976
989
|
* Returns all local operations and clears the local operations list.
|
|
@@ -982,158 +995,144 @@ var _RepTree = class _RepTree {
|
|
|
982
995
|
return ops;
|
|
983
996
|
}
|
|
984
997
|
/**
|
|
985
|
-
* This is the first
|
|
998
|
+
* This is the first node that will contain all other nodes.
|
|
986
999
|
* If you plan to replicate a tree then don't use this method and instead merge
|
|
987
|
-
* in the ops from another tree (that will also contain the root
|
|
988
|
-
* @returns The root
|
|
1000
|
+
* in the ops from another tree (that will also contain the root node).
|
|
1001
|
+
* @returns The root node
|
|
989
1002
|
*/
|
|
990
1003
|
createRoot() {
|
|
991
|
-
if (this.
|
|
992
|
-
throw new Error("Root
|
|
1004
|
+
if (this.rootNodeId) {
|
|
1005
|
+
throw new Error("Root node already exists");
|
|
993
1006
|
}
|
|
994
|
-
this.
|
|
995
|
-
const
|
|
996
|
-
if (!
|
|
997
|
-
throw new Error("Root
|
|
1007
|
+
this.rootNodeId = this.newNodeInternalWithUUID(null);
|
|
1008
|
+
const rootNode = this.state.getNode(this.rootNodeId);
|
|
1009
|
+
if (!rootNode) {
|
|
1010
|
+
throw new Error("Root node not found");
|
|
998
1011
|
}
|
|
999
|
-
return new
|
|
1012
|
+
return new Node(this, rootNode);
|
|
1000
1013
|
}
|
|
1001
|
-
|
|
1014
|
+
newNode(parentId, props = null) {
|
|
1002
1015
|
const typedProps = props;
|
|
1003
|
-
const
|
|
1016
|
+
const nodeId = this.newNodeInternalWithUUID(parentId);
|
|
1004
1017
|
if (typedProps) {
|
|
1005
|
-
this.
|
|
1018
|
+
this.setNodeProperties(nodeId, typedProps);
|
|
1006
1019
|
}
|
|
1007
|
-
const
|
|
1008
|
-
if (!
|
|
1009
|
-
throw new Error("Failed to create
|
|
1020
|
+
const node = this.state.getNode(nodeId);
|
|
1021
|
+
if (!node) {
|
|
1022
|
+
throw new Error("Failed to create node");
|
|
1010
1023
|
}
|
|
1011
|
-
return new
|
|
1024
|
+
return new Node(this, node);
|
|
1012
1025
|
}
|
|
1013
|
-
|
|
1026
|
+
newNamedNode(parentId, name, props = null) {
|
|
1014
1027
|
const typedProps = props;
|
|
1015
|
-
const
|
|
1028
|
+
const nodeId = this.newNodeInternalWithUUID(parentId);
|
|
1016
1029
|
if (typedProps) {
|
|
1017
|
-
this.
|
|
1030
|
+
this.setNodeProperties(nodeId, typedProps);
|
|
1018
1031
|
}
|
|
1019
|
-
this.
|
|
1020
|
-
const
|
|
1021
|
-
if (!
|
|
1022
|
-
throw new Error("Failed to create named
|
|
1032
|
+
this.setNodeProperty(nodeId, "name", name);
|
|
1033
|
+
const node = this.state.getNode(nodeId);
|
|
1034
|
+
if (!node) {
|
|
1035
|
+
throw new Error("Failed to create named node");
|
|
1023
1036
|
}
|
|
1024
|
-
return new
|
|
1037
|
+
return new Node(this, node);
|
|
1025
1038
|
}
|
|
1026
|
-
|
|
1027
|
-
this.
|
|
1028
|
-
const op =
|
|
1039
|
+
moveNode(nodeId, parentId) {
|
|
1040
|
+
this.moveClock++;
|
|
1041
|
+
const op = newMoveNodeOp(this.moveClock, this.peerId, nodeId, parentId);
|
|
1029
1042
|
this.localOps.push(op);
|
|
1030
1043
|
this.applyMove(op);
|
|
1031
1044
|
}
|
|
1032
|
-
|
|
1033
|
-
this.
|
|
1045
|
+
deleteNode(nodeId) {
|
|
1046
|
+
this.moveNode(nodeId, _RepTree.NULL_NODE_ID);
|
|
1034
1047
|
}
|
|
1035
|
-
|
|
1048
|
+
setTransientNodeProperty(nodeId, key, value) {
|
|
1036
1049
|
if (!isJsonValue(value)) {
|
|
1037
1050
|
throw new Error(`Unsupported transient property value for key "${key}"`);
|
|
1038
1051
|
}
|
|
1039
|
-
this.
|
|
1040
|
-
const op =
|
|
1052
|
+
this.propClock++;
|
|
1053
|
+
const op = newSetTransientNodePropertyOp(this.propClock, this.peerId, nodeId, key, value);
|
|
1041
1054
|
this.localOps.push(op);
|
|
1042
1055
|
this.applyProperty(op);
|
|
1043
1056
|
}
|
|
1044
1057
|
/**
|
|
1045
1058
|
* Promotes all transient (temporary) properties to persistent properties.
|
|
1046
|
-
* @param
|
|
1047
|
-
* @returns
|
|
1059
|
+
* @param nodeId - The ID of the node to commit transients for.
|
|
1060
|
+
* @returns
|
|
1048
1061
|
*/
|
|
1049
|
-
commitTransients(
|
|
1050
|
-
const
|
|
1051
|
-
if (!
|
|
1062
|
+
commitTransients(nodeId) {
|
|
1063
|
+
const node = this.state.getNode(nodeId);
|
|
1064
|
+
if (!node) {
|
|
1052
1065
|
return;
|
|
1053
1066
|
}
|
|
1054
|
-
const transientProps =
|
|
1067
|
+
const transientProps = node.getTransientProperties();
|
|
1055
1068
|
for (const prop of transientProps) {
|
|
1056
|
-
this.
|
|
1069
|
+
this.setNodeProperty(nodeId, prop.key, prop.value);
|
|
1057
1070
|
}
|
|
1058
1071
|
for (const prop of transientProps) {
|
|
1059
|
-
this.transientPropertiesAndTheirOpIds.delete(`${prop.key}@${
|
|
1060
|
-
}
|
|
1061
|
-
|
|
1062
|
-
}
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
if (v === void 0) return true;
|
|
1066
|
-
if (v === null) return true;
|
|
1067
|
-
const t = typeof v;
|
|
1068
|
-
if (t === "string" || t === "number" || t === "boolean") return true;
|
|
1069
|
-
if (t === "bigint" || t === "function" || t === "symbol") return false;
|
|
1070
|
-
if (Array.isArray(v)) return v.every(isJsonValue2);
|
|
1071
|
-
if (t === "object") {
|
|
1072
|
-
if (v instanceof Date) return false;
|
|
1073
|
-
if (v instanceof Map || v instanceof Set || v instanceof RegExp) return false;
|
|
1074
|
-
if (ArrayBuffer.isView(v)) return false;
|
|
1075
|
-
const proto = Object.getPrototypeOf(v);
|
|
1076
|
-
if (proto !== Object.prototype && proto !== null) return false;
|
|
1077
|
-
for (const val of Object.values(v)) {
|
|
1078
|
-
if (!isJsonValue2(val)) return false;
|
|
1079
|
-
}
|
|
1080
|
-
return true;
|
|
1081
|
-
}
|
|
1082
|
-
return false;
|
|
1083
|
-
};
|
|
1084
|
-
if (!isJsonValue2(value)) {
|
|
1072
|
+
this.transientPropertiesAndTheirOpIds.delete(`${prop.key}@${nodeId}`);
|
|
1073
|
+
}
|
|
1074
|
+
node.clearAllTransientProperties();
|
|
1075
|
+
}
|
|
1076
|
+
setNodeProperty(nodeId, key, value) {
|
|
1077
|
+
if (!isJsonValue(value)) {
|
|
1085
1078
|
throw new Error(`Unsupported property value for key "${key}"`);
|
|
1086
1079
|
}
|
|
1087
|
-
this.
|
|
1088
|
-
const op =
|
|
1080
|
+
this.propClock++;
|
|
1081
|
+
const op = newSetNodePropertyOp(this.propClock, this.peerId, nodeId, key, value);
|
|
1089
1082
|
this.localOps.push(op);
|
|
1090
1083
|
this.applyProperty(op);
|
|
1091
1084
|
}
|
|
1092
|
-
|
|
1085
|
+
setNodeProperties(nodeId, props) {
|
|
1093
1086
|
const typedProps = props;
|
|
1094
1087
|
for (const [key, value] of Object.entries(typedProps)) {
|
|
1095
|
-
this.
|
|
1088
|
+
this.setNodeProperty(nodeId, key, value);
|
|
1096
1089
|
}
|
|
1097
1090
|
}
|
|
1098
|
-
|
|
1091
|
+
getNodeByPath(path) {
|
|
1099
1092
|
path = path.replace(/^\/+/, "");
|
|
1100
1093
|
path = path.replace(/\/+$/, "");
|
|
1101
|
-
const
|
|
1102
|
-
if (!
|
|
1094
|
+
const root = this.root;
|
|
1095
|
+
if (!root) {
|
|
1103
1096
|
return void 0;
|
|
1104
1097
|
}
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
throw new Error("The root vertex is not found");
|
|
1098
|
+
if (path === "") {
|
|
1099
|
+
return root;
|
|
1108
1100
|
}
|
|
1109
|
-
|
|
1110
|
-
return vertex;
|
|
1101
|
+
return this.getNodeByPathArray(root, path.split("/"));
|
|
1111
1102
|
}
|
|
1112
|
-
|
|
1103
|
+
getNodeByPathArray(node, path) {
|
|
1113
1104
|
if (path.length === 0) {
|
|
1114
|
-
return
|
|
1105
|
+
return node ?? void 0;
|
|
1115
1106
|
}
|
|
1116
1107
|
const targetName = path[0];
|
|
1117
|
-
const children = this.getChildren(
|
|
1108
|
+
const children = this.getChildren(node.id);
|
|
1118
1109
|
for (const child of children) {
|
|
1119
1110
|
if (child.getProperty("name") === targetName) {
|
|
1120
|
-
return this.
|
|
1111
|
+
return this.getNodeByPathArray(child, path.slice(1));
|
|
1121
1112
|
}
|
|
1122
1113
|
}
|
|
1123
1114
|
return void 0;
|
|
1124
1115
|
}
|
|
1125
1116
|
printTree() {
|
|
1126
|
-
if (!this.
|
|
1117
|
+
if (!this.rootNodeId) {
|
|
1127
1118
|
return "";
|
|
1128
1119
|
}
|
|
1129
|
-
return this.state.printTree(this.
|
|
1120
|
+
return this.state.printTree(this.rootNodeId);
|
|
1130
1121
|
}
|
|
1131
1122
|
merge(ops) {
|
|
1132
1123
|
this.applyOps(ops);
|
|
1133
1124
|
}
|
|
1134
1125
|
applyOps(ops) {
|
|
1135
|
-
|
|
1136
|
-
|
|
1126
|
+
const moveOps = ops.filter((op) => isMoveNodeOp(op));
|
|
1127
|
+
const propertyOps = ops.filter((op) => isAnyPropertyOp(op));
|
|
1128
|
+
for (const op of moveOps) {
|
|
1129
|
+
if (this.knownOps.has(this.getOpKey(op))) {
|
|
1130
|
+
continue;
|
|
1131
|
+
}
|
|
1132
|
+
this.applyOperation(op);
|
|
1133
|
+
}
|
|
1134
|
+
for (const op of propertyOps) {
|
|
1135
|
+
if (this.knownOps.has(this.getOpKey(op))) {
|
|
1137
1136
|
continue;
|
|
1138
1137
|
}
|
|
1139
1138
|
this.applyOperation(op);
|
|
@@ -1141,7 +1140,7 @@ var _RepTree = class _RepTree {
|
|
|
1141
1140
|
}
|
|
1142
1141
|
/** Applies operations in an optimized way, sorting move ops by OpId to avoid undo-do-redo cycles */
|
|
1143
1142
|
applyOpsOptimizedForLotsOfMoves(ops) {
|
|
1144
|
-
const newMoveOps = ops.filter((op) =>
|
|
1143
|
+
const newMoveOps = ops.filter((op) => isMoveNodeOp(op) && !this.knownOps.has(this.getOpKey(op)));
|
|
1145
1144
|
if (newMoveOps.length > 0) {
|
|
1146
1145
|
const allMoveOps = [...this.moveOps, ...newMoveOps];
|
|
1147
1146
|
allMoveOps.sort((a, b) => compareOpId(a.id, b.id));
|
|
@@ -1150,7 +1149,7 @@ var _RepTree = class _RepTree {
|
|
|
1150
1149
|
this.applyMove(op);
|
|
1151
1150
|
}
|
|
1152
1151
|
}
|
|
1153
|
-
const propertyOps = ops.filter((op) => isAnyPropertyOp(op) && !this.knownOps.has(
|
|
1152
|
+
const propertyOps = ops.filter((op) => isAnyPropertyOp(op) && !this.knownOps.has(this.getOpKey(op)));
|
|
1154
1153
|
for (let i = 0, len = propertyOps.length; i < len; i++) {
|
|
1155
1154
|
const op = propertyOps[i];
|
|
1156
1155
|
this.applyProperty(op);
|
|
@@ -1160,10 +1159,10 @@ var _RepTree = class _RepTree {
|
|
|
1160
1159
|
if (this.root?.id !== other.root?.id) {
|
|
1161
1160
|
return false;
|
|
1162
1161
|
}
|
|
1163
|
-
if (!this.
|
|
1162
|
+
if (!this.rootNodeId) {
|
|
1164
1163
|
return true;
|
|
1165
1164
|
}
|
|
1166
|
-
return _RepTree.
|
|
1165
|
+
return _RepTree.compareNodes(this.rootNodeId, this, other);
|
|
1167
1166
|
}
|
|
1168
1167
|
compareMoveOps(other) {
|
|
1169
1168
|
const movesA = this.moveOps;
|
|
@@ -1181,65 +1180,65 @@ var _RepTree = class _RepTree {
|
|
|
1181
1180
|
/** Checks if the given `ancestorId` is an ancestor of `childId` in the tree */
|
|
1182
1181
|
isAncestor(childId, ancestorId) {
|
|
1183
1182
|
let targetId = childId;
|
|
1184
|
-
let
|
|
1185
|
-
const
|
|
1186
|
-
while (
|
|
1187
|
-
if (
|
|
1188
|
-
if (!
|
|
1189
|
-
if (
|
|
1183
|
+
let node;
|
|
1184
|
+
const visitedNodes = /* @__PURE__ */ new Set();
|
|
1185
|
+
while (node = this.state.getNode(targetId)) {
|
|
1186
|
+
if (node.parentId === ancestorId) return true;
|
|
1187
|
+
if (!node.parentId) return false;
|
|
1188
|
+
if (visitedNodes.has(targetId)) {
|
|
1190
1189
|
console.error(`isAncestor: cycle detected in the tree structure.`);
|
|
1191
1190
|
return false;
|
|
1192
1191
|
}
|
|
1193
|
-
|
|
1194
|
-
targetId =
|
|
1192
|
+
visitedNodes.add(targetId);
|
|
1193
|
+
targetId = node.parentId;
|
|
1195
1194
|
}
|
|
1196
1195
|
return false;
|
|
1197
1196
|
}
|
|
1198
|
-
|
|
1199
|
-
const
|
|
1200
|
-
if (
|
|
1201
|
-
callback(
|
|
1197
|
+
observeNode(nodeId, callback) {
|
|
1198
|
+
const node = this.getNode(nodeId);
|
|
1199
|
+
if (node) {
|
|
1200
|
+
callback(node);
|
|
1202
1201
|
}
|
|
1203
|
-
const unsubscribe = this.observe(
|
|
1204
|
-
const
|
|
1205
|
-
if (
|
|
1206
|
-
callback(
|
|
1202
|
+
const unsubscribe = this.observe(nodeId, (_) => {
|
|
1203
|
+
const node2 = this.getNode(nodeId);
|
|
1204
|
+
if (node2) {
|
|
1205
|
+
callback(node2);
|
|
1207
1206
|
}
|
|
1208
1207
|
});
|
|
1209
1208
|
return () => {
|
|
1210
1209
|
unsubscribe();
|
|
1211
1210
|
};
|
|
1212
1211
|
}
|
|
1213
|
-
|
|
1212
|
+
observeNodeMove(callback) {
|
|
1214
1213
|
const listener = (events) => {
|
|
1215
1214
|
const moveEvent = events.find((e) => e.type === "move");
|
|
1216
1215
|
if (moveEvent) {
|
|
1217
|
-
const
|
|
1218
|
-
if (
|
|
1219
|
-
callback(
|
|
1216
|
+
const node = this.getNode(moveEvent.nodeId);
|
|
1217
|
+
if (node) {
|
|
1218
|
+
callback(node, moveEvent.oldParentId === void 0);
|
|
1220
1219
|
}
|
|
1221
1220
|
}
|
|
1222
1221
|
};
|
|
1223
1222
|
this.state.addGlobalChangeCallback(listener);
|
|
1224
1223
|
return () => this.state.removeGlobalChangeCallback(listener);
|
|
1225
1224
|
}
|
|
1226
|
-
observe(
|
|
1227
|
-
this.state.addChangeCallback(
|
|
1228
|
-
return () => this.state.removeChangeCallback(
|
|
1225
|
+
observe(nodeId, callback) {
|
|
1226
|
+
this.state.addChangeCallback(nodeId, callback);
|
|
1227
|
+
return () => this.state.removeChangeCallback(nodeId, callback);
|
|
1229
1228
|
}
|
|
1230
1229
|
observeOpApplied(callback) {
|
|
1231
1230
|
this.opAppliedCallbacks.push(callback);
|
|
1232
1231
|
return () => this.opAppliedCallbacks = this.opAppliedCallbacks.filter((l) => l !== callback);
|
|
1233
1232
|
}
|
|
1234
|
-
static
|
|
1235
|
-
const childrenA = treeA.state.getChildrenIds(
|
|
1236
|
-
const childrenB = treeB.state.getChildrenIds(
|
|
1233
|
+
static compareNodes(nodeId, treeA, treeB) {
|
|
1234
|
+
const childrenA = treeA.state.getChildrenIds(nodeId);
|
|
1235
|
+
const childrenB = treeB.state.getChildrenIds(nodeId);
|
|
1237
1236
|
if (childrenA.length !== childrenB.length) {
|
|
1238
1237
|
return false;
|
|
1239
1238
|
}
|
|
1240
|
-
if (
|
|
1241
|
-
const propertiesA = treeA.
|
|
1242
|
-
const propertiesB = treeB.
|
|
1239
|
+
if (nodeId !== null) {
|
|
1240
|
+
const propertiesA = treeA.getNodeProperties(nodeId);
|
|
1241
|
+
const propertiesB = treeB.getNodeProperties(nodeId);
|
|
1243
1242
|
if (propertiesA.length !== propertiesB.length) {
|
|
1244
1243
|
return false;
|
|
1245
1244
|
}
|
|
@@ -1253,39 +1252,44 @@ var _RepTree = class _RepTree {
|
|
|
1253
1252
|
if (!childrenB.includes(childId)) {
|
|
1254
1253
|
return false;
|
|
1255
1254
|
}
|
|
1256
|
-
if (!_RepTree.
|
|
1255
|
+
if (!_RepTree.compareNodes(childId, treeA, treeB)) {
|
|
1257
1256
|
return false;
|
|
1258
1257
|
}
|
|
1259
1258
|
}
|
|
1260
1259
|
return true;
|
|
1261
1260
|
}
|
|
1262
|
-
|
|
1263
|
-
this.
|
|
1264
|
-
const op =
|
|
1261
|
+
newNodeInternal(nodeId, parentId) {
|
|
1262
|
+
this.moveClock++;
|
|
1263
|
+
const op = newMoveNodeOp(this.moveClock, this.peerId, nodeId, parentId);
|
|
1265
1264
|
this.localOps.push(op);
|
|
1266
1265
|
this.applyMove(op);
|
|
1267
|
-
this.
|
|
1268
|
-
return
|
|
1266
|
+
this.setNodeProperty(nodeId, "_c", (/* @__PURE__ */ new Date()).toISOString());
|
|
1267
|
+
return nodeId;
|
|
1269
1268
|
}
|
|
1270
|
-
|
|
1271
|
-
const
|
|
1272
|
-
return this.
|
|
1269
|
+
newNodeInternalWithUUID(parentId) {
|
|
1270
|
+
const nodeId = uuid();
|
|
1271
|
+
return this.newNodeInternal(nodeId, parentId);
|
|
1273
1272
|
}
|
|
1274
|
-
|
|
1275
|
-
const
|
|
1276
|
-
if (this.state.
|
|
1273
|
+
ensureNullNode() {
|
|
1274
|
+
const nodeId = _RepTree.NULL_NODE_ID;
|
|
1275
|
+
if (this.state.getNode(nodeId)) {
|
|
1277
1276
|
return;
|
|
1278
1277
|
}
|
|
1279
|
-
this.
|
|
1278
|
+
this.newNodeInternal(nodeId, null);
|
|
1280
1279
|
}
|
|
1281
1280
|
/** Updates the lamport clock with the counter value of the operation */
|
|
1282
|
-
|
|
1283
|
-
if (operation.id.counter > this.
|
|
1284
|
-
this.
|
|
1281
|
+
updateMoveClock(operation) {
|
|
1282
|
+
if (operation.id.counter > this.moveClock) {
|
|
1283
|
+
this.moveClock = operation.id.counter;
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
updatePropClock(operation) {
|
|
1287
|
+
if (operation.id.counter > this.propClock) {
|
|
1288
|
+
this.propClock = operation.id.counter;
|
|
1285
1289
|
}
|
|
1286
1290
|
}
|
|
1287
1291
|
applyPendingMovesForParent(parentId) {
|
|
1288
|
-
if (!this.state.
|
|
1292
|
+
if (!this.state.getNode(parentId)) {
|
|
1289
1293
|
return;
|
|
1290
1294
|
}
|
|
1291
1295
|
const pendingMoves = this.pendingMovesWithMissingParent.get(parentId);
|
|
@@ -1298,14 +1302,15 @@ var _RepTree = class _RepTree {
|
|
|
1298
1302
|
}
|
|
1299
1303
|
}
|
|
1300
1304
|
applyMove(op) {
|
|
1301
|
-
if (op.parentId !== null && !this.state.
|
|
1305
|
+
if (op.parentId !== null && !this.state.getNode(op.parentId)) {
|
|
1302
1306
|
if (!this.pendingMovesWithMissingParent.has(op.parentId)) {
|
|
1303
1307
|
this.pendingMovesWithMissingParent.set(op.parentId, []);
|
|
1304
1308
|
}
|
|
1305
1309
|
this.pendingMovesWithMissingParent.get(op.parentId).push(op);
|
|
1310
|
+
this.markOpSeen(op, true);
|
|
1306
1311
|
return;
|
|
1307
1312
|
}
|
|
1308
|
-
this.
|
|
1313
|
+
this.updateMoveClock(op);
|
|
1309
1314
|
const lastOp = this.moveOps.length > 0 ? this.moveOps[this.moveOps.length - 1] : null;
|
|
1310
1315
|
if (lastOp === null || isOpIdGreaterThan(op.id, lastOp.id)) {
|
|
1311
1316
|
this.moveOps.push(op);
|
|
@@ -1332,130 +1337,138 @@ var _RepTree = class _RepTree {
|
|
|
1332
1337
|
this.applyPendingMovesForParent(op.targetId);
|
|
1333
1338
|
}
|
|
1334
1339
|
setLLWPropertyAndItsOpId(op) {
|
|
1335
|
-
this.
|
|
1340
|
+
this.propertyOpsByKey.set(`${op.key}@${op.targetId}`, op);
|
|
1336
1341
|
this.state.setProperty(op.targetId, op.key, op.value);
|
|
1337
|
-
this.reportOpAsApplied(op);
|
|
1342
|
+
this.reportOpAsApplied(op, false);
|
|
1343
|
+
this.refreshPropStateVector();
|
|
1338
1344
|
}
|
|
1339
1345
|
setTransientPropertyAndItsOpId(op) {
|
|
1340
1346
|
this.transientPropertiesAndTheirOpIds.set(`${op.key}@${op.targetId}`, op.id);
|
|
1341
1347
|
this.state.setTransientProperty(op.targetId, op.key, op.value);
|
|
1342
|
-
this.reportOpAsApplied(op);
|
|
1348
|
+
this.reportOpAsApplied(op, false);
|
|
1343
1349
|
}
|
|
1344
1350
|
applyProperty(op) {
|
|
1345
|
-
const
|
|
1346
|
-
if (!
|
|
1351
|
+
const targetNode = this.state.getNode(op.targetId);
|
|
1352
|
+
if (!targetNode) {
|
|
1347
1353
|
if (op.transient) {
|
|
1348
1354
|
return;
|
|
1349
1355
|
}
|
|
1350
|
-
if (!this.
|
|
1351
|
-
this.
|
|
1356
|
+
if (!this.pendingPropertiesWithMissingNode.has(op.targetId)) {
|
|
1357
|
+
this.pendingPropertiesWithMissingNode.set(op.targetId, []);
|
|
1352
1358
|
}
|
|
1353
|
-
this.
|
|
1359
|
+
this.pendingPropertiesWithMissingNode.get(op.targetId).push(op);
|
|
1360
|
+
this.markOpSeen(op, false);
|
|
1354
1361
|
return;
|
|
1355
1362
|
}
|
|
1356
|
-
this.
|
|
1357
|
-
this.applyLLWProperty(op,
|
|
1363
|
+
this.updatePropClock(op);
|
|
1364
|
+
this.applyLLWProperty(op, targetNode);
|
|
1358
1365
|
}
|
|
1359
|
-
applyLLWProperty(op,
|
|
1366
|
+
applyLLWProperty(op, targetNode) {
|
|
1360
1367
|
const prevTransientOpId = this.transientPropertiesAndTheirOpIds.get(`${op.key}@${op.targetId}`);
|
|
1361
|
-
const prevOpId = this.
|
|
1368
|
+
const prevOpId = this.propertyOpsByKey.get(`${op.key}@${op.targetId}`)?.id;
|
|
1362
1369
|
if (!op.transient) {
|
|
1363
|
-
this.setPropertyOps.push(op);
|
|
1364
1370
|
if (!prevOpId || isOpIdGreaterThan(op.id, prevOpId)) {
|
|
1365
1371
|
this.setLLWPropertyAndItsOpId(op);
|
|
1366
1372
|
} else {
|
|
1367
|
-
this.
|
|
1373
|
+
this.markOpSeen(op, false);
|
|
1368
1374
|
}
|
|
1369
1375
|
if (prevTransientOpId && isOpIdGreaterThan(op.id, prevTransientOpId)) {
|
|
1370
1376
|
this.transientPropertiesAndTheirOpIds.delete(`${op.key}@${op.targetId}`);
|
|
1371
|
-
|
|
1377
|
+
targetNode.removeTransientProperty(op.key);
|
|
1372
1378
|
}
|
|
1373
1379
|
} else {
|
|
1374
1380
|
if (!prevTransientOpId || isOpIdGreaterThan(op.id, prevTransientOpId)) {
|
|
1375
1381
|
this.setTransientPropertyAndItsOpId(op);
|
|
1382
|
+
} else {
|
|
1383
|
+
this.markOpSeen(op, false);
|
|
1376
1384
|
}
|
|
1377
1385
|
}
|
|
1378
1386
|
}
|
|
1379
1387
|
applyOperation(op) {
|
|
1380
|
-
if (
|
|
1388
|
+
if (isMoveNodeOp(op)) {
|
|
1381
1389
|
this.applyMove(op);
|
|
1382
1390
|
} else if (isAnyPropertyOp(op)) {
|
|
1383
1391
|
this.applyProperty(op);
|
|
1384
1392
|
}
|
|
1385
1393
|
}
|
|
1386
|
-
|
|
1387
|
-
this.knownOps.add(
|
|
1388
|
-
if (this._stateVectorEnabled) {
|
|
1389
|
-
|
|
1394
|
+
markOpSeen(op, includeInStateVector) {
|
|
1395
|
+
this.knownOps.add(this.getOpKey(op));
|
|
1396
|
+
if (includeInStateVector && this._stateVectorEnabled) {
|
|
1397
|
+
if (isMoveNodeOp(op)) {
|
|
1398
|
+
this.moveStateVector.updateFromOp(op);
|
|
1399
|
+
} else if (isAnyPropertyOp(op)) {
|
|
1400
|
+
this.propStateVector.updateFromOp(op);
|
|
1401
|
+
}
|
|
1390
1402
|
}
|
|
1403
|
+
}
|
|
1404
|
+
reportOpAsApplied(op, includeInStateVector = true) {
|
|
1405
|
+
this.markOpSeen(op, includeInStateVector);
|
|
1391
1406
|
for (const callback of this.opAppliedCallbacks) {
|
|
1392
1407
|
callback(op);
|
|
1393
1408
|
}
|
|
1394
1409
|
}
|
|
1395
1410
|
tryToMove(op) {
|
|
1396
|
-
let
|
|
1397
|
-
if (
|
|
1398
|
-
this.parentIdBeforeMove.set(op.id,
|
|
1411
|
+
let targetNode = this.state.getNode(op.targetId);
|
|
1412
|
+
if (targetNode) {
|
|
1413
|
+
this.parentIdBeforeMove.set(op.id, targetNode.parentId);
|
|
1399
1414
|
}
|
|
1400
1415
|
if (op.targetId === op.parentId) return;
|
|
1401
1416
|
if (op.parentId && this.isAncestor(op.parentId, op.targetId)) return;
|
|
1402
|
-
this.state.
|
|
1403
|
-
if (!
|
|
1404
|
-
const pendingProperties = this.
|
|
1405
|
-
this.
|
|
1417
|
+
this.state.moveNode(op.targetId, op.parentId);
|
|
1418
|
+
if (!targetNode) {
|
|
1419
|
+
const pendingProperties = this.pendingPropertiesWithMissingNode.get(op.targetId) || [];
|
|
1420
|
+
this.pendingPropertiesWithMissingNode.delete(op.targetId);
|
|
1406
1421
|
for (const prop of pendingProperties) {
|
|
1407
1422
|
this.applyProperty(prop);
|
|
1408
1423
|
}
|
|
1409
1424
|
}
|
|
1410
1425
|
}
|
|
1411
1426
|
undoMove(op) {
|
|
1412
|
-
const
|
|
1413
|
-
if (!
|
|
1414
|
-
console.error(`An attempt to undo move operation ${opIdToString(op.id)} failed because the target
|
|
1427
|
+
const targetNode = this.state.getNode(op.targetId);
|
|
1428
|
+
if (!targetNode) {
|
|
1429
|
+
console.error(`An attempt to undo move operation ${opIdToString(op.id)} failed because the target node ${op.targetId} not found`);
|
|
1415
1430
|
return;
|
|
1416
1431
|
}
|
|
1417
1432
|
const prevParentId = this.parentIdBeforeMove.get(op.id);
|
|
1418
1433
|
if (prevParentId === void 0) {
|
|
1419
1434
|
return;
|
|
1420
1435
|
}
|
|
1421
|
-
this.state.
|
|
1436
|
+
this.state.moveNode(op.targetId, prevParentId);
|
|
1422
1437
|
}
|
|
1423
|
-
// --- Range-Based State Vector Methods ---
|
|
1438
|
+
// --- Range-Based State Vector Methods ---
|
|
1424
1439
|
/**
|
|
1425
|
-
* Returns the current state
|
|
1426
|
-
* Returns
|
|
1440
|
+
* Returns the current state vectors for move and property streams.
|
|
1441
|
+
* Returns readonly references to the internal state vectors.
|
|
1427
1442
|
*/
|
|
1428
|
-
|
|
1443
|
+
getStateVectors() {
|
|
1429
1444
|
if (!this._stateVectorEnabled) {
|
|
1430
1445
|
return null;
|
|
1431
1446
|
}
|
|
1432
|
-
return
|
|
1447
|
+
return {
|
|
1448
|
+
move: this.moveStateVector.getState(),
|
|
1449
|
+
prop: this.propStateVector.getState()
|
|
1450
|
+
};
|
|
1433
1451
|
}
|
|
1434
1452
|
/**
|
|
1435
|
-
* Determines which operations are needed to synchronize
|
|
1453
|
+
* Determines which operations are needed to synchronize
|
|
1436
1454
|
* with the provided state vector.
|
|
1437
|
-
*
|
|
1438
|
-
* @param
|
|
1439
|
-
* @returns Operations that should be sent to the other peer, sorted by OpId.
|
|
1455
|
+
*
|
|
1456
|
+
* @param theirStateVectors The state vectors from another peer
|
|
1457
|
+
* @returns Operations that should be sent to the other peer, sorted by OpId within each stream.
|
|
1440
1458
|
*/
|
|
1441
|
-
getMissingOps(
|
|
1459
|
+
getMissingOps(theirStateVectors) {
|
|
1442
1460
|
if (!this._stateVectorEnabled) {
|
|
1443
|
-
return [...this.moveOps, ...this.
|
|
1444
|
-
}
|
|
1445
|
-
const
|
|
1446
|
-
const
|
|
1447
|
-
const
|
|
1448
|
-
const
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
}
|
|
1455
|
-
}
|
|
1456
|
-
}
|
|
1457
|
-
missingOps.sort((a, b) => compareOpId(a.id, b.id));
|
|
1458
|
-
return missingOps;
|
|
1461
|
+
return [...this.moveOps, ...this.getPropertyOps()];
|
|
1462
|
+
}
|
|
1463
|
+
const otherMoveStateVector = new StateVector(theirStateVectors.move);
|
|
1464
|
+
const otherPropStateVector = new StateVector(theirStateVectors.prop);
|
|
1465
|
+
const missingMoveRanges = this.moveStateVector.diff(otherMoveStateVector);
|
|
1466
|
+
const missingPropRanges = this.propStateVector.diff(otherPropStateVector);
|
|
1467
|
+
const missingMoveOps = this.filterOpsByRanges(this.moveOps, missingMoveRanges);
|
|
1468
|
+
const missingPropOps = this.filterOpsByRanges(this.getPropertyOps(), missingPropRanges);
|
|
1469
|
+
missingMoveOps.sort((a, b) => compareOpId(a.id, b.id));
|
|
1470
|
+
missingPropOps.sort((a, b) => compareOpId(a.id, b.id));
|
|
1471
|
+
return [...missingMoveOps, ...missingPropOps];
|
|
1459
1472
|
}
|
|
1460
1473
|
/**
|
|
1461
1474
|
* Gets or sets whether state vector tracking is enabled
|
|
@@ -1471,42 +1484,69 @@ var _RepTree = class _RepTree {
|
|
|
1471
1484
|
if (value === this._stateVectorEnabled) return;
|
|
1472
1485
|
if (value) {
|
|
1473
1486
|
this._stateVectorEnabled = true;
|
|
1474
|
-
this.
|
|
1487
|
+
this.moveStateVector = StateVector.fromOperations(this.moveOps);
|
|
1488
|
+
this.propStateVector = StateVector.fromOperations(this.getPropertyOps());
|
|
1475
1489
|
} else {
|
|
1476
1490
|
this._stateVectorEnabled = false;
|
|
1477
|
-
this.
|
|
1491
|
+
this.moveStateVector = new StateVector();
|
|
1492
|
+
this.propStateVector = new StateVector();
|
|
1478
1493
|
}
|
|
1479
1494
|
}
|
|
1480
1495
|
/**
|
|
1481
|
-
* Parses the
|
|
1496
|
+
* Parses the node properties with a provided schema that has a `parse` method (e.g., Zod schema)
|
|
1482
1497
|
*/
|
|
1483
|
-
|
|
1484
|
-
const propsArray = this.
|
|
1498
|
+
parseNode(nodeId, schema) {
|
|
1499
|
+
const propsArray = this.getNodeProperties(nodeId);
|
|
1485
1500
|
const propsObject = {};
|
|
1486
1501
|
for (const { key, value } of propsArray) {
|
|
1487
1502
|
propsObject[key] = value;
|
|
1488
1503
|
}
|
|
1489
1504
|
return schema.parse(propsObject);
|
|
1490
1505
|
}
|
|
1506
|
+
getPropertyOps() {
|
|
1507
|
+
return Array.from(this.propertyOpsByKey.values());
|
|
1508
|
+
}
|
|
1509
|
+
refreshPropStateVector() {
|
|
1510
|
+
if (!this._stateVectorEnabled) {
|
|
1511
|
+
return;
|
|
1512
|
+
}
|
|
1513
|
+
this.propStateVector = StateVector.fromOperations(this.getPropertyOps());
|
|
1514
|
+
}
|
|
1515
|
+
getOpKey(op) {
|
|
1516
|
+
const stream = isMoveNodeOp(op) ? "move" : "prop";
|
|
1517
|
+
return `${stream}:${opIdToString(op.id)}`;
|
|
1518
|
+
}
|
|
1519
|
+
filterOpsByRanges(ops, ranges) {
|
|
1520
|
+
const missingOps = [];
|
|
1521
|
+
for (const op of ops) {
|
|
1522
|
+
for (const range of ranges) {
|
|
1523
|
+
if (op.id.peerId === range.peerId && op.id.counter >= range.start && op.id.counter <= range.end) {
|
|
1524
|
+
missingOps.push(op);
|
|
1525
|
+
break;
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
return missingOps;
|
|
1530
|
+
}
|
|
1491
1531
|
};
|
|
1492
|
-
_RepTree.
|
|
1532
|
+
_RepTree.NULL_NODE_ID = "0";
|
|
1493
1533
|
var RepTree = _RepTree;
|
|
1494
1534
|
export {
|
|
1535
|
+
Node,
|
|
1536
|
+
NodeState,
|
|
1495
1537
|
RepTree,
|
|
1496
1538
|
StateVector,
|
|
1497
1539
|
TreeState,
|
|
1498
|
-
|
|
1499
|
-
VertexState,
|
|
1500
|
-
bindVertex,
|
|
1540
|
+
bindNode,
|
|
1501
1541
|
compareOpId,
|
|
1502
1542
|
createOpId,
|
|
1503
1543
|
equalsOpId,
|
|
1504
1544
|
isAnyPropertyOp,
|
|
1505
|
-
|
|
1545
|
+
isMoveNodeOp,
|
|
1506
1546
|
isOpIdGreaterThan,
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1547
|
+
newMoveNodeOp,
|
|
1548
|
+
newSetNodePropertyOp,
|
|
1549
|
+
newSetTransientNodePropertyOp,
|
|
1510
1550
|
opIdToString,
|
|
1511
1551
|
tryParseOpIdStr,
|
|
1512
1552
|
uuid
|