@dxos/app-graph 0.6.3-main.9e4e207 → 0.6.3-next.2f65b78
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 +191 -582
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node/index.cjs +185 -587
- package/dist/lib/node/index.cjs.map +4 -4
- package/dist/lib/node/meta.json +1 -1
- package/dist/types/src/graph-builder.d.ts +7 -99
- package/dist/types/src/graph-builder.d.ts.map +1 -1
- package/dist/types/src/graph.d.ts +47 -96
- package/dist/types/src/graph.d.ts.map +1 -1
- package/dist/types/src/helpers.d.ts +12 -0
- package/dist/types/src/helpers.d.ts.map +1 -0
- package/dist/types/src/index.d.ts +1 -0
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/node.d.ts +40 -99
- package/dist/types/src/node.d.ts.map +1 -1
- package/dist/types/src/stories/EchoGraph.stories.d.ts.map +1 -1
- package/package.json +12 -14
- package/src/graph-builder.ts +19 -332
- package/src/graph.test.ts +179 -431
- package/src/graph.ts +149 -336
- package/src/helpers.ts +27 -0
- package/src/index.ts +1 -0
- package/src/node.ts +42 -15
- package/src/stories/EchoGraph.stories.tsx +102 -84
- package/dist/types/src/graph-builder.test.d.ts +0 -2
- package/dist/types/src/graph-builder.test.d.ts.map +0 -1
- package/src/graph-builder.test.ts +0 -310
package/dist/lib/node/index.cjs
CHANGED
|
@@ -18,83 +18,65 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
18
18
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
19
|
var node_exports = {};
|
|
20
20
|
__export(node_exports, {
|
|
21
|
-
ACTION_GROUP_TYPE: () => ACTION_GROUP_TYPE,
|
|
22
|
-
ACTION_TYPE: () => ACTION_TYPE,
|
|
23
21
|
Graph: () => Graph,
|
|
24
22
|
GraphBuilder: () => GraphBuilder,
|
|
25
23
|
ROOT_ID: () => ROOT_ID,
|
|
26
|
-
ROOT_TYPE: () => ROOT_TYPE,
|
|
27
24
|
actionGroupSymbol: () => actionGroupSymbol,
|
|
28
|
-
cleanup: () => cleanup,
|
|
29
|
-
createExtension: () => createExtension,
|
|
30
|
-
getGraph: () => getGraph,
|
|
31
25
|
isAction: () => isAction,
|
|
32
26
|
isActionGroup: () => isActionGroup,
|
|
33
27
|
isActionLike: () => isActionLike,
|
|
34
28
|
isGraphNode: () => isGraphNode,
|
|
35
|
-
|
|
36
|
-
toSignal: () => toSignal
|
|
29
|
+
manageNodes: () => manageNodes
|
|
37
30
|
});
|
|
38
31
|
module.exports = __toCommonJS(node_exports);
|
|
39
32
|
var import_signals_core = require("@preact/signals-core");
|
|
40
|
-
var import_async = require("@dxos/async");
|
|
41
33
|
var import_echo_schema = require("@dxos/echo-schema");
|
|
42
34
|
var import_invariant = require("@dxos/invariant");
|
|
43
35
|
var import_util = require("@dxos/util");
|
|
44
|
-
var
|
|
45
|
-
var import_echo_schema2 = require("@dxos/echo-schema");
|
|
46
|
-
var import_invariant2 = require("@dxos/invariant");
|
|
47
|
-
var import_util2 = require("@dxos/util");
|
|
36
|
+
var import_async = require("@dxos/async");
|
|
48
37
|
var isGraphNode = (data) => data && typeof data === "object" && "id" in data && "properties" in data && data.properties ? typeof data.properties === "object" && "data" in data : false;
|
|
49
38
|
var isAction = (data) => isGraphNode(data) ? typeof data.data === "function" : false;
|
|
50
39
|
var actionGroupSymbol = Symbol("ActionGroup");
|
|
51
40
|
var isActionGroup = (data) => isGraphNode(data) ? data.data === actionGroupSymbol : false;
|
|
52
41
|
var isActionLike = (data) => isAction(data) || isActionGroup(data);
|
|
53
42
|
var __dxlog_file = "/home/runner/work/dxos/dxos/packages/sdk/app-graph/src/graph.ts";
|
|
54
|
-
var graphSymbol = Symbol("graph");
|
|
55
|
-
var getGraph = (node) => {
|
|
56
|
-
const graph = node[graphSymbol];
|
|
57
|
-
(0, import_invariant.invariant)(graph, "Node is not associated with a graph.", {
|
|
58
|
-
F: __dxlog_file,
|
|
59
|
-
L: 20,
|
|
60
|
-
S: void 0,
|
|
61
|
-
A: [
|
|
62
|
-
"graph",
|
|
63
|
-
"'Node is not associated with a graph.'"
|
|
64
|
-
]
|
|
65
|
-
});
|
|
66
|
-
return graph;
|
|
67
|
-
};
|
|
68
43
|
var ROOT_ID = "root";
|
|
69
|
-
var ROOT_TYPE = "dxos.org/type/GraphRoot";
|
|
70
|
-
var ACTION_TYPE = "dxos.org/type/GraphAction";
|
|
71
|
-
var ACTION_GROUP_TYPE = "dxos.org/type/GraphActionGroup";
|
|
72
|
-
var NODE_TIMEOUT = 5e3;
|
|
73
44
|
var Graph = class {
|
|
74
|
-
constructor(
|
|
75
|
-
this.
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
...node,
|
|
82
|
-
[graphSymbol]: this
|
|
83
|
-
});
|
|
84
|
-
};
|
|
85
|
-
this._onInitialNode = onInitialNode;
|
|
86
|
-
this._onInitialNodes = onInitialNodes;
|
|
87
|
-
this._onRemoveNode = onRemoveNode;
|
|
88
|
-
this._nodes[ROOT_ID] = this._constructNode({
|
|
89
|
-
id: ROOT_ID,
|
|
90
|
-
type: ROOT_TYPE,
|
|
91
|
-
properties: {},
|
|
92
|
-
data: null
|
|
93
|
-
});
|
|
94
|
-
this._edges[ROOT_ID] = (0, import_echo_schema.create)({
|
|
95
|
-
inbound: [],
|
|
96
|
-
outbound: []
|
|
45
|
+
constructor() {
|
|
46
|
+
this._nodes = (0, import_echo_schema.create)({
|
|
47
|
+
[ROOT_ID]: {
|
|
48
|
+
id: ROOT_ID,
|
|
49
|
+
properties: {},
|
|
50
|
+
data: null
|
|
51
|
+
}
|
|
97
52
|
});
|
|
53
|
+
this._edges = (0, import_echo_schema.create)({});
|
|
54
|
+
this._constructNode = (nodeBase) => {
|
|
55
|
+
const node = {
|
|
56
|
+
...nodeBase,
|
|
57
|
+
edges: ({ direction = "outbound" } = {}) => {
|
|
58
|
+
return this._edges[this.getEdgeKey(node.id, direction)];
|
|
59
|
+
},
|
|
60
|
+
nodes: ({ direction, filter } = {}) => {
|
|
61
|
+
const nodes = this._getNodes({
|
|
62
|
+
id: node.id,
|
|
63
|
+
direction
|
|
64
|
+
}).filter((n) => !isActionLike(n));
|
|
65
|
+
return filter ? nodes.filter((n) => filter(n, node)) : nodes;
|
|
66
|
+
},
|
|
67
|
+
node: (id) => {
|
|
68
|
+
return this._getNodes({
|
|
69
|
+
id
|
|
70
|
+
}).find((node2) => node2.id === id);
|
|
71
|
+
},
|
|
72
|
+
actions: () => {
|
|
73
|
+
return this._getNodes({
|
|
74
|
+
id: node.id
|
|
75
|
+
}).filter(isActionLike);
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
return node;
|
|
79
|
+
};
|
|
98
80
|
}
|
|
99
81
|
/**
|
|
100
82
|
* Alias for `findNode('root')`.
|
|
@@ -105,14 +87,11 @@ var Graph = class {
|
|
|
105
87
|
/**
|
|
106
88
|
* Convert the graph to a JSON object.
|
|
107
89
|
*/
|
|
108
|
-
toJSON({ id = ROOT_ID, maxLength = 32
|
|
90
|
+
toJSON({ id = ROOT_ID, maxLength = 32 } = {}) {
|
|
109
91
|
const toJSON = (node, seen = []) => {
|
|
110
|
-
const nodes =
|
|
111
|
-
onlyLoaded
|
|
112
|
-
});
|
|
92
|
+
const nodes = node.nodes();
|
|
113
93
|
const obj = {
|
|
114
|
-
id: node.id.length > maxLength ? `${node.id.slice(0, maxLength - 3)}...` : node.id
|
|
115
|
-
type: node.type
|
|
94
|
+
id: node.id.length > maxLength ? `${node.id.slice(0, maxLength - 3)}...` : node.id
|
|
116
95
|
};
|
|
117
96
|
if (node.properties.label) {
|
|
118
97
|
obj.label = node.properties.label;
|
|
@@ -131,7 +110,7 @@ var Graph = class {
|
|
|
131
110
|
const root = this.findNode(id);
|
|
132
111
|
(0, import_invariant.invariant)(root, `Node not found: ${id}`, {
|
|
133
112
|
F: __dxlog_file,
|
|
134
|
-
L:
|
|
113
|
+
L: 91,
|
|
135
114
|
S: this,
|
|
136
115
|
A: [
|
|
137
116
|
"root",
|
|
@@ -142,310 +121,112 @@ var Graph = class {
|
|
|
142
121
|
}
|
|
143
122
|
/**
|
|
144
123
|
* Find the node with the given id in the graph.
|
|
145
|
-
*
|
|
146
|
-
* If a node is not found within the graph and an `onInitialNode` callback is provided,
|
|
147
|
-
* it is called with the id and type of the node, potentially initializing the node.
|
|
148
124
|
*/
|
|
149
|
-
findNode(id
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
}
|
|
154
|
-
/**
|
|
155
|
-
* Wait for a node to be added to the graph.
|
|
156
|
-
*
|
|
157
|
-
* If the node is already present in the graph, the promise resolves immediately.
|
|
158
|
-
*
|
|
159
|
-
* @param id The id of the node to wait for.
|
|
160
|
-
* @param timeout The time in milliseconds to wait for the node to be added.
|
|
161
|
-
*/
|
|
162
|
-
waitForNode(id, timeout = NODE_TIMEOUT) {
|
|
163
|
-
if (this._nodes[id]) {
|
|
164
|
-
return Promise.resolve(this._nodes[id]);
|
|
125
|
+
findNode(id) {
|
|
126
|
+
const nodeBase = this._nodes[id];
|
|
127
|
+
if (!nodeBase) {
|
|
128
|
+
return void 0;
|
|
165
129
|
}
|
|
166
|
-
|
|
167
|
-
return trigger.wait({
|
|
168
|
-
timeout
|
|
169
|
-
});
|
|
170
|
-
}
|
|
171
|
-
/**
|
|
172
|
-
* Nodes that this node is connected to in default order.
|
|
173
|
-
*/
|
|
174
|
-
nodes(node, options = {}) {
|
|
175
|
-
const { onlyLoaded, relation, filter, type } = options;
|
|
176
|
-
const nodes = this._getNodes({
|
|
177
|
-
node,
|
|
178
|
-
relation,
|
|
179
|
-
type,
|
|
180
|
-
onlyLoaded
|
|
181
|
-
});
|
|
182
|
-
return nodes.filter((n) => (0, import_signals_core.untracked)(() => !isActionLike(n))).filter((n) => filter?.(n, node) ?? true);
|
|
183
|
-
}
|
|
184
|
-
/**
|
|
185
|
-
* Edges that this node is connected to in default order.
|
|
186
|
-
*/
|
|
187
|
-
edges(node, { relation = "outbound" } = {}) {
|
|
188
|
-
return this._edges[node.id]?.[relation] ?? [];
|
|
130
|
+
return this._constructNode(nodeBase);
|
|
189
131
|
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
return [
|
|
195
|
-
...this._getNodes({
|
|
196
|
-
node,
|
|
197
|
-
type: ACTION_GROUP_TYPE,
|
|
198
|
-
onlyLoaded
|
|
199
|
-
}),
|
|
200
|
-
...this._getNodes({
|
|
201
|
-
node,
|
|
202
|
-
type: ACTION_TYPE,
|
|
203
|
-
onlyLoaded
|
|
204
|
-
})
|
|
205
|
-
];
|
|
206
|
-
}
|
|
207
|
-
/**
|
|
208
|
-
* Recursive depth-first traversal of the graph.
|
|
209
|
-
*
|
|
210
|
-
* @param options.node The node to start traversing from.
|
|
211
|
-
* @param options.relation The relation to traverse graph edges.
|
|
212
|
-
* @param options.visitor A callback which is called for each node visited during traversal.
|
|
213
|
-
*/
|
|
214
|
-
traverse({ visitor, node = this.root, relation = "outbound", onlyLoaded }, path = []) {
|
|
215
|
-
if (path.includes(node.id)) {
|
|
216
|
-
return;
|
|
217
|
-
}
|
|
218
|
-
const shouldContinue = visitor(node, [
|
|
219
|
-
...path,
|
|
220
|
-
node.id
|
|
221
|
-
]);
|
|
222
|
-
if (shouldContinue === false) {
|
|
223
|
-
return;
|
|
132
|
+
_getNodes({ id, direction = "outbound" }) {
|
|
133
|
+
const edges = this._edges[this.getEdgeKey(id, direction)];
|
|
134
|
+
if (!edges) {
|
|
135
|
+
return [];
|
|
224
136
|
}
|
|
225
|
-
|
|
226
|
-
node,
|
|
227
|
-
relation,
|
|
228
|
-
onlyLoaded
|
|
229
|
-
})).forEach((child) => this.traverse({
|
|
230
|
-
node: child,
|
|
231
|
-
relation,
|
|
232
|
-
visitor,
|
|
233
|
-
onlyLoaded
|
|
234
|
-
}, [
|
|
235
|
-
...path,
|
|
236
|
-
node.id
|
|
237
|
-
]));
|
|
137
|
+
return edges.map((id2) => this.findNode(id2)).filter(import_util.nonNullable);
|
|
238
138
|
}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
*
|
|
242
|
-
* @param options.node The node to start traversing from.
|
|
243
|
-
* @param options.relation The relation to traverse graph edges.
|
|
244
|
-
* @param options.visitor A callback which is called for each node visited during traversal.
|
|
245
|
-
*/
|
|
246
|
-
subscribeTraverse({ visitor, node = this.root, relation = "outbound", onlyLoaded }, currentPath = []) {
|
|
247
|
-
return (0, import_signals_core.effect)(() => {
|
|
248
|
-
const path = [
|
|
249
|
-
...currentPath,
|
|
250
|
-
node.id
|
|
251
|
-
];
|
|
252
|
-
const result = visitor(node, path);
|
|
253
|
-
if (result === false) {
|
|
254
|
-
return;
|
|
255
|
-
}
|
|
256
|
-
const nodes = this._getNodes({
|
|
257
|
-
node,
|
|
258
|
-
relation,
|
|
259
|
-
onlyLoaded
|
|
260
|
-
});
|
|
261
|
-
const nodeSubscriptions = nodes.map((n) => this.subscribeTraverse({
|
|
262
|
-
node: n,
|
|
263
|
-
visitor,
|
|
264
|
-
onlyLoaded
|
|
265
|
-
}, path));
|
|
266
|
-
return () => {
|
|
267
|
-
nodeSubscriptions.forEach((unsubscribe) => unsubscribe());
|
|
268
|
-
};
|
|
269
|
-
});
|
|
270
|
-
}
|
|
271
|
-
/**
|
|
272
|
-
* Get the path between two nodes in the graph.
|
|
273
|
-
*/
|
|
274
|
-
getPath({ source = "root", target }) {
|
|
275
|
-
const start = this.findNode(source);
|
|
276
|
-
if (!start) {
|
|
277
|
-
return void 0;
|
|
278
|
-
}
|
|
279
|
-
let found;
|
|
280
|
-
this.traverse({
|
|
281
|
-
onlyLoaded: true,
|
|
282
|
-
node: start,
|
|
283
|
-
visitor: (node, path) => {
|
|
284
|
-
if (found) {
|
|
285
|
-
return false;
|
|
286
|
-
}
|
|
287
|
-
if (node.id === target) {
|
|
288
|
-
found = path;
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
});
|
|
292
|
-
return found;
|
|
139
|
+
getEdgeKey(id, direction) {
|
|
140
|
+
return `${id}-${direction}`;
|
|
293
141
|
}
|
|
294
142
|
/**
|
|
295
143
|
* Add nodes to the graph.
|
|
296
|
-
*
|
|
297
|
-
* @internal
|
|
298
144
|
*/
|
|
299
|
-
|
|
300
|
-
return
|
|
145
|
+
addNodes(...nodes) {
|
|
146
|
+
return nodes.map((node) => this._addNode(node));
|
|
301
147
|
}
|
|
302
148
|
_addNode({ nodes, edges, ..._node }) {
|
|
303
149
|
return (0, import_signals_core.untracked)(() => {
|
|
304
|
-
const
|
|
305
|
-
const node = existingNode ?? this._constructNode({
|
|
150
|
+
const node = {
|
|
306
151
|
data: null,
|
|
307
152
|
properties: {},
|
|
308
153
|
..._node
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
const { data, properties, type } = _node;
|
|
312
|
-
if (data && data !== node.data) {
|
|
313
|
-
node.data = data;
|
|
314
|
-
}
|
|
315
|
-
if (type !== node.type) {
|
|
316
|
-
node.type = type;
|
|
317
|
-
}
|
|
318
|
-
for (const key in properties) {
|
|
319
|
-
if (properties[key] !== node.properties[key]) {
|
|
320
|
-
node.properties[key] = properties[key];
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
} else {
|
|
324
|
-
this._nodes[node.id] = node;
|
|
325
|
-
this._edges[node.id] = (0, import_echo_schema.create)({
|
|
326
|
-
inbound: [],
|
|
327
|
-
outbound: []
|
|
328
|
-
});
|
|
329
|
-
}
|
|
330
|
-
const trigger = this._waitingForNodes[node.id];
|
|
331
|
-
if (trigger) {
|
|
332
|
-
trigger.wake(node);
|
|
333
|
-
delete this._waitingForNodes[node.id];
|
|
334
|
-
}
|
|
154
|
+
};
|
|
155
|
+
this._nodes[node.id] = node;
|
|
335
156
|
if (nodes) {
|
|
336
157
|
nodes.forEach((subNode) => {
|
|
337
158
|
this._addNode(subNode);
|
|
338
|
-
this.
|
|
159
|
+
this.addEdge({
|
|
339
160
|
source: node.id,
|
|
340
161
|
target: subNode.id
|
|
341
162
|
});
|
|
342
163
|
});
|
|
343
164
|
}
|
|
344
165
|
if (edges) {
|
|
345
|
-
edges.forEach(([id,
|
|
166
|
+
edges.forEach(([id, direction]) => direction === "outbound" ? this.addEdge({
|
|
346
167
|
source: node.id,
|
|
347
168
|
target: id
|
|
348
|
-
}) : this.
|
|
169
|
+
}) : this.addEdge({
|
|
349
170
|
source: id,
|
|
350
171
|
target: node.id
|
|
351
172
|
}));
|
|
352
173
|
}
|
|
353
|
-
return node;
|
|
174
|
+
return this._constructNode(node);
|
|
354
175
|
});
|
|
355
176
|
}
|
|
356
177
|
/**
|
|
357
178
|
* Remove nodes from the graph.
|
|
358
179
|
*
|
|
359
|
-
* @param
|
|
180
|
+
* @param id The id of the node to remove.
|
|
360
181
|
* @param edges Whether to remove edges connected to the node from the graph as well.
|
|
361
|
-
* @internal
|
|
362
182
|
*/
|
|
363
|
-
|
|
364
|
-
(0, import_signals_core.batch)(() => ids.forEach((id) => this._removeNode(id, edges)));
|
|
365
|
-
}
|
|
366
|
-
_removeNode(id, edges = false) {
|
|
183
|
+
removeNode(id, edges = false) {
|
|
367
184
|
(0, import_signals_core.untracked)(() => {
|
|
368
185
|
const node = this.findNode(id);
|
|
369
186
|
if (!node) {
|
|
370
187
|
return;
|
|
371
188
|
}
|
|
372
189
|
if (edges) {
|
|
190
|
+
delete this._edges[this.getEdgeKey(id, "outbound")];
|
|
191
|
+
delete this._edges[this.getEdgeKey(id, "inbound")];
|
|
373
192
|
this._getNodes({
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
target: node2.id
|
|
380
|
-
});
|
|
381
|
-
});
|
|
193
|
+
id
|
|
194
|
+
}).forEach((node2) => this.removeEdge({
|
|
195
|
+
source: id,
|
|
196
|
+
target: node2.id
|
|
197
|
+
}));
|
|
382
198
|
this._getNodes({
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
target: id
|
|
390
|
-
});
|
|
391
|
-
});
|
|
392
|
-
delete this._edges[id];
|
|
199
|
+
id,
|
|
200
|
+
direction: "inbound"
|
|
201
|
+
}).forEach((node2) => this.removeEdge({
|
|
202
|
+
source: node2.id,
|
|
203
|
+
target: id
|
|
204
|
+
}));
|
|
393
205
|
}
|
|
394
206
|
delete this._nodes[id];
|
|
395
|
-
this._onRemoveNode?.(id);
|
|
396
207
|
});
|
|
397
208
|
}
|
|
398
209
|
/**
|
|
399
|
-
* Add
|
|
400
|
-
*
|
|
401
|
-
* @internal
|
|
210
|
+
* Add an edge to the graph.
|
|
402
211
|
*/
|
|
403
|
-
|
|
404
|
-
(0, import_signals_core.batch)(() => edges.forEach((edge) => this._addEdge(edge)));
|
|
405
|
-
}
|
|
406
|
-
_addEdge({ source, target }) {
|
|
212
|
+
addEdge({ source, target }) {
|
|
407
213
|
(0, import_signals_core.untracked)(() => {
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
this._edges[target] = (0, import_echo_schema.create)({
|
|
416
|
-
inbound: [],
|
|
417
|
-
outbound: []
|
|
418
|
-
});
|
|
214
|
+
const outbound = this._edges[this.getEdgeKey(source, "outbound")];
|
|
215
|
+
if (!outbound) {
|
|
216
|
+
this._edges[this.getEdgeKey(source, "outbound")] = [
|
|
217
|
+
target
|
|
218
|
+
];
|
|
219
|
+
} else if (!outbound.includes(target)) {
|
|
220
|
+
outbound.push(target);
|
|
419
221
|
}
|
|
420
|
-
const
|
|
421
|
-
if (!
|
|
422
|
-
|
|
222
|
+
const inbound = this._edges[this.getEdgeKey(target, "inbound")];
|
|
223
|
+
if (!inbound) {
|
|
224
|
+
this._edges[this.getEdgeKey(target, "inbound")] = [
|
|
225
|
+
source
|
|
226
|
+
];
|
|
227
|
+
} else if (!inbound.includes(source)) {
|
|
228
|
+
inbound.push(source);
|
|
423
229
|
}
|
|
424
|
-
const targetEdges = this._edges[target];
|
|
425
|
-
if (!targetEdges.inbound.includes(source)) {
|
|
426
|
-
targetEdges.inbound.push(source);
|
|
427
|
-
}
|
|
428
|
-
});
|
|
429
|
-
}
|
|
430
|
-
/**
|
|
431
|
-
* Remove edges from the graph.
|
|
432
|
-
* @internal
|
|
433
|
-
*/
|
|
434
|
-
_removeEdges(edges) {
|
|
435
|
-
(0, import_signals_core.batch)(() => edges.forEach((edge) => this._removeEdge(edge)));
|
|
436
|
-
}
|
|
437
|
-
_removeEdge({ source, target }) {
|
|
438
|
-
(0, import_signals_core.untracked)(() => {
|
|
439
|
-
(0, import_signals_core.batch)(() => {
|
|
440
|
-
const outboundIndex = this._edges[source]?.outbound.findIndex((id) => id === target);
|
|
441
|
-
if (outboundIndex !== void 0 && outboundIndex !== -1) {
|
|
442
|
-
this._edges[source].outbound.splice(outboundIndex, 1);
|
|
443
|
-
}
|
|
444
|
-
const inboundIndex = this._edges[target]?.inbound.findIndex((id) => id === source);
|
|
445
|
-
if (inboundIndex !== void 0 && inboundIndex !== -1) {
|
|
446
|
-
this._edges[target].inbound.splice(inboundIndex, 1);
|
|
447
|
-
}
|
|
448
|
-
});
|
|
449
230
|
});
|
|
450
231
|
}
|
|
451
232
|
/**
|
|
@@ -454,322 +235,139 @@ var Graph = class {
|
|
|
454
235
|
* Edges not included in the sorted list are appended to the end of the list.
|
|
455
236
|
*
|
|
456
237
|
* @param nodeId The id of the node to sort edges for.
|
|
457
|
-
* @param
|
|
238
|
+
* @param direction The direction of the edges from the node to sort.
|
|
458
239
|
* @param edges The ordered list of edges.
|
|
459
|
-
* @ignore
|
|
460
240
|
*/
|
|
461
|
-
|
|
241
|
+
sortEdges(nodeId, direction, edges) {
|
|
462
242
|
(0, import_signals_core.untracked)(() => {
|
|
463
|
-
(
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
}
|
|
473
|
-
});
|
|
243
|
+
const current = this._edges[this.getEdgeKey(nodeId, direction)];
|
|
244
|
+
if (current) {
|
|
245
|
+
const unsorted = current.filter((id) => !edges.includes(id)) ?? [];
|
|
246
|
+
const sorted = edges.filter((id) => current.includes(id)) ?? [];
|
|
247
|
+
current.splice(0, current.length, ...[
|
|
248
|
+
...sorted,
|
|
249
|
+
...unsorted
|
|
250
|
+
]);
|
|
251
|
+
}
|
|
474
252
|
});
|
|
475
253
|
}
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
this.
|
|
482
|
-
if (
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
source: id,
|
|
489
|
-
target: node.id
|
|
490
|
-
}));
|
|
254
|
+
/**
|
|
255
|
+
* Remove an edge from the graph.
|
|
256
|
+
*/
|
|
257
|
+
removeEdge({ source, target }) {
|
|
258
|
+
(0, import_signals_core.untracked)(() => {
|
|
259
|
+
const outboundIndex = this._edges[this.getEdgeKey(source, "outbound")]?.findIndex((id) => id === target);
|
|
260
|
+
if (outboundIndex !== -1) {
|
|
261
|
+
this._edges[this.getEdgeKey(source, "outbound")].splice(outboundIndex, 1);
|
|
262
|
+
}
|
|
263
|
+
const inboundIndex = this._edges[this.getEdgeKey(target, "inbound")]?.findIndex((id) => id === source);
|
|
264
|
+
if (inboundIndex !== -1) {
|
|
265
|
+
this._edges[this.getEdgeKey(target, "inbound")].splice(inboundIndex, 1);
|
|
491
266
|
}
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Recursive depth-first traversal.
|
|
271
|
+
*
|
|
272
|
+
* @param options.node The node to start traversing from.
|
|
273
|
+
* @param options.direction The direction to traverse graph edges.
|
|
274
|
+
* @param options.filter A predicate to filter nodes which are passed to the `visitor` callback.
|
|
275
|
+
* @param options.visitor A callback which is called for each node visited during traversal.
|
|
276
|
+
*/
|
|
277
|
+
traverse({ node = this.root, direction = "outbound", filter, visitor }, path = []) {
|
|
278
|
+
if (path.includes(node.id)) {
|
|
279
|
+
return;
|
|
492
280
|
}
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
281
|
+
if (!filter || filter(node)) {
|
|
282
|
+
visitor?.(node, [
|
|
283
|
+
...path,
|
|
284
|
+
node.id
|
|
285
|
+
]);
|
|
498
286
|
}
|
|
287
|
+
Object.values(this._getNodes({
|
|
288
|
+
id: node.id,
|
|
289
|
+
direction
|
|
290
|
+
})).forEach((child) => this.traverse({
|
|
291
|
+
node: child,
|
|
292
|
+
direction,
|
|
293
|
+
filter,
|
|
294
|
+
visitor
|
|
295
|
+
}, [
|
|
296
|
+
...path,
|
|
297
|
+
node.id
|
|
298
|
+
]));
|
|
499
299
|
}
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
id: getId("resolver"),
|
|
508
|
-
resolver
|
|
509
|
-
} : void 0,
|
|
510
|
-
connector ? {
|
|
511
|
-
...rest,
|
|
512
|
-
id: getId("connector"),
|
|
513
|
-
connector
|
|
514
|
-
} : void 0,
|
|
515
|
-
actionGroups ? {
|
|
516
|
-
...rest,
|
|
517
|
-
id: getId("actionGroups"),
|
|
518
|
-
type: ACTION_GROUP_TYPE,
|
|
519
|
-
relation: "outbound",
|
|
520
|
-
connector: ({ node }) => actionGroups({
|
|
521
|
-
node
|
|
522
|
-
})?.map((arg) => ({
|
|
523
|
-
...arg,
|
|
524
|
-
data: actionGroupSymbol,
|
|
525
|
-
type: ACTION_GROUP_TYPE
|
|
526
|
-
}))
|
|
527
|
-
} : void 0,
|
|
528
|
-
actions ? {
|
|
529
|
-
...rest,
|
|
530
|
-
id: getId("actions"),
|
|
531
|
-
type: ACTION_TYPE,
|
|
532
|
-
relation: "outbound",
|
|
533
|
-
connector: ({ node }) => actions({
|
|
534
|
-
node
|
|
535
|
-
})?.map((arg) => ({
|
|
536
|
-
...arg,
|
|
537
|
-
type: ACTION_TYPE
|
|
538
|
-
}))
|
|
539
|
-
} : void 0
|
|
540
|
-
].filter(import_util2.nonNullable);
|
|
541
|
-
};
|
|
542
|
-
var Dispatcher = class {
|
|
543
|
-
constructor() {
|
|
544
|
-
this.stateIndex = 0;
|
|
545
|
-
this.state = {};
|
|
546
|
-
this.cleanup = [];
|
|
547
|
-
}
|
|
548
|
-
};
|
|
549
|
-
var BuilderInternal = class {
|
|
550
|
-
};
|
|
551
|
-
var memoize = (fn, key = "result") => {
|
|
552
|
-
const dispatcher = BuilderInternal.currentDispatcher;
|
|
553
|
-
(0, import_invariant2.invariant)(dispatcher?.currentExtension, "memoize must be called within an extension", {
|
|
554
|
-
F: __dxlog_file2,
|
|
555
|
-
L: 129,
|
|
556
|
-
S: void 0,
|
|
557
|
-
A: [
|
|
558
|
-
"dispatcher?.currentExtension",
|
|
559
|
-
"'memoize must be called within an extension'"
|
|
560
|
-
]
|
|
561
|
-
});
|
|
562
|
-
const all = dispatcher.state[dispatcher.currentExtension][dispatcher.stateIndex] ?? {};
|
|
563
|
-
const current = all[key];
|
|
564
|
-
const result = current ? current.result : fn();
|
|
565
|
-
dispatcher.state[dispatcher.currentExtension][dispatcher.stateIndex] = {
|
|
566
|
-
...all,
|
|
567
|
-
[key]: {
|
|
568
|
-
result
|
|
300
|
+
/**
|
|
301
|
+
* Get the path between two nodes in the graph.
|
|
302
|
+
*/
|
|
303
|
+
getPath({ source = "root", target }) {
|
|
304
|
+
const start = this.findNode(source);
|
|
305
|
+
if (!start) {
|
|
306
|
+
return void 0;
|
|
569
307
|
}
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
L: 144,
|
|
580
|
-
S: void 0,
|
|
581
|
-
A: [
|
|
582
|
-
"dispatcher",
|
|
583
|
-
"'cleanup must be called within an extension'"
|
|
584
|
-
]
|
|
308
|
+
let found;
|
|
309
|
+
this.traverse({
|
|
310
|
+
node: start,
|
|
311
|
+
filter: () => !found,
|
|
312
|
+
visitor: (node, path) => {
|
|
313
|
+
if (node.id === target) {
|
|
314
|
+
found = path;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
585
317
|
});
|
|
586
|
-
|
|
587
|
-
}
|
|
588
|
-
};
|
|
589
|
-
var toSignal = (subscribe, get, key) => {
|
|
590
|
-
const thisSignal = memoize(() => {
|
|
591
|
-
return (0, import_signals_core2.signal)(get());
|
|
592
|
-
}, key);
|
|
593
|
-
const unsubscribe = memoize(() => {
|
|
594
|
-
return subscribe(() => thisSignal.value = get());
|
|
595
|
-
}, key);
|
|
596
|
-
cleanup(() => {
|
|
597
|
-
unsubscribe();
|
|
598
|
-
});
|
|
599
|
-
return thisSignal.value;
|
|
318
|
+
return found;
|
|
319
|
+
}
|
|
600
320
|
};
|
|
601
321
|
var GraphBuilder = class {
|
|
602
322
|
constructor() {
|
|
603
|
-
this.
|
|
604
|
-
this.
|
|
605
|
-
this._resolverSubscriptions = /* @__PURE__ */ new Map();
|
|
606
|
-
this._connectorSubscriptions = /* @__PURE__ */ new Map();
|
|
607
|
-
this._nodeChanged = {};
|
|
608
|
-
this._graph = new Graph({
|
|
609
|
-
onInitialNode: (id, type) => this._onInitialNode(id, type),
|
|
610
|
-
onInitialNodes: (node, relation, type) => this._onInitialNodes(node, relation, type),
|
|
611
|
-
onRemoveNode: (id) => this._onRemoveNode(id)
|
|
612
|
-
});
|
|
613
|
-
}
|
|
614
|
-
get graph() {
|
|
615
|
-
return this._graph;
|
|
323
|
+
this._extensions = /* @__PURE__ */ new Map();
|
|
324
|
+
this._unsubscribe = new import_async.EventSubscriptions();
|
|
616
325
|
}
|
|
617
326
|
/**
|
|
618
327
|
* Register a node builder which will be called in order to construct the graph.
|
|
619
328
|
*/
|
|
620
|
-
addExtension(extension) {
|
|
621
|
-
|
|
622
|
-
extension.forEach((ext) => this.addExtension(ext));
|
|
623
|
-
return this;
|
|
624
|
-
}
|
|
625
|
-
this._dispatcher.state[extension.id] = [];
|
|
626
|
-
this._extensions[extension.id] = extension;
|
|
329
|
+
addExtension(id, extension) {
|
|
330
|
+
this._extensions.set(id, extension);
|
|
627
331
|
return this;
|
|
628
332
|
}
|
|
629
333
|
/**
|
|
630
334
|
* Remove a node builder from the graph builder.
|
|
631
335
|
*/
|
|
632
336
|
removeExtension(id) {
|
|
633
|
-
|
|
337
|
+
this._extensions.delete(id);
|
|
634
338
|
return this;
|
|
635
339
|
}
|
|
636
|
-
destroy() {
|
|
637
|
-
this._dispatcher.cleanup.forEach((fn) => fn());
|
|
638
|
-
this._resolverSubscriptions.forEach((unsubscribe) => unsubscribe());
|
|
639
|
-
this._connectorSubscriptions.forEach((unsubscribe) => unsubscribe());
|
|
640
|
-
this._resolverSubscriptions.clear();
|
|
641
|
-
this._connectorSubscriptions.clear();
|
|
642
|
-
}
|
|
643
340
|
/**
|
|
644
|
-
*
|
|
341
|
+
* Construct the graph, starting by calling all registered extensions.
|
|
342
|
+
* @param previousGraph If provided, the graph will be updated in place.
|
|
645
343
|
*/
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
]);
|
|
655
|
-
const nodes = Object.values(this._extensions).filter((extension) => relation === (extension.relation ?? "outbound")).flatMap((extension) => extension.connector?.({
|
|
656
|
-
node
|
|
657
|
-
}) ?? []).map((arg) => ({
|
|
658
|
-
id: arg.id,
|
|
659
|
-
type: arg.type,
|
|
660
|
-
data: arg.data ?? null,
|
|
661
|
-
properties: arg.properties ?? {}
|
|
662
|
-
}));
|
|
663
|
-
await Promise.all(nodes.map((n) => this.traverse({
|
|
664
|
-
node: n,
|
|
665
|
-
relation,
|
|
666
|
-
visitor
|
|
667
|
-
}, [
|
|
668
|
-
...path,
|
|
669
|
-
node.id
|
|
670
|
-
])));
|
|
671
|
-
}
|
|
672
|
-
_onInitialNode(nodeId, nodeType) {
|
|
673
|
-
this._nodeChanged[nodeId] = this._nodeChanged[nodeId] ?? (0, import_signals_core2.signal)({});
|
|
674
|
-
let initialized;
|
|
675
|
-
for (const { id, type, resolver } of Object.values(this._extensions)) {
|
|
676
|
-
if (!resolver || nodeType && type !== nodeType) {
|
|
677
|
-
continue;
|
|
678
|
-
}
|
|
679
|
-
const unsubscribe = (0, import_signals_core2.effect)(() => {
|
|
680
|
-
this._dispatcher.currentExtension = id;
|
|
681
|
-
this._dispatcher.stateIndex = 0;
|
|
682
|
-
BuilderInternal.currentDispatcher = this._dispatcher;
|
|
683
|
-
const node = resolver({
|
|
684
|
-
id: nodeId
|
|
685
|
-
});
|
|
686
|
-
BuilderInternal.currentDispatcher = void 0;
|
|
687
|
-
if (node && initialized) {
|
|
688
|
-
this.graph._addNodes([
|
|
689
|
-
node
|
|
690
|
-
]);
|
|
691
|
-
if (this._nodeChanged[initialized.id]) {
|
|
692
|
-
this._nodeChanged[initialized.id].value = {};
|
|
693
|
-
}
|
|
694
|
-
} else if (node) {
|
|
695
|
-
initialized = node;
|
|
696
|
-
}
|
|
697
|
-
});
|
|
698
|
-
if (initialized) {
|
|
699
|
-
this._resolverSubscriptions.set(nodeId, unsubscribe);
|
|
700
|
-
break;
|
|
701
|
-
} else {
|
|
702
|
-
unsubscribe();
|
|
703
|
-
}
|
|
704
|
-
}
|
|
705
|
-
return initialized;
|
|
706
|
-
}
|
|
707
|
-
_onInitialNodes(node, nodesRelation, nodesType) {
|
|
708
|
-
this._nodeChanged[node.id] = this._nodeChanged[node.id] ?? (0, import_signals_core2.signal)({});
|
|
709
|
-
let initialized;
|
|
710
|
-
let previous = [];
|
|
711
|
-
this._connectorSubscriptions.set(node.id, (0, import_signals_core2.effect)(() => {
|
|
712
|
-
Object.keys(this._extensions);
|
|
713
|
-
this._nodeChanged[node.id].value;
|
|
714
|
-
const nodes = [];
|
|
715
|
-
for (const { id, connector, filter, type, relation = "outbound" } of Object.values(this._extensions)) {
|
|
716
|
-
if (!connector || relation !== nodesRelation || nodesType && type !== nodesType || filter && !filter(node)) {
|
|
717
|
-
continue;
|
|
718
|
-
}
|
|
719
|
-
this._dispatcher.currentExtension = id;
|
|
720
|
-
this._dispatcher.stateIndex = 0;
|
|
721
|
-
BuilderInternal.currentDispatcher = this._dispatcher;
|
|
722
|
-
nodes.push(...connector({
|
|
723
|
-
node
|
|
724
|
-
}) ?? []);
|
|
725
|
-
BuilderInternal.currentDispatcher = void 0;
|
|
726
|
-
}
|
|
727
|
-
const ids = nodes.map((n) => n.id);
|
|
728
|
-
const removed = previous.filter((id) => !ids.includes(id));
|
|
729
|
-
previous = ids;
|
|
730
|
-
if (initialized) {
|
|
731
|
-
this.graph._removeNodes(removed, true);
|
|
732
|
-
this.graph._addNodes(nodes);
|
|
733
|
-
this.graph._addEdges(nodes.map(({ id }) => ({
|
|
734
|
-
source: node.id,
|
|
735
|
-
target: id
|
|
736
|
-
})));
|
|
737
|
-
this.graph._sortEdges(node.id, "outbound", nodes.map(({ id }) => id));
|
|
738
|
-
nodes.forEach((n) => {
|
|
739
|
-
if (this._nodeChanged[n.id]) {
|
|
740
|
-
this._nodeChanged[n.id].value = {};
|
|
741
|
-
}
|
|
742
|
-
});
|
|
743
|
-
} else {
|
|
744
|
-
initialized = nodes;
|
|
745
|
-
}
|
|
746
|
-
}));
|
|
747
|
-
return initialized;
|
|
344
|
+
build(previousGraph) {
|
|
345
|
+
this._unsubscribe.clear();
|
|
346
|
+
const graph = previousGraph ?? new Graph();
|
|
347
|
+
Array.from(this._extensions.values()).forEach((builder) => {
|
|
348
|
+
const unsubscribe = builder(graph);
|
|
349
|
+
unsubscribe && this._unsubscribe.add(unsubscribe);
|
|
350
|
+
});
|
|
351
|
+
return graph;
|
|
748
352
|
}
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
353
|
+
};
|
|
354
|
+
var manageNodes = ({ graph, condition, nodes, removeEdges }) => {
|
|
355
|
+
if (condition) {
|
|
356
|
+
return graph.addNodes(...nodes);
|
|
357
|
+
} else {
|
|
358
|
+
nodes.forEach(({ id }) => graph.removeNode(id, removeEdges));
|
|
754
359
|
}
|
|
755
360
|
};
|
|
756
361
|
// Annotate the CommonJS export names for ESM import in node:
|
|
757
362
|
0 && (module.exports = {
|
|
758
|
-
ACTION_GROUP_TYPE,
|
|
759
|
-
ACTION_TYPE,
|
|
760
363
|
Graph,
|
|
761
364
|
GraphBuilder,
|
|
762
365
|
ROOT_ID,
|
|
763
|
-
ROOT_TYPE,
|
|
764
366
|
actionGroupSymbol,
|
|
765
|
-
cleanup,
|
|
766
|
-
createExtension,
|
|
767
|
-
getGraph,
|
|
768
367
|
isAction,
|
|
769
368
|
isActionGroup,
|
|
770
369
|
isActionLike,
|
|
771
370
|
isGraphNode,
|
|
772
|
-
|
|
773
|
-
toSignal
|
|
371
|
+
manageNodes
|
|
774
372
|
});
|
|
775
373
|
//# sourceMappingURL=index.cjs.map
|