@dxos/app-graph 0.8.4-main.69d29f4 → 0.8.4-main.6fa680abb7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/browser/index.mjs +480 -198
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +480 -198
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/types/src/graph-builder.d.ts +11 -7
- package/dist/types/src/graph-builder.d.ts.map +1 -1
- package/dist/types/src/graph.d.ts +13 -17
- package/dist/types/src/graph.d.ts.map +1 -1
- package/dist/types/src/node-matcher.d.ts +4 -4
- package/dist/types/src/node-matcher.d.ts.map +1 -1
- package/dist/types/src/node.d.ts +15 -5
- package/dist/types/src/node.d.ts.map +1 -1
- package/dist/types/src/stories/EchoGraph.stories.d.ts.map +1 -1
- package/dist/types/src/util.d.ts +34 -0
- package/dist/types/src/util.d.ts.map +1 -0
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +25 -25
- package/src/graph-builder.test.ts +444 -102
- package/src/graph-builder.ts +191 -72
- package/src/graph.test.ts +187 -52
- package/src/graph.ts +179 -98
- package/src/node-matcher.ts +8 -10
- package/src/node.ts +20 -5
- package/src/stories/EchoGraph.stories.tsx +86 -56
- package/src/stories/Tree.tsx +1 -1
- package/src/util.ts +71 -0
|
@@ -42,6 +42,8 @@ __export(graph_exports, {
|
|
|
42
42
|
getRoot: () => getRoot,
|
|
43
43
|
initialize: () => initialize,
|
|
44
44
|
make: () => make,
|
|
45
|
+
relationFromKey: () => relationFromKey,
|
|
46
|
+
relationKey: () => relationKey,
|
|
45
47
|
removeEdge: () => removeEdge,
|
|
46
48
|
removeEdges: () => removeEdges,
|
|
47
49
|
removeNode: () => removeNode,
|
|
@@ -58,7 +60,7 @@ import * as Pipeable from "effect/Pipeable";
|
|
|
58
60
|
import * as Record from "effect/Record";
|
|
59
61
|
import { Event, Trigger } from "@dxos/async";
|
|
60
62
|
import { todo } from "@dxos/debug";
|
|
61
|
-
import { invariant } from "@dxos/invariant";
|
|
63
|
+
import { invariant as invariant2 } from "@dxos/invariant";
|
|
62
64
|
import { log } from "@dxos/log";
|
|
63
65
|
import { isNonNullable } from "@dxos/util";
|
|
64
66
|
|
|
@@ -70,29 +72,79 @@ __export(node_exports, {
|
|
|
70
72
|
RootId: () => RootId,
|
|
71
73
|
RootType: () => RootType,
|
|
72
74
|
actionGroupSymbol: () => actionGroupSymbol,
|
|
75
|
+
actionRelation: () => actionRelation,
|
|
76
|
+
childRelation: () => childRelation,
|
|
73
77
|
isAction: () => isAction,
|
|
74
78
|
isActionGroup: () => isActionGroup,
|
|
75
79
|
isActionLike: () => isActionLike,
|
|
76
|
-
isGraphNode: () => isGraphNode
|
|
80
|
+
isGraphNode: () => isGraphNode,
|
|
81
|
+
relation: () => relation
|
|
77
82
|
});
|
|
78
83
|
var RootId = "root";
|
|
79
|
-
var RootType = "dxos.
|
|
80
|
-
var ActionType = "dxos.
|
|
81
|
-
var ActionGroupType = "dxos.
|
|
84
|
+
var RootType = "org.dxos.type.graph-root";
|
|
85
|
+
var ActionType = "org.dxos.type.graph-action";
|
|
86
|
+
var ActionGroupType = "org.dxos.type.graph-action-group";
|
|
87
|
+
var relation = (kind, direction = "outbound") => ({
|
|
88
|
+
kind,
|
|
89
|
+
direction
|
|
90
|
+
});
|
|
91
|
+
var childRelation = (direction = "outbound") => relation("child", direction);
|
|
92
|
+
var actionRelation = (direction = "outbound") => relation("action", direction);
|
|
82
93
|
var isGraphNode = (data) => data && typeof data === "object" && "id" in data && "properties" in data && data.properties ? typeof data.properties === "object" && "data" in data : false;
|
|
83
94
|
var isAction = (data) => isGraphNode(data) ? typeof data.data === "function" && data.type === ActionType : false;
|
|
84
|
-
var actionGroupSymbol = Symbol("ActionGroup");
|
|
95
|
+
var actionGroupSymbol = /* @__PURE__ */ Symbol("ActionGroup");
|
|
85
96
|
var isActionGroup = (data) => isGraphNode(data) ? data.data === actionGroupSymbol && data.type === ActionGroupType : false;
|
|
86
97
|
var isActionLike = (data) => isAction(data) || isActionGroup(data);
|
|
87
98
|
|
|
99
|
+
// src/util.ts
|
|
100
|
+
import { invariant } from "@dxos/invariant";
|
|
101
|
+
var __dxlog_file = "/__w/dxos/dxos/packages/sdk/app-graph/src/util.ts";
|
|
102
|
+
var Separators = {
|
|
103
|
+
primary: "",
|
|
104
|
+
secondary: "",
|
|
105
|
+
path: "/"
|
|
106
|
+
};
|
|
107
|
+
var normalizeRelation = (relation2) => relation2 == null ? childRelation() : typeof relation2 === "string" ? relation(relation2) : relation2;
|
|
108
|
+
var shallowEqual = (a, b) => {
|
|
109
|
+
if (a === b) return true;
|
|
110
|
+
if (a == null || b == null || typeof a !== "object" || typeof b !== "object") return false;
|
|
111
|
+
const keysA = Object.keys(a);
|
|
112
|
+
const keysB = Object.keys(b);
|
|
113
|
+
if (keysA.length !== keysB.length) {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
return keysA.every((k) => a[k] === b[k]);
|
|
117
|
+
};
|
|
118
|
+
var nodeArgsUnchanged = (prev, next) => {
|
|
119
|
+
if (prev.length !== next.length) {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
return prev.every((prevNode, idx) => {
|
|
123
|
+
const nextNode = next[idx];
|
|
124
|
+
return prevNode.id === nextNode.id && prevNode.type === nextNode.type && shallowEqual(prevNode.data, nextNode.data) && shallowEqual(prevNode.properties, nextNode.properties);
|
|
125
|
+
});
|
|
126
|
+
};
|
|
127
|
+
var qualifyId = (parentId, segmentId) => `${parentId}${Separators.path}${segmentId}`;
|
|
128
|
+
var validateSegmentId = (id) => {
|
|
129
|
+
invariant(!id.includes(Separators.path), `Node segment ID must not contain '${Separators.path}': ${id}`, {
|
|
130
|
+
F: __dxlog_file,
|
|
131
|
+
L: 70,
|
|
132
|
+
S: void 0,
|
|
133
|
+
A: [
|
|
134
|
+
"!id.includes(Separators.path)",
|
|
135
|
+
"`Node segment ID must not contain '${Separators.path}': ${id}`"
|
|
136
|
+
]
|
|
137
|
+
});
|
|
138
|
+
};
|
|
139
|
+
|
|
88
140
|
// src/graph.ts
|
|
89
|
-
var
|
|
90
|
-
var graphSymbol = Symbol("graph");
|
|
141
|
+
var __dxlog_file2 = "/__w/dxos/dxos/packages/sdk/app-graph/src/graph.ts";
|
|
142
|
+
var graphSymbol = /* @__PURE__ */ Symbol("graph");
|
|
91
143
|
var getGraph = (node) => {
|
|
92
144
|
const graph = node[graphSymbol];
|
|
93
|
-
|
|
94
|
-
F:
|
|
95
|
-
L:
|
|
145
|
+
invariant2(graph, "Node is not associated with a graph.", {
|
|
146
|
+
F: __dxlog_file2,
|
|
147
|
+
L: 33,
|
|
96
148
|
S: void 0,
|
|
97
149
|
A: [
|
|
98
150
|
"graph",
|
|
@@ -101,8 +153,8 @@ var getGraph = (node) => {
|
|
|
101
153
|
});
|
|
102
154
|
return graph;
|
|
103
155
|
};
|
|
104
|
-
var GraphTypeId = Symbol.for("@dxos/app-graph/Graph");
|
|
105
|
-
var GraphKind = Symbol.for("@dxos/app-graph/GraphKind");
|
|
156
|
+
var GraphTypeId = /* @__PURE__ */ Symbol.for("@dxos/app-graph/Graph");
|
|
157
|
+
var GraphKind = /* @__PURE__ */ Symbol.for("@dxos/app-graph/GraphKind");
|
|
106
158
|
var GraphImpl = class {
|
|
107
159
|
[GraphTypeId] = GraphTypeId;
|
|
108
160
|
[GraphKind] = "writable";
|
|
@@ -115,6 +167,7 @@ var GraphImpl = class {
|
|
|
115
167
|
_onRemoveNode;
|
|
116
168
|
_registry;
|
|
117
169
|
_expanded = Record.empty();
|
|
170
|
+
_pendingExpands = /* @__PURE__ */ new Set();
|
|
118
171
|
_initialized = Record.empty();
|
|
119
172
|
_initialEdges = Record.empty();
|
|
120
173
|
_initialNodes = Record.fromEntries([
|
|
@@ -136,9 +189,9 @@ var GraphImpl = class {
|
|
|
136
189
|
_nodeOrThrow = Atom2.family((id) => {
|
|
137
190
|
return Atom2.make((get2) => {
|
|
138
191
|
const node = get2(this._node(id));
|
|
139
|
-
|
|
140
|
-
F:
|
|
141
|
-
L:
|
|
192
|
+
invariant2(Option.isSome(node), `Node not available: ${id}`, {
|
|
193
|
+
F: __dxlog_file2,
|
|
194
|
+
L: 172,
|
|
142
195
|
S: this,
|
|
143
196
|
A: [
|
|
144
197
|
"Option.isSome(node)",
|
|
@@ -149,30 +202,33 @@ var GraphImpl = class {
|
|
|
149
202
|
});
|
|
150
203
|
});
|
|
151
204
|
_edges = Atom2.family((id) => {
|
|
152
|
-
const initial = Record.get(this._initialEdges, id).pipe(Option.getOrElse(() => ({
|
|
153
|
-
inbound: [],
|
|
154
|
-
outbound: []
|
|
155
|
-
})));
|
|
205
|
+
const initial = Record.get(this._initialEdges, id).pipe(Option.getOrElse(() => ({})));
|
|
156
206
|
return Atom2.make(initial).pipe(Atom2.keepAlive, Atom2.withLabel(`graph:edges:${id}`));
|
|
157
207
|
});
|
|
158
208
|
// NOTE: Currently the argument to the family needs to be referentially stable for the atom to be referentially stable.
|
|
159
209
|
// TODO(wittjosiah): Atom feature request, support for something akin to `ComplexMap` to allow for complex arguments.
|
|
160
210
|
_connections = Atom2.family((key) => {
|
|
161
211
|
return Atom2.make((get2) => {
|
|
162
|
-
|
|
212
|
+
if (!key || key.indexOf(Separators.primary) <= 0) {
|
|
213
|
+
return [];
|
|
214
|
+
}
|
|
215
|
+
const { id, relation: relation2 } = relationFromConnectionKey(key);
|
|
163
216
|
const edges = get2(this._edges(id));
|
|
164
|
-
return edges[
|
|
217
|
+
return (edges[relationKey(relation2)] ?? []).map((id2) => get2(this._node(id2))).filter(Option.isSome).map((o) => o.value);
|
|
165
218
|
}).pipe(Atom2.withLabel(`graph:connections:${key}`));
|
|
166
219
|
});
|
|
167
220
|
_actions = Atom2.family((id) => {
|
|
168
221
|
return Atom2.make((get2) => {
|
|
169
|
-
|
|
222
|
+
if (!id) {
|
|
223
|
+
return [];
|
|
224
|
+
}
|
|
225
|
+
return get2(this._connections(connectionKey(id, actionRelation())));
|
|
170
226
|
}).pipe(Atom2.withLabel(`graph:actions:${id}`));
|
|
171
227
|
});
|
|
172
228
|
_json = Atom2.family((id) => {
|
|
173
229
|
return Atom2.make((get2) => {
|
|
174
230
|
const toJSON2 = (node, seen = []) => {
|
|
175
|
-
const nodes = get2(this._connections(
|
|
231
|
+
const nodes = get2(this._connections(connectionKey(node.id, "child")));
|
|
176
232
|
const obj = {
|
|
177
233
|
id: node.id,
|
|
178
234
|
type: node.type
|
|
@@ -220,8 +276,8 @@ var GraphImpl = class {
|
|
|
220
276
|
nodeOrThrow(id) {
|
|
221
277
|
return nodeOrThrowImpl(this, id);
|
|
222
278
|
}
|
|
223
|
-
connections(id,
|
|
224
|
-
return connectionsImpl(this, id,
|
|
279
|
+
connections(id, relation2) {
|
|
280
|
+
return connectionsImpl(this, id, relation2);
|
|
225
281
|
}
|
|
226
282
|
actions(id) {
|
|
227
283
|
return actionsImpl(this, id);
|
|
@@ -258,9 +314,9 @@ var nodeOrThrowImpl = (graph, id) => {
|
|
|
258
314
|
const internal = getInternal(graph);
|
|
259
315
|
return internal._nodeOrThrow(id);
|
|
260
316
|
};
|
|
261
|
-
var connectionsImpl = (graph, id,
|
|
317
|
+
var connectionsImpl = (graph, id, relation2) => {
|
|
262
318
|
const internal = getInternal(graph);
|
|
263
|
-
return internal._connections(
|
|
319
|
+
return internal._connections(connectionKey(id, relation2));
|
|
264
320
|
};
|
|
265
321
|
var actionsImpl = (graph, id) => {
|
|
266
322
|
const internal = getInternal(graph);
|
|
@@ -299,19 +355,28 @@ function getNodeOrThrow(graphOrId, id) {
|
|
|
299
355
|
function getRoot(graph) {
|
|
300
356
|
return getNodeOrThrowImpl(graph, RootId);
|
|
301
357
|
}
|
|
302
|
-
var getConnectionsImpl = (graph, id,
|
|
358
|
+
var getConnectionsImpl = (graph, id, relation2) => {
|
|
303
359
|
const internal = getInternal(graph);
|
|
304
|
-
return internal._registry.get(connectionsImpl(graph, id,
|
|
360
|
+
return internal._registry.get(connectionsImpl(graph, id, relation2));
|
|
305
361
|
};
|
|
306
|
-
function getConnections(graphOrId, idOrRelation,
|
|
362
|
+
function getConnections(graphOrId, idOrRelation, relation2) {
|
|
307
363
|
if (typeof graphOrId === "string") {
|
|
308
364
|
const id = graphOrId;
|
|
309
|
-
const rel =
|
|
365
|
+
const rel = idOrRelation;
|
|
310
366
|
return (graph) => getConnectionsImpl(graph, id, rel);
|
|
311
367
|
} else {
|
|
312
368
|
const graph = graphOrId;
|
|
313
369
|
const id = idOrRelation;
|
|
314
|
-
|
|
370
|
+
invariant2(relation2 !== void 0, "Relation is required.", {
|
|
371
|
+
F: __dxlog_file2,
|
|
372
|
+
L: 446,
|
|
373
|
+
S: this,
|
|
374
|
+
A: [
|
|
375
|
+
"relation !== undefined",
|
|
376
|
+
"'Relation is required.'"
|
|
377
|
+
]
|
|
378
|
+
});
|
|
379
|
+
const rel = relation2;
|
|
315
380
|
return getConnectionsImpl(graph, id, rel);
|
|
316
381
|
}
|
|
317
382
|
}
|
|
@@ -342,7 +407,7 @@ function getEdges(graphOrId, id) {
|
|
|
342
407
|
}
|
|
343
408
|
}
|
|
344
409
|
var traverseImpl = (graph, options, path = []) => {
|
|
345
|
-
const { visitor, source = RootId, relation
|
|
410
|
+
const { visitor, source = RootId, relation: relation2 } = options;
|
|
346
411
|
if (path.includes(source)) {
|
|
347
412
|
return;
|
|
348
413
|
}
|
|
@@ -354,14 +419,25 @@ var traverseImpl = (graph, options, path = []) => {
|
|
|
354
419
|
if (shouldContinue === false) {
|
|
355
420
|
return;
|
|
356
421
|
}
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
422
|
+
const relations = Array.isArray(relation2) ? relation2 : [
|
|
423
|
+
relation2
|
|
424
|
+
];
|
|
425
|
+
const seen = /* @__PURE__ */ new Set();
|
|
426
|
+
for (const rel of relations) {
|
|
427
|
+
for (const connected of getConnections(graph, source, rel)) {
|
|
428
|
+
if (!seen.has(connected.id)) {
|
|
429
|
+
seen.add(connected.id);
|
|
430
|
+
traverseImpl(graph, {
|
|
431
|
+
source: connected.id,
|
|
432
|
+
relation: relation2,
|
|
433
|
+
visitor
|
|
434
|
+
}, [
|
|
435
|
+
...path,
|
|
436
|
+
source
|
|
437
|
+
]);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
365
441
|
};
|
|
366
442
|
function traverse(graphOrOptions, optionsOrPath, path) {
|
|
367
443
|
if (typeof graphOrOptions === "object" && "visitor" in graphOrOptions) {
|
|
@@ -380,6 +456,7 @@ var getPathImpl = (graph, params) => {
|
|
|
380
456
|
let found = Option.none();
|
|
381
457
|
traverseImpl(graph, {
|
|
382
458
|
source: node.id,
|
|
459
|
+
relation: "child",
|
|
383
460
|
visitor: (node2, path) => {
|
|
384
461
|
if (Option.isSome(found)) {
|
|
385
462
|
return false;
|
|
@@ -436,14 +513,14 @@ var initializeImpl = async (graph, id) => {
|
|
|
436
513
|
id,
|
|
437
514
|
initialized
|
|
438
515
|
}, {
|
|
439
|
-
F:
|
|
440
|
-
L:
|
|
516
|
+
F: __dxlog_file2,
|
|
517
|
+
L: 668,
|
|
441
518
|
S: void 0,
|
|
442
519
|
C: (f, a) => f(...a)
|
|
443
520
|
});
|
|
444
521
|
if (!initialized) {
|
|
445
|
-
await internal._onInitialize?.(id);
|
|
446
522
|
Record.set(internal._initialized, id, true);
|
|
523
|
+
await internal._onInitialize?.(id);
|
|
447
524
|
}
|
|
448
525
|
return graph;
|
|
449
526
|
};
|
|
@@ -456,61 +533,93 @@ function initialize(graphOrId, id) {
|
|
|
456
533
|
return initializeImpl(graph, id);
|
|
457
534
|
}
|
|
458
535
|
}
|
|
459
|
-
var expandImpl = (graph, id,
|
|
536
|
+
var expandImpl = (graph, id, relation2) => {
|
|
460
537
|
const internal = getInternal(graph);
|
|
461
|
-
const
|
|
538
|
+
const normalizedRelation = normalizeRelation(relation2);
|
|
539
|
+
const key = `${id}${Separators.primary}${relationKey(normalizedRelation)}`;
|
|
540
|
+
const nodeOpt = internal._registry.get(internal._node(id));
|
|
541
|
+
if (Option.isNone(nodeOpt)) {
|
|
542
|
+
internal._pendingExpands.add(key);
|
|
543
|
+
log("expand", {
|
|
544
|
+
key,
|
|
545
|
+
deferred: true
|
|
546
|
+
}, {
|
|
547
|
+
F: __dxlog_file2,
|
|
548
|
+
L: 714,
|
|
549
|
+
S: void 0,
|
|
550
|
+
C: (f, a) => f(...a)
|
|
551
|
+
});
|
|
552
|
+
return graph;
|
|
553
|
+
}
|
|
462
554
|
const expanded = Record.get(internal._expanded, key).pipe(Option.getOrElse(() => false));
|
|
463
555
|
log("expand", {
|
|
464
556
|
key,
|
|
465
557
|
expanded
|
|
466
558
|
}, {
|
|
467
|
-
F:
|
|
468
|
-
L:
|
|
559
|
+
F: __dxlog_file2,
|
|
560
|
+
L: 719,
|
|
469
561
|
S: void 0,
|
|
470
562
|
C: (f, a) => f(...a)
|
|
471
563
|
});
|
|
472
564
|
if (!expanded) {
|
|
473
|
-
internal._onExpand?.(id, relation);
|
|
474
565
|
Record.set(internal._expanded, key, true);
|
|
566
|
+
internal._onExpand?.(id, normalizedRelation);
|
|
475
567
|
}
|
|
476
568
|
return graph;
|
|
477
569
|
};
|
|
478
|
-
function expand(graphOrId, idOrRelation,
|
|
570
|
+
function expand(graphOrId, idOrRelation, relation2) {
|
|
479
571
|
if (typeof graphOrId === "string") {
|
|
480
572
|
const id = graphOrId;
|
|
481
|
-
const rel =
|
|
573
|
+
const rel = idOrRelation;
|
|
482
574
|
return (graph) => expandImpl(graph, id, rel);
|
|
483
575
|
} else {
|
|
484
576
|
const graph = graphOrId;
|
|
485
577
|
const id = idOrRelation;
|
|
486
|
-
|
|
578
|
+
invariant2(relation2 !== void 0, "Relation is required.", {
|
|
579
|
+
F: __dxlog_file2,
|
|
580
|
+
L: 755,
|
|
581
|
+
S: this,
|
|
582
|
+
A: [
|
|
583
|
+
"relation !== undefined",
|
|
584
|
+
"'Relation is required.'"
|
|
585
|
+
]
|
|
586
|
+
});
|
|
587
|
+
const rel = relation2;
|
|
487
588
|
return expandImpl(graph, id, rel);
|
|
488
589
|
}
|
|
489
590
|
}
|
|
490
|
-
var sortEdgesImpl = (graph, id,
|
|
591
|
+
var sortEdgesImpl = (graph, id, relation2, order) => {
|
|
491
592
|
const internal = getInternal(graph);
|
|
492
593
|
const edgesAtom = internal._edges(id);
|
|
493
594
|
const edges = internal._registry.get(edgesAtom);
|
|
494
|
-
const
|
|
495
|
-
const
|
|
496
|
-
|
|
595
|
+
const relationId = relationKey(relation2);
|
|
596
|
+
const current = edges[relationId] ?? [];
|
|
597
|
+
const unsorted = current.filter((id2) => !order.includes(id2));
|
|
598
|
+
const sorted = order.filter((id2) => current.includes(id2));
|
|
599
|
+
const newOrder = [
|
|
497
600
|
...sorted,
|
|
498
601
|
...unsorted
|
|
499
|
-
]
|
|
500
|
-
|
|
602
|
+
];
|
|
603
|
+
if (newOrder.length === current.length && newOrder.every((id2, i) => id2 === current[i])) {
|
|
604
|
+
return graph;
|
|
605
|
+
}
|
|
606
|
+
internal._registry.set(edgesAtom, {
|
|
607
|
+
...edges,
|
|
608
|
+
[relationId]: newOrder
|
|
609
|
+
});
|
|
501
610
|
return graph;
|
|
502
611
|
};
|
|
503
612
|
function sortEdges(graphOrId, idOrRelation, relationOrOrder, order) {
|
|
504
613
|
if (typeof graphOrId === "string") {
|
|
505
614
|
const id = graphOrId;
|
|
506
|
-
const
|
|
615
|
+
const relation2 = idOrRelation;
|
|
507
616
|
const order2 = relationOrOrder;
|
|
508
|
-
return (graph) => sortEdgesImpl(graph, id,
|
|
617
|
+
return (graph) => sortEdgesImpl(graph, id, relation2, order2);
|
|
509
618
|
} else {
|
|
510
619
|
const graph = graphOrId;
|
|
511
620
|
const id = idOrRelation;
|
|
512
|
-
const
|
|
513
|
-
return sortEdgesImpl(graph, id,
|
|
621
|
+
const relation2 = relationOrOrder;
|
|
622
|
+
return sortEdgesImpl(graph, id, relation2, order);
|
|
514
623
|
}
|
|
515
624
|
}
|
|
516
625
|
var addNodesImpl = (graph, nodes) => {
|
|
@@ -536,7 +645,7 @@ var addNodeImpl = (graph, nodeArg) => {
|
|
|
536
645
|
Option.match(existingNode, {
|
|
537
646
|
onSome: (existing) => {
|
|
538
647
|
const typeChanged = existing.type !== type;
|
|
539
|
-
const dataChanged = existing.data
|
|
648
|
+
const dataChanged = !shallowEqual(existing.data, data);
|
|
540
649
|
const propertiesChanged = Object.keys(properties).some((key) => existing.properties[key] !== properties[key]);
|
|
541
650
|
log("existing node", {
|
|
542
651
|
id,
|
|
@@ -544,8 +653,8 @@ var addNodeImpl = (graph, nodeArg) => {
|
|
|
544
653
|
dataChanged,
|
|
545
654
|
propertiesChanged
|
|
546
655
|
}, {
|
|
547
|
-
F:
|
|
548
|
-
L:
|
|
656
|
+
F: __dxlog_file2,
|
|
657
|
+
L: 877,
|
|
549
658
|
S: void 0,
|
|
550
659
|
C: (f, a) => f(...a)
|
|
551
660
|
});
|
|
@@ -556,8 +665,8 @@ var addNodeImpl = (graph, nodeArg) => {
|
|
|
556
665
|
data,
|
|
557
666
|
properties
|
|
558
667
|
}, {
|
|
559
|
-
F:
|
|
560
|
-
L:
|
|
668
|
+
F: __dxlog_file2,
|
|
669
|
+
L: 884,
|
|
561
670
|
S: void 0,
|
|
562
671
|
C: (f, a) => f(...a)
|
|
563
672
|
});
|
|
@@ -585,8 +694,8 @@ var addNodeImpl = (graph, nodeArg) => {
|
|
|
585
694
|
data,
|
|
586
695
|
properties
|
|
587
696
|
}, {
|
|
588
|
-
F:
|
|
589
|
-
L:
|
|
697
|
+
F: __dxlog_file2,
|
|
698
|
+
L: 897,
|
|
590
699
|
S: void 0,
|
|
591
700
|
C: (f, a) => f(...a)
|
|
592
701
|
});
|
|
@@ -602,13 +711,24 @@ var addNodeImpl = (graph, nodeArg) => {
|
|
|
602
711
|
id,
|
|
603
712
|
node: newNode
|
|
604
713
|
});
|
|
714
|
+
const prefix = `${id}${Separators.primary}`;
|
|
715
|
+
const toApply = [
|
|
716
|
+
...internal._pendingExpands
|
|
717
|
+
].filter((k) => k.startsWith(prefix));
|
|
718
|
+
for (const pendingKey of toApply) {
|
|
719
|
+
internal._pendingExpands.delete(pendingKey);
|
|
720
|
+
const relation2 = relationFromKey(pendingKey.slice(prefix.length));
|
|
721
|
+
Record.set(internal._expanded, pendingKey, true);
|
|
722
|
+
internal._onExpand?.(id, relation2);
|
|
723
|
+
}
|
|
605
724
|
}
|
|
606
725
|
});
|
|
607
726
|
if (nodes) {
|
|
608
727
|
addNodesImpl(graph, nodes);
|
|
609
728
|
const _edges = nodes.map((node) => ({
|
|
610
729
|
source: id,
|
|
611
|
-
target: node.id
|
|
730
|
+
target: node.id,
|
|
731
|
+
relation: "child"
|
|
612
732
|
}));
|
|
613
733
|
addEdgesImpl(graph, _edges);
|
|
614
734
|
}
|
|
@@ -653,17 +773,27 @@ var removeNodeImpl = (graph, id, edges = false) => {
|
|
|
653
773
|
node: Option.none()
|
|
654
774
|
});
|
|
655
775
|
if (edges) {
|
|
656
|
-
const
|
|
657
|
-
const edgesToRemove = [
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
776
|
+
const nodeEdges = internal._registry.get(internal._edges(id));
|
|
777
|
+
const edgesToRemove = [];
|
|
778
|
+
for (const [relationKeyValue, relatedIds] of Object.entries(nodeEdges)) {
|
|
779
|
+
const relation2 = relationFromKey(relationKeyValue);
|
|
780
|
+
const isInboundRelation = relation2.direction === "inbound";
|
|
781
|
+
for (const relatedId of relatedIds) {
|
|
782
|
+
if (isInboundRelation) {
|
|
783
|
+
edgesToRemove.push({
|
|
784
|
+
source: relatedId,
|
|
785
|
+
target: id,
|
|
786
|
+
relation: inverseRelation(relation2)
|
|
787
|
+
});
|
|
788
|
+
} else {
|
|
789
|
+
edgesToRemove.push({
|
|
790
|
+
source: id,
|
|
791
|
+
target: relatedId,
|
|
792
|
+
relation: relation2
|
|
793
|
+
});
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
}
|
|
667
797
|
removeEdgesImpl(graph, edgesToRemove);
|
|
668
798
|
}
|
|
669
799
|
internal._onRemoveNode?.(id);
|
|
@@ -697,45 +827,53 @@ function addEdges(graphOrEdges, edges) {
|
|
|
697
827
|
}
|
|
698
828
|
}
|
|
699
829
|
var addEdgeImpl = (graph, edgeArg) => {
|
|
830
|
+
const relation2 = normalizeRelation(edgeArg.relation);
|
|
831
|
+
const relationId = relationKey(relation2);
|
|
832
|
+
const inverse = inverseRelation(relation2);
|
|
833
|
+
const inverseId = relationKey(inverse);
|
|
700
834
|
const internal = getInternal(graph);
|
|
701
835
|
const sourceAtom = internal._edges(edgeArg.source);
|
|
702
836
|
const source = internal._registry.get(sourceAtom);
|
|
703
|
-
|
|
704
|
-
|
|
837
|
+
const sourceList = source[relationId] ?? [];
|
|
838
|
+
if (!sourceList.includes(edgeArg.target)) {
|
|
839
|
+
log("add edge", {
|
|
705
840
|
source: edgeArg.source,
|
|
706
|
-
target: edgeArg.target
|
|
841
|
+
target: edgeArg.target,
|
|
842
|
+
relation: relationId
|
|
707
843
|
}, {
|
|
708
|
-
F:
|
|
709
|
-
L:
|
|
844
|
+
F: __dxlog_file2,
|
|
845
|
+
L: 1081,
|
|
710
846
|
S: void 0,
|
|
711
847
|
C: (f, a) => f(...a)
|
|
712
848
|
});
|
|
713
849
|
internal._registry.set(sourceAtom, {
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
...
|
|
850
|
+
...source,
|
|
851
|
+
[relationId]: [
|
|
852
|
+
...sourceList,
|
|
717
853
|
edgeArg.target
|
|
718
854
|
]
|
|
719
855
|
});
|
|
720
856
|
}
|
|
721
857
|
const targetAtom = internal._edges(edgeArg.target);
|
|
722
858
|
const target = internal._registry.get(targetAtom);
|
|
723
|
-
|
|
724
|
-
|
|
859
|
+
const targetList = target[inverseId] ?? [];
|
|
860
|
+
if (!targetList.includes(edgeArg.source)) {
|
|
861
|
+
log("add inverse edge", {
|
|
725
862
|
source: edgeArg.source,
|
|
726
|
-
target: edgeArg.target
|
|
863
|
+
target: edgeArg.target,
|
|
864
|
+
relation: inverseId
|
|
727
865
|
}, {
|
|
728
|
-
F:
|
|
729
|
-
L:
|
|
866
|
+
F: __dxlog_file2,
|
|
867
|
+
L: 1089,
|
|
730
868
|
S: void 0,
|
|
731
869
|
C: (f, a) => f(...a)
|
|
732
870
|
});
|
|
733
871
|
internal._registry.set(targetAtom, {
|
|
734
|
-
|
|
735
|
-
|
|
872
|
+
...target,
|
|
873
|
+
[inverseId]: [
|
|
874
|
+
...targetList,
|
|
736
875
|
edgeArg.source
|
|
737
|
-
]
|
|
738
|
-
outbound: target.outbound
|
|
876
|
+
]
|
|
739
877
|
});
|
|
740
878
|
}
|
|
741
879
|
return graph;
|
|
@@ -768,32 +906,39 @@ function removeEdges(graphOrEdges, edgesOrRemoveOrphans, removeOrphans) {
|
|
|
768
906
|
}
|
|
769
907
|
}
|
|
770
908
|
var removeEdgeImpl = (graph, edgeArg, removeOrphans = false) => {
|
|
909
|
+
const relation2 = normalizeRelation(edgeArg.relation);
|
|
910
|
+
const relationId = relationKey(relation2);
|
|
911
|
+
const inverse = inverseRelation(relation2);
|
|
912
|
+
const inverseId = relationKey(inverse);
|
|
771
913
|
const internal = getInternal(graph);
|
|
772
914
|
const sourceAtom = internal._edges(edgeArg.source);
|
|
773
915
|
const source = internal._registry.get(sourceAtom);
|
|
774
|
-
|
|
916
|
+
const sourceList = source[relationId] ?? [];
|
|
917
|
+
if (sourceList.includes(edgeArg.target)) {
|
|
775
918
|
internal._registry.set(sourceAtom, {
|
|
776
|
-
|
|
777
|
-
|
|
919
|
+
...source,
|
|
920
|
+
[relationId]: sourceList.filter((id) => id !== edgeArg.target)
|
|
778
921
|
});
|
|
779
922
|
}
|
|
780
923
|
const targetAtom = internal._edges(edgeArg.target);
|
|
781
924
|
const target = internal._registry.get(targetAtom);
|
|
782
|
-
|
|
925
|
+
const targetList = target[inverseId] ?? [];
|
|
926
|
+
if (targetList.includes(edgeArg.source)) {
|
|
783
927
|
internal._registry.set(targetAtom, {
|
|
784
|
-
|
|
785
|
-
|
|
928
|
+
...target,
|
|
929
|
+
[inverseId]: targetList.filter((id) => id !== edgeArg.source)
|
|
786
930
|
});
|
|
787
931
|
}
|
|
788
932
|
if (removeOrphans) {
|
|
789
|
-
const
|
|
790
|
-
const
|
|
791
|
-
|
|
933
|
+
const sourceAfter = internal._registry.get(sourceAtom);
|
|
934
|
+
const targetAfter = internal._registry.get(targetAtom);
|
|
935
|
+
const isEmpty = (edges) => Object.values(edges).every((ids) => ids.length === 0);
|
|
936
|
+
if (isEmpty(sourceAfter) && edgeArg.source !== RootId) {
|
|
792
937
|
removeNodesImpl(graph, [
|
|
793
938
|
edgeArg.source
|
|
794
939
|
]);
|
|
795
940
|
}
|
|
796
|
-
if (
|
|
941
|
+
if (isEmpty(targetAfter) && edgeArg.target !== RootId) {
|
|
797
942
|
removeNodesImpl(graph, [
|
|
798
943
|
edgeArg.target
|
|
799
944
|
]);
|
|
@@ -816,6 +961,57 @@ function removeEdge(graphOrEdgeArg, edgeArgOrRemoveOrphans, removeOrphans) {
|
|
|
816
961
|
var make = (params) => {
|
|
817
962
|
return new GraphImpl(params);
|
|
818
963
|
};
|
|
964
|
+
var relationKey = (relation2) => {
|
|
965
|
+
const normalized = normalizeRelation(relation2);
|
|
966
|
+
return `${normalized.kind}${Separators.secondary}${normalized.direction}`;
|
|
967
|
+
};
|
|
968
|
+
var relationFromKey = (encoded) => {
|
|
969
|
+
const separatorIndex = encoded.lastIndexOf(Separators.secondary);
|
|
970
|
+
invariant2(separatorIndex > 0 && separatorIndex < encoded.length - 1, `Invalid relation key: ${encoded}`, {
|
|
971
|
+
F: __dxlog_file2,
|
|
972
|
+
L: 1234,
|
|
973
|
+
S: void 0,
|
|
974
|
+
A: [
|
|
975
|
+
"separatorIndex > 0 && separatorIndex < encoded.length - 1",
|
|
976
|
+
"`Invalid relation key: ${encoded}`"
|
|
977
|
+
]
|
|
978
|
+
});
|
|
979
|
+
const kind = encoded.slice(0, separatorIndex);
|
|
980
|
+
const directionRaw = encoded.slice(separatorIndex + 1);
|
|
981
|
+
invariant2(directionRaw === "outbound" || directionRaw === "inbound", `Invalid relation direction: ${directionRaw}`, {
|
|
982
|
+
F: __dxlog_file2,
|
|
983
|
+
L: 1237,
|
|
984
|
+
S: void 0,
|
|
985
|
+
A: [
|
|
986
|
+
"directionRaw === 'outbound' || directionRaw === 'inbound'",
|
|
987
|
+
"`Invalid relation direction: ${directionRaw}`"
|
|
988
|
+
]
|
|
989
|
+
});
|
|
990
|
+
return relation(kind, directionRaw);
|
|
991
|
+
};
|
|
992
|
+
var connectionKey = (id, relation2) => `${id}${Separators.primary}${relationKey(relation2)}`;
|
|
993
|
+
var relationFromConnectionKey = (key) => {
|
|
994
|
+
const separatorIndex = key.indexOf(Separators.primary);
|
|
995
|
+
invariant2(separatorIndex > 0 && separatorIndex < key.length - 1, `Invalid connection key: ${key}`, {
|
|
996
|
+
F: __dxlog_file2,
|
|
997
|
+
L: 1246,
|
|
998
|
+
S: void 0,
|
|
999
|
+
A: [
|
|
1000
|
+
"separatorIndex > 0 && separatorIndex < key.length - 1",
|
|
1001
|
+
"`Invalid connection key: ${key}`"
|
|
1002
|
+
]
|
|
1003
|
+
});
|
|
1004
|
+
const id = key.slice(0, separatorIndex);
|
|
1005
|
+
const encodedRelation = key.slice(separatorIndex + 1);
|
|
1006
|
+
return {
|
|
1007
|
+
id,
|
|
1008
|
+
relation: relationFromKey(encodedRelation)
|
|
1009
|
+
};
|
|
1010
|
+
};
|
|
1011
|
+
var inverseRelation = (relation2) => {
|
|
1012
|
+
const normalized = normalizeRelation(relation2);
|
|
1013
|
+
return relation(normalized.kind, normalized.direction === "outbound" ? "inbound" : "outbound");
|
|
1014
|
+
};
|
|
819
1015
|
|
|
820
1016
|
// src/graph-builder.ts
|
|
821
1017
|
var graph_builder_exports = {};
|
|
@@ -829,6 +1025,7 @@ __export(graph_builder_exports, {
|
|
|
829
1025
|
destroy: () => destroy,
|
|
830
1026
|
explore: () => explore,
|
|
831
1027
|
flattenExtensions: () => flattenExtensions,
|
|
1028
|
+
flush: () => flush,
|
|
832
1029
|
from: () => from,
|
|
833
1030
|
make: () => make2,
|
|
834
1031
|
removeExtension: () => removeExtension
|
|
@@ -840,8 +1037,9 @@ import * as Function2 from "effect/Function";
|
|
|
840
1037
|
import * as Option3 from "effect/Option";
|
|
841
1038
|
import * as Pipeable2 from "effect/Pipeable";
|
|
842
1039
|
import * as Record2 from "effect/Record";
|
|
1040
|
+
import { scheduleTask, yieldOrContinue } from "main-thread-scheduling";
|
|
843
1041
|
import { log as log2 } from "@dxos/log";
|
|
844
|
-
import { byPosition, getDebugName,
|
|
1042
|
+
import { byPosition, getDebugName, isNonNullable as isNonNullable2 } from "@dxos/util";
|
|
845
1043
|
|
|
846
1044
|
// src/node-matcher.ts
|
|
847
1045
|
var node_matcher_exports = {};
|
|
@@ -865,18 +1063,16 @@ var whenNodeType = (type) => (node) => node.type === type ? Option2.some(node) :
|
|
|
865
1063
|
var whenEchoType = (type) => (node) => Obj.instanceOf(type, node.data) ? Option2.some(node.data) : Option2.none();
|
|
866
1064
|
var whenEchoObject = (node) => Obj.isObject(node.data) ? Option2.some(node.data) : Option2.none();
|
|
867
1065
|
var whenAll = (...matchers) => (node) => {
|
|
868
|
-
for (const
|
|
869
|
-
|
|
870
|
-
if (Option2.isNone(result)) {
|
|
1066
|
+
for (const candidate of matchers) {
|
|
1067
|
+
if (Option2.isNone(candidate(node))) {
|
|
871
1068
|
return Option2.none();
|
|
872
1069
|
}
|
|
873
1070
|
}
|
|
874
1071
|
return Option2.some(node);
|
|
875
1072
|
};
|
|
876
1073
|
var whenAny = (...matchers) => (node) => {
|
|
877
|
-
for (const
|
|
878
|
-
|
|
879
|
-
if (Option2.isSome(result)) {
|
|
1074
|
+
for (const candidate of matchers) {
|
|
1075
|
+
if (Option2.isSome(candidate(node))) {
|
|
880
1076
|
return Option2.some(node);
|
|
881
1077
|
}
|
|
882
1078
|
}
|
|
@@ -887,25 +1083,40 @@ var whenEchoObjectMatches = (node) => Obj.isObject(node.data) ? Option2.some(nod
|
|
|
887
1083
|
var whenNot = (matcher) => (node) => Option2.isNone(matcher(node)) ? Option2.some(node) : Option2.none();
|
|
888
1084
|
|
|
889
1085
|
// src/graph-builder.ts
|
|
890
|
-
var
|
|
891
|
-
var GraphBuilderTypeId = Symbol.for("@dxos/app-graph/GraphBuilder");
|
|
1086
|
+
var __dxlog_file3 = "/__w/dxos/dxos/packages/sdk/app-graph/src/graph-builder.ts";
|
|
1087
|
+
var GraphBuilderTypeId = /* @__PURE__ */ Symbol.for("@dxos/app-graph/GraphBuilder");
|
|
892
1088
|
var GraphBuilderImpl = class {
|
|
893
1089
|
[GraphBuilderTypeId] = GraphBuilderTypeId;
|
|
894
1090
|
pipe() {
|
|
895
1091
|
return Pipeable2.pipeArguments(this, arguments);
|
|
896
1092
|
}
|
|
897
1093
|
// TODO(wittjosiah): Use Context.
|
|
1094
|
+
/** Active subscriptions keyed by composite ID, cleaned up on node removal. */
|
|
898
1095
|
_subscriptions = /* @__PURE__ */ new Map();
|
|
1096
|
+
/** Connector updates pending flush, keyed by connector key. */
|
|
1097
|
+
_dirtyConnectors = /* @__PURE__ */ new Map();
|
|
1098
|
+
/** Last-flushed node IDs per connector key, used for edge removal on update. */
|
|
1099
|
+
_connectorPrevious = /* @__PURE__ */ new Map();
|
|
1100
|
+
/** Last-flushed node args per connector key, used for change detection. */
|
|
1101
|
+
_connectorPreviousArgs = /* @__PURE__ */ new Map();
|
|
1102
|
+
/** Whether a dirty-flush task is already scheduled. */
|
|
1103
|
+
_flushScheduled = false;
|
|
1104
|
+
/** Resolves when the current flush completes. */
|
|
1105
|
+
_flushPromise = Promise.resolve();
|
|
1106
|
+
/** Registered builder extensions keyed by extension ID. */
|
|
899
1107
|
_extensions = Atom3.make(Record2.empty()).pipe(Atom3.keepAlive, Atom3.withLabel("graph-builder:extensions"));
|
|
1108
|
+
/** Triggers signalling that a node's resolver has fired at least once. */
|
|
900
1109
|
_initialized = {};
|
|
1110
|
+
/** Shared atom registry for reactive subscriptions. */
|
|
901
1111
|
_registry;
|
|
1112
|
+
/** Backing graph with internal accessors for node atoms and construction. */
|
|
902
1113
|
_graph;
|
|
903
1114
|
constructor({ registry, ...params } = {}) {
|
|
904
1115
|
this._registry = registry ?? Registry2.make();
|
|
905
1116
|
const graph = make({
|
|
906
1117
|
...params,
|
|
907
1118
|
registry: this._registry,
|
|
908
|
-
onExpand: (id,
|
|
1119
|
+
onExpand: (id, relation2) => this._onExpand(id, relation2),
|
|
909
1120
|
onInitialize: (id) => this._onInitialize(id),
|
|
910
1121
|
onRemoveNode: (id) => this._onRemoveNode(id)
|
|
911
1122
|
});
|
|
@@ -917,6 +1128,52 @@ var GraphBuilderImpl = class {
|
|
|
917
1128
|
get extensions() {
|
|
918
1129
|
return this._extensions;
|
|
919
1130
|
}
|
|
1131
|
+
/** Apply a set of node changes for a single connector key. */
|
|
1132
|
+
_applyConnectorUpdate(key, nodes, previous) {
|
|
1133
|
+
const { id, relation: relation2 } = relationFromConnectorKey(key);
|
|
1134
|
+
const ids = nodes.map((node) => node.id);
|
|
1135
|
+
const removed = previous.filter((pid) => !ids.includes(pid));
|
|
1136
|
+
this._connectorPrevious.set(key, ids);
|
|
1137
|
+
this._connectorPreviousArgs.set(key, nodes);
|
|
1138
|
+
removeEdges(this._graph, removed.map((target) => ({
|
|
1139
|
+
source: id,
|
|
1140
|
+
target,
|
|
1141
|
+
relation: relation2
|
|
1142
|
+
})), true);
|
|
1143
|
+
addNodes(this._graph, nodes);
|
|
1144
|
+
addEdges(this._graph, nodes.map((node) => ({
|
|
1145
|
+
source: id,
|
|
1146
|
+
target: node.id,
|
|
1147
|
+
relation: relation2
|
|
1148
|
+
})));
|
|
1149
|
+
if (ids.length > 0) {
|
|
1150
|
+
const sortedIds = [
|
|
1151
|
+
...nodes
|
|
1152
|
+
].sort((a, b) => byPosition(a.properties ?? {}, b.properties ?? {})).map((n) => n.id);
|
|
1153
|
+
sortEdges(this._graph, id, relation2, sortedIds);
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
_scheduleDirtyFlush() {
|
|
1157
|
+
if (!this._flushScheduled) {
|
|
1158
|
+
this._flushScheduled = true;
|
|
1159
|
+
this._flushPromise = scheduleTask(() => {
|
|
1160
|
+
this._flushScheduled = false;
|
|
1161
|
+
while (this._dirtyConnectors.size > 0) {
|
|
1162
|
+
const entries = [
|
|
1163
|
+
...this._dirtyConnectors.entries()
|
|
1164
|
+
];
|
|
1165
|
+
this._dirtyConnectors.clear();
|
|
1166
|
+
Atom3.batch(() => {
|
|
1167
|
+
for (const [key, { nodes, previous }] of entries) {
|
|
1168
|
+
this._applyConnectorUpdate(key, nodes, previous);
|
|
1169
|
+
}
|
|
1170
|
+
});
|
|
1171
|
+
}
|
|
1172
|
+
}, {
|
|
1173
|
+
strategy: "smooth"
|
|
1174
|
+
});
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
920
1177
|
_resolvers = Atom3.family((id) => {
|
|
921
1178
|
return Atom3.make((get2) => {
|
|
922
1179
|
return Function2.pipe(get2(this._extensions), Record2.values, Array2.sortBy(byPosition), Array2.map(({ resolver }) => resolver), Array2.filter(isNonNullable2), Array2.map((resolver) => get2(resolver(id))), Array2.filter(isNonNullable2), Array2.head);
|
|
@@ -924,82 +1181,77 @@ var GraphBuilderImpl = class {
|
|
|
924
1181
|
});
|
|
925
1182
|
_connectors = Atom3.family((key) => {
|
|
926
1183
|
return Atom3.make((get2) => {
|
|
927
|
-
const
|
|
1184
|
+
const { id, relation: relation2 } = relationFromConnectorKey(key);
|
|
928
1185
|
const node = this._graph.node(id);
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
1186
|
+
const sourceNode = Option3.getOrElse(get2(node), () => void 0);
|
|
1187
|
+
if (!sourceNode) {
|
|
1188
|
+
return [];
|
|
1189
|
+
}
|
|
1190
|
+
const extensions = Function2.pipe(get2(this._extensions), Record2.values, Array2.sortBy(byPosition), Array2.filter((ext) => relationKey(ext.relation ?? "child") === relationKey(relation2) && ext.connector != null));
|
|
1191
|
+
const nodes = [];
|
|
1192
|
+
for (const ext of extensions) {
|
|
1193
|
+
const result = get2(ext.connector(node));
|
|
1194
|
+
nodes.push(...result);
|
|
1195
|
+
}
|
|
1196
|
+
return nodes;
|
|
939
1197
|
}).pipe(Atom3.withLabel(`graph-builder:connectors:${key}`));
|
|
940
1198
|
});
|
|
941
|
-
_onExpand(id,
|
|
1199
|
+
_onExpand(id, relation2) {
|
|
942
1200
|
log2("onExpand", {
|
|
943
1201
|
id,
|
|
944
|
-
relation,
|
|
1202
|
+
relation: relation2,
|
|
945
1203
|
registry: getDebugName(this._registry)
|
|
946
1204
|
}, {
|
|
947
|
-
F:
|
|
948
|
-
L:
|
|
1205
|
+
F: __dxlog_file3,
|
|
1206
|
+
L: 261,
|
|
949
1207
|
S: this,
|
|
950
1208
|
C: (f, a) => f(...a)
|
|
951
1209
|
});
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
1210
|
+
this._expandRelation(id, relation2);
|
|
1211
|
+
if (relation2.kind === "child" && relation2.direction === "outbound") {
|
|
1212
|
+
expand(this._graph, id, "action");
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
_expandRelation(id, relation2) {
|
|
1216
|
+
const key = connectorKey(id, relation2);
|
|
1217
|
+
const connectors = this._connectors(key);
|
|
1218
|
+
const cancel = this._registry.subscribe(connectors, (rawNodes) => {
|
|
1219
|
+
const nodes = qualifyNodeArgs(id)(rawNodes);
|
|
1220
|
+
const previous = this._connectorPrevious.get(key) ?? [];
|
|
955
1221
|
const ids = nodes.map((n) => n.id);
|
|
956
|
-
|
|
957
|
-
|
|
1222
|
+
if (ids.length === previous.length && ids.every((nodeId, idx) => nodeId === previous[idx])) {
|
|
1223
|
+
const prevArgs = this._connectorPreviousArgs.get(key);
|
|
1224
|
+
if (prevArgs && nodeArgsUnchanged(prevArgs, nodes)) {
|
|
1225
|
+
return;
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
958
1228
|
log2("update", {
|
|
959
1229
|
id,
|
|
960
|
-
relation,
|
|
961
|
-
ids
|
|
962
|
-
removed
|
|
1230
|
+
relation: relation2,
|
|
1231
|
+
ids
|
|
963
1232
|
}, {
|
|
964
|
-
F:
|
|
965
|
-
L:
|
|
1233
|
+
F: __dxlog_file3,
|
|
1234
|
+
L: 288,
|
|
966
1235
|
S: this,
|
|
967
1236
|
C: (f, a) => f(...a)
|
|
968
1237
|
});
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
})), true);
|
|
975
|
-
addNodes(this._graph, nodes);
|
|
976
|
-
addEdges(this._graph, nodes.map((node) => relation === "outbound" ? {
|
|
977
|
-
source: id,
|
|
978
|
-
target: node.id
|
|
979
|
-
} : {
|
|
980
|
-
source: node.id,
|
|
981
|
-
target: id
|
|
982
|
-
}));
|
|
983
|
-
sortEdges(this._graph, id, relation, nodes.map(({ id: id2 }) => id2));
|
|
984
|
-
});
|
|
985
|
-
};
|
|
986
|
-
if (typeof requestAnimationFrame === "function") {
|
|
987
|
-
requestAnimationFrame(update);
|
|
988
|
-
} else {
|
|
989
|
-
update();
|
|
990
|
-
}
|
|
1238
|
+
this._dirtyConnectors.set(key, {
|
|
1239
|
+
nodes,
|
|
1240
|
+
previous
|
|
1241
|
+
});
|
|
1242
|
+
this._scheduleDirtyFlush();
|
|
991
1243
|
}, {
|
|
992
1244
|
immediate: true
|
|
993
1245
|
});
|
|
994
|
-
this._subscriptions.set(id, cancel);
|
|
1246
|
+
this._subscriptions.set(subscriptionKey(id, "expand", key), cancel);
|
|
995
1247
|
}
|
|
996
1248
|
// TODO(wittjosiah): If the same node is added by a connector, the resolver should probably cancel itself?
|
|
997
1249
|
async _onInitialize(id) {
|
|
998
1250
|
log2("onInitialize", {
|
|
999
1251
|
id
|
|
1000
1252
|
}, {
|
|
1001
|
-
F:
|
|
1002
|
-
L:
|
|
1253
|
+
F: __dxlog_file3,
|
|
1254
|
+
L: 300,
|
|
1003
1255
|
S: this,
|
|
1004
1256
|
C: (f, a) => f(...a)
|
|
1005
1257
|
});
|
|
@@ -1008,9 +1260,14 @@ var GraphBuilderImpl = class {
|
|
|
1008
1260
|
const trigger = this._initialized[id];
|
|
1009
1261
|
Option3.match(node, {
|
|
1010
1262
|
onSome: (node2) => {
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
]);
|
|
1263
|
+
const connectorOwned = [
|
|
1264
|
+
...this._connectorPrevious.values()
|
|
1265
|
+
].some((ids) => ids.includes(id));
|
|
1266
|
+
if (!connectorOwned) {
|
|
1267
|
+
addNodes(this._graph, [
|
|
1268
|
+
node2
|
|
1269
|
+
]);
|
|
1270
|
+
}
|
|
1014
1271
|
trigger?.wake();
|
|
1015
1272
|
},
|
|
1016
1273
|
onNone: () => {
|
|
@@ -1023,11 +1280,16 @@ var GraphBuilderImpl = class {
|
|
|
1023
1280
|
}, {
|
|
1024
1281
|
immediate: true
|
|
1025
1282
|
});
|
|
1026
|
-
this._subscriptions.set(id, cancel);
|
|
1283
|
+
this._subscriptions.set(subscriptionKey(id, "init"), cancel);
|
|
1027
1284
|
}
|
|
1028
1285
|
_onRemoveNode(id) {
|
|
1029
|
-
|
|
1030
|
-
this._subscriptions
|
|
1286
|
+
const prefix = `${id}${Separators.primary}`;
|
|
1287
|
+
for (const [key, cleanup] of this._subscriptions) {
|
|
1288
|
+
if (key.startsWith(prefix)) {
|
|
1289
|
+
cleanup();
|
|
1290
|
+
this._subscriptions.delete(key);
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1031
1293
|
}
|
|
1032
1294
|
};
|
|
1033
1295
|
var make2 = (params) => {
|
|
@@ -1080,14 +1342,11 @@ function removeExtension(builderOrId, id) {
|
|
|
1080
1342
|
}
|
|
1081
1343
|
var exploreImpl = async (builder, options, path = []) => {
|
|
1082
1344
|
const internal = builder;
|
|
1083
|
-
const { registry = Registry2.make(), source = RootId, relation
|
|
1345
|
+
const { registry = Registry2.make(), source = RootId, relation: relation2, visitor } = options;
|
|
1084
1346
|
if (path.includes(source)) {
|
|
1085
1347
|
return;
|
|
1086
1348
|
}
|
|
1087
|
-
|
|
1088
|
-
const { yieldOrContinue } = await import("main-thread-scheduling");
|
|
1089
|
-
await yieldOrContinue("idle");
|
|
1090
|
-
}
|
|
1349
|
+
await yieldOrContinue("idle");
|
|
1091
1350
|
const node = registry.get(internal._graph.nodeOrThrow(source));
|
|
1092
1351
|
const shouldContinue = await visitor(node, [
|
|
1093
1352
|
...path,
|
|
@@ -1096,13 +1355,13 @@ var exploreImpl = async (builder, options, path = []) => {
|
|
|
1096
1355
|
if (shouldContinue === false) {
|
|
1097
1356
|
return;
|
|
1098
1357
|
}
|
|
1099
|
-
const nodes =
|
|
1358
|
+
const nodes = Function2.pipe(internal._registry.get(internal._extensions), Record2.values, Array2.map((extension) => extension.connector), Array2.filter(isNonNullable2), Array2.flatMap((connector) => registry.get(connector(internal._graph.node(source)))), qualifyNodeArgs(source));
|
|
1100
1359
|
await Promise.all(nodes.map((nodeArg) => {
|
|
1101
1360
|
registry.set(internal._graph._node(nodeArg.id), internal._graph._constructNode(nodeArg));
|
|
1102
1361
|
return exploreImpl(builder, {
|
|
1103
1362
|
registry,
|
|
1104
1363
|
source: nodeArg.id,
|
|
1105
|
-
relation,
|
|
1364
|
+
relation: relation2,
|
|
1106
1365
|
visitor
|
|
1107
1366
|
}, [
|
|
1108
1367
|
...path,
|
|
@@ -1138,8 +1397,12 @@ function destroy(builder) {
|
|
|
1138
1397
|
return destroyImpl(builder);
|
|
1139
1398
|
}
|
|
1140
1399
|
}
|
|
1400
|
+
var flush = (builder) => {
|
|
1401
|
+
return builder._flushPromise;
|
|
1402
|
+
};
|
|
1141
1403
|
var createExtensionRaw = (extension) => {
|
|
1142
|
-
const { id, position = "static", relation = "
|
|
1404
|
+
const { id, position = "static", relation: relation2 = "child", resolver: _resolver, connector: _connector, actions: _actions, actionGroups: _actionGroups } = extension;
|
|
1405
|
+
const normalizedRelation = normalizeRelation(relation2);
|
|
1143
1406
|
const getId = (key) => `${id}/${key}`;
|
|
1144
1407
|
const resolver = _resolver && Atom3.family((id2) => _resolver(id2).pipe(Atom3.withLabel(`graph-builder:_resolver:${id2}`)));
|
|
1145
1408
|
const connector = _connector && Atom3.family((node) => _connector(node).pipe(Atom3.withLabel(`graph-builder:_connector:${id}`)));
|
|
@@ -1154,7 +1417,7 @@ var createExtensionRaw = (extension) => {
|
|
|
1154
1417
|
connector ? {
|
|
1155
1418
|
id: getId("connector"),
|
|
1156
1419
|
position,
|
|
1157
|
-
relation,
|
|
1420
|
+
relation: normalizedRelation,
|
|
1158
1421
|
connector: Atom3.family((node) => Atom3.make((get2) => {
|
|
1159
1422
|
try {
|
|
1160
1423
|
return get2(connector(node));
|
|
@@ -1164,8 +1427,8 @@ var createExtensionRaw = (extension) => {
|
|
|
1164
1427
|
node,
|
|
1165
1428
|
error
|
|
1166
1429
|
}, {
|
|
1167
|
-
F:
|
|
1168
|
-
L:
|
|
1430
|
+
F: __dxlog_file3,
|
|
1431
|
+
L: 596,
|
|
1169
1432
|
S: void 0,
|
|
1170
1433
|
C: (f, a) => f(...a)
|
|
1171
1434
|
});
|
|
@@ -1176,7 +1439,7 @@ var createExtensionRaw = (extension) => {
|
|
|
1176
1439
|
actionGroups ? {
|
|
1177
1440
|
id: getId("actionGroups"),
|
|
1178
1441
|
position,
|
|
1179
|
-
relation:
|
|
1442
|
+
relation: actionRelation(),
|
|
1180
1443
|
connector: Atom3.family((node) => Atom3.make((get2) => {
|
|
1181
1444
|
try {
|
|
1182
1445
|
return get2(actionGroups(node)).map((arg) => ({
|
|
@@ -1190,8 +1453,8 @@ var createExtensionRaw = (extension) => {
|
|
|
1190
1453
|
node,
|
|
1191
1454
|
error
|
|
1192
1455
|
}, {
|
|
1193
|
-
F:
|
|
1194
|
-
L:
|
|
1456
|
+
F: __dxlog_file3,
|
|
1457
|
+
L: 617,
|
|
1195
1458
|
S: void 0,
|
|
1196
1459
|
C: (f, a) => f(...a)
|
|
1197
1460
|
});
|
|
@@ -1202,7 +1465,7 @@ var createExtensionRaw = (extension) => {
|
|
|
1202
1465
|
actions ? {
|
|
1203
1466
|
id: getId("actions"),
|
|
1204
1467
|
position,
|
|
1205
|
-
relation:
|
|
1468
|
+
relation: actionRelation(),
|
|
1206
1469
|
connector: Atom3.family((node) => Atom3.make((get2) => {
|
|
1207
1470
|
try {
|
|
1208
1471
|
return get2(actions(node)).map((arg) => ({
|
|
@@ -1215,8 +1478,8 @@ var createExtensionRaw = (extension) => {
|
|
|
1215
1478
|
node,
|
|
1216
1479
|
error
|
|
1217
1480
|
}, {
|
|
1218
|
-
F:
|
|
1219
|
-
L:
|
|
1481
|
+
F: __dxlog_file3,
|
|
1482
|
+
L: 634,
|
|
1220
1483
|
S: void 0,
|
|
1221
1484
|
C: (f, a) => f(...a)
|
|
1222
1485
|
});
|
|
@@ -1232,8 +1495,8 @@ var runEffectSyncWithFallback = (effect, context2, extensionId, fallback) => {
|
|
|
1232
1495
|
extension: extensionId,
|
|
1233
1496
|
error
|
|
1234
1497
|
}, {
|
|
1235
|
-
F:
|
|
1236
|
-
L:
|
|
1498
|
+
F: __dxlog_file3,
|
|
1499
|
+
L: 677,
|
|
1237
1500
|
S: void 0,
|
|
1238
1501
|
C: (f, a) => f(...a)
|
|
1239
1502
|
});
|
|
@@ -1241,7 +1504,7 @@ var runEffectSyncWithFallback = (effect, context2, extensionId, fallback) => {
|
|
|
1241
1504
|
})));
|
|
1242
1505
|
};
|
|
1243
1506
|
var createExtension = (options) => Effect.map(Effect.context(), (context2) => {
|
|
1244
|
-
const { id, match: match3, actions, connector, resolver, relation, position } = options;
|
|
1507
|
+
const { id, match: match3, actions, connector, resolver, relation: relation2, position } = options;
|
|
1245
1508
|
const connectorExtension = connector ? createConnectorWithRuntime(id, match3, connector, context2) : void 0;
|
|
1246
1509
|
const actionsExtension = actions ? (node) => Atom3.make((get2) => Function2.pipe(get2(node), Option3.flatMap(match3), Option3.map((matched) => runEffectSyncWithFallback(actions(matched, get2), context2, id, []).map((action) => ({
|
|
1247
1510
|
...action,
|
|
@@ -1251,7 +1514,7 @@ var createExtension = (options) => Effect.map(Effect.context(), (context2) => {
|
|
|
1251
1514
|
const resolverExtension = resolver ? (nodeId) => Atom3.make((get2) => runEffectSyncWithFallback(resolver(nodeId, get2), context2, id, null) ?? null) : void 0;
|
|
1252
1515
|
return createExtensionRaw({
|
|
1253
1516
|
id,
|
|
1254
|
-
relation,
|
|
1517
|
+
relation: relation2,
|
|
1255
1518
|
position,
|
|
1256
1519
|
connector: connectorExtension,
|
|
1257
1520
|
actions: actionsExtension,
|
|
@@ -1265,16 +1528,35 @@ var createConnectorWithRuntime = (extensionId, matcher, factory, context2) => {
|
|
|
1265
1528
|
return (node) => Atom3.make((get2) => Function2.pipe(get2(node), Option3.flatMap(matcher), Option3.map((data) => runEffectSyncWithFallback(factory(data, get2), context2, extensionId, [])), Option3.getOrElse(() => [])));
|
|
1266
1529
|
};
|
|
1267
1530
|
var createTypeExtension = (options) => {
|
|
1268
|
-
const { id, type, actions, connector, relation, position } = options;
|
|
1531
|
+
const { id, type, actions, connector, relation: relation2, position } = options;
|
|
1269
1532
|
return createExtension({
|
|
1270
1533
|
id,
|
|
1271
1534
|
match: whenEchoType(type),
|
|
1272
1535
|
actions,
|
|
1273
1536
|
connector,
|
|
1274
|
-
relation,
|
|
1537
|
+
relation: relation2,
|
|
1275
1538
|
position
|
|
1276
1539
|
});
|
|
1277
1540
|
};
|
|
1541
|
+
var qualifyNodeArgs = (parentId) => (nodes) => nodes.map((node) => {
|
|
1542
|
+
validateSegmentId(node.id);
|
|
1543
|
+
const qualified = qualifyId(parentId, node.id);
|
|
1544
|
+
return {
|
|
1545
|
+
...node,
|
|
1546
|
+
id: qualified,
|
|
1547
|
+
nodes: node.nodes ? qualifyNodeArgs(qualified)(node.nodes) : void 0
|
|
1548
|
+
};
|
|
1549
|
+
});
|
|
1550
|
+
var connectorKey = (id, relation2) => `${id}${Separators.primary}${relationKey(relation2)}`;
|
|
1551
|
+
var relationFromConnectorKey = (key) => {
|
|
1552
|
+
const separatorIndex = key.indexOf(Separators.primary);
|
|
1553
|
+
const id = key.slice(0, separatorIndex);
|
|
1554
|
+
return {
|
|
1555
|
+
id,
|
|
1556
|
+
relation: relationFromKey(key.slice(separatorIndex + 1))
|
|
1557
|
+
};
|
|
1558
|
+
};
|
|
1559
|
+
var subscriptionKey = (id, kind, detail) => detail != null ? `${id}${Separators.primary}${kind}${Separators.primary}${detail}` : `${id}${Separators.primary}${kind}`;
|
|
1278
1560
|
var flattenExtensions = (extension, acc = []) => {
|
|
1279
1561
|
if (Array2.isArray(extension)) {
|
|
1280
1562
|
return [
|