@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.
@@ -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
- memoize: () => memoize,
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 import_signals_core2 = require("@preact/signals-core");
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({ onInitialNode, onInitialNodes, onRemoveNode } = {}) {
75
- this._waitingForNodes = {};
76
- this._initialized = {};
77
- this._nodes = {};
78
- this._edges = {};
79
- this._constructNode = (node) => {
80
- return (0, import_echo_schema.create)({
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, onlyLoaded = true } = {}) {
90
+ toJSON({ id = ROOT_ID, maxLength = 32 } = {}) {
109
91
  const toJSON = (node, seen = []) => {
110
- const nodes = this.nodes(node, {
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: 140,
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, type) {
150
- const existingNode = this._nodes[id];
151
- const nodeArg = !existingNode && this._onInitialNode?.(id, type);
152
- return existingNode ?? (nodeArg ? this._addNode(nodeArg) : void 0);
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
- const trigger = this._waitingForNodes[id] ?? (this._waitingForNodes[id] = new import_async.Trigger());
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
- * Actions or action groups that this node is connected to in default order.
192
- */
193
- actions(node, { onlyLoaded } = {}) {
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
- Object.values(this._getNodes({
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
- * Recursive depth-first traversal of the graph wrapping each visitor call in an effect.
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
- _addNodes(nodes) {
300
- return (0, import_signals_core.batch)(() => nodes.map((node) => this._addNode(node)));
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 existingNode = this._nodes[_node.id];
305
- const node = existingNode ?? this._constructNode({
150
+ const node = {
306
151
  data: null,
307
152
  properties: {},
308
153
  ..._node
309
- });
310
- if (existingNode) {
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._addEdge({
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, relation]) => relation === "outbound" ? this._addEdge({
166
+ edges.forEach(([id, direction]) => direction === "outbound" ? this.addEdge({
346
167
  source: node.id,
347
168
  target: id
348
- }) : this._addEdge({
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 ids The id of the node to remove.
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
- _removeNodes(ids, edges = false) {
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
- node,
375
- onlyLoaded: true
376
- }).forEach((node2) => {
377
- this._removeEdge({
378
- source: id,
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
- node,
384
- relation: "inbound",
385
- onlyLoaded: true
386
- }).forEach((node2) => {
387
- this._removeEdge({
388
- source: node2.id,
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 edges to the graph.
400
- *
401
- * @internal
210
+ * Add an edge to the graph.
402
211
  */
403
- _addEdges(edges) {
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
- if (!this._edges[source]) {
409
- this._edges[source] = (0, import_echo_schema.create)({
410
- inbound: [],
411
- outbound: []
412
- });
413
- }
414
- if (!this._edges[target]) {
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 sourceEdges = this._edges[source];
421
- if (!sourceEdges.outbound.includes(target)) {
422
- sourceEdges.outbound.push(target);
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 relation The relation of the edges from the node to sort.
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
- _sortEdges(nodeId, relation, edges) {
241
+ sortEdges(nodeId, direction, edges) {
462
242
  (0, import_signals_core.untracked)(() => {
463
- (0, import_signals_core.batch)(() => {
464
- const current = this._edges[nodeId];
465
- if (current) {
466
- const unsorted = current[relation].filter((id) => !edges.includes(id)) ?? [];
467
- const sorted = edges.filter((id) => current[relation].includes(id)) ?? [];
468
- current[relation].splice(0, current[relation].length, ...[
469
- ...sorted,
470
- ...unsorted
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
- _getNodes({ node, relation = "outbound", type, onlyLoaded }) {
477
- const key = `${node.id}-${relation}-${type}`;
478
- const initialized = this._initialized[key];
479
- if (!initialized && !onlyLoaded && this._onInitialNodes) {
480
- const args = this._onInitialNodes(node, relation, type)?.filter((n) => !type || n.type === type);
481
- this._initialized[key] = true;
482
- if (args && args.length > 0) {
483
- const nodes = this._addNodes(args);
484
- this._addEdges(nodes.map(({ id }) => relation === "outbound" ? {
485
- source: node.id,
486
- target: id
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
- const edges = this._edges[node.id];
494
- if (!edges) {
495
- return [];
496
- } else {
497
- return edges[relation].map((id) => this._nodes[id]).filter(import_util.nonNullable).filter((n) => !type || n.type === type);
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
- var __dxlog_file2 = "/home/runner/work/dxos/dxos/packages/sdk/app-graph/src/graph-builder.ts";
502
- var createExtension = (extension) => {
503
- const { id, resolver, connector, actions, actionGroups, ...rest } = extension;
504
- const getId = (key) => `${id}/${key}`;
505
- return [
506
- resolver ? {
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
- dispatcher.stateIndex++;
572
- return result;
573
- };
574
- var cleanup = (fn) => {
575
- memoize(() => {
576
- const dispatcher = BuilderInternal.currentDispatcher;
577
- (0, import_invariant2.invariant)(dispatcher, "cleanup must be called within an extension", {
578
- F: __dxlog_file2,
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
- dispatcher.cleanup.push(fn);
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._dispatcher = new Dispatcher();
604
- this._extensions = (0, import_echo_schema2.create)({});
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
- if (Array.isArray(extension)) {
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
- delete this._extensions[id];
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
- * Traverse a graph using just the connector extensions, without subscribing to any signals or persisting any nodes.
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
- // TODO(wittjosiah): Rename? This is not traversing the graph proper.
647
- async traverse({ node, relation = "outbound", visitor }, path = []) {
648
- if (path.includes(node.id)) {
649
- return;
650
- }
651
- visitor(node, [
652
- ...path,
653
- node.id
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
- _onRemoveNode(nodeId) {
750
- this._resolverSubscriptions.get(nodeId)?.();
751
- this._connectorSubscriptions.get(nodeId)?.();
752
- this._resolverSubscriptions.delete(nodeId);
753
- this._connectorSubscriptions.delete(nodeId);
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
- memoize,
773
- toSignal
371
+ manageNodes
774
372
  });
775
373
  //# sourceMappingURL=index.cjs.map