reptree 0.8.2 → 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 -464
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +190 -179
- package/dist/index.d.ts +190 -179
- package/dist/index.js +437 -457
- 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 _Vertex {
|
|
|
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,56 +547,48 @@ var Vertex = class _Vertex {
|
|
|
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
|
-
|
|
544
|
-
throw new Error("Passing children inside props is not supported at the moment");
|
|
545
|
-
}
|
|
546
|
-
const normalized = _Vertex.normalizePropsForCreation(props);
|
|
547
|
-
return this.tree.newVertex(this.id, normalized);
|
|
552
|
+
return this.tree.newNode(this.id, props);
|
|
548
553
|
}
|
|
549
|
-
/** Creates a new named child
|
|
554
|
+
/** Creates a new named child node of this node. */
|
|
550
555
|
newNamedChild(name, props) {
|
|
551
|
-
|
|
552
|
-
throw new Error("Passing children inside props is not supported at the moment");
|
|
553
|
-
}
|
|
554
|
-
const normalized = _Vertex.normalizePropsForCreation(props);
|
|
555
|
-
return this.tree.newNamedVertex(this.id, name, normalized);
|
|
556
|
+
return this.tree.newNamedNode(this.id, name, props);
|
|
556
557
|
}
|
|
557
|
-
/** Sets a property on this
|
|
558
|
+
/** Sets a property on this node. */
|
|
558
559
|
setProperty(key, value) {
|
|
559
560
|
const existingValue = this.getProperty(key, false);
|
|
560
561
|
if (existingValue === value) {
|
|
561
562
|
return;
|
|
562
563
|
}
|
|
563
|
-
this.tree.
|
|
564
|
+
this.tree.setNodeProperty(this.id, key, value);
|
|
564
565
|
}
|
|
565
|
-
/** 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. */
|
|
566
567
|
setTransientProperty(key, value) {
|
|
567
568
|
const existingValue = this.getProperty(key);
|
|
568
569
|
if (existingValue === value) {
|
|
569
570
|
return;
|
|
570
571
|
}
|
|
571
|
-
this.tree.
|
|
572
|
+
this.tree.setTransientNodeProperty(this.id, key, value);
|
|
572
573
|
}
|
|
573
574
|
/** Promotes all transient (temporary) properties to persistent properties. */
|
|
574
575
|
commitTransients() {
|
|
575
576
|
this.tree.commitTransients(this.id);
|
|
576
577
|
}
|
|
577
|
-
/** Sets multiple properties on this
|
|
578
|
+
/** Sets multiple properties on this node. */
|
|
578
579
|
setProperties(props) {
|
|
579
580
|
for (const [key, value] of Object.entries(props)) {
|
|
580
581
|
this.setProperty(key, value);
|
|
581
582
|
}
|
|
582
583
|
}
|
|
583
|
-
/** Returns the value of a property on this
|
|
584
|
+
/** Returns the value of a property on this node. */
|
|
584
585
|
getProperty(key, includingTransient = true) {
|
|
585
|
-
return this.tree.
|
|
586
|
+
return this.tree.getNodeProperty(this.id, key, includingTransient);
|
|
586
587
|
}
|
|
587
|
-
/** Returns all properties on this
|
|
588
|
+
/** Returns all properties on this node. */
|
|
588
589
|
getProperties() {
|
|
589
590
|
const props = {};
|
|
590
|
-
this.tree.
|
|
591
|
+
this.tree.getNodeProperties(this.id).forEach((p) => {
|
|
591
592
|
props[p.key] = p.value;
|
|
592
593
|
});
|
|
593
594
|
return props;
|
|
@@ -595,21 +596,21 @@ var Vertex = class _Vertex {
|
|
|
595
596
|
findAllChildrenWithProperty(key, value) {
|
|
596
597
|
return this.children.filter((c) => c.getProperty(key) === value);
|
|
597
598
|
}
|
|
598
|
-
|
|
599
|
+
findFirstChildNodeWithProperty(key, value) {
|
|
599
600
|
return this.children.find((c) => c.getProperty(key) === value);
|
|
600
601
|
}
|
|
601
602
|
findFirstTypedChildWithProperty(key, value) {
|
|
602
|
-
return this.
|
|
603
|
+
return this.findFirstChildNodeWithProperty(key, value)?.getAsTypedObject();
|
|
603
604
|
}
|
|
604
605
|
findAllTypedChildrenWithProperty(key, value) {
|
|
605
606
|
return this.findAllChildrenWithProperty(key, value).map((c) => c.getAsTypedObject());
|
|
606
607
|
}
|
|
607
|
-
/** Observes changes to this
|
|
608
|
+
/** Observes changes to this node. */
|
|
608
609
|
observe(listener) {
|
|
609
610
|
const unobserve = this.tree.observe(this.id, listener);
|
|
610
611
|
return () => unobserve();
|
|
611
612
|
}
|
|
612
|
-
/** Observes changes to the children of this
|
|
613
|
+
/** Observes changes to the children of this node. */
|
|
613
614
|
observeChildren(listener) {
|
|
614
615
|
const unobserve = this.tree.observe(this.id, (events) => {
|
|
615
616
|
if (events.some((e) => e.type === "children")) {
|
|
@@ -624,66 +625,14 @@ var Vertex = class _Vertex {
|
|
|
624
625
|
});
|
|
625
626
|
}
|
|
626
627
|
delete() {
|
|
627
|
-
this.tree.
|
|
628
|
+
this.tree.deleteNode(this.id);
|
|
628
629
|
}
|
|
629
630
|
moveTo(parent) {
|
|
630
|
-
this.tree.
|
|
631
|
+
this.tree.moveNode(this.id, parent.id);
|
|
631
632
|
}
|
|
632
|
-
/** Returns a live reactive object bound to this
|
|
633
|
+
/** Returns a live reactive object bound to this node. Accepts schema or options. */
|
|
633
634
|
bind(schemaOrOptions) {
|
|
634
|
-
return
|
|
635
|
-
}
|
|
636
|
-
/**
|
|
637
|
-
* Normalizes an input props object for vertex creation:
|
|
638
|
-
* - Filters unsupported field types with a console warning
|
|
639
|
-
* - When a name param is provided to newNamedChild, ignores conflicting name in props
|
|
640
|
-
*/
|
|
641
|
-
static normalizePropsForCreation(props) {
|
|
642
|
-
if (!props) return null;
|
|
643
|
-
const input = props;
|
|
644
|
-
const out = {};
|
|
645
|
-
const skipped = [];
|
|
646
|
-
const isJsonValue2 = (v) => {
|
|
647
|
-
if (v === null) return true;
|
|
648
|
-
const t = typeof v;
|
|
649
|
-
if (t === "string" || t === "number" || t === "boolean") return true;
|
|
650
|
-
if (Array.isArray(v)) return v.every(isJsonValue2);
|
|
651
|
-
if (t === "object") {
|
|
652
|
-
const proto = Object.getPrototypeOf(v);
|
|
653
|
-
if (proto !== Object.prototype && proto !== null) return false;
|
|
654
|
-
for (const val of Object.values(v)) {
|
|
655
|
-
if (!isJsonValue2(val)) return false;
|
|
656
|
-
}
|
|
657
|
-
return true;
|
|
658
|
-
}
|
|
659
|
-
return false;
|
|
660
|
-
};
|
|
661
|
-
for (const [rawKey, rawValue] of Object.entries(input)) {
|
|
662
|
-
if (rawValue === void 0) {
|
|
663
|
-
continue;
|
|
664
|
-
}
|
|
665
|
-
if (rawKey === "children") continue;
|
|
666
|
-
let key = rawKey;
|
|
667
|
-
let value = rawValue;
|
|
668
|
-
if (key === "_c") {
|
|
669
|
-
if (value instanceof Date) {
|
|
670
|
-
value = value.toISOString();
|
|
671
|
-
} else if (typeof value === "string") {
|
|
672
|
-
} else {
|
|
673
|
-
skipped.push(rawKey);
|
|
674
|
-
continue;
|
|
675
|
-
}
|
|
676
|
-
}
|
|
677
|
-
if (!isJsonValue2(value)) {
|
|
678
|
-
skipped.push(rawKey);
|
|
679
|
-
continue;
|
|
680
|
-
}
|
|
681
|
-
out[key] = value;
|
|
682
|
-
}
|
|
683
|
-
if (skipped.length > 0) {
|
|
684
|
-
throw new Error(`Unsupported property types for keys: ${skipped.join(", ")}`);
|
|
685
|
-
}
|
|
686
|
-
return Object.keys(out).length > 0 ? out : null;
|
|
635
|
+
return bindNode(this.tree, this.id, schemaOrOptions);
|
|
687
636
|
}
|
|
688
637
|
};
|
|
689
638
|
|
|
@@ -734,7 +683,7 @@ var StateVector = class _StateVector {
|
|
|
734
683
|
/**
|
|
735
684
|
* Updates the state vector with a newly applied operation.
|
|
736
685
|
* Assumes ranges are sorted and non-overlapping.
|
|
737
|
-
*
|
|
686
|
+
*
|
|
738
687
|
* @param peerId The peer ID of the operation
|
|
739
688
|
* @param counter The counter value of the operation
|
|
740
689
|
*/
|
|
@@ -795,7 +744,7 @@ var StateVector = class _StateVector {
|
|
|
795
744
|
}
|
|
796
745
|
/**
|
|
797
746
|
* Updates the state vector with a newly applied operation.
|
|
798
|
-
*
|
|
747
|
+
*
|
|
799
748
|
* @param op The operation that was just applied
|
|
800
749
|
*/
|
|
801
750
|
updateFromOp(op) {
|
|
@@ -811,7 +760,7 @@ var StateVector = class _StateVector {
|
|
|
811
760
|
/**
|
|
812
761
|
* Calculates which operation ranges we have that the other state vector is missing
|
|
813
762
|
* by comparing state vectors.
|
|
814
|
-
*
|
|
763
|
+
*
|
|
815
764
|
* @param other The other state vector to compare against
|
|
816
765
|
* @returns Array of operation ID ranges that we have but they don't
|
|
817
766
|
*/
|
|
@@ -835,7 +784,7 @@ var StateVector = class _StateVector {
|
|
|
835
784
|
}
|
|
836
785
|
/**
|
|
837
786
|
* Checks if the state vector contains the given operation ID
|
|
838
|
-
*
|
|
787
|
+
*
|
|
839
788
|
* @param opId The operation ID to check
|
|
840
789
|
* @returns true if the operation is in the state vector, false otherwise
|
|
841
790
|
*/
|
|
@@ -931,50 +880,54 @@ function isJsonValue(v) {
|
|
|
931
880
|
var _RepTree = class _RepTree {
|
|
932
881
|
/**
|
|
933
882
|
* @param peerId - The peer ID of the current client. Should be unique across all peers.
|
|
934
|
-
* @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
|
|
935
884
|
*/
|
|
936
885
|
constructor(peerId, ops) {
|
|
937
|
-
this.
|
|
886
|
+
this.moveClock = 0;
|
|
887
|
+
this.propClock = 0;
|
|
938
888
|
this.moveOps = [];
|
|
939
|
-
this.
|
|
940
|
-
this.propertiesAndTheirOpIds = /* @__PURE__ */ new Map();
|
|
889
|
+
this.propertyOpsByKey = /* @__PURE__ */ new Map();
|
|
941
890
|
this.transientPropertiesAndTheirOpIds = /* @__PURE__ */ new Map();
|
|
942
891
|
this.localOps = [];
|
|
943
892
|
this.pendingMovesWithMissingParent = /* @__PURE__ */ new Map();
|
|
944
|
-
this.
|
|
893
|
+
this.pendingPropertiesWithMissingNode = /* @__PURE__ */ new Map();
|
|
945
894
|
this.knownOps = /* @__PURE__ */ new Set();
|
|
946
895
|
this.parentIdBeforeMove = /* @__PURE__ */ new Map();
|
|
947
896
|
this.opAppliedCallbacks = [];
|
|
948
897
|
this._stateVectorEnabled = true;
|
|
949
898
|
this.peerId = peerId;
|
|
950
899
|
this.state = new TreeState();
|
|
951
|
-
this.
|
|
900
|
+
this.moveStateVector = new StateVector();
|
|
901
|
+
this.propStateVector = new StateVector();
|
|
952
902
|
if (ops && ops.length > 0) {
|
|
953
903
|
this.applyOps(ops);
|
|
954
904
|
const root = this.root;
|
|
955
905
|
if (!root) {
|
|
956
|
-
throw new Error("There has to be a root
|
|
906
|
+
throw new Error("There has to be a root node in the operations");
|
|
957
907
|
}
|
|
958
908
|
} else {
|
|
959
|
-
this.
|
|
909
|
+
this.ensureNullNode();
|
|
960
910
|
}
|
|
961
911
|
}
|
|
912
|
+
dispose() {
|
|
913
|
+
this.state.dispose();
|
|
914
|
+
}
|
|
962
915
|
get root() {
|
|
963
|
-
if (!this.
|
|
964
|
-
const
|
|
965
|
-
for (const
|
|
966
|
-
if (
|
|
967
|
-
this.
|
|
968
|
-
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);
|
|
969
922
|
}
|
|
970
923
|
}
|
|
971
924
|
return void 0;
|
|
972
925
|
}
|
|
973
|
-
const
|
|
974
|
-
if (!
|
|
975
|
-
throw new Error("Root
|
|
926
|
+
const rootNode = this.state.getNode(this.rootNodeId);
|
|
927
|
+
if (!rootNode) {
|
|
928
|
+
throw new Error("Root node not found");
|
|
976
929
|
}
|
|
977
|
-
return new
|
|
930
|
+
return new Node(this, rootNode);
|
|
978
931
|
}
|
|
979
932
|
replicate(newPeerId) {
|
|
980
933
|
return new _RepTree(newPeerId, this.getAllOps());
|
|
@@ -983,54 +936,54 @@ var _RepTree = class _RepTree {
|
|
|
983
936
|
return this.moveOps;
|
|
984
937
|
}
|
|
985
938
|
getAllOps() {
|
|
986
|
-
return [...this.moveOps, ...this.
|
|
939
|
+
return [...this.moveOps, ...this.getPropertyOps()];
|
|
987
940
|
}
|
|
988
|
-
|
|
989
|
-
const
|
|
990
|
-
return
|
|
941
|
+
getNode(nodeId) {
|
|
942
|
+
const node = this.state.getNode(nodeId);
|
|
943
|
+
return node ? new Node(this, node) : void 0;
|
|
991
944
|
}
|
|
992
|
-
|
|
993
|
-
return this.state.
|
|
945
|
+
getAllNodes() {
|
|
946
|
+
return this.state.getAllNodes().map((v) => new Node(this, v));
|
|
994
947
|
}
|
|
995
|
-
getParent(
|
|
996
|
-
const parentId = this.state.
|
|
997
|
-
const parent = parentId ? this.state.
|
|
998
|
-
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;
|
|
999
952
|
}
|
|
1000
|
-
getChildren(
|
|
1001
|
-
return this.state.getChildren(
|
|
953
|
+
getChildren(nodeId) {
|
|
954
|
+
return this.state.getChildren(nodeId).map((v) => new Node(this, v));
|
|
1002
955
|
}
|
|
1003
|
-
getChildrenIds(
|
|
1004
|
-
return this.state.getChildrenIds(
|
|
956
|
+
getChildrenIds(nodeId) {
|
|
957
|
+
return this.state.getChildrenIds(nodeId);
|
|
1005
958
|
}
|
|
1006
|
-
/** Returns the ancestors of the given
|
|
1007
|
-
getAncestors(
|
|
959
|
+
/** Returns the ancestors of the given node. The first element is the root node. */
|
|
960
|
+
getAncestors(nodeId) {
|
|
1008
961
|
const ancestors = [];
|
|
1009
|
-
let
|
|
1010
|
-
while (
|
|
1011
|
-
const
|
|
1012
|
-
if (
|
|
1013
|
-
ancestors.push(new
|
|
1014
|
-
|
|
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;
|
|
1015
968
|
} else {
|
|
1016
969
|
break;
|
|
1017
970
|
}
|
|
1018
971
|
}
|
|
1019
972
|
return ancestors;
|
|
1020
973
|
}
|
|
1021
|
-
|
|
1022
|
-
const
|
|
1023
|
-
if (!
|
|
974
|
+
getNodeProperty(nodeId, key, includingTransient = true) {
|
|
975
|
+
const node = this.state.getNode(nodeId);
|
|
976
|
+
if (!node) {
|
|
1024
977
|
return void 0;
|
|
1025
978
|
}
|
|
1026
|
-
return
|
|
979
|
+
return node.getProperty(key, includingTransient);
|
|
1027
980
|
}
|
|
1028
|
-
|
|
1029
|
-
const
|
|
1030
|
-
if (!
|
|
981
|
+
getNodeProperties(nodeId) {
|
|
982
|
+
const node = this.state.getNode(nodeId);
|
|
983
|
+
if (!node) {
|
|
1031
984
|
return [];
|
|
1032
985
|
}
|
|
1033
|
-
return
|
|
986
|
+
return node.getAllProperties();
|
|
1034
987
|
}
|
|
1035
988
|
/**
|
|
1036
989
|
* Returns all local operations and clears the local operations list.
|
|
@@ -1042,158 +995,144 @@ var _RepTree = class _RepTree {
|
|
|
1042
995
|
return ops;
|
|
1043
996
|
}
|
|
1044
997
|
/**
|
|
1045
|
-
* This is the first
|
|
998
|
+
* This is the first node that will contain all other nodes.
|
|
1046
999
|
* If you plan to replicate a tree then don't use this method and instead merge
|
|
1047
|
-
* in the ops from another tree (that will also contain the root
|
|
1048
|
-
* @returns The root
|
|
1000
|
+
* in the ops from another tree (that will also contain the root node).
|
|
1001
|
+
* @returns The root node
|
|
1049
1002
|
*/
|
|
1050
1003
|
createRoot() {
|
|
1051
|
-
if (this.
|
|
1052
|
-
throw new Error("Root
|
|
1004
|
+
if (this.rootNodeId) {
|
|
1005
|
+
throw new Error("Root node already exists");
|
|
1053
1006
|
}
|
|
1054
|
-
this.
|
|
1055
|
-
const
|
|
1056
|
-
if (!
|
|
1057
|
-
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");
|
|
1058
1011
|
}
|
|
1059
|
-
return new
|
|
1012
|
+
return new Node(this, rootNode);
|
|
1060
1013
|
}
|
|
1061
|
-
|
|
1014
|
+
newNode(parentId, props = null) {
|
|
1062
1015
|
const typedProps = props;
|
|
1063
|
-
const
|
|
1016
|
+
const nodeId = this.newNodeInternalWithUUID(parentId);
|
|
1064
1017
|
if (typedProps) {
|
|
1065
|
-
this.
|
|
1018
|
+
this.setNodeProperties(nodeId, typedProps);
|
|
1066
1019
|
}
|
|
1067
|
-
const
|
|
1068
|
-
if (!
|
|
1069
|
-
throw new Error("Failed to create
|
|
1020
|
+
const node = this.state.getNode(nodeId);
|
|
1021
|
+
if (!node) {
|
|
1022
|
+
throw new Error("Failed to create node");
|
|
1070
1023
|
}
|
|
1071
|
-
return new
|
|
1024
|
+
return new Node(this, node);
|
|
1072
1025
|
}
|
|
1073
|
-
|
|
1026
|
+
newNamedNode(parentId, name, props = null) {
|
|
1074
1027
|
const typedProps = props;
|
|
1075
|
-
const
|
|
1028
|
+
const nodeId = this.newNodeInternalWithUUID(parentId);
|
|
1076
1029
|
if (typedProps) {
|
|
1077
|
-
this.
|
|
1030
|
+
this.setNodeProperties(nodeId, typedProps);
|
|
1078
1031
|
}
|
|
1079
|
-
this.
|
|
1080
|
-
const
|
|
1081
|
-
if (!
|
|
1082
|
-
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");
|
|
1083
1036
|
}
|
|
1084
|
-
return new
|
|
1037
|
+
return new Node(this, node);
|
|
1085
1038
|
}
|
|
1086
|
-
|
|
1087
|
-
this.
|
|
1088
|
-
const op =
|
|
1039
|
+
moveNode(nodeId, parentId) {
|
|
1040
|
+
this.moveClock++;
|
|
1041
|
+
const op = newMoveNodeOp(this.moveClock, this.peerId, nodeId, parentId);
|
|
1089
1042
|
this.localOps.push(op);
|
|
1090
1043
|
this.applyMove(op);
|
|
1091
1044
|
}
|
|
1092
|
-
|
|
1093
|
-
this.
|
|
1045
|
+
deleteNode(nodeId) {
|
|
1046
|
+
this.moveNode(nodeId, _RepTree.NULL_NODE_ID);
|
|
1094
1047
|
}
|
|
1095
|
-
|
|
1048
|
+
setTransientNodeProperty(nodeId, key, value) {
|
|
1096
1049
|
if (!isJsonValue(value)) {
|
|
1097
1050
|
throw new Error(`Unsupported transient property value for key "${key}"`);
|
|
1098
1051
|
}
|
|
1099
|
-
this.
|
|
1100
|
-
const op =
|
|
1052
|
+
this.propClock++;
|
|
1053
|
+
const op = newSetTransientNodePropertyOp(this.propClock, this.peerId, nodeId, key, value);
|
|
1101
1054
|
this.localOps.push(op);
|
|
1102
1055
|
this.applyProperty(op);
|
|
1103
1056
|
}
|
|
1104
1057
|
/**
|
|
1105
1058
|
* Promotes all transient (temporary) properties to persistent properties.
|
|
1106
|
-
* @param
|
|
1107
|
-
* @returns
|
|
1059
|
+
* @param nodeId - The ID of the node to commit transients for.
|
|
1060
|
+
* @returns
|
|
1108
1061
|
*/
|
|
1109
|
-
commitTransients(
|
|
1110
|
-
const
|
|
1111
|
-
if (!
|
|
1062
|
+
commitTransients(nodeId) {
|
|
1063
|
+
const node = this.state.getNode(nodeId);
|
|
1064
|
+
if (!node) {
|
|
1112
1065
|
return;
|
|
1113
1066
|
}
|
|
1114
|
-
const transientProps =
|
|
1067
|
+
const transientProps = node.getTransientProperties();
|
|
1115
1068
|
for (const prop of transientProps) {
|
|
1116
|
-
this.
|
|
1069
|
+
this.setNodeProperty(nodeId, prop.key, prop.value);
|
|
1117
1070
|
}
|
|
1118
1071
|
for (const prop of transientProps) {
|
|
1119
|
-
this.transientPropertiesAndTheirOpIds.delete(`${prop.key}@${
|
|
1120
|
-
}
|
|
1121
|
-
|
|
1122
|
-
}
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
if (v === void 0) return true;
|
|
1126
|
-
if (v === null) return true;
|
|
1127
|
-
const t = typeof v;
|
|
1128
|
-
if (t === "string" || t === "number" || t === "boolean") return true;
|
|
1129
|
-
if (t === "bigint" || t === "function" || t === "symbol") return false;
|
|
1130
|
-
if (Array.isArray(v)) return v.every(isJsonValue2);
|
|
1131
|
-
if (t === "object") {
|
|
1132
|
-
if (v instanceof Date) return false;
|
|
1133
|
-
if (v instanceof Map || v instanceof Set || v instanceof RegExp) return false;
|
|
1134
|
-
if (ArrayBuffer.isView(v)) return false;
|
|
1135
|
-
const proto = Object.getPrototypeOf(v);
|
|
1136
|
-
if (proto !== Object.prototype && proto !== null) return false;
|
|
1137
|
-
for (const val of Object.values(v)) {
|
|
1138
|
-
if (!isJsonValue2(val)) return false;
|
|
1139
|
-
}
|
|
1140
|
-
return true;
|
|
1141
|
-
}
|
|
1142
|
-
return false;
|
|
1143
|
-
};
|
|
1144
|
-
if (!isJsonValue2(value)) {
|
|
1072
|
+
this.transientPropertiesAndTheirOpIds.delete(`${prop.key}@${nodeId}`);
|
|
1073
|
+
}
|
|
1074
|
+
node.clearAllTransientProperties();
|
|
1075
|
+
}
|
|
1076
|
+
setNodeProperty(nodeId, key, value) {
|
|
1077
|
+
if (!isJsonValue(value)) {
|
|
1145
1078
|
throw new Error(`Unsupported property value for key "${key}"`);
|
|
1146
1079
|
}
|
|
1147
|
-
this.
|
|
1148
|
-
const op =
|
|
1080
|
+
this.propClock++;
|
|
1081
|
+
const op = newSetNodePropertyOp(this.propClock, this.peerId, nodeId, key, value);
|
|
1149
1082
|
this.localOps.push(op);
|
|
1150
1083
|
this.applyProperty(op);
|
|
1151
1084
|
}
|
|
1152
|
-
|
|
1085
|
+
setNodeProperties(nodeId, props) {
|
|
1153
1086
|
const typedProps = props;
|
|
1154
1087
|
for (const [key, value] of Object.entries(typedProps)) {
|
|
1155
|
-
this.
|
|
1088
|
+
this.setNodeProperty(nodeId, key, value);
|
|
1156
1089
|
}
|
|
1157
1090
|
}
|
|
1158
|
-
|
|
1091
|
+
getNodeByPath(path) {
|
|
1159
1092
|
path = path.replace(/^\/+/, "");
|
|
1160
1093
|
path = path.replace(/\/+$/, "");
|
|
1161
|
-
const
|
|
1162
|
-
if (!
|
|
1094
|
+
const root = this.root;
|
|
1095
|
+
if (!root) {
|
|
1163
1096
|
return void 0;
|
|
1164
1097
|
}
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
throw new Error("The root vertex is not found");
|
|
1098
|
+
if (path === "") {
|
|
1099
|
+
return root;
|
|
1168
1100
|
}
|
|
1169
|
-
|
|
1170
|
-
return vertex;
|
|
1101
|
+
return this.getNodeByPathArray(root, path.split("/"));
|
|
1171
1102
|
}
|
|
1172
|
-
|
|
1103
|
+
getNodeByPathArray(node, path) {
|
|
1173
1104
|
if (path.length === 0) {
|
|
1174
|
-
return
|
|
1105
|
+
return node ?? void 0;
|
|
1175
1106
|
}
|
|
1176
1107
|
const targetName = path[0];
|
|
1177
|
-
const children = this.getChildren(
|
|
1108
|
+
const children = this.getChildren(node.id);
|
|
1178
1109
|
for (const child of children) {
|
|
1179
1110
|
if (child.getProperty("name") === targetName) {
|
|
1180
|
-
return this.
|
|
1111
|
+
return this.getNodeByPathArray(child, path.slice(1));
|
|
1181
1112
|
}
|
|
1182
1113
|
}
|
|
1183
1114
|
return void 0;
|
|
1184
1115
|
}
|
|
1185
1116
|
printTree() {
|
|
1186
|
-
if (!this.
|
|
1117
|
+
if (!this.rootNodeId) {
|
|
1187
1118
|
return "";
|
|
1188
1119
|
}
|
|
1189
|
-
return this.state.printTree(this.
|
|
1120
|
+
return this.state.printTree(this.rootNodeId);
|
|
1190
1121
|
}
|
|
1191
1122
|
merge(ops) {
|
|
1192
1123
|
this.applyOps(ops);
|
|
1193
1124
|
}
|
|
1194
1125
|
applyOps(ops) {
|
|
1195
|
-
|
|
1196
|
-
|
|
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))) {
|
|
1197
1136
|
continue;
|
|
1198
1137
|
}
|
|
1199
1138
|
this.applyOperation(op);
|
|
@@ -1201,7 +1140,7 @@ var _RepTree = class _RepTree {
|
|
|
1201
1140
|
}
|
|
1202
1141
|
/** Applies operations in an optimized way, sorting move ops by OpId to avoid undo-do-redo cycles */
|
|
1203
1142
|
applyOpsOptimizedForLotsOfMoves(ops) {
|
|
1204
|
-
const newMoveOps = ops.filter((op) =>
|
|
1143
|
+
const newMoveOps = ops.filter((op) => isMoveNodeOp(op) && !this.knownOps.has(this.getOpKey(op)));
|
|
1205
1144
|
if (newMoveOps.length > 0) {
|
|
1206
1145
|
const allMoveOps = [...this.moveOps, ...newMoveOps];
|
|
1207
1146
|
allMoveOps.sort((a, b) => compareOpId(a.id, b.id));
|
|
@@ -1210,7 +1149,7 @@ var _RepTree = class _RepTree {
|
|
|
1210
1149
|
this.applyMove(op);
|
|
1211
1150
|
}
|
|
1212
1151
|
}
|
|
1213
|
-
const propertyOps = ops.filter((op) => isAnyPropertyOp(op) && !this.knownOps.has(
|
|
1152
|
+
const propertyOps = ops.filter((op) => isAnyPropertyOp(op) && !this.knownOps.has(this.getOpKey(op)));
|
|
1214
1153
|
for (let i = 0, len = propertyOps.length; i < len; i++) {
|
|
1215
1154
|
const op = propertyOps[i];
|
|
1216
1155
|
this.applyProperty(op);
|
|
@@ -1220,10 +1159,10 @@ var _RepTree = class _RepTree {
|
|
|
1220
1159
|
if (this.root?.id !== other.root?.id) {
|
|
1221
1160
|
return false;
|
|
1222
1161
|
}
|
|
1223
|
-
if (!this.
|
|
1162
|
+
if (!this.rootNodeId) {
|
|
1224
1163
|
return true;
|
|
1225
1164
|
}
|
|
1226
|
-
return _RepTree.
|
|
1165
|
+
return _RepTree.compareNodes(this.rootNodeId, this, other);
|
|
1227
1166
|
}
|
|
1228
1167
|
compareMoveOps(other) {
|
|
1229
1168
|
const movesA = this.moveOps;
|
|
@@ -1241,65 +1180,65 @@ var _RepTree = class _RepTree {
|
|
|
1241
1180
|
/** Checks if the given `ancestorId` is an ancestor of `childId` in the tree */
|
|
1242
1181
|
isAncestor(childId, ancestorId) {
|
|
1243
1182
|
let targetId = childId;
|
|
1244
|
-
let
|
|
1245
|
-
const
|
|
1246
|
-
while (
|
|
1247
|
-
if (
|
|
1248
|
-
if (!
|
|
1249
|
-
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)) {
|
|
1250
1189
|
console.error(`isAncestor: cycle detected in the tree structure.`);
|
|
1251
1190
|
return false;
|
|
1252
1191
|
}
|
|
1253
|
-
|
|
1254
|
-
targetId =
|
|
1192
|
+
visitedNodes.add(targetId);
|
|
1193
|
+
targetId = node.parentId;
|
|
1255
1194
|
}
|
|
1256
1195
|
return false;
|
|
1257
1196
|
}
|
|
1258
|
-
|
|
1259
|
-
const
|
|
1260
|
-
if (
|
|
1261
|
-
callback(
|
|
1197
|
+
observeNode(nodeId, callback) {
|
|
1198
|
+
const node = this.getNode(nodeId);
|
|
1199
|
+
if (node) {
|
|
1200
|
+
callback(node);
|
|
1262
1201
|
}
|
|
1263
|
-
const unsubscribe = this.observe(
|
|
1264
|
-
const
|
|
1265
|
-
if (
|
|
1266
|
-
callback(
|
|
1202
|
+
const unsubscribe = this.observe(nodeId, (_) => {
|
|
1203
|
+
const node2 = this.getNode(nodeId);
|
|
1204
|
+
if (node2) {
|
|
1205
|
+
callback(node2);
|
|
1267
1206
|
}
|
|
1268
1207
|
});
|
|
1269
1208
|
return () => {
|
|
1270
1209
|
unsubscribe();
|
|
1271
1210
|
};
|
|
1272
1211
|
}
|
|
1273
|
-
|
|
1212
|
+
observeNodeMove(callback) {
|
|
1274
1213
|
const listener = (events) => {
|
|
1275
1214
|
const moveEvent = events.find((e) => e.type === "move");
|
|
1276
1215
|
if (moveEvent) {
|
|
1277
|
-
const
|
|
1278
|
-
if (
|
|
1279
|
-
callback(
|
|
1216
|
+
const node = this.getNode(moveEvent.nodeId);
|
|
1217
|
+
if (node) {
|
|
1218
|
+
callback(node, moveEvent.oldParentId === void 0);
|
|
1280
1219
|
}
|
|
1281
1220
|
}
|
|
1282
1221
|
};
|
|
1283
1222
|
this.state.addGlobalChangeCallback(listener);
|
|
1284
1223
|
return () => this.state.removeGlobalChangeCallback(listener);
|
|
1285
1224
|
}
|
|
1286
|
-
observe(
|
|
1287
|
-
this.state.addChangeCallback(
|
|
1288
|
-
return () => this.state.removeChangeCallback(
|
|
1225
|
+
observe(nodeId, callback) {
|
|
1226
|
+
this.state.addChangeCallback(nodeId, callback);
|
|
1227
|
+
return () => this.state.removeChangeCallback(nodeId, callback);
|
|
1289
1228
|
}
|
|
1290
1229
|
observeOpApplied(callback) {
|
|
1291
1230
|
this.opAppliedCallbacks.push(callback);
|
|
1292
1231
|
return () => this.opAppliedCallbacks = this.opAppliedCallbacks.filter((l) => l !== callback);
|
|
1293
1232
|
}
|
|
1294
|
-
static
|
|
1295
|
-
const childrenA = treeA.state.getChildrenIds(
|
|
1296
|
-
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);
|
|
1297
1236
|
if (childrenA.length !== childrenB.length) {
|
|
1298
1237
|
return false;
|
|
1299
1238
|
}
|
|
1300
|
-
if (
|
|
1301
|
-
const propertiesA = treeA.
|
|
1302
|
-
const propertiesB = treeB.
|
|
1239
|
+
if (nodeId !== null) {
|
|
1240
|
+
const propertiesA = treeA.getNodeProperties(nodeId);
|
|
1241
|
+
const propertiesB = treeB.getNodeProperties(nodeId);
|
|
1303
1242
|
if (propertiesA.length !== propertiesB.length) {
|
|
1304
1243
|
return false;
|
|
1305
1244
|
}
|
|
@@ -1313,39 +1252,44 @@ var _RepTree = class _RepTree {
|
|
|
1313
1252
|
if (!childrenB.includes(childId)) {
|
|
1314
1253
|
return false;
|
|
1315
1254
|
}
|
|
1316
|
-
if (!_RepTree.
|
|
1255
|
+
if (!_RepTree.compareNodes(childId, treeA, treeB)) {
|
|
1317
1256
|
return false;
|
|
1318
1257
|
}
|
|
1319
1258
|
}
|
|
1320
1259
|
return true;
|
|
1321
1260
|
}
|
|
1322
|
-
|
|
1323
|
-
this.
|
|
1324
|
-
const op =
|
|
1261
|
+
newNodeInternal(nodeId, parentId) {
|
|
1262
|
+
this.moveClock++;
|
|
1263
|
+
const op = newMoveNodeOp(this.moveClock, this.peerId, nodeId, parentId);
|
|
1325
1264
|
this.localOps.push(op);
|
|
1326
1265
|
this.applyMove(op);
|
|
1327
|
-
this.
|
|
1328
|
-
return
|
|
1266
|
+
this.setNodeProperty(nodeId, "_c", (/* @__PURE__ */ new Date()).toISOString());
|
|
1267
|
+
return nodeId;
|
|
1329
1268
|
}
|
|
1330
|
-
|
|
1331
|
-
const
|
|
1332
|
-
return this.
|
|
1269
|
+
newNodeInternalWithUUID(parentId) {
|
|
1270
|
+
const nodeId = uuid();
|
|
1271
|
+
return this.newNodeInternal(nodeId, parentId);
|
|
1333
1272
|
}
|
|
1334
|
-
|
|
1335
|
-
const
|
|
1336
|
-
if (this.state.
|
|
1273
|
+
ensureNullNode() {
|
|
1274
|
+
const nodeId = _RepTree.NULL_NODE_ID;
|
|
1275
|
+
if (this.state.getNode(nodeId)) {
|
|
1337
1276
|
return;
|
|
1338
1277
|
}
|
|
1339
|
-
this.
|
|
1278
|
+
this.newNodeInternal(nodeId, null);
|
|
1340
1279
|
}
|
|
1341
1280
|
/** Updates the lamport clock with the counter value of the operation */
|
|
1342
|
-
|
|
1343
|
-
if (operation.id.counter > this.
|
|
1344
|
-
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;
|
|
1345
1289
|
}
|
|
1346
1290
|
}
|
|
1347
1291
|
applyPendingMovesForParent(parentId) {
|
|
1348
|
-
if (!this.state.
|
|
1292
|
+
if (!this.state.getNode(parentId)) {
|
|
1349
1293
|
return;
|
|
1350
1294
|
}
|
|
1351
1295
|
const pendingMoves = this.pendingMovesWithMissingParent.get(parentId);
|
|
@@ -1358,14 +1302,15 @@ var _RepTree = class _RepTree {
|
|
|
1358
1302
|
}
|
|
1359
1303
|
}
|
|
1360
1304
|
applyMove(op) {
|
|
1361
|
-
if (op.parentId !== null && !this.state.
|
|
1305
|
+
if (op.parentId !== null && !this.state.getNode(op.parentId)) {
|
|
1362
1306
|
if (!this.pendingMovesWithMissingParent.has(op.parentId)) {
|
|
1363
1307
|
this.pendingMovesWithMissingParent.set(op.parentId, []);
|
|
1364
1308
|
}
|
|
1365
1309
|
this.pendingMovesWithMissingParent.get(op.parentId).push(op);
|
|
1310
|
+
this.markOpSeen(op, true);
|
|
1366
1311
|
return;
|
|
1367
1312
|
}
|
|
1368
|
-
this.
|
|
1313
|
+
this.updateMoveClock(op);
|
|
1369
1314
|
const lastOp = this.moveOps.length > 0 ? this.moveOps[this.moveOps.length - 1] : null;
|
|
1370
1315
|
if (lastOp === null || isOpIdGreaterThan(op.id, lastOp.id)) {
|
|
1371
1316
|
this.moveOps.push(op);
|
|
@@ -1392,130 +1337,138 @@ var _RepTree = class _RepTree {
|
|
|
1392
1337
|
this.applyPendingMovesForParent(op.targetId);
|
|
1393
1338
|
}
|
|
1394
1339
|
setLLWPropertyAndItsOpId(op) {
|
|
1395
|
-
this.
|
|
1340
|
+
this.propertyOpsByKey.set(`${op.key}@${op.targetId}`, op);
|
|
1396
1341
|
this.state.setProperty(op.targetId, op.key, op.value);
|
|
1397
|
-
this.reportOpAsApplied(op);
|
|
1342
|
+
this.reportOpAsApplied(op, false);
|
|
1343
|
+
this.refreshPropStateVector();
|
|
1398
1344
|
}
|
|
1399
1345
|
setTransientPropertyAndItsOpId(op) {
|
|
1400
1346
|
this.transientPropertiesAndTheirOpIds.set(`${op.key}@${op.targetId}`, op.id);
|
|
1401
1347
|
this.state.setTransientProperty(op.targetId, op.key, op.value);
|
|
1402
|
-
this.reportOpAsApplied(op);
|
|
1348
|
+
this.reportOpAsApplied(op, false);
|
|
1403
1349
|
}
|
|
1404
1350
|
applyProperty(op) {
|
|
1405
|
-
const
|
|
1406
|
-
if (!
|
|
1351
|
+
const targetNode = this.state.getNode(op.targetId);
|
|
1352
|
+
if (!targetNode) {
|
|
1407
1353
|
if (op.transient) {
|
|
1408
1354
|
return;
|
|
1409
1355
|
}
|
|
1410
|
-
if (!this.
|
|
1411
|
-
this.
|
|
1356
|
+
if (!this.pendingPropertiesWithMissingNode.has(op.targetId)) {
|
|
1357
|
+
this.pendingPropertiesWithMissingNode.set(op.targetId, []);
|
|
1412
1358
|
}
|
|
1413
|
-
this.
|
|
1359
|
+
this.pendingPropertiesWithMissingNode.get(op.targetId).push(op);
|
|
1360
|
+
this.markOpSeen(op, false);
|
|
1414
1361
|
return;
|
|
1415
1362
|
}
|
|
1416
|
-
this.
|
|
1417
|
-
this.applyLLWProperty(op,
|
|
1363
|
+
this.updatePropClock(op);
|
|
1364
|
+
this.applyLLWProperty(op, targetNode);
|
|
1418
1365
|
}
|
|
1419
|
-
applyLLWProperty(op,
|
|
1366
|
+
applyLLWProperty(op, targetNode) {
|
|
1420
1367
|
const prevTransientOpId = this.transientPropertiesAndTheirOpIds.get(`${op.key}@${op.targetId}`);
|
|
1421
|
-
const prevOpId = this.
|
|
1368
|
+
const prevOpId = this.propertyOpsByKey.get(`${op.key}@${op.targetId}`)?.id;
|
|
1422
1369
|
if (!op.transient) {
|
|
1423
|
-
this.setPropertyOps.push(op);
|
|
1424
1370
|
if (!prevOpId || isOpIdGreaterThan(op.id, prevOpId)) {
|
|
1425
1371
|
this.setLLWPropertyAndItsOpId(op);
|
|
1426
1372
|
} else {
|
|
1427
|
-
this.
|
|
1373
|
+
this.markOpSeen(op, false);
|
|
1428
1374
|
}
|
|
1429
1375
|
if (prevTransientOpId && isOpIdGreaterThan(op.id, prevTransientOpId)) {
|
|
1430
1376
|
this.transientPropertiesAndTheirOpIds.delete(`${op.key}@${op.targetId}`);
|
|
1431
|
-
|
|
1377
|
+
targetNode.removeTransientProperty(op.key);
|
|
1432
1378
|
}
|
|
1433
1379
|
} else {
|
|
1434
1380
|
if (!prevTransientOpId || isOpIdGreaterThan(op.id, prevTransientOpId)) {
|
|
1435
1381
|
this.setTransientPropertyAndItsOpId(op);
|
|
1382
|
+
} else {
|
|
1383
|
+
this.markOpSeen(op, false);
|
|
1436
1384
|
}
|
|
1437
1385
|
}
|
|
1438
1386
|
}
|
|
1439
1387
|
applyOperation(op) {
|
|
1440
|
-
if (
|
|
1388
|
+
if (isMoveNodeOp(op)) {
|
|
1441
1389
|
this.applyMove(op);
|
|
1442
1390
|
} else if (isAnyPropertyOp(op)) {
|
|
1443
1391
|
this.applyProperty(op);
|
|
1444
1392
|
}
|
|
1445
1393
|
}
|
|
1446
|
-
|
|
1447
|
-
this.knownOps.add(
|
|
1448
|
-
if (this._stateVectorEnabled) {
|
|
1449
|
-
|
|
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
|
+
}
|
|
1450
1402
|
}
|
|
1403
|
+
}
|
|
1404
|
+
reportOpAsApplied(op, includeInStateVector = true) {
|
|
1405
|
+
this.markOpSeen(op, includeInStateVector);
|
|
1451
1406
|
for (const callback of this.opAppliedCallbacks) {
|
|
1452
1407
|
callback(op);
|
|
1453
1408
|
}
|
|
1454
1409
|
}
|
|
1455
1410
|
tryToMove(op) {
|
|
1456
|
-
let
|
|
1457
|
-
if (
|
|
1458
|
-
this.parentIdBeforeMove.set(op.id,
|
|
1411
|
+
let targetNode = this.state.getNode(op.targetId);
|
|
1412
|
+
if (targetNode) {
|
|
1413
|
+
this.parentIdBeforeMove.set(op.id, targetNode.parentId);
|
|
1459
1414
|
}
|
|
1460
1415
|
if (op.targetId === op.parentId) return;
|
|
1461
1416
|
if (op.parentId && this.isAncestor(op.parentId, op.targetId)) return;
|
|
1462
|
-
this.state.
|
|
1463
|
-
if (!
|
|
1464
|
-
const pendingProperties = this.
|
|
1465
|
-
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);
|
|
1466
1421
|
for (const prop of pendingProperties) {
|
|
1467
1422
|
this.applyProperty(prop);
|
|
1468
1423
|
}
|
|
1469
1424
|
}
|
|
1470
1425
|
}
|
|
1471
1426
|
undoMove(op) {
|
|
1472
|
-
const
|
|
1473
|
-
if (!
|
|
1474
|
-
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`);
|
|
1475
1430
|
return;
|
|
1476
1431
|
}
|
|
1477
1432
|
const prevParentId = this.parentIdBeforeMove.get(op.id);
|
|
1478
1433
|
if (prevParentId === void 0) {
|
|
1479
1434
|
return;
|
|
1480
1435
|
}
|
|
1481
|
-
this.state.
|
|
1436
|
+
this.state.moveNode(op.targetId, prevParentId);
|
|
1482
1437
|
}
|
|
1483
|
-
// --- Range-Based State Vector Methods ---
|
|
1438
|
+
// --- Range-Based State Vector Methods ---
|
|
1484
1439
|
/**
|
|
1485
|
-
* Returns the current state
|
|
1486
|
-
* Returns
|
|
1440
|
+
* Returns the current state vectors for move and property streams.
|
|
1441
|
+
* Returns readonly references to the internal state vectors.
|
|
1487
1442
|
*/
|
|
1488
|
-
|
|
1443
|
+
getStateVectors() {
|
|
1489
1444
|
if (!this._stateVectorEnabled) {
|
|
1490
1445
|
return null;
|
|
1491
1446
|
}
|
|
1492
|
-
return
|
|
1447
|
+
return {
|
|
1448
|
+
move: this.moveStateVector.getState(),
|
|
1449
|
+
prop: this.propStateVector.getState()
|
|
1450
|
+
};
|
|
1493
1451
|
}
|
|
1494
1452
|
/**
|
|
1495
|
-
* Determines which operations are needed to synchronize
|
|
1453
|
+
* Determines which operations are needed to synchronize
|
|
1496
1454
|
* with the provided state vector.
|
|
1497
|
-
*
|
|
1498
|
-
* @param
|
|
1499
|
-
* @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.
|
|
1500
1458
|
*/
|
|
1501
|
-
getMissingOps(
|
|
1459
|
+
getMissingOps(theirStateVectors) {
|
|
1502
1460
|
if (!this._stateVectorEnabled) {
|
|
1503
|
-
return [...this.moveOps, ...this.
|
|
1504
|
-
}
|
|
1505
|
-
const
|
|
1506
|
-
const
|
|
1507
|
-
const
|
|
1508
|
-
const
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
}
|
|
1515
|
-
}
|
|
1516
|
-
}
|
|
1517
|
-
missingOps.sort((a, b) => compareOpId(a.id, b.id));
|
|
1518
|
-
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];
|
|
1519
1472
|
}
|
|
1520
1473
|
/**
|
|
1521
1474
|
* Gets or sets whether state vector tracking is enabled
|
|
@@ -1531,42 +1484,69 @@ var _RepTree = class _RepTree {
|
|
|
1531
1484
|
if (value === this._stateVectorEnabled) return;
|
|
1532
1485
|
if (value) {
|
|
1533
1486
|
this._stateVectorEnabled = true;
|
|
1534
|
-
this.
|
|
1487
|
+
this.moveStateVector = StateVector.fromOperations(this.moveOps);
|
|
1488
|
+
this.propStateVector = StateVector.fromOperations(this.getPropertyOps());
|
|
1535
1489
|
} else {
|
|
1536
1490
|
this._stateVectorEnabled = false;
|
|
1537
|
-
this.
|
|
1491
|
+
this.moveStateVector = new StateVector();
|
|
1492
|
+
this.propStateVector = new StateVector();
|
|
1538
1493
|
}
|
|
1539
1494
|
}
|
|
1540
1495
|
/**
|
|
1541
|
-
* Parses the
|
|
1496
|
+
* Parses the node properties with a provided schema that has a `parse` method (e.g., Zod schema)
|
|
1542
1497
|
*/
|
|
1543
|
-
|
|
1544
|
-
const propsArray = this.
|
|
1498
|
+
parseNode(nodeId, schema) {
|
|
1499
|
+
const propsArray = this.getNodeProperties(nodeId);
|
|
1545
1500
|
const propsObject = {};
|
|
1546
1501
|
for (const { key, value } of propsArray) {
|
|
1547
1502
|
propsObject[key] = value;
|
|
1548
1503
|
}
|
|
1549
1504
|
return schema.parse(propsObject);
|
|
1550
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
|
+
}
|
|
1551
1531
|
};
|
|
1552
|
-
_RepTree.
|
|
1532
|
+
_RepTree.NULL_NODE_ID = "0";
|
|
1553
1533
|
var RepTree = _RepTree;
|
|
1554
1534
|
export {
|
|
1535
|
+
Node,
|
|
1536
|
+
NodeState,
|
|
1555
1537
|
RepTree,
|
|
1556
1538
|
StateVector,
|
|
1557
1539
|
TreeState,
|
|
1558
|
-
|
|
1559
|
-
VertexState,
|
|
1560
|
-
bindVertex,
|
|
1540
|
+
bindNode,
|
|
1561
1541
|
compareOpId,
|
|
1562
1542
|
createOpId,
|
|
1563
1543
|
equalsOpId,
|
|
1564
1544
|
isAnyPropertyOp,
|
|
1565
|
-
|
|
1545
|
+
isMoveNodeOp,
|
|
1566
1546
|
isOpIdGreaterThan,
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1547
|
+
newMoveNodeOp,
|
|
1548
|
+
newSetNodePropertyOp,
|
|
1549
|
+
newSetTransientNodePropertyOp,
|
|
1570
1550
|
opIdToString,
|
|
1571
1551
|
tryParseOpIdStr,
|
|
1572
1552
|
uuid
|