@dxos/app-graph 0.6.13 → 0.6.14-main.1366248

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