@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.
@@ -2,49 +2,58 @@ import { type MaybePromise, type MakeOptional } from '@dxos/util';
2
2
  /**
3
3
  * Represents a node in the graph.
4
4
  */
5
- export type Node<TData = any, TProperties extends Record<string, any> = Record<string, any>> = Readonly<{
5
+ export type NodeBase<TData = any, TProperties extends Record<string, any> = Record<string, any>> = {
6
6
  /**
7
7
  * Globally unique ID.
8
8
  */
9
9
  id: string;
10
- /**
11
- * Typename of the data the node represents.
12
- */
13
- type: string;
14
10
  /**
15
11
  * Properties of the node relevant to displaying the node.
16
12
  */
17
- properties: Readonly<TProperties>;
13
+ properties: TProperties;
18
14
  /**
19
15
  * Data the node represents.
20
16
  */
21
17
  data: TData;
22
- }>;
18
+ };
23
19
  export type NodeFilter<T = any, U extends Record<string, any> = Record<string, any>> = (node: Node<unknown, Record<string, any>>, connectedNode: Node) => node is Node<T, U>;
24
- export type Relation = 'outbound' | 'inbound';
25
- export declare const isGraphNode: (data: unknown) => data is Readonly<{
20
+ export type EdgeDirection = 'outbound' | 'inbound';
21
+ export type ConnectedNodes = {
26
22
  /**
27
- * Globally unique ID.
23
+ * Edges that this node is connected to in default order.
28
24
  */
29
- id: string;
25
+ edges(params?: {
26
+ direction?: EdgeDirection;
27
+ }): Readonly<string[]>;
30
28
  /**
31
- * Typename of the data the node represents.
29
+ * Nodes that this node is connected to in default order.
32
30
  */
33
- type: string;
31
+ nodes<T = any, U extends Record<string, any> = Record<string, any>>(params?: {
32
+ direction?: EdgeDirection;
33
+ filter?: NodeFilter<T, U>;
34
+ }): Node<T>[];
34
35
  /**
35
- * Properties of the node relevant to displaying the node.
36
+ * Get a specific connected node by id.
36
37
  */
37
- properties: Readonly<Record<string, any>>;
38
+ node(id: string): Node | undefined;
39
+ };
40
+ export type ConnectedActions = {
38
41
  /**
39
- * Data the node represents.
42
+ * Actions or action groups that this node is connected to in default order.
40
43
  */
41
- data: any;
42
- }>;
43
- export type NodeArg<TData, TProperties extends Record<string, any> = Record<string, any>> = MakeOptional<Node<TData, TProperties>, 'data' | 'properties'> & {
44
+ actions(): ActionLike[];
45
+ };
46
+ export type Node<TData = any, TProperties extends Record<string, any> = Record<string, any>> = Readonly<Omit<NodeBase<TData, TProperties>, 'properties'> & {
47
+ properties: Readonly<TProperties>;
48
+ } & ConnectedNodes & ConnectedActions>;
49
+ export declare const isGraphNode: (data: unknown) => data is Readonly<Omit<NodeBase<any, Record<string, any>>, "properties"> & {
50
+ properties: Readonly<Record<string, any>>;
51
+ } & ConnectedNodes & ConnectedActions>;
52
+ export type NodeArg<TData, TProperties extends Record<string, any> = Record<string, any>> = MakeOptional<NodeBase<TData, TProperties>, 'data' | 'properties'> & {
44
53
  /** Will automatically add nodes with an edge from this node to each. */
45
54
  nodes?: NodeArg<unknown>[];
46
55
  /** Will automatically add specified edges. */
47
- edges?: [string, Relation][];
56
+ edges?: [string, EdgeDirection][];
48
57
  };
49
58
  export type InvokeParams = {
50
59
  /** Node the invoked action is connected to. */
@@ -52,91 +61,23 @@ export type InvokeParams = {
52
61
  caller?: string;
53
62
  };
54
63
  export type ActionData = (params: InvokeParams) => MaybePromise<void>;
55
- export type Action<TProperties extends Record<string, any> = Record<string, any>> = Readonly<Omit<Node<ActionData, TProperties>, 'properties'> & {
64
+ export type Action<TProperties extends Record<string, any> = Record<string, any>> = Readonly<Omit<NodeBase<ActionData, TProperties>, 'properties'> & {
56
65
  properties: Readonly<TProperties>;
57
- }>;
58
- export declare const isAction: (data: unknown) => data is Readonly<Omit<Readonly<{
59
- /**
60
- * Globally unique ID.
61
- */
62
- id: string;
63
- /**
64
- * Typename of the data the node represents.
65
- */
66
- type: string;
67
- /**
68
- * Properties of the node relevant to displaying the node.
69
- */
70
- properties: Readonly<Record<string, any>>;
71
- /**
72
- * Data the node represents.
73
- */
74
- data: ActionData;
75
- }>, "properties"> & {
66
+ } & ConnectedNodes>;
67
+ export declare const isAction: (data: unknown) => data is Readonly<Omit<NodeBase<ActionData, Record<string, any>>, "properties"> & {
76
68
  properties: Readonly<Record<string, any>>;
77
- }>;
69
+ } & ConnectedNodes>;
78
70
  export declare const actionGroupSymbol: unique symbol;
79
- export type ActionGroup = Readonly<Omit<Node<typeof actionGroupSymbol, Record<string, any>>, 'properties'> & {
71
+ export type ActionGroup = Readonly<Omit<NodeBase<typeof actionGroupSymbol, Record<string, any>>, 'properties'> & {
80
72
  properties: Readonly<Record<string, any>>;
81
- }>;
82
- export declare const isActionGroup: (data: unknown) => data is Readonly<Omit<Readonly<{
83
- /**
84
- * Globally unique ID.
85
- */
86
- id: string;
87
- /**
88
- * Typename of the data the node represents.
89
- */
90
- type: string;
91
- /**
92
- * Properties of the node relevant to displaying the node.
93
- */
94
- properties: Readonly<Record<string, any>>;
95
- /**
96
- * Data the node represents.
97
- */
98
- data: typeof actionGroupSymbol;
99
- }>, "properties"> & {
73
+ } & ConnectedActions>;
74
+ export declare const isActionGroup: (data: unknown) => data is Readonly<Omit<NodeBase<typeof actionGroupSymbol, Record<string, any>>, "properties"> & {
100
75
  properties: Readonly<Record<string, any>>;
101
- }>;
76
+ } & ConnectedActions>;
102
77
  export type ActionLike = Action | ActionGroup;
103
- export declare const isActionLike: (data: unknown) => data is Readonly<Omit<Readonly<{
104
- /**
105
- * Globally unique ID.
106
- */
107
- id: string;
108
- /**
109
- * Typename of the data the node represents.
110
- */
111
- type: string;
112
- /**
113
- * Properties of the node relevant to displaying the node.
114
- */
115
- properties: Readonly<Record<string, any>>;
116
- /**
117
- * Data the node represents.
118
- */
119
- data: ActionData;
120
- }>, "properties"> & {
121
- properties: Readonly<Record<string, any>>;
122
- }> | Readonly<Omit<Readonly<{
123
- /**
124
- * Globally unique ID.
125
- */
126
- id: string;
127
- /**
128
- * Typename of the data the node represents.
129
- */
130
- type: string;
131
- /**
132
- * Properties of the node relevant to displaying the node.
133
- */
78
+ export declare const isActionLike: (data: unknown) => data is Readonly<Omit<NodeBase<ActionData, Record<string, any>>, "properties"> & {
134
79
  properties: Readonly<Record<string, any>>;
135
- /**
136
- * Data the node represents.
137
- */
138
- data: typeof actionGroupSymbol;
139
- }>, "properties"> & {
80
+ } & ConnectedNodes> | Readonly<Omit<NodeBase<typeof actionGroupSymbol, Record<string, any>>, "properties"> & {
140
81
  properties: Readonly<Record<string, any>>;
141
- }>;
82
+ } & ConnectedActions>;
142
83
  //# sourceMappingURL=node.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../../../src/node.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../../../src/node.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"EchoGraph.stories.d.ts","sourceRoot":"","sources":["../../../../src/stories/EchoGraph.stories.tsx"],"names":[],"mappings":"AAIA,OAAO,YAAY,CAAC;;;;;AA4BpB,wBAGE;AAuNF,eAAO,MAAM,OAAO;;CAEnB,CAAC"}
1
+ {"version":3,"file":"EchoGraph.stories.d.ts","sourceRoot":"","sources":["../../../../src/stories/EchoGraph.stories.tsx"],"names":[],"mappings":"AAIA,OAAO,YAAY,CAAC;;;;;AAsBpB,wBAGE;AA+OF,eAAO,MAAM,OAAO;;CAEnB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/app-graph",
3
- "version": "0.6.3-main.9e4e207",
3
+ "version": "0.6.3-next.2f65b78",
4
4
  "description": "Constructs knowledge graphs for the purpose of building applications on top of",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
@@ -21,14 +21,12 @@
21
21
  ],
22
22
  "dependencies": {
23
23
  "@preact/signals-core": "^1.6.0",
24
- "main-thread-scheduling": "^14.1.1",
25
- "@dxos/debug": "0.6.3-main.9e4e207",
26
- "@dxos/echo-schema": "0.6.3-main.9e4e207",
27
- "@dxos/echo-signals": "0.6.3-main.9e4e207",
28
- "@dxos/async": "0.6.3-main.9e4e207",
29
- "@dxos/invariant": "0.6.3-main.9e4e207",
30
- "@dxos/log": "0.6.3-main.9e4e207",
31
- "@dxos/util": "0.6.3-main.9e4e207"
24
+ "@dxos/async": "0.6.3-next.2f65b78",
25
+ "@dxos/debug": "0.6.3-next.2f65b78",
26
+ "@dxos/echo-signals": "0.6.3-next.2f65b78",
27
+ "@dxos/echo-schema": "0.6.3-next.2f65b78",
28
+ "@dxos/util": "0.6.3-next.2f65b78",
29
+ "@dxos/invariant": "0.6.3-next.2f65b78"
32
30
  },
33
31
  "devDependencies": {
34
32
  "@phosphor-icons/react": "^2.1.5",
@@ -37,11 +35,11 @@
37
35
  "react": "^18.2.0",
38
36
  "react-dom": "^18.2.0",
39
37
  "vite": "^5.2.9",
40
- "@dxos/react-client": "0.6.3-main.9e4e207",
41
- "@dxos/react-ui": "0.6.3-main.9e4e207",
42
- "@dxos/react-ui-theme": "0.6.3-main.9e4e207",
43
- "@dxos/random": "0.6.3-main.9e4e207",
44
- "@dxos/storybook-utils": "0.6.3-main.9e4e207"
38
+ "@dxos/random": "0.6.3-next.2f65b78",
39
+ "@dxos/react-client": "0.6.3-next.2f65b78",
40
+ "@dxos/react-ui": "0.6.3-next.2f65b78",
41
+ "@dxos/react-ui-theme": "0.6.3-next.2f65b78",
42
+ "@dxos/storybook-utils": "0.6.3-next.2f65b78"
45
43
  },
46
44
  "peerDependencies": {
47
45
  "react": "^18.2.0",
@@ -2,219 +2,24 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import { type Signal, effect, signal } from '@preact/signals-core';
6
- // import { yieldOrContinue } from 'main-thread-scheduling';
5
+ import { EventSubscriptions, type UnsubscribeCallback } from '@dxos/async';
7
6
 
8
- import { type UnsubscribeCallback } from '@dxos/async';
9
- import { create } from '@dxos/echo-schema';
10
- import { invariant } from '@dxos/invariant';
11
- import { nonNullable } from '@dxos/util';
7
+ import { Graph } from './graph';
12
8
 
13
- import { ACTION_GROUP_TYPE, ACTION_TYPE, Graph } from './graph';
14
- import { type Relation, type NodeArg, type Node, type ActionData, actionGroupSymbol } from './node';
15
-
16
- /**
17
- * Graph builder extension for adding nodes to the graph based on just the node id.
18
- * This is useful for creating the first node in a graph or for hydrating cached nodes with data.
19
- *
20
- * @param params.id The id of the node to resolve.
21
- */
22
- export type ResolverExtension = (params: { id: string }) => NodeArg<any> | undefined;
23
-
24
- /**
25
- * Graph builder extension for adding nodes to the graph based on a connection to an existing node.
26
- *
27
- * @param params.node The existing node the returned nodes will be connected to.
28
- */
29
- export type ConnectorExtension<T = any> = (params: { node: Node<T> }) => NodeArg<any>[] | undefined;
30
-
31
- /**
32
- * Constrained case of the connector extension for more easily adding actions to the graph.
33
- */
34
- export type ActionsExtension<T = any> = (params: {
35
- node: Node<T>;
36
- }) => Omit<NodeArg<ActionData>, 'type' | 'nodes' | 'edges'>[] | undefined;
37
-
38
- /**
39
- * Constrained case of the connector extension for more easily adding action groups to the graph.
40
- */
41
- export type ActionGroupsExtension<T = any> = (params: {
42
- node: Node<T>;
43
- }) => Omit<NodeArg<typeof actionGroupSymbol>, 'type' | 'data' | 'nodes' | 'edges'>[] | undefined;
44
-
45
- type GuardedNodeType<T> = T extends (value: any) => value is infer N ? (N extends Node<infer D> ? D : unknown) : never;
46
-
47
- /**
48
- * A graph builder extension is used to add nodes to the graph.
49
- *
50
- * @param params.id The unique id of the extension.
51
- * @param params.relation The relation the graph is being expanded from the existing node.
52
- * @param params.type If provided, all nodes returned are expected to have this type.
53
- * @param params.filter A filter function to determine if an extension should act on a node.
54
- * @param params.resolver A function to add nodes to the graph based on just the node id.
55
- * @param params.connector A function to add nodes to the graph based on a connection to an existing node.
56
- * @param params.actions A function to add actions to the graph based on a connection to an existing node.
57
- * @param params.actionGroups A function to add action groups to the graph based on a connection to an existing node.
58
- */
59
- export type CreateExtensionOptions<T = any> = {
60
- id: string;
61
- relation?: Relation;
62
- type?: string;
63
- filter?: (node: Node) => node is Node<T>;
64
- resolver?: ResolverExtension;
65
- connector?: ConnectorExtension<GuardedNodeType<CreateExtensionOptions<T>['filter']>>;
66
- actions?: ActionsExtension<GuardedNodeType<CreateExtensionOptions<T>['filter']>>;
67
- actionGroups?: ActionGroupsExtension<GuardedNodeType<CreateExtensionOptions<T>['filter']>>;
68
- };
69
-
70
- /**
71
- * Create a graph builder extension.
72
- */
73
- export const createExtension = <T = any>(extension: CreateExtensionOptions<T>): BuilderExtension[] => {
74
- const { id, resolver, connector, actions, actionGroups, ...rest } = extension;
75
- const getId = (key: string) => `${id}/${key}`;
76
- return [
77
- resolver ? { id: getId('resolver'), resolver } : undefined,
78
- connector ? { ...rest, id: getId('connector'), connector } : undefined,
79
- actionGroups
80
- ? ({
81
- ...rest,
82
- id: getId('actionGroups'),
83
- type: ACTION_GROUP_TYPE,
84
- relation: 'outbound',
85
- connector: ({ node }) =>
86
- actionGroups({ node })?.map((arg) => ({ ...arg, data: actionGroupSymbol, type: ACTION_GROUP_TYPE })),
87
- } satisfies BuilderExtension)
88
- : undefined,
89
- actions
90
- ? ({
91
- ...rest,
92
- id: getId('actions'),
93
- type: ACTION_TYPE,
94
- relation: 'outbound',
95
- connector: ({ node }) => actions({ node })?.map((arg) => ({ ...arg, type: ACTION_TYPE })),
96
- } satisfies BuilderExtension)
97
- : undefined,
98
- ].filter(nonNullable);
99
- };
100
-
101
- export type GraphBuilderTraverseOptions = {
102
- node: Node;
103
- relation?: Relation;
104
- visitor: (node: Node, path: string[]) => void;
105
- };
106
-
107
- /**
108
- * The dispatcher is used to keep track of the current extension and state when memoizing functions.
109
- */
110
- class Dispatcher {
111
- currentExtension?: string;
112
- stateIndex = 0;
113
- state: Record<string, any[]> = {};
114
- cleanup: (() => void)[] = [];
115
- }
116
-
117
- class BuilderInternal {
118
- // This must be static to avoid passing the dispatcher instance to every memoized function.
119
- // If the dispatcher is not set that means that the memoized function is being called outside of the graph builder.
120
- static currentDispatcher?: Dispatcher;
121
- }
122
-
123
- /**
124
- * Allows code to be memoized within the context of a graph builder extension.
125
- * This is useful for creating instances which should be subscribed to rather than recreated.
126
- */
127
- export const memoize = <T>(fn: () => T, key = 'result'): T => {
128
- const dispatcher = BuilderInternal.currentDispatcher;
129
- invariant(dispatcher?.currentExtension, 'memoize must be called within an extension');
130
- const all = dispatcher.state[dispatcher.currentExtension][dispatcher.stateIndex] ?? {};
131
- const current = all[key];
132
- const result = current ? current.result : fn();
133
- dispatcher.state[dispatcher.currentExtension][dispatcher.stateIndex] = { ...all, [key]: { result } };
134
- dispatcher.stateIndex++;
135
- return result;
136
- };
137
-
138
- /**
139
- * Register a cleanup function to be called when the graph builder is destroyed.
140
- */
141
- export const cleanup = (fn: () => void): void => {
142
- memoize(() => {
143
- const dispatcher = BuilderInternal.currentDispatcher;
144
- invariant(dispatcher, 'cleanup must be called within an extension');
145
- dispatcher.cleanup.push(fn);
146
- });
147
- };
148
-
149
- /**
150
- * Convert a subscribe/get pair into a signal.
151
- */
152
- export const toSignal = <T>(
153
- subscribe: (onChange: () => void) => () => void,
154
- get: () => T | undefined,
155
- key?: string,
156
- ) => {
157
- const thisSignal = memoize(() => {
158
- return signal(get());
159
- }, key);
160
- const unsubscribe = memoize(() => {
161
- return subscribe(() => (thisSignal.value = get()));
162
- }, key);
163
- cleanup(() => {
164
- unsubscribe();
165
- });
166
- return thisSignal.value;
167
- };
168
-
169
- export type BuilderExtension = {
170
- id: string;
171
- resolver?: ResolverExtension;
172
- connector?: ConnectorExtension;
173
- // Only for connector.
174
- relation?: Relation;
175
- type?: string;
176
- filter?: (node: Node) => boolean;
177
- };
178
-
179
- type ExtensionArg = BuilderExtension | BuilderExtension[] | ExtensionArg[];
9
+ export type BuilderExtension = (graph: Graph) => UnsubscribeCallback | void;
180
10
 
181
11
  /**
182
12
  * The builder provides an extensible way to compose the construction of the graph.
183
13
  */
184
- // TODO(wittjosiah): Add api for setting subscription set and/or radius.
185
- // Should unsubscribe from nodes that are not in the set/radius.
186
- // Should track LRU nodes that are not in the set/radius and remove them beyond a certain threshold.
187
14
  export class GraphBuilder {
188
- private readonly _dispatcher = new Dispatcher();
189
- private readonly _extensions = create<Record<string, BuilderExtension>>({});
190
- private readonly _resolverSubscriptions = new Map<string, UnsubscribeCallback>();
191
- private readonly _connectorSubscriptions = new Map<string, UnsubscribeCallback>();
192
- private readonly _nodeChanged: Record<string, Signal<{}>> = {};
193
- private _graph: Graph;
194
-
195
- constructor() {
196
- this._graph = new Graph({
197
- onInitialNode: (id, type) => this._onInitialNode(id, type),
198
- onInitialNodes: (node, relation, type) => this._onInitialNodes(node, relation, type),
199
- onRemoveNode: (id) => this._onRemoveNode(id),
200
- });
201
- }
202
-
203
- get graph() {
204
- return this._graph;
205
- }
15
+ private readonly _extensions = new Map<string, BuilderExtension>();
16
+ private readonly _unsubscribe = new EventSubscriptions();
206
17
 
207
18
  /**
208
19
  * Register a node builder which will be called in order to construct the graph.
209
20
  */
210
- addExtension(extension: ExtensionArg): GraphBuilder {
211
- if (Array.isArray(extension)) {
212
- extension.forEach((ext) => this.addExtension(ext));
213
- return this;
214
- }
215
-
216
- this._dispatcher.state[extension.id] = [];
217
- this._extensions[extension.id] = extension;
21
+ addExtension(id: string, extension: BuilderExtension): GraphBuilder {
22
+ this._extensions.set(id, extension);
218
23
  return this;
219
24
  }
220
25
 
@@ -222,143 +27,25 @@ export class GraphBuilder {
222
27
  * Remove a node builder from the graph builder.
223
28
  */
224
29
  removeExtension(id: string): GraphBuilder {
225
- delete this._extensions[id];
30
+ this._extensions.delete(id);
226
31
  return this;
227
32
  }
228
33
 
229
- destroy() {
230
- this._dispatcher.cleanup.forEach((fn) => fn());
231
- this._resolverSubscriptions.forEach((unsubscribe) => unsubscribe());
232
- this._connectorSubscriptions.forEach((unsubscribe) => unsubscribe());
233
- this._resolverSubscriptions.clear();
234
- this._connectorSubscriptions.clear();
235
- }
236
-
237
34
  /**
238
- * Traverse a graph using just the connector extensions, without subscribing to any signals or persisting any nodes.
35
+ * Construct the graph, starting by calling all registered extensions.
36
+ * @param previousGraph If provided, the graph will be updated in place.
239
37
  */
240
- // TODO(wittjosiah): Rename? This is not traversing the graph proper.
241
- async traverse({ node, relation = 'outbound', visitor }: GraphBuilderTraverseOptions, path: string[] = []) {
242
- // Break cycles.
243
- if (path.includes(node.id)) {
244
- return;
245
- }
38
+ build(previousGraph?: Graph): Graph {
39
+ // Clear previous extension subscriptions.
40
+ this._unsubscribe.clear();
246
41
 
247
- // TODO(wittjosiah): Failed in test environment. ESM only?
248
- // await yieldOrContinue('idle');
249
- visitor(node, [...path, node.id]);
42
+ const graph: Graph = previousGraph ?? new Graph();
250
43
 
251
- const nodes = Object.values(this._extensions)
252
- .filter((extension) => relation === (extension.relation ?? 'outbound'))
253
- .flatMap((extension) => extension.connector?.({ node }) ?? [])
254
- .map(
255
- (arg): Node => ({
256
- id: arg.id,
257
- type: arg.type,
258
- data: arg.data ?? null,
259
- properties: arg.properties ?? {},
260
- }),
261
- );
262
-
263
- await Promise.all(nodes.map((n) => this.traverse({ node: n, relation, visitor }, [...path, node.id])));
264
- }
265
-
266
- private _onInitialNode(nodeId: string, nodeType?: string) {
267
- this._nodeChanged[nodeId] = this._nodeChanged[nodeId] ?? signal({});
268
- let initialized: NodeArg<any> | undefined;
269
- for (const { id, type, resolver } of Object.values(this._extensions)) {
270
- if (!resolver || (nodeType && type !== nodeType)) {
271
- continue;
272
- }
273
-
274
- const unsubscribe = effect(() => {
275
- this._dispatcher.currentExtension = id;
276
- this._dispatcher.stateIndex = 0;
277
- BuilderInternal.currentDispatcher = this._dispatcher;
278
- const node = resolver({ id: nodeId });
279
- BuilderInternal.currentDispatcher = undefined;
280
- if (node && initialized) {
281
- this.graph._addNodes([node]);
282
- if (this._nodeChanged[initialized.id]) {
283
- this._nodeChanged[initialized.id].value = {};
284
- }
285
- } else if (node) {
286
- initialized = node;
287
- }
288
- });
289
-
290
- if (initialized) {
291
- this._resolverSubscriptions.set(nodeId, unsubscribe);
292
- break;
293
- } else {
294
- unsubscribe();
295
- }
296
- }
297
-
298
- return initialized;
299
- }
300
-
301
- private _onInitialNodes(node: Node, nodesRelation: Relation, nodesType?: string) {
302
- this._nodeChanged[node.id] = this._nodeChanged[node.id] ?? signal({});
303
- let initialized: NodeArg<any>[] | undefined;
304
- let previous: string[] = [];
305
- this._connectorSubscriptions.set(
306
- node.id,
307
- effect(() => {
308
- // Subscribe to extensions being added.
309
- Object.keys(this._extensions);
310
- // Subscribe to connected node changes.
311
- this._nodeChanged[node.id].value;
312
-
313
- // TODO(wittjosiah): Consider allowing extensions to collaborate on the same node by merging their results.
314
- const nodes: NodeArg<any>[] = [];
315
- for (const { id, connector, filter, type, relation = 'outbound' } of Object.values(this._extensions)) {
316
- if (
317
- !connector ||
318
- relation !== nodesRelation ||
319
- (nodesType && type !== nodesType) ||
320
- (filter && !filter(node))
321
- ) {
322
- continue;
323
- }
324
-
325
- this._dispatcher.currentExtension = id;
326
- this._dispatcher.stateIndex = 0;
327
- BuilderInternal.currentDispatcher = this._dispatcher;
328
- nodes.push(...(connector({ node }) ?? []));
329
- BuilderInternal.currentDispatcher = undefined;
330
- }
331
- const ids = nodes.map((n) => n.id);
332
- const removed = previous.filter((id) => !ids.includes(id));
333
- previous = ids;
334
-
335
- if (initialized) {
336
- this.graph._removeNodes(removed, true);
337
- this.graph._addNodes(nodes);
338
- this.graph._addEdges(nodes.map(({ id }) => ({ source: node.id, target: id })));
339
- this.graph._sortEdges(
340
- node.id,
341
- 'outbound',
342
- nodes.map(({ id }) => id),
343
- );
344
- nodes.forEach((n) => {
345
- if (this._nodeChanged[n.id]) {
346
- this._nodeChanged[n.id].value = {};
347
- }
348
- });
349
- } else {
350
- initialized = nodes;
351
- }
352
- }),
353
- );
354
-
355
- return initialized;
356
- }
44
+ Array.from(this._extensions.values()).forEach((builder) => {
45
+ const unsubscribe = builder(graph);
46
+ unsubscribe && this._unsubscribe.add(unsubscribe);
47
+ });
357
48
 
358
- private _onRemoveNode(nodeId: string) {
359
- this._resolverSubscriptions.get(nodeId)?.();
360
- this._connectorSubscriptions.get(nodeId)?.();
361
- this._resolverSubscriptions.delete(nodeId);
362
- this._connectorSubscriptions.delete(nodeId);
49
+ return graph;
363
50
  }
364
51
  }