@dxos/app-graph 0.6.11-staging.e6894a4 → 0.6.12-main.5cc132e

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