@dxos/app-graph 0.8.2-main.fbd8ed0 → 0.8.2-staging.7ac8446

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