@dxos/app-graph 0.8.4-main.c4373fc → 0.8.4-main.c85a9c8dae

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 (46) hide show
  1. package/dist/lib/browser/index.mjs +1350 -686
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/lib/node-esm/index.mjs +1349 -686
  5. package/dist/lib/node-esm/index.mjs.map +4 -4
  6. package/dist/lib/node-esm/meta.json +1 -1
  7. package/dist/types/src/atoms.d.ts +8 -0
  8. package/dist/types/src/atoms.d.ts.map +1 -0
  9. package/dist/types/src/graph-builder.d.ts +112 -66
  10. package/dist/types/src/graph-builder.d.ts.map +1 -1
  11. package/dist/types/src/graph.d.ts +187 -221
  12. package/dist/types/src/graph.d.ts.map +1 -1
  13. package/dist/types/src/index.d.ts +6 -3
  14. package/dist/types/src/index.d.ts.map +1 -1
  15. package/dist/types/src/node-matcher.d.ts +218 -0
  16. package/dist/types/src/node-matcher.d.ts.map +1 -0
  17. package/dist/types/src/node-matcher.test.d.ts +2 -0
  18. package/dist/types/src/node-matcher.test.d.ts.map +1 -0
  19. package/dist/types/src/node.d.ts +42 -5
  20. package/dist/types/src/node.d.ts.map +1 -1
  21. package/dist/types/src/stories/EchoGraph.stories.d.ts.map +1 -1
  22. package/dist/types/src/util.d.ts +24 -0
  23. package/dist/types/src/util.d.ts.map +1 -0
  24. package/dist/types/tsconfig.tsbuildinfo +1 -1
  25. package/package.json +36 -34
  26. package/src/atoms.ts +25 -0
  27. package/src/graph-builder.test.ts +626 -119
  28. package/src/graph-builder.ts +667 -288
  29. package/src/graph.test.ts +429 -121
  30. package/src/graph.ts +1041 -403
  31. package/src/index.ts +9 -3
  32. package/src/node-matcher.test.ts +301 -0
  33. package/src/node-matcher.ts +282 -0
  34. package/src/node.ts +53 -8
  35. package/src/stories/EchoGraph.stories.tsx +158 -119
  36. package/src/stories/Tree.tsx +1 -1
  37. package/src/util.ts +55 -0
  38. package/dist/types/src/experimental/graph-projections.test.d.ts +0 -25
  39. package/dist/types/src/experimental/graph-projections.test.d.ts.map +0 -1
  40. package/dist/types/src/signals-integration.test.d.ts +0 -2
  41. package/dist/types/src/signals-integration.test.d.ts.map +0 -1
  42. package/dist/types/src/testing.d.ts +0 -5
  43. package/dist/types/src/testing.d.ts.map +0 -1
  44. package/src/experimental/graph-projections.test.ts +0 -56
  45. package/src/signals-integration.test.ts +0 -218
  46. package/src/testing.ts +0 -20
@@ -1,35 +1,135 @@
1
1
  import { createRequire } from 'node:module';const require = createRequire(import.meta.url);
2
+ var __defProp = Object.defineProperty;
3
+ var __export = (target, all) => {
4
+ for (var name in all)
5
+ __defProp(target, name, { get: all[name], enumerable: true });
6
+ };
7
+
8
+ // src/atoms.ts
9
+ var atoms_exports = {};
10
+ __export(atoms_exports, {
11
+ fromObservable: () => fromObservable
12
+ });
13
+ import { Atom } from "@effect-atom/atom-react";
14
+ var observableFamily = Atom.family((observable) => {
15
+ return Atom.make((get2) => {
16
+ const subscription = observable.subscribe((value) => get2.setSelf(value));
17
+ get2.addFinalizer(() => subscription.unsubscribe());
18
+ return observable.get();
19
+ });
20
+ });
21
+ var fromObservable = (observable) => {
22
+ return observableFamily(observable);
23
+ };
2
24
 
3
25
  // src/graph.ts
4
- import { Registry, Rx } from "@effect-rx/rx-react";
26
+ var graph_exports = {};
27
+ __export(graph_exports, {
28
+ GraphKind: () => GraphKind,
29
+ GraphTypeId: () => GraphTypeId,
30
+ addEdge: () => addEdge,
31
+ addEdges: () => addEdges,
32
+ addNode: () => addNode,
33
+ addNodes: () => addNodes,
34
+ expand: () => expand,
35
+ getActions: () => getActions,
36
+ getConnections: () => getConnections,
37
+ getEdges: () => getEdges,
38
+ getGraph: () => getGraph,
39
+ getNode: () => getNode,
40
+ getNodeOrThrow: () => getNodeOrThrow,
41
+ getPath: () => getPath,
42
+ getRoot: () => getRoot,
43
+ initialize: () => initialize,
44
+ make: () => make,
45
+ relationFromKey: () => relationFromKey,
46
+ relationKey: () => relationKey,
47
+ removeEdge: () => removeEdge,
48
+ removeEdges: () => removeEdges,
49
+ removeNode: () => removeNode,
50
+ removeNodes: () => removeNodes,
51
+ sortEdges: () => sortEdges,
52
+ toJSON: () => toJSON,
53
+ traverse: () => traverse,
54
+ waitForPath: () => waitForPath
55
+ });
56
+ import { Atom as Atom2, Registry } from "@effect-atom/atom-react";
5
57
  import * as Function from "effect/Function";
6
58
  import * as Option from "effect/Option";
59
+ import * as Pipeable from "effect/Pipeable";
7
60
  import * as Record from "effect/Record";
8
61
  import { Event, Trigger } from "@dxos/async";
9
62
  import { todo } from "@dxos/debug";
10
63
  import { invariant } from "@dxos/invariant";
11
64
  import { log } from "@dxos/log";
12
65
  import { isNonNullable } from "@dxos/util";
13
- function _define_property(obj, key, value) {
14
- if (key in obj) {
15
- Object.defineProperty(obj, key, {
16
- value,
17
- enumerable: true,
18
- configurable: true,
19
- writable: true
20
- });
21
- } else {
22
- obj[key] = value;
66
+
67
+ // src/node.ts
68
+ var node_exports = {};
69
+ __export(node_exports, {
70
+ ActionGroupType: () => ActionGroupType,
71
+ ActionType: () => ActionType,
72
+ RootId: () => RootId,
73
+ RootType: () => RootType,
74
+ actionGroupSymbol: () => actionGroupSymbol,
75
+ actionRelation: () => actionRelation,
76
+ childRelation: () => childRelation,
77
+ isAction: () => isAction,
78
+ isActionGroup: () => isActionGroup,
79
+ isActionLike: () => isActionLike,
80
+ isGraphNode: () => isGraphNode,
81
+ relation: () => relation
82
+ });
83
+ var RootId = "root";
84
+ var RootType = "dxos.org/type/GraphRoot";
85
+ var ActionType = "dxos.org/type/GraphAction";
86
+ var ActionGroupType = "dxos.org/type/GraphActionGroup";
87
+ var relation = (kind, direction = "outbound") => ({
88
+ kind,
89
+ direction
90
+ });
91
+ var childRelation = (direction = "outbound") => relation("child", direction);
92
+ var actionRelation = (direction = "outbound") => relation("action", direction);
93
+ var isGraphNode = (data) => data && typeof data === "object" && "id" in data && "properties" in data && data.properties ? typeof data.properties === "object" && "data" in data : false;
94
+ var isAction = (data) => isGraphNode(data) ? typeof data.data === "function" && data.type === ActionType : false;
95
+ var actionGroupSymbol = /* @__PURE__ */ Symbol("ActionGroup");
96
+ var isActionGroup = (data) => isGraphNode(data) ? data.data === actionGroupSymbol && data.type === ActionGroupType : false;
97
+ var isActionLike = (data) => isAction(data) || isActionGroup(data);
98
+
99
+ // src/util.ts
100
+ var Separators = {
101
+ primary: "",
102
+ secondary: ""
103
+ };
104
+ var normalizeRelation = (relation2) => relation2 == null ? childRelation() : typeof relation2 === "string" ? relation(relation2) : relation2;
105
+ var shallowEqual = (a, b) => {
106
+ if (a === b) return true;
107
+ if (a == null || b == null || typeof a !== "object" || typeof b !== "object") return false;
108
+ const keysA = Object.keys(a);
109
+ const keysB = Object.keys(b);
110
+ if (keysA.length !== keysB.length) {
111
+ return false;
23
112
  }
24
- return obj;
25
- }
113
+ return keysA.every((k) => a[k] === b[k]);
114
+ };
115
+ var nodeArgsUnchanged = (prev, next) => {
116
+ if (prev.length !== next.length) {
117
+ return false;
118
+ }
119
+ return prev.every((prevNode, idx) => {
120
+ const nextNode = next[idx];
121
+ return prevNode.id === nextNode.id && prevNode.type === nextNode.type && shallowEqual(prevNode.data, nextNode.data) && shallowEqual(prevNode.properties, nextNode.properties);
122
+ });
123
+ };
124
+
125
+ // src/graph.ts
26
126
  var __dxlog_file = "/__w/dxos/dxos/packages/sdk/app-graph/src/graph.ts";
27
- var graphSymbol = Symbol("graph");
127
+ var graphSymbol = /* @__PURE__ */ Symbol("graph");
28
128
  var getGraph = (node) => {
29
129
  const graph = node[graphSymbol];
30
130
  invariant(graph, "Node is not associated with a graph.", {
31
131
  F: __dxlog_file,
32
- L: 27,
132
+ L: 33,
33
133
  S: void 0,
34
134
  A: [
35
135
  "graph",
@@ -38,499 +138,1228 @@ var getGraph = (node) => {
38
138
  });
39
139
  return graph;
40
140
  };
41
- var ROOT_ID = "root";
42
- var ROOT_TYPE = "dxos.org/type/GraphRoot";
43
- var ACTION_TYPE = "dxos.org/type/GraphAction";
44
- var ACTION_GROUP_TYPE = "dxos.org/type/GraphActionGroup";
45
- var Graph = class {
46
- toJSON(id = ROOT_ID) {
47
- return this._registry.get(this._json(id));
141
+ var GraphTypeId = /* @__PURE__ */ Symbol.for("@dxos/app-graph/Graph");
142
+ var GraphKind = /* @__PURE__ */ Symbol.for("@dxos/app-graph/GraphKind");
143
+ var GraphImpl = class {
144
+ [GraphTypeId] = GraphTypeId;
145
+ [GraphKind] = "writable";
146
+ pipe() {
147
+ return Pipeable.pipeArguments(this, arguments);
48
148
  }
49
- json(id = ROOT_ID) {
50
- return this._json(id);
149
+ onNodeChanged = new Event();
150
+ _onExpand;
151
+ _onInitialize;
152
+ _onRemoveNode;
153
+ _registry;
154
+ _expanded = Record.empty();
155
+ _pendingExpands = /* @__PURE__ */ new Set();
156
+ _initialized = Record.empty();
157
+ _initialEdges = Record.empty();
158
+ _initialNodes = Record.fromEntries([
159
+ [
160
+ RootId,
161
+ this._constructNode({
162
+ id: RootId,
163
+ type: RootType,
164
+ data: null,
165
+ properties: {}
166
+ })
167
+ ]
168
+ ]);
169
+ /** @internal */
170
+ _node = Atom2.family((id) => {
171
+ const initial = Option.flatten(Record.get(this._initialNodes, id));
172
+ return Atom2.make(initial).pipe(Atom2.keepAlive, Atom2.withLabel(`graph:node:${id}`));
173
+ });
174
+ _nodeOrThrow = Atom2.family((id) => {
175
+ return Atom2.make((get2) => {
176
+ const node = get2(this._node(id));
177
+ invariant(Option.isSome(node), `Node not available: ${id}`, {
178
+ F: __dxlog_file,
179
+ L: 172,
180
+ S: this,
181
+ A: [
182
+ "Option.isSome(node)",
183
+ "`Node not available: ${id}`"
184
+ ]
185
+ });
186
+ return node.value;
187
+ });
188
+ });
189
+ _edges = Atom2.family((id) => {
190
+ const initial = Record.get(this._initialEdges, id).pipe(Option.getOrElse(() => ({})));
191
+ return Atom2.make(initial).pipe(Atom2.keepAlive, Atom2.withLabel(`graph:edges:${id}`));
192
+ });
193
+ // NOTE: Currently the argument to the family needs to be referentially stable for the atom to be referentially stable.
194
+ // TODO(wittjosiah): Atom feature request, support for something akin to `ComplexMap` to allow for complex arguments.
195
+ _connections = Atom2.family((key) => {
196
+ return Atom2.make((get2) => {
197
+ const { id, relation: relation2 } = relationFromConnectionKey(key);
198
+ const edges = get2(this._edges(id));
199
+ return (edges[relationKey(relation2)] ?? []).map((id2) => get2(this._node(id2))).filter(Option.isSome).map((o) => o.value);
200
+ }).pipe(Atom2.withLabel(`graph:connections:${key}`));
201
+ });
202
+ _actions = Atom2.family((id) => {
203
+ return Atom2.make((get2) => {
204
+ return get2(this._connections(connectionKey(id, actionRelation())));
205
+ }).pipe(Atom2.withLabel(`graph:actions:${id}`));
206
+ });
207
+ _json = Atom2.family((id) => {
208
+ return Atom2.make((get2) => {
209
+ const toJSON2 = (node, seen = []) => {
210
+ const nodes = get2(this._connections(connectionKey(node.id, "child")));
211
+ const obj = {
212
+ id: node.id,
213
+ type: node.type
214
+ };
215
+ if (node.properties.label) {
216
+ obj.label = node.properties.label;
217
+ }
218
+ if (nodes.length) {
219
+ obj.nodes = nodes.map((n) => {
220
+ const nextSeen = [
221
+ ...seen,
222
+ node.id
223
+ ];
224
+ return nextSeen.includes(n.id) ? void 0 : toJSON2(n, nextSeen);
225
+ }).filter(isNonNullable);
226
+ }
227
+ return obj;
228
+ };
229
+ const root = get2(this._nodeOrThrow(id));
230
+ return toJSON2(root);
231
+ }).pipe(Atom2.withLabel(`graph:json:${id}`));
232
+ });
233
+ constructor({ registry, nodes, edges, onInitialize, onExpand, onRemoveNode } = {}) {
234
+ this._registry = registry ?? Registry.make();
235
+ this._onInitialize = onInitialize;
236
+ this._onExpand = onExpand;
237
+ this._onRemoveNode = onRemoveNode;
238
+ if (nodes) {
239
+ nodes.forEach((node) => {
240
+ Record.set(this._initialNodes, node.id, this._constructNode(node));
241
+ });
242
+ }
243
+ if (edges) {
244
+ Object.entries(edges).forEach(([source, edges2]) => {
245
+ Record.set(this._initialEdges, source, edges2);
246
+ });
247
+ }
248
+ }
249
+ json(id = RootId) {
250
+ return jsonImpl(this, id);
51
251
  }
52
252
  node(id) {
53
- return this._node(id);
253
+ return nodeImpl(this, id);
54
254
  }
55
255
  nodeOrThrow(id) {
56
- return this._nodeOrThrow(id);
256
+ return nodeOrThrowImpl(this, id);
57
257
  }
58
- connections(id, relation = "outbound") {
59
- return this._connections(`${id}$${relation}`);
258
+ connections(id, relation2) {
259
+ return connectionsImpl(this, id, relation2);
60
260
  }
61
261
  actions(id) {
62
- return this._actions(id);
262
+ return actionsImpl(this, id);
63
263
  }
64
264
  edges(id) {
65
- return this._edges(id);
265
+ return edgesImpl(this, id);
66
266
  }
67
- get root() {
68
- return this.getNodeOrThrow(ROOT_ID);
267
+ /** @internal */
268
+ _constructNode(node) {
269
+ return Option.some({
270
+ [graphSymbol]: this,
271
+ data: null,
272
+ properties: {},
273
+ ...node
274
+ });
69
275
  }
70
- getNode(id) {
71
- return this._registry.get(this.node(id));
276
+ };
277
+ var getInternal = (graph) => {
278
+ return graph;
279
+ };
280
+ var toJSON = (graph, id = RootId) => {
281
+ const internal = getInternal(graph);
282
+ return internal._registry.get(internal._json(id));
283
+ };
284
+ var jsonImpl = (graph, id = RootId) => {
285
+ const internal = getInternal(graph);
286
+ return internal._json(id);
287
+ };
288
+ var nodeImpl = (graph, id) => {
289
+ const internal = getInternal(graph);
290
+ return internal._node(id);
291
+ };
292
+ var nodeOrThrowImpl = (graph, id) => {
293
+ const internal = getInternal(graph);
294
+ return internal._nodeOrThrow(id);
295
+ };
296
+ var connectionsImpl = (graph, id, relation2) => {
297
+ const internal = getInternal(graph);
298
+ return internal._connections(connectionKey(id, relation2));
299
+ };
300
+ var actionsImpl = (graph, id) => {
301
+ const internal = getInternal(graph);
302
+ return internal._actions(id);
303
+ };
304
+ var edgesImpl = (graph, id) => {
305
+ const internal = getInternal(graph);
306
+ return internal._edges(id);
307
+ };
308
+ var getNodeImpl = (graph, id) => {
309
+ const internal = getInternal(graph);
310
+ return internal._registry.get(nodeImpl(graph, id));
311
+ };
312
+ function getNode(graphOrId, id) {
313
+ if (typeof graphOrId === "string") {
314
+ const id2 = graphOrId;
315
+ return (graph) => getNodeImpl(graph, id2);
316
+ } else {
317
+ const graph = graphOrId;
318
+ return getNodeImpl(graph, id);
72
319
  }
73
- getNodeOrThrow(id) {
74
- return this._registry.get(this.nodeOrThrow(id));
320
+ }
321
+ var getNodeOrThrowImpl = (graph, id) => {
322
+ const internal = getInternal(graph);
323
+ return internal._registry.get(nodeOrThrowImpl(graph, id));
324
+ };
325
+ function getNodeOrThrow(graphOrId, id) {
326
+ if (typeof graphOrId === "string") {
327
+ const id2 = graphOrId;
328
+ return (graph) => getNodeOrThrowImpl(graph, id2);
329
+ } else {
330
+ const graph = graphOrId;
331
+ return getNodeOrThrowImpl(graph, id);
75
332
  }
76
- getConnections(id, relation = "outbound") {
77
- return this._registry.get(this.connections(id, relation));
333
+ }
334
+ function getRoot(graph) {
335
+ return getNodeOrThrowImpl(graph, RootId);
336
+ }
337
+ var getConnectionsImpl = (graph, id, relation2) => {
338
+ const internal = getInternal(graph);
339
+ return internal._registry.get(connectionsImpl(graph, id, relation2));
340
+ };
341
+ function getConnections(graphOrId, idOrRelation, relation2) {
342
+ if (typeof graphOrId === "string") {
343
+ const id = graphOrId;
344
+ const rel = idOrRelation;
345
+ return (graph) => getConnectionsImpl(graph, id, rel);
346
+ } else {
347
+ const graph = graphOrId;
348
+ const id = idOrRelation;
349
+ invariant(relation2 !== void 0, "Relation is required.", {
350
+ F: __dxlog_file,
351
+ L: 440,
352
+ S: this,
353
+ A: [
354
+ "relation !== undefined",
355
+ "'Relation is required.'"
356
+ ]
357
+ });
358
+ const rel = relation2;
359
+ return getConnectionsImpl(graph, id, rel);
78
360
  }
79
- getActions(id) {
80
- return this._registry.get(this.actions(id));
361
+ }
362
+ var getActionsImpl = (graph, id) => {
363
+ const internal = getInternal(graph);
364
+ return internal._registry.get(actionsImpl(graph, id));
365
+ };
366
+ function getActions(graphOrId, id) {
367
+ if (typeof graphOrId === "string") {
368
+ const id2 = graphOrId;
369
+ return (graph) => getActionsImpl(graph, id2);
370
+ } else {
371
+ const graph = graphOrId;
372
+ return getActionsImpl(graph, id);
81
373
  }
82
- getEdges(id) {
83
- return this._registry.get(this.edges(id));
374
+ }
375
+ var getEdgesImpl = (graph, id) => {
376
+ const internal = getInternal(graph);
377
+ return internal._registry.get(edgesImpl(graph, id));
378
+ };
379
+ function getEdges(graphOrId, id) {
380
+ if (typeof graphOrId === "string") {
381
+ const id2 = graphOrId;
382
+ return (graph) => getEdgesImpl(graph, id2);
383
+ } else {
384
+ const graph = graphOrId;
385
+ return getEdgesImpl(graph, id);
84
386
  }
85
- async initialize(id) {
86
- const initialized = Record.get(this._initialized, id).pipe(Option.getOrElse(() => false));
87
- log("initialize", {
88
- id,
89
- initialized
90
- }, {
91
- F: __dxlog_file,
92
- L: 386,
93
- S: this,
94
- C: (f, a) => f(...a)
387
+ }
388
+ var traverseImpl = (graph, options, path = []) => {
389
+ const { visitor, source = RootId, relation: relation2 } = options;
390
+ if (path.includes(source)) {
391
+ return;
392
+ }
393
+ const node = getNodeOrThrow(graph, source);
394
+ const shouldContinue = visitor(node, [
395
+ ...path,
396
+ source
397
+ ]);
398
+ if (shouldContinue === false) {
399
+ return;
400
+ }
401
+ Object.values(getConnections(graph, source, relation2)).forEach((child) => traverseImpl(graph, {
402
+ source: child.id,
403
+ relation: relation2,
404
+ visitor
405
+ }, [
406
+ ...path,
407
+ source
408
+ ]));
409
+ };
410
+ function traverse(graphOrOptions, optionsOrPath, path) {
411
+ if (typeof graphOrOptions === "object" && "visitor" in graphOrOptions) {
412
+ const options = graphOrOptions;
413
+ const pathArg = Array.isArray(optionsOrPath) ? optionsOrPath : void 0;
414
+ return (graph) => traverseImpl(graph, options, pathArg);
415
+ } else {
416
+ const graph = graphOrOptions;
417
+ const options = optionsOrPath;
418
+ const pathArg = path ?? (Array.isArray(optionsOrPath) ? optionsOrPath : void 0);
419
+ return traverseImpl(graph, options, pathArg);
420
+ }
421
+ }
422
+ var getPathImpl = (graph, params) => {
423
+ return Function.pipe(getNode(graph, params.source ?? "root"), Option.flatMap((node) => {
424
+ let found = Option.none();
425
+ traverseImpl(graph, {
426
+ source: node.id,
427
+ relation: "child",
428
+ visitor: (node2, path) => {
429
+ if (Option.isSome(found)) {
430
+ return false;
431
+ }
432
+ if (node2.id === params.target) {
433
+ found = Option.some(path);
434
+ }
435
+ }
95
436
  });
96
- if (!initialized) {
97
- await this._onInitialize?.(id);
98
- Record.set(this._initialized, id, true);
437
+ return found;
438
+ }));
439
+ };
440
+ function getPath(graphOrParams, params) {
441
+ if (params === void 0 && typeof graphOrParams === "object" && "target" in graphOrParams) {
442
+ const params2 = graphOrParams;
443
+ return (graph) => getPathImpl(graph, params2);
444
+ } else {
445
+ const graph = graphOrParams;
446
+ return getPathImpl(graph, params);
447
+ }
448
+ }
449
+ var waitForPathImpl = (graph, params, options) => {
450
+ const { timeout = 5e3, interval = 500 } = options ?? {};
451
+ const path = getPathImpl(graph, params);
452
+ if (Option.isSome(path)) {
453
+ return Promise.resolve(path.value);
454
+ }
455
+ const trigger = new Trigger();
456
+ const i = setInterval(() => {
457
+ const path2 = getPathImpl(graph, params);
458
+ if (Option.isSome(path2)) {
459
+ trigger.wake(path2.value);
99
460
  }
461
+ }, interval);
462
+ return trigger.wait({
463
+ timeout
464
+ }).finally(() => clearInterval(i));
465
+ };
466
+ function waitForPath(graphOrParams, paramsOrOptions, options) {
467
+ if (typeof graphOrParams === "object" && "target" in graphOrParams) {
468
+ const params = graphOrParams;
469
+ const opts = typeof paramsOrOptions === "object" && !("target" in paramsOrOptions) ? paramsOrOptions : void 0;
470
+ return (graph) => waitForPathImpl(graph, params, opts);
471
+ } else {
472
+ const graph = graphOrParams;
473
+ const params = paramsOrOptions;
474
+ return waitForPathImpl(graph, params, options);
475
+ }
476
+ }
477
+ var initializeImpl = async (graph, id) => {
478
+ const internal = getInternal(graph);
479
+ const initialized = Record.get(internal._initialized, id).pipe(Option.getOrElse(() => false));
480
+ log("initialize", {
481
+ id,
482
+ initialized
483
+ }, {
484
+ F: __dxlog_file,
485
+ L: 655,
486
+ S: void 0,
487
+ C: (f, a) => f(...a)
488
+ });
489
+ if (!initialized) {
490
+ Record.set(internal._initialized, id, true);
491
+ await internal._onInitialize?.(id);
492
+ }
493
+ return graph;
494
+ };
495
+ function initialize(graphOrId, id) {
496
+ if (typeof graphOrId === "string") {
497
+ const id2 = graphOrId;
498
+ return (graph) => initializeImpl(graph, id2);
499
+ } else {
500
+ const graph = graphOrId;
501
+ return initializeImpl(graph, id);
100
502
  }
101
- expand(id, relation = "outbound") {
102
- const key = `${id}$${relation}`;
103
- const expanded = Record.get(this._expanded, key).pipe(Option.getOrElse(() => false));
503
+ }
504
+ var expandImpl = (graph, id, relation2) => {
505
+ const internal = getInternal(graph);
506
+ const normalizedRelation = normalizeRelation(relation2);
507
+ const key = `${id}${Separators.primary}${relationKey(normalizedRelation)}`;
508
+ const nodeOpt = internal._registry.get(internal._node(id));
509
+ if (Option.isNone(nodeOpt)) {
510
+ internal._pendingExpands.add(key);
104
511
  log("expand", {
105
512
  key,
106
- expanded
513
+ deferred: true
107
514
  }, {
108
515
  F: __dxlog_file,
109
- L: 396,
110
- S: this,
516
+ L: 701,
517
+ S: void 0,
111
518
  C: (f, a) => f(...a)
112
519
  });
113
- if (!expanded) {
114
- this._onExpand?.(id, relation);
115
- Record.set(this._expanded, key, true);
116
- }
520
+ return graph;
117
521
  }
118
- addNodes(nodes) {
119
- Rx.batch(() => {
120
- nodes.map((node) => this.addNode(node));
522
+ const expanded = Record.get(internal._expanded, key).pipe(Option.getOrElse(() => false));
523
+ log("expand", {
524
+ key,
525
+ expanded
526
+ }, {
527
+ F: __dxlog_file,
528
+ L: 706,
529
+ S: void 0,
530
+ C: (f, a) => f(...a)
531
+ });
532
+ if (!expanded) {
533
+ Record.set(internal._expanded, key, true);
534
+ internal._onExpand?.(id, normalizedRelation);
535
+ }
536
+ return graph;
537
+ };
538
+ function expand(graphOrId, idOrRelation, relation2) {
539
+ if (typeof graphOrId === "string") {
540
+ const id = graphOrId;
541
+ const rel = idOrRelation;
542
+ return (graph) => expandImpl(graph, id, rel);
543
+ } else {
544
+ const graph = graphOrId;
545
+ const id = idOrRelation;
546
+ invariant(relation2 !== void 0, "Relation is required.", {
547
+ F: __dxlog_file,
548
+ L: 742,
549
+ S: this,
550
+ A: [
551
+ "relation !== undefined",
552
+ "'Relation is required.'"
553
+ ]
121
554
  });
555
+ const rel = relation2;
556
+ return expandImpl(graph, id, rel);
122
557
  }
123
- addNode({ nodes, edges, ...nodeArg }) {
124
- const { id, type, data = null, properties = {} } = nodeArg;
125
- const nodeRx = this._node(id);
126
- const node = this._registry.get(nodeRx);
127
- Option.match(node, {
128
- onSome: (node2) => {
129
- const typeChanged = node2.type !== type;
130
- const dataChanged = node2.data !== data;
131
- const propertiesChanged = Object.keys(properties).some((key) => node2.properties[key] !== properties[key]);
132
- log("existing node", {
133
- id,
134
- typeChanged,
135
- dataChanged,
136
- propertiesChanged
137
- }, {
138
- F: __dxlog_file,
139
- L: 418,
140
- S: this,
141
- C: (f, a) => f(...a)
142
- });
143
- if (typeChanged || dataChanged || propertiesChanged) {
144
- log("updating node", {
145
- id,
146
- type,
147
- data,
148
- properties
149
- }, {
150
- F: __dxlog_file,
151
- L: 420,
152
- S: this,
153
- C: (f, a) => f(...a)
154
- });
155
- const newNode = Option.some({
156
- ...node2,
157
- type,
158
- data,
159
- properties: {
160
- ...node2.properties,
161
- ...properties
162
- }
163
- });
164
- this._registry.set(nodeRx, newNode);
165
- this.onNodeChanged.emit({
166
- id,
167
- node: newNode
168
- });
169
- }
170
- },
171
- onNone: () => {
172
- log("new node", {
558
+ }
559
+ var sortEdgesImpl = (graph, id, relation2, order) => {
560
+ const internal = getInternal(graph);
561
+ const edgesAtom = internal._edges(id);
562
+ const edges = internal._registry.get(edgesAtom);
563
+ const relationId = relationKey(relation2);
564
+ const current = edges[relationId] ?? [];
565
+ const unsorted = current.filter((id2) => !order.includes(id2));
566
+ const sorted = order.filter((id2) => current.includes(id2));
567
+ const newOrder = [
568
+ ...sorted,
569
+ ...unsorted
570
+ ];
571
+ if (newOrder.length === current.length && newOrder.every((id2, i) => id2 === current[i])) {
572
+ return graph;
573
+ }
574
+ internal._registry.set(edgesAtom, {
575
+ ...edges,
576
+ [relationId]: newOrder
577
+ });
578
+ return graph;
579
+ };
580
+ function sortEdges(graphOrId, idOrRelation, relationOrOrder, order) {
581
+ if (typeof graphOrId === "string") {
582
+ const id = graphOrId;
583
+ const relation2 = idOrRelation;
584
+ const order2 = relationOrOrder;
585
+ return (graph) => sortEdgesImpl(graph, id, relation2, order2);
586
+ } else {
587
+ const graph = graphOrId;
588
+ const id = idOrRelation;
589
+ const relation2 = relationOrOrder;
590
+ return sortEdgesImpl(graph, id, relation2, order);
591
+ }
592
+ }
593
+ var addNodesImpl = (graph, nodes) => {
594
+ Atom2.batch(() => {
595
+ nodes.map((node) => addNodeImpl(graph, node));
596
+ });
597
+ return graph;
598
+ };
599
+ function addNodes(graphOrNodes, nodes) {
600
+ if (nodes === void 0) {
601
+ const nodes2 = graphOrNodes;
602
+ return (graph) => addNodesImpl(graph, nodes2);
603
+ } else {
604
+ const graph = graphOrNodes;
605
+ return addNodesImpl(graph, nodes);
606
+ }
607
+ }
608
+ var addNodeImpl = (graph, nodeArg) => {
609
+ const internal = getInternal(graph);
610
+ const { nodes, edges, id, type, data = null, properties = {}, ...rest } = nodeArg;
611
+ const nodeAtom = internal._node(id);
612
+ const existingNode = internal._registry.get(nodeAtom);
613
+ Option.match(existingNode, {
614
+ onSome: (existing) => {
615
+ const typeChanged = existing.type !== type;
616
+ const dataChanged = !shallowEqual(existing.data, data);
617
+ const propertiesChanged = Object.keys(properties).some((key) => existing.properties[key] !== properties[key]);
618
+ log("existing node", {
619
+ id,
620
+ typeChanged,
621
+ dataChanged,
622
+ propertiesChanged
623
+ }, {
624
+ F: __dxlog_file,
625
+ L: 864,
626
+ S: void 0,
627
+ C: (f, a) => f(...a)
628
+ });
629
+ if (typeChanged || dataChanged || propertiesChanged) {
630
+ log("updating node", {
173
631
  id,
174
632
  type,
175
633
  data,
176
634
  properties
177
635
  }, {
178
636
  F: __dxlog_file,
179
- L: 427,
180
- S: this,
637
+ L: 871,
638
+ S: void 0,
181
639
  C: (f, a) => f(...a)
182
640
  });
183
- const newNode = this._constructNode({
184
- id,
641
+ const newNode = Option.some({
642
+ ...existing,
643
+ ...rest,
185
644
  type,
186
645
  data,
187
- properties
646
+ properties: {
647
+ ...existing.properties,
648
+ ...properties
649
+ }
188
650
  });
189
- this._registry.set(nodeRx, newNode);
190
- this.onNodeChanged.emit({
651
+ internal._registry.set(nodeAtom, newNode);
652
+ graph.onNodeChanged.emit({
191
653
  id,
192
654
  node: newNode
193
655
  });
194
656
  }
657
+ },
658
+ onNone: () => {
659
+ log("new node", {
660
+ id,
661
+ type,
662
+ data,
663
+ properties
664
+ }, {
665
+ F: __dxlog_file,
666
+ L: 884,
667
+ S: void 0,
668
+ C: (f, a) => f(...a)
669
+ });
670
+ const newNode = internal._constructNode({
671
+ id,
672
+ type,
673
+ data,
674
+ properties,
675
+ ...rest
676
+ });
677
+ internal._registry.set(nodeAtom, newNode);
678
+ graph.onNodeChanged.emit({
679
+ id,
680
+ node: newNode
681
+ });
682
+ const prefix = `${id}${Separators.primary}`;
683
+ const toApply = [
684
+ ...internal._pendingExpands
685
+ ].filter((k) => k.startsWith(prefix));
686
+ for (const pendingKey of toApply) {
687
+ internal._pendingExpands.delete(pendingKey);
688
+ const relation2 = relationFromKey(pendingKey.slice(prefix.length));
689
+ Record.set(internal._expanded, pendingKey, true);
690
+ internal._onExpand?.(id, relation2);
691
+ }
692
+ }
693
+ });
694
+ if (nodes) {
695
+ addNodesImpl(graph, nodes);
696
+ const _edges = nodes.map((node) => ({
697
+ source: id,
698
+ target: node.id,
699
+ relation: "child"
700
+ }));
701
+ addEdgesImpl(graph, _edges);
702
+ }
703
+ if (edges) {
704
+ todo();
705
+ }
706
+ return graph;
707
+ };
708
+ function addNode(graphOrNodeArg, nodeArg) {
709
+ if (nodeArg === void 0) {
710
+ const nodeArg2 = graphOrNodeArg;
711
+ return (graph) => addNodeImpl(graph, nodeArg2);
712
+ } else {
713
+ const graph = graphOrNodeArg;
714
+ return addNodeImpl(graph, nodeArg);
715
+ }
716
+ }
717
+ var removeNodesImpl = (graph, ids, edges = false) => {
718
+ Atom2.batch(() => {
719
+ ids.map((id) => removeNodeImpl(graph, id, edges));
720
+ });
721
+ return graph;
722
+ };
723
+ function removeNodes(graphOrIds, idsOrEdges, edges) {
724
+ if (Array.isArray(graphOrIds)) {
725
+ const ids = graphOrIds;
726
+ const edgesArg = typeof idsOrEdges === "boolean" ? idsOrEdges : false;
727
+ return (graph) => removeNodesImpl(graph, ids, edgesArg);
728
+ } else {
729
+ const graph = graphOrIds;
730
+ const ids = idsOrEdges;
731
+ const edgesArg = edges ?? false;
732
+ return removeNodesImpl(graph, ids, edgesArg);
733
+ }
734
+ }
735
+ var removeNodeImpl = (graph, id, edges = false) => {
736
+ const internal = getInternal(graph);
737
+ const nodeAtom = internal._node(id);
738
+ internal._registry.set(nodeAtom, Option.none());
739
+ graph.onNodeChanged.emit({
740
+ id,
741
+ node: Option.none()
742
+ });
743
+ if (edges) {
744
+ const nodeEdges = internal._registry.get(internal._edges(id));
745
+ const edgesToRemove = [];
746
+ for (const [relationKeyValue, relatedIds] of Object.entries(nodeEdges)) {
747
+ const relation2 = relationFromKey(relationKeyValue);
748
+ const isInboundRelation = relation2.direction === "inbound";
749
+ for (const relatedId of relatedIds) {
750
+ if (isInboundRelation) {
751
+ edgesToRemove.push({
752
+ source: relatedId,
753
+ target: id,
754
+ relation: inverseRelation(relation2)
755
+ });
756
+ } else {
757
+ edgesToRemove.push({
758
+ source: id,
759
+ target: relatedId,
760
+ relation: relation2
761
+ });
762
+ }
763
+ }
764
+ }
765
+ removeEdgesImpl(graph, edgesToRemove);
766
+ }
767
+ internal._onRemoveNode?.(id);
768
+ return graph;
769
+ };
770
+ function removeNode(graphOrId, idOrEdges, edges) {
771
+ if (typeof graphOrId === "string") {
772
+ const id = graphOrId;
773
+ const edgesArg = typeof idOrEdges === "boolean" ? idOrEdges : false;
774
+ return (graph) => removeNodeImpl(graph, id, edgesArg);
775
+ } else {
776
+ const graph = graphOrId;
777
+ const id = idOrEdges;
778
+ const edgesArg = edges ?? false;
779
+ return removeNodeImpl(graph, id, edgesArg);
780
+ }
781
+ }
782
+ var addEdgesImpl = (graph, edges) => {
783
+ Atom2.batch(() => {
784
+ edges.map((edge) => addEdgeImpl(graph, edge));
785
+ });
786
+ return graph;
787
+ };
788
+ function addEdges(graphOrEdges, edges) {
789
+ if (edges === void 0) {
790
+ const edges2 = graphOrEdges;
791
+ return (graph) => addEdgesImpl(graph, edges2);
792
+ } else {
793
+ const graph = graphOrEdges;
794
+ return addEdgesImpl(graph, edges);
795
+ }
796
+ }
797
+ var addEdgeImpl = (graph, edgeArg) => {
798
+ const relation2 = normalizeRelation(edgeArg.relation);
799
+ const relationId = relationKey(relation2);
800
+ const inverse = inverseRelation(relation2);
801
+ const inverseId = relationKey(inverse);
802
+ const internal = getInternal(graph);
803
+ const sourceAtom = internal._edges(edgeArg.source);
804
+ const source = internal._registry.get(sourceAtom);
805
+ const sourceList = source[relationId] ?? [];
806
+ if (!sourceList.includes(edgeArg.target)) {
807
+ log("add edge", {
808
+ source: edgeArg.source,
809
+ target: edgeArg.target,
810
+ relation: relationId
811
+ }, {
812
+ F: __dxlog_file,
813
+ L: 1068,
814
+ S: void 0,
815
+ C: (f, a) => f(...a)
195
816
  });
196
- if (nodes) {
197
- this.addNodes(nodes);
198
- const _edges = nodes.map((node2) => ({
199
- source: id,
200
- target: node2.id
201
- }));
202
- this.addEdges(_edges);
817
+ internal._registry.set(sourceAtom, {
818
+ ...source,
819
+ [relationId]: [
820
+ ...sourceList,
821
+ edgeArg.target
822
+ ]
823
+ });
824
+ }
825
+ const targetAtom = internal._edges(edgeArg.target);
826
+ const target = internal._registry.get(targetAtom);
827
+ const targetList = target[inverseId] ?? [];
828
+ if (!targetList.includes(edgeArg.source)) {
829
+ log("add inverse edge", {
830
+ source: edgeArg.source,
831
+ target: edgeArg.target,
832
+ relation: inverseId
833
+ }, {
834
+ F: __dxlog_file,
835
+ L: 1076,
836
+ S: void 0,
837
+ C: (f, a) => f(...a)
838
+ });
839
+ internal._registry.set(targetAtom, {
840
+ ...target,
841
+ [inverseId]: [
842
+ ...targetList,
843
+ edgeArg.source
844
+ ]
845
+ });
846
+ }
847
+ return graph;
848
+ };
849
+ function addEdge(graphOrEdgeArg, edgeArg) {
850
+ if (edgeArg === void 0) {
851
+ const edgeArg2 = graphOrEdgeArg;
852
+ return (graph) => addEdgeImpl(graph, edgeArg2);
853
+ } else {
854
+ const graph = graphOrEdgeArg;
855
+ return addEdgeImpl(graph, edgeArg);
856
+ }
857
+ }
858
+ var removeEdgesImpl = (graph, edges, removeOrphans = false) => {
859
+ Atom2.batch(() => {
860
+ edges.map((edge) => removeEdgeImpl(graph, edge, removeOrphans));
861
+ });
862
+ return graph;
863
+ };
864
+ function removeEdges(graphOrEdges, edgesOrRemoveOrphans, removeOrphans) {
865
+ if (Array.isArray(graphOrEdges)) {
866
+ const edges = graphOrEdges;
867
+ const removeOrphansArg = typeof edgesOrRemoveOrphans === "boolean" ? edgesOrRemoveOrphans : false;
868
+ return (graph) => removeEdgesImpl(graph, edges, removeOrphansArg);
869
+ } else {
870
+ const graph = graphOrEdges;
871
+ const edges = edgesOrRemoveOrphans;
872
+ const removeOrphansArg = removeOrphans ?? false;
873
+ return removeEdgesImpl(graph, edges, removeOrphansArg);
874
+ }
875
+ }
876
+ var removeEdgeImpl = (graph, edgeArg, removeOrphans = false) => {
877
+ const relation2 = normalizeRelation(edgeArg.relation);
878
+ const relationId = relationKey(relation2);
879
+ const inverse = inverseRelation(relation2);
880
+ const inverseId = relationKey(inverse);
881
+ const internal = getInternal(graph);
882
+ const sourceAtom = internal._edges(edgeArg.source);
883
+ const source = internal._registry.get(sourceAtom);
884
+ const sourceList = source[relationId] ?? [];
885
+ if (sourceList.includes(edgeArg.target)) {
886
+ internal._registry.set(sourceAtom, {
887
+ ...source,
888
+ [relationId]: sourceList.filter((id) => id !== edgeArg.target)
889
+ });
890
+ }
891
+ const targetAtom = internal._edges(edgeArg.target);
892
+ const target = internal._registry.get(targetAtom);
893
+ const targetList = target[inverseId] ?? [];
894
+ if (targetList.includes(edgeArg.source)) {
895
+ internal._registry.set(targetAtom, {
896
+ ...target,
897
+ [inverseId]: targetList.filter((id) => id !== edgeArg.source)
898
+ });
899
+ }
900
+ if (removeOrphans) {
901
+ const sourceAfter = internal._registry.get(sourceAtom);
902
+ const targetAfter = internal._registry.get(targetAtom);
903
+ const isEmpty = (edges) => Object.values(edges).every((ids) => ids.length === 0);
904
+ if (isEmpty(sourceAfter) && edgeArg.source !== RootId) {
905
+ removeNodesImpl(graph, [
906
+ edgeArg.source
907
+ ]);
203
908
  }
204
- if (edges) {
205
- todo();
909
+ if (isEmpty(targetAfter) && edgeArg.target !== RootId) {
910
+ removeNodesImpl(graph, [
911
+ edgeArg.target
912
+ ]);
913
+ }
914
+ }
915
+ return graph;
916
+ };
917
+ function removeEdge(graphOrEdgeArg, edgeArgOrRemoveOrphans, removeOrphans) {
918
+ if (edgeArgOrRemoveOrphans === void 0 || typeof edgeArgOrRemoveOrphans === "boolean" || "source" in graphOrEdgeArg) {
919
+ const edgeArg = graphOrEdgeArg;
920
+ const removeOrphansArg = typeof edgeArgOrRemoveOrphans === "boolean" ? edgeArgOrRemoveOrphans : false;
921
+ return (graph) => removeEdgeImpl(graph, edgeArg, removeOrphansArg);
922
+ } else {
923
+ const graph = graphOrEdgeArg;
924
+ const edgeArg = edgeArgOrRemoveOrphans;
925
+ const removeOrphansArg = removeOrphans ?? false;
926
+ return removeEdgeImpl(graph, edgeArg, removeOrphansArg);
927
+ }
928
+ }
929
+ var make = (params) => {
930
+ return new GraphImpl(params);
931
+ };
932
+ var relationKey = (relation2) => {
933
+ const normalized = normalizeRelation(relation2);
934
+ return `${normalized.kind}${Separators.secondary}${normalized.direction}`;
935
+ };
936
+ var relationFromKey = (encoded) => {
937
+ const separatorIndex = encoded.lastIndexOf(Separators.secondary);
938
+ invariant(separatorIndex > 0 && separatorIndex < encoded.length - 1, `Invalid relation key: ${encoded}`, {
939
+ F: __dxlog_file,
940
+ L: 1221,
941
+ S: void 0,
942
+ A: [
943
+ "separatorIndex > 0 && separatorIndex < encoded.length - 1",
944
+ "`Invalid relation key: ${encoded}`"
945
+ ]
946
+ });
947
+ const kind = encoded.slice(0, separatorIndex);
948
+ const directionRaw = encoded.slice(separatorIndex + 1);
949
+ invariant(directionRaw === "outbound" || directionRaw === "inbound", `Invalid relation direction: ${directionRaw}`, {
950
+ F: __dxlog_file,
951
+ L: 1224,
952
+ S: void 0,
953
+ A: [
954
+ "directionRaw === 'outbound' || directionRaw === 'inbound'",
955
+ "`Invalid relation direction: ${directionRaw}`"
956
+ ]
957
+ });
958
+ return relation(kind, directionRaw);
959
+ };
960
+ var connectionKey = (id, relation2) => `${id}${Separators.primary}${relationKey(relation2)}`;
961
+ var relationFromConnectionKey = (key) => {
962
+ const separatorIndex = key.indexOf(Separators.primary);
963
+ invariant(separatorIndex > 0 && separatorIndex < key.length - 1, `Invalid connection key: ${key}`, {
964
+ F: __dxlog_file,
965
+ L: 1233,
966
+ S: void 0,
967
+ A: [
968
+ "separatorIndex > 0 && separatorIndex < key.length - 1",
969
+ "`Invalid connection key: ${key}`"
970
+ ]
971
+ });
972
+ const id = key.slice(0, separatorIndex);
973
+ const encodedRelation = key.slice(separatorIndex + 1);
974
+ return {
975
+ id,
976
+ relation: relationFromKey(encodedRelation)
977
+ };
978
+ };
979
+ var inverseRelation = (relation2) => {
980
+ const normalized = normalizeRelation(relation2);
981
+ return relation(normalized.kind, normalized.direction === "outbound" ? "inbound" : "outbound");
982
+ };
983
+
984
+ // src/graph-builder.ts
985
+ var graph_builder_exports = {};
986
+ __export(graph_builder_exports, {
987
+ GraphBuilderTypeId: () => GraphBuilderTypeId,
988
+ addExtension: () => addExtension,
989
+ createConnector: () => createConnector,
990
+ createExtension: () => createExtension,
991
+ createExtensionRaw: () => createExtensionRaw,
992
+ createTypeExtension: () => createTypeExtension,
993
+ destroy: () => destroy,
994
+ explore: () => explore,
995
+ flattenExtensions: () => flattenExtensions,
996
+ flush: () => flush,
997
+ from: () => from,
998
+ make: () => make2,
999
+ removeExtension: () => removeExtension
1000
+ });
1001
+ import { Atom as Atom3, Registry as Registry2 } from "@effect-atom/atom-react";
1002
+ import * as Array2 from "effect/Array";
1003
+ import * as Effect from "effect/Effect";
1004
+ import * as Function2 from "effect/Function";
1005
+ import * as Option3 from "effect/Option";
1006
+ import * as Pipeable2 from "effect/Pipeable";
1007
+ import * as Record2 from "effect/Record";
1008
+ import { scheduleTask, yieldOrContinue } from "main-thread-scheduling";
1009
+ import { log as log2 } from "@dxos/log";
1010
+ import { byPosition, getDebugName, isNonNullable as isNonNullable2 } from "@dxos/util";
1011
+
1012
+ // src/node-matcher.ts
1013
+ var node_matcher_exports = {};
1014
+ __export(node_matcher_exports, {
1015
+ whenAll: () => whenAll,
1016
+ whenAny: () => whenAny,
1017
+ whenEchoObject: () => whenEchoObject,
1018
+ whenEchoObjectMatches: () => whenEchoObjectMatches,
1019
+ whenEchoType: () => whenEchoType,
1020
+ whenEchoTypeMatches: () => whenEchoTypeMatches,
1021
+ whenId: () => whenId,
1022
+ whenNodeType: () => whenNodeType,
1023
+ whenNot: () => whenNot,
1024
+ whenRoot: () => whenRoot
1025
+ });
1026
+ import * as Option2 from "effect/Option";
1027
+ import { Obj } from "@dxos/echo";
1028
+ var whenRoot = (node) => node.id === RootId ? Option2.some(node) : Option2.none();
1029
+ var whenId = (id) => (node) => node.id === id ? Option2.some(node) : Option2.none();
1030
+ var whenNodeType = (type) => (node) => node.type === type ? Option2.some(node) : Option2.none();
1031
+ var whenEchoType = (type) => (node) => Obj.instanceOf(type, node.data) ? Option2.some(node.data) : Option2.none();
1032
+ var whenEchoObject = (node) => Obj.isObject(node.data) ? Option2.some(node.data) : Option2.none();
1033
+ var whenAll = (...matchers) => (node) => {
1034
+ for (const candidate of matchers) {
1035
+ if (Option2.isNone(candidate(node))) {
1036
+ return Option2.none();
1037
+ }
1038
+ }
1039
+ return Option2.some(node);
1040
+ };
1041
+ var whenAny = (...matchers) => (node) => {
1042
+ for (const candidate of matchers) {
1043
+ if (Option2.isSome(candidate(node))) {
1044
+ return Option2.some(node);
206
1045
  }
207
1046
  }
208
- removeNodes(ids, edges = false) {
209
- Rx.batch(() => {
210
- ids.map((id) => this.removeNode(id, edges));
1047
+ return Option2.none();
1048
+ };
1049
+ var whenEchoTypeMatches = (type) => (node) => Obj.instanceOf(type, node.data) ? Option2.some(node) : Option2.none();
1050
+ var whenEchoObjectMatches = (node) => Obj.isObject(node.data) ? Option2.some(node) : Option2.none();
1051
+ var whenNot = (matcher) => (node) => Option2.isNone(matcher(node)) ? Option2.some(node) : Option2.none();
1052
+
1053
+ // src/graph-builder.ts
1054
+ var __dxlog_file2 = "/__w/dxos/dxos/packages/sdk/app-graph/src/graph-builder.ts";
1055
+ var GraphBuilderTypeId = /* @__PURE__ */ Symbol.for("@dxos/app-graph/GraphBuilder");
1056
+ var GraphBuilderImpl = class {
1057
+ [GraphBuilderTypeId] = GraphBuilderTypeId;
1058
+ pipe() {
1059
+ return Pipeable2.pipeArguments(this, arguments);
1060
+ }
1061
+ // TODO(wittjosiah): Use Context.
1062
+ _subscriptions = /* @__PURE__ */ new Map();
1063
+ _dirtyConnectors = /* @__PURE__ */ new Map();
1064
+ _connectorPrevious = /* @__PURE__ */ new Map();
1065
+ _connectorPreviousArgs = /* @__PURE__ */ new Map();
1066
+ _flushScheduled = false;
1067
+ _flushPromise = Promise.resolve();
1068
+ _extensions = Atom3.make(Record2.empty()).pipe(Atom3.keepAlive, Atom3.withLabel("graph-builder:extensions"));
1069
+ _initialized = {};
1070
+ _registry;
1071
+ _graph;
1072
+ constructor({ registry, ...params } = {}) {
1073
+ this._registry = registry ?? Registry2.make();
1074
+ const graph = make({
1075
+ ...params,
1076
+ registry: this._registry,
1077
+ onExpand: (id, relation2) => this._onExpand(id, relation2),
1078
+ onInitialize: (id) => this._onInitialize(id),
1079
+ onRemoveNode: (id) => this._onRemoveNode(id)
211
1080
  });
1081
+ this._graph = graph;
1082
+ }
1083
+ get graph() {
1084
+ return this._graph;
1085
+ }
1086
+ get extensions() {
1087
+ return this._extensions;
212
1088
  }
213
- removeNode(id, edges = false) {
214
- const nodeRx = this._node(id);
215
- this._registry.set(nodeRx, Option.none());
216
- this.onNodeChanged.emit({
1089
+ /** Apply a set of node changes for a single connector key. */
1090
+ _applyConnectorUpdate(key, nodes, previous) {
1091
+ const { id, relation: relation2 } = relationFromConnectorKey(key);
1092
+ const ids = nodes.map((node) => node.id);
1093
+ const removed = previous.filter((pid) => !ids.includes(pid));
1094
+ this._connectorPrevious.set(key, ids);
1095
+ this._connectorPreviousArgs.set(key, nodes);
1096
+ removeEdges(this._graph, removed.map((target) => ({
1097
+ source: id,
1098
+ target,
1099
+ relation: relation2
1100
+ })), true);
1101
+ addNodes(this._graph, nodes);
1102
+ addEdges(this._graph, nodes.map((node) => ({
1103
+ source: id,
1104
+ target: node.id,
1105
+ relation: relation2
1106
+ })));
1107
+ if (ids.length > 0) {
1108
+ const sortedIds = [
1109
+ ...nodes
1110
+ ].sort((a, b) => byPosition(a.properties ?? {}, b.properties ?? {})).map((n) => n.id);
1111
+ sortEdges(this._graph, id, relation2, sortedIds);
1112
+ }
1113
+ }
1114
+ _scheduleDirtyFlush() {
1115
+ if (!this._flushScheduled) {
1116
+ this._flushScheduled = true;
1117
+ this._flushPromise = scheduleTask(() => {
1118
+ this._flushScheduled = false;
1119
+ while (this._dirtyConnectors.size > 0) {
1120
+ const entries = [
1121
+ ...this._dirtyConnectors.entries()
1122
+ ];
1123
+ this._dirtyConnectors.clear();
1124
+ Atom3.batch(() => {
1125
+ for (const [key, { nodes, previous }] of entries) {
1126
+ this._applyConnectorUpdate(key, nodes, previous);
1127
+ }
1128
+ });
1129
+ }
1130
+ }, {
1131
+ strategy: "smooth"
1132
+ });
1133
+ }
1134
+ }
1135
+ _resolvers = Atom3.family((id) => {
1136
+ return Atom3.make((get2) => {
1137
+ return Function2.pipe(get2(this._extensions), Record2.values, Array2.sortBy(byPosition), Array2.map(({ resolver }) => resolver), Array2.filter(isNonNullable2), Array2.map((resolver) => get2(resolver(id))), Array2.filter(isNonNullable2), Array2.head);
1138
+ });
1139
+ });
1140
+ _connectors = Atom3.family((key) => {
1141
+ return Atom3.make((get2) => {
1142
+ const { id, relation: relation2 } = relationFromConnectorKey(key);
1143
+ const node = this._graph.node(id);
1144
+ const sourceNode = Option3.getOrElse(get2(node), () => void 0);
1145
+ if (!sourceNode) {
1146
+ return [];
1147
+ }
1148
+ const extensions = Function2.pipe(get2(this._extensions), Record2.values, Array2.sortBy(byPosition), Array2.filter((ext) => relationKey(ext.relation ?? "child") === relationKey(relation2) && ext.connector != null));
1149
+ const nodes = [];
1150
+ for (const ext of extensions) {
1151
+ const result = get2(ext.connector(node));
1152
+ nodes.push(...result);
1153
+ }
1154
+ return nodes;
1155
+ }).pipe(Atom3.withLabel(`graph-builder:connectors:${key}`));
1156
+ });
1157
+ _onExpand(id, relation2) {
1158
+ log2("onExpand", {
217
1159
  id,
218
- node: Option.none()
1160
+ relation: relation2,
1161
+ registry: getDebugName(this._registry)
1162
+ }, {
1163
+ F: __dxlog_file2,
1164
+ L: 251,
1165
+ S: this,
1166
+ C: (f, a) => f(...a)
219
1167
  });
220
- if (edges) {
221
- const { inbound, outbound } = this._registry.get(this._edges(id));
222
- const edges2 = [
223
- ...inbound.map((source) => ({
224
- source,
225
- target: id
226
- })),
227
- ...outbound.map((target) => ({
228
- source: id,
229
- target
230
- }))
231
- ];
232
- this.removeEdges(edges2);
1168
+ this._expandRelation(id, relation2);
1169
+ if (relation2.kind === "child" && relation2.direction === "outbound") {
1170
+ expand(this._graph, id, "action");
233
1171
  }
234
- this._onRemoveNode?.(id);
235
- }
236
- addEdges(edges) {
237
- Rx.batch(() => {
238
- edges.map((edge) => this.addEdge(edge));
239
- });
240
1172
  }
241
- addEdge(edgeArg) {
242
- const sourceRx = this._edges(edgeArg.source);
243
- const source = this._registry.get(sourceRx);
244
- if (!source.outbound.includes(edgeArg.target)) {
245
- log("add outbound edge", {
246
- source: edgeArg.source,
247
- target: edgeArg.target
248
- }, {
249
- F: __dxlog_file,
250
- L: 482,
251
- S: this,
252
- C: (f, a) => f(...a)
253
- });
254
- this._registry.set(sourceRx, {
255
- inbound: source.inbound,
256
- outbound: [
257
- ...source.outbound,
258
- edgeArg.target
259
- ]
260
- });
261
- }
262
- const targetRx = this._edges(edgeArg.target);
263
- const target = this._registry.get(targetRx);
264
- if (!target.inbound.includes(edgeArg.source)) {
265
- log("add inbound edge", {
266
- source: edgeArg.source,
267
- target: edgeArg.target
1173
+ _expandRelation(id, relation2) {
1174
+ const key = connectorKey(id, relation2);
1175
+ const connectors = this._connectors(key);
1176
+ const cancel = this._registry.subscribe(connectors, (nodes) => {
1177
+ const previous = this._connectorPrevious.get(key) ?? [];
1178
+ const ids = nodes.map((n) => n.id);
1179
+ if (ids.length === previous.length && ids.every((nodeId, idx) => nodeId === previous[idx])) {
1180
+ const prevArgs = this._connectorPreviousArgs.get(key);
1181
+ if (prevArgs && nodeArgsUnchanged(prevArgs, nodes)) {
1182
+ return;
1183
+ }
1184
+ }
1185
+ log2("update", {
1186
+ id,
1187
+ relation: relation2,
1188
+ ids
268
1189
  }, {
269
- F: __dxlog_file,
270
- L: 489,
1190
+ F: __dxlog_file2,
1191
+ L: 277,
271
1192
  S: this,
272
1193
  C: (f, a) => f(...a)
273
1194
  });
274
- this._registry.set(targetRx, {
275
- inbound: [
276
- ...target.inbound,
277
- edgeArg.source
278
- ],
279
- outbound: target.outbound
1195
+ this._dirtyConnectors.set(key, {
1196
+ nodes,
1197
+ previous
280
1198
  });
281
- }
282
- }
283
- removeEdges(edges, removeOrphans = false) {
284
- Rx.batch(() => {
285
- edges.map((edge) => this.removeEdge(edge, removeOrphans));
1199
+ this._scheduleDirtyFlush();
1200
+ }, {
1201
+ immediate: true
286
1202
  });
1203
+ this._subscriptions.set(subscriptionKey(id, "expand", key), cancel);
287
1204
  }
288
- removeEdge(edgeArg, removeOrphans = false) {
289
- const sourceRx = this._edges(edgeArg.source);
290
- const source = this._registry.get(sourceRx);
291
- if (source.outbound.includes(edgeArg.target)) {
292
- this._registry.set(sourceRx, {
293
- inbound: source.inbound,
294
- outbound: source.outbound.filter((id) => id !== edgeArg.target)
295
- });
296
- }
297
- const targetRx = this._edges(edgeArg.target);
298
- const target = this._registry.get(targetRx);
299
- if (target.inbound.includes(edgeArg.source)) {
300
- this._registry.set(targetRx, {
301
- inbound: target.inbound.filter((id) => id !== edgeArg.source),
302
- outbound: target.outbound
1205
+ // TODO(wittjosiah): If the same node is added by a connector, the resolver should probably cancel itself?
1206
+ async _onInitialize(id) {
1207
+ log2("onInitialize", {
1208
+ id
1209
+ }, {
1210
+ F: __dxlog_file2,
1211
+ L: 289,
1212
+ S: this,
1213
+ C: (f, a) => f(...a)
1214
+ });
1215
+ const resolver = this._resolvers(id);
1216
+ const cancel = this._registry.subscribe(resolver, (node) => {
1217
+ const trigger = this._initialized[id];
1218
+ Option3.match(node, {
1219
+ onSome: (node2) => {
1220
+ addNodes(this._graph, [
1221
+ node2
1222
+ ]);
1223
+ trigger?.wake();
1224
+ },
1225
+ onNone: () => {
1226
+ trigger?.wake();
1227
+ removeNodes(this._graph, [
1228
+ id
1229
+ ]);
1230
+ }
303
1231
  });
304
- }
305
- if (removeOrphans) {
306
- const source2 = this._registry.get(sourceRx);
307
- const target2 = this._registry.get(targetRx);
308
- if (source2.outbound.length === 0 && source2.inbound.length === 0 && edgeArg.source !== ROOT_ID) {
309
- this.removeNodes([
310
- edgeArg.source
311
- ]);
312
- }
313
- if (target2.outbound.length === 0 && target2.inbound.length === 0 && edgeArg.target !== ROOT_ID) {
314
- this.removeNodes([
315
- edgeArg.target
316
- ]);
1232
+ }, {
1233
+ immediate: true
1234
+ });
1235
+ this._subscriptions.set(subscriptionKey(id, "init"), cancel);
1236
+ }
1237
+ _onRemoveNode(id) {
1238
+ const prefix = `${id}${Separators.primary}`;
1239
+ for (const [key, cleanup] of this._subscriptions) {
1240
+ if (key.startsWith(prefix)) {
1241
+ cleanup();
1242
+ this._subscriptions.delete(key);
317
1243
  }
318
1244
  }
319
1245
  }
320
- sortEdges(id, relation, order) {
321
- const edgesRx = this._edges(id);
322
- const edges = this._registry.get(edgesRx);
323
- const unsorted = edges[relation].filter((id2) => !order.includes(id2)) ?? [];
324
- const sorted = order.filter((id2) => edges[relation].includes(id2)) ?? [];
325
- edges[relation].splice(0, edges[relation].length, ...[
326
- ...sorted,
327
- ...unsorted
328
- ]);
329
- this._registry.set(edgesRx, edges);
1246
+ };
1247
+ var make2 = (params) => {
1248
+ return new GraphBuilderImpl(params);
1249
+ };
1250
+ var from = (pickle, registry) => {
1251
+ if (!pickle) {
1252
+ return make2({
1253
+ registry
1254
+ });
330
1255
  }
331
- traverse({ visitor, source = ROOT_ID, relation = "outbound" }, path = []) {
332
- if (path.includes(source)) {
333
- return;
334
- }
335
- const node = this.getNodeOrThrow(source);
336
- const shouldContinue = visitor(node, [
337
- ...path,
338
- source
339
- ]);
340
- if (shouldContinue === false) {
341
- return;
342
- }
343
- Object.values(this.getConnections(source, relation)).forEach((child) => this.traverse({
344
- source: child.id,
345
- relation,
1256
+ const { nodes, edges } = JSON.parse(pickle);
1257
+ return make2({
1258
+ nodes,
1259
+ edges,
1260
+ registry
1261
+ });
1262
+ };
1263
+ var addExtensionImpl = (builder, extensions) => {
1264
+ const internal = builder;
1265
+ flattenExtensions(extensions).forEach((extension) => {
1266
+ const extensions2 = internal._registry.get(internal._extensions);
1267
+ internal._registry.set(internal._extensions, Record2.set(extensions2, extension.id, extension));
1268
+ });
1269
+ return builder;
1270
+ };
1271
+ function addExtension(builderOrExtensions, extensions) {
1272
+ if (extensions === void 0) {
1273
+ const extensions2 = builderOrExtensions;
1274
+ return (builder) => addExtensionImpl(builder, extensions2);
1275
+ } else {
1276
+ const builder = builderOrExtensions;
1277
+ return addExtensionImpl(builder, extensions);
1278
+ }
1279
+ }
1280
+ var removeExtensionImpl = (builder, id) => {
1281
+ const internal = builder;
1282
+ const extensions = internal._registry.get(internal._extensions);
1283
+ internal._registry.set(internal._extensions, Record2.remove(extensions, id));
1284
+ return builder;
1285
+ };
1286
+ function removeExtension(builderOrId, id) {
1287
+ if (typeof builderOrId === "string") {
1288
+ const id2 = builderOrId;
1289
+ return (builder) => removeExtensionImpl(builder, id2);
1290
+ } else {
1291
+ const builder = builderOrId;
1292
+ return removeExtensionImpl(builder, id);
1293
+ }
1294
+ }
1295
+ var exploreImpl = async (builder, options, path = []) => {
1296
+ const internal = builder;
1297
+ const { registry = Registry2.make(), source = RootId, relation: relation2, visitor } = options;
1298
+ if (path.includes(source)) {
1299
+ return;
1300
+ }
1301
+ await yieldOrContinue("idle");
1302
+ const node = registry.get(internal._graph.nodeOrThrow(source));
1303
+ const shouldContinue = await visitor(node, [
1304
+ ...path,
1305
+ node.id
1306
+ ]);
1307
+ if (shouldContinue === false) {
1308
+ return;
1309
+ }
1310
+ const nodes = Object.values(internal._registry.get(internal._extensions)).filter((extension) => relationKey(extension.relation ?? "child") === relationKey(relation2)).map((extension) => extension.connector).filter(isNonNullable2).flatMap((connector) => registry.get(connector(internal._graph.node(source))));
1311
+ await Promise.all(nodes.map((nodeArg) => {
1312
+ registry.set(internal._graph._node(nodeArg.id), internal._graph._constructNode(nodeArg));
1313
+ return exploreImpl(builder, {
1314
+ registry,
1315
+ source: nodeArg.id,
1316
+ relation: relation2,
346
1317
  visitor
347
1318
  }, [
348
1319
  ...path,
349
- source
350
- ]));
351
- }
352
- getPath({ source = "root", target }) {
353
- return Function.pipe(this.getNode(source), Option.flatMap((node) => {
354
- let found = Option.none();
355
- this.traverse({
356
- source: node.id,
357
- visitor: (node2, path) => {
358
- if (Option.isSome(found)) {
359
- return false;
360
- }
361
- if (node2.id === target) {
362
- found = Option.some(path);
363
- }
364
- }
365
- });
366
- return found;
367
- }));
368
- }
369
- async waitForPath(params, { timeout = 5e3, interval = 500 } = {}) {
370
- const path = this.getPath(params);
371
- if (Option.isSome(path)) {
372
- return path.value;
373
- }
374
- const trigger = new Trigger();
375
- const i = setInterval(() => {
376
- const path2 = this.getPath(params);
377
- if (Option.isSome(path2)) {
378
- trigger.wake(path2.value);
379
- }
380
- }, interval);
381
- return trigger.wait({
382
- timeout
383
- }).finally(() => clearInterval(i));
384
- }
385
- /** @internal */
386
- _constructNode(node) {
387
- return Option.some({
388
- [graphSymbol]: this,
389
- data: null,
390
- properties: {},
391
- ...node
392
- });
1320
+ node.id
1321
+ ]);
1322
+ }));
1323
+ if (registry !== internal._registry) {
1324
+ registry.reset();
1325
+ registry.dispose();
393
1326
  }
394
- constructor({ registry, nodes, edges, onInitialize, onExpand, onRemoveNode } = {}) {
395
- _define_property(this, "onNodeChanged", new Event());
396
- _define_property(this, "_onExpand", void 0);
397
- _define_property(this, "_onInitialize", void 0);
398
- _define_property(this, "_onRemoveNode", void 0);
399
- _define_property(this, "_registry", void 0);
400
- _define_property(this, "_expanded", Record.empty());
401
- _define_property(this, "_initialized", Record.empty());
402
- _define_property(this, "_initialEdges", Record.empty());
403
- _define_property(this, "_initialNodes", Record.fromEntries([
404
- [
405
- ROOT_ID,
406
- this._constructNode({
407
- id: ROOT_ID,
408
- type: ROOT_TYPE,
409
- data: null,
410
- properties: {}
411
- })
412
- ]
413
- ]));
414
- _define_property(this, "_node", Rx.family((id) => {
415
- const initial = Option.flatten(Record.get(this._initialNodes, id));
416
- return Rx.make(initial).pipe(Rx.keepAlive, Rx.withLabel(`graph:node:${id}`));
417
- }));
418
- _define_property(this, "_nodeOrThrow", Rx.family((id) => {
419
- return Rx.make((get2) => {
420
- const node = get2(this._node(id));
421
- invariant(Option.isSome(node), `Node not available: ${id}`, {
422
- F: __dxlog_file,
423
- L: 254,
424
- S: this,
425
- A: [
426
- "Option.isSome(node)",
427
- "`Node not available: ${id}`"
428
- ]
429
- });
430
- return node.value;
431
- });
432
- }));
433
- _define_property(this, "_edges", Rx.family((id) => {
434
- const initial = Record.get(this._initialEdges, id).pipe(Option.getOrElse(() => ({
435
- inbound: [],
436
- outbound: []
437
- })));
438
- return Rx.make(initial).pipe(Rx.keepAlive, Rx.withLabel(`graph:edges:${id}`));
439
- }));
440
- _define_property(this, "_connections", Rx.family((key) => {
441
- return Rx.make((get2) => {
442
- const [id, relation] = key.split("$");
443
- const edges2 = get2(this._edges(id));
444
- return edges2[relation].map((id2) => get2(this._node(id2))).filter(Option.isSome).map((o) => o.value);
445
- }).pipe(Rx.withLabel(`graph:connections:${key}`));
446
- }));
447
- _define_property(this, "_actions", Rx.family((id) => {
448
- return Rx.make((get2) => {
449
- return get2(this._connections(`${id}$outbound`)).filter((node) => node.type === ACTION_TYPE || node.type === ACTION_GROUP_TYPE);
450
- }).pipe(Rx.withLabel(`graph:actions:${id}`));
451
- }));
452
- _define_property(this, "_json", Rx.family((id) => {
453
- return Rx.make((get2) => {
454
- const toJSON = (node, seen = []) => {
455
- const nodes2 = get2(this.connections(node.id));
456
- const obj = {
457
- id: node.id.length > 32 ? `${node.id.slice(0, 32)}...` : node.id,
458
- type: node.type
459
- };
460
- if (node.properties.label) {
461
- obj.label = node.properties.label;
462
- }
463
- if (nodes2.length) {
464
- obj.nodes = nodes2.map((n) => {
465
- const nextSeen = [
466
- ...seen,
467
- node.id
468
- ];
469
- return nextSeen.includes(n.id) ? void 0 : toJSON(n, nextSeen);
470
- }).filter(isNonNullable);
471
- }
472
- return obj;
473
- };
474
- const root = get2(this.nodeOrThrow(id));
475
- return toJSON(root);
476
- }).pipe(Rx.withLabel(`graph:json:${id}`));
477
- }));
478
- this._registry = registry ?? Registry.make();
479
- this._onInitialize = onInitialize;
480
- this._onExpand = onExpand;
481
- this._onRemoveNode = onRemoveNode;
482
- if (nodes) {
483
- nodes.forEach((node) => {
484
- Record.set(this._initialNodes, node.id, this._constructNode(node));
485
- });
486
- }
487
- if (edges) {
488
- Object.entries(edges).forEach(([source, edges2]) => {
489
- Record.set(this._initialEdges, source, edges2);
490
- });
491
- }
1327
+ };
1328
+ function explore(builderOrOptions, optionsOrPath, path) {
1329
+ if (typeof builderOrOptions === "object" && "visitor" in builderOrOptions) {
1330
+ const options = builderOrOptions;
1331
+ const path2 = Array2.isArray(optionsOrPath) ? optionsOrPath : void 0;
1332
+ return (builder) => exploreImpl(builder, options, path2);
1333
+ } else {
1334
+ const builder = builderOrOptions;
1335
+ const options = optionsOrPath;
1336
+ const pathArg = path ?? (Array2.isArray(optionsOrPath) ? optionsOrPath : void 0);
1337
+ return exploreImpl(builder, options, pathArg);
492
1338
  }
1339
+ }
1340
+ var destroyImpl = (builder) => {
1341
+ const internal = builder;
1342
+ internal._subscriptions.forEach((unsubscribe) => unsubscribe());
1343
+ internal._subscriptions.clear();
493
1344
  };
494
-
495
- // src/graph-builder.ts
496
- import { Registry as Registry2, Rx as Rx2 } from "@effect-rx/rx-react";
497
- import { effect } from "@preact/signals-core";
498
- import * as Array from "effect/Array";
499
- import * as Function2 from "effect/Function";
500
- import * as Option2 from "effect/Option";
501
- import * as Record2 from "effect/Record";
502
- import { log as log2 } from "@dxos/log";
503
- import { byPosition, getDebugName, isNode, isNonNullable as isNonNullable2 } from "@dxos/util";
504
-
505
- // src/node.ts
506
- var isGraphNode = (data) => data && typeof data === "object" && "id" in data && "properties" in data && data.properties ? typeof data.properties === "object" && "data" in data : false;
507
- var isAction = (data) => isGraphNode(data) ? typeof data.data === "function" && data.type === ACTION_TYPE : false;
508
- var actionGroupSymbol = Symbol("ActionGroup");
509
- var isActionGroup = (data) => isGraphNode(data) ? data.data === actionGroupSymbol && data.type === ACTION_GROUP_TYPE : false;
510
- var isActionLike = (data) => isAction(data) || isActionGroup(data);
511
-
512
- // src/graph-builder.ts
513
- function _define_property2(obj, key, value) {
514
- if (key in obj) {
515
- Object.defineProperty(obj, key, {
516
- value,
517
- enumerable: true,
518
- configurable: true,
519
- writable: true
520
- });
1345
+ function destroy(builder) {
1346
+ if (builder === void 0) {
1347
+ return (builder2) => destroyImpl(builder2);
521
1348
  } else {
522
- obj[key] = value;
1349
+ return destroyImpl(builder);
523
1350
  }
524
- return obj;
525
1351
  }
526
- var __dxlog_file2 = "/__w/dxos/dxos/packages/sdk/app-graph/src/graph-builder.ts";
527
- var createExtension = (extension) => {
528
- const { id, position = "static", relation = "outbound", resolver: _resolver, connector: _connector, actions: _actions, actionGroups: _actionGroups } = extension;
1352
+ var flush = (builder) => {
1353
+ return builder._flushPromise;
1354
+ };
1355
+ var createExtensionRaw = (extension) => {
1356
+ const { id, position = "static", relation: relation2 = "child", resolver: _resolver, connector: _connector, actions: _actions, actionGroups: _actionGroups } = extension;
1357
+ const normalizedRelation = normalizeRelation(relation2);
529
1358
  const getId = (key) => `${id}/${key}`;
530
- const resolver = _resolver && Rx2.family((id2) => _resolver(id2).pipe(Rx2.withLabel(`graph-builder:_resolver:${id2}`)));
531
- const connector = _connector && Rx2.family((node) => _connector(node).pipe(Rx2.withLabel(`graph-builder:_connector:${id}`)));
532
- const actionGroups = _actionGroups && Rx2.family((node) => _actionGroups(node).pipe(Rx2.withLabel(`graph-builder:_actionGroups:${id}`)));
533
- const actions = _actions && Rx2.family((node) => _actions(node).pipe(Rx2.withLabel(`graph-builder:_actions:${id}`)));
1359
+ const resolver = _resolver && Atom3.family((id2) => _resolver(id2).pipe(Atom3.withLabel(`graph-builder:_resolver:${id2}`)));
1360
+ const connector = _connector && Atom3.family((node) => _connector(node).pipe(Atom3.withLabel(`graph-builder:_connector:${id}`)));
1361
+ const actionGroups = _actionGroups && Atom3.family((node) => _actionGroups(node).pipe(Atom3.withLabel(`graph-builder:_actionGroups:${id}`)));
1362
+ const actions = _actions && Atom3.family((node) => _actions(node).pipe(Atom3.withLabel(`graph-builder:_actions:${id}`)));
534
1363
  return [
535
1364
  resolver ? {
536
1365
  id: getId("resolver"),
@@ -540,77 +1369,139 @@ var createExtension = (extension) => {
540
1369
  connector ? {
541
1370
  id: getId("connector"),
542
1371
  position,
543
- relation,
544
- connector: Rx2.family((node) => Rx2.make((get2) => {
1372
+ relation: normalizedRelation,
1373
+ connector: Atom3.family((node) => Atom3.make((get2) => {
545
1374
  try {
546
1375
  return get2(connector(node));
547
- } catch {
1376
+ } catch (error) {
548
1377
  log2.warn("Error in connector", {
549
1378
  id: getId("connector"),
550
- node
1379
+ node,
1380
+ error
551
1381
  }, {
552
1382
  F: __dxlog_file2,
553
- L: 112,
1383
+ L: 579,
554
1384
  S: void 0,
555
1385
  C: (f, a) => f(...a)
556
1386
  });
557
1387
  return [];
558
1388
  }
559
- }).pipe(Rx2.withLabel(`graph-builder:connector:${id}`)))
1389
+ }).pipe(Atom3.withLabel(`graph-builder:connector:${id}`)))
560
1390
  } : void 0,
561
1391
  actionGroups ? {
562
1392
  id: getId("actionGroups"),
563
1393
  position,
564
- relation: "outbound",
565
- connector: Rx2.family((node) => Rx2.make((get2) => {
1394
+ relation: actionRelation(),
1395
+ connector: Atom3.family((node) => Atom3.make((get2) => {
566
1396
  try {
567
1397
  return get2(actionGroups(node)).map((arg) => ({
568
1398
  ...arg,
569
1399
  data: actionGroupSymbol,
570
- type: ACTION_GROUP_TYPE
1400
+ type: ActionGroupType
571
1401
  }));
572
- } catch {
1402
+ } catch (error) {
573
1403
  log2.warn("Error in actionGroups", {
574
1404
  id: getId("actionGroups"),
575
- node
1405
+ node,
1406
+ error
576
1407
  }, {
577
1408
  F: __dxlog_file2,
578
- L: 133,
1409
+ L: 600,
579
1410
  S: void 0,
580
1411
  C: (f, a) => f(...a)
581
1412
  });
582
1413
  return [];
583
1414
  }
584
- }).pipe(Rx2.withLabel(`graph-builder:connector:actionGroups:${id}`)))
1415
+ }).pipe(Atom3.withLabel(`graph-builder:connector:actionGroups:${id}`)))
585
1416
  } : void 0,
586
1417
  actions ? {
587
1418
  id: getId("actions"),
588
1419
  position,
589
- relation: "outbound",
590
- connector: Rx2.family((node) => Rx2.make((get2) => {
1420
+ relation: actionRelation(),
1421
+ connector: Atom3.family((node) => Atom3.make((get2) => {
591
1422
  try {
592
1423
  return get2(actions(node)).map((arg) => ({
593
1424
  ...arg,
594
- type: ACTION_TYPE
1425
+ type: ActionType
595
1426
  }));
596
- } catch {
1427
+ } catch (error) {
597
1428
  log2.warn("Error in actions", {
598
1429
  id: getId("actions"),
599
- node
1430
+ node,
1431
+ error
600
1432
  }, {
601
1433
  F: __dxlog_file2,
602
- L: 150,
1434
+ L: 617,
603
1435
  S: void 0,
604
1436
  C: (f, a) => f(...a)
605
1437
  });
606
1438
  return [];
607
1439
  }
608
- }).pipe(Rx2.withLabel(`graph-builder:connector:actions:${id}`)))
1440
+ }).pipe(Atom3.withLabel(`graph-builder:connector:actions:${id}`)))
609
1441
  } : void 0
610
1442
  ].filter(isNonNullable2);
611
1443
  };
1444
+ var runEffectSyncWithFallback = (effect, context2, extensionId, fallback) => {
1445
+ return Effect.runSync(effect.pipe(Effect.provide(context2), Effect.catchAll((error) => {
1446
+ log2.warn("Extension failed", {
1447
+ extension: extensionId,
1448
+ error
1449
+ }, {
1450
+ F: __dxlog_file2,
1451
+ L: 660,
1452
+ S: void 0,
1453
+ C: (f, a) => f(...a)
1454
+ });
1455
+ return Effect.succeed(fallback);
1456
+ })));
1457
+ };
1458
+ var createExtension = (options) => Effect.map(Effect.context(), (context2) => {
1459
+ const { id, match: match3, actions, connector, resolver, relation: relation2, position } = options;
1460
+ const connectorExtension = connector ? createConnectorWithRuntime(id, match3, connector, context2) : void 0;
1461
+ const actionsExtension = actions ? (node) => Atom3.make((get2) => Function2.pipe(get2(node), Option3.flatMap(match3), Option3.map((matched) => runEffectSyncWithFallback(actions(matched, get2), context2, id, []).map((action) => ({
1462
+ ...action,
1463
+ // Attach captured context for action execution.
1464
+ _actionContext: context2
1465
+ }))), Option3.getOrElse(() => []))) : void 0;
1466
+ const resolverExtension = resolver ? (nodeId) => Atom3.make((get2) => runEffectSyncWithFallback(resolver(nodeId, get2), context2, id, null) ?? null) : void 0;
1467
+ return createExtensionRaw({
1468
+ id,
1469
+ relation: relation2,
1470
+ position,
1471
+ connector: connectorExtension,
1472
+ actions: actionsExtension,
1473
+ resolver: resolverExtension
1474
+ });
1475
+ });
1476
+ var createConnector = (matcher, factory) => {
1477
+ return (node) => Atom3.make((get2) => Function2.pipe(get2(node), Option3.flatMap(matcher), Option3.map((data) => factory(data, get2)), Option3.getOrElse(() => [])));
1478
+ };
1479
+ var createConnectorWithRuntime = (extensionId, matcher, factory, context2) => {
1480
+ return (node) => Atom3.make((get2) => Function2.pipe(get2(node), Option3.flatMap(matcher), Option3.map((data) => runEffectSyncWithFallback(factory(data, get2), context2, extensionId, [])), Option3.getOrElse(() => [])));
1481
+ };
1482
+ var createTypeExtension = (options) => {
1483
+ const { id, type, actions, connector, relation: relation2, position } = options;
1484
+ return createExtension({
1485
+ id,
1486
+ match: whenEchoType(type),
1487
+ actions,
1488
+ connector,
1489
+ relation: relation2,
1490
+ position
1491
+ });
1492
+ };
1493
+ var connectorKey = (id, relation2) => `${id}${Separators.primary}${relationKey(relation2)}`;
1494
+ var relationFromConnectorKey = (key) => {
1495
+ const separatorIndex = key.indexOf(Separators.primary);
1496
+ const id = key.slice(0, separatorIndex);
1497
+ return {
1498
+ id,
1499
+ relation: relationFromKey(key.slice(separatorIndex + 1))
1500
+ };
1501
+ };
1502
+ var subscriptionKey = (id, kind, detail) => detail != null ? `${id}${Separators.primary}${kind}${Separators.primary}${detail}` : `${id}${Separators.primary}${kind}`;
612
1503
  var flattenExtensions = (extension, acc = []) => {
613
- if (Array.isArray(extension)) {
1504
+ if (Array2.isArray(extension)) {
614
1505
  return [
615
1506
  ...acc,
616
1507
  ...extension.flatMap((ext) => flattenExtensions(ext, acc))
@@ -622,239 +1513,11 @@ var flattenExtensions = (extension, acc = []) => {
622
1513
  ];
623
1514
  }
624
1515
  };
625
- var GraphBuilder = class _GraphBuilder {
626
- static from(pickle, registry) {
627
- if (!pickle) {
628
- return new _GraphBuilder({
629
- registry
630
- });
631
- }
632
- const { nodes, edges } = JSON.parse(pickle);
633
- return new _GraphBuilder({
634
- nodes,
635
- edges,
636
- registry
637
- });
638
- }
639
- get graph() {
640
- return this._graph;
641
- }
642
- get extensions() {
643
- return this._extensions;
644
- }
645
- addExtension(extensions) {
646
- flattenExtensions(extensions).forEach((extension) => {
647
- const extensions2 = this._registry.get(this._extensions);
648
- this._registry.set(this._extensions, Record2.set(extensions2, extension.id, extension));
649
- });
650
- return this;
651
- }
652
- removeExtension(id) {
653
- const extensions = this._registry.get(this._extensions);
654
- this._registry.set(this._extensions, Record2.remove(extensions, id));
655
- return this;
656
- }
657
- async explore({ registry = Registry2.make(), source = ROOT_ID, relation = "outbound", visitor }, path = []) {
658
- if (path.includes(source)) {
659
- return;
660
- }
661
- if (!isNode()) {
662
- const { yieldOrContinue } = await import("main-thread-scheduling");
663
- await yieldOrContinue("idle");
664
- }
665
- const node = registry.get(this._graph.nodeOrThrow(source));
666
- const shouldContinue = await visitor(node, [
667
- ...path,
668
- node.id
669
- ]);
670
- if (shouldContinue === false) {
671
- return;
672
- }
673
- 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))));
674
- await Promise.all(nodes.map((nodeArg) => {
675
- registry.set(this._graph._node(nodeArg.id), this._graph._constructNode(nodeArg));
676
- return this.explore({
677
- registry,
678
- source: nodeArg.id,
679
- relation,
680
- visitor
681
- }, [
682
- ...path,
683
- node.id
684
- ]);
685
- }));
686
- if (registry !== this._registry) {
687
- registry.reset();
688
- registry.dispose();
689
- }
690
- }
691
- destroy() {
692
- this._subscriptions.forEach((unsubscribe) => unsubscribe());
693
- this._subscriptions.clear();
694
- }
695
- _onExpand(id, relation) {
696
- log2("onExpand", {
697
- id,
698
- relation,
699
- registry: getDebugName(this._registry)
700
- }, {
701
- F: __dxlog_file2,
702
- L: 327,
703
- S: this,
704
- C: (f, a) => f(...a)
705
- });
706
- const connectors = this._connectors(`${id}+${relation}`);
707
- let previous = [];
708
- const cancel = this._registry.subscribe(connectors, (nodes) => {
709
- const ids = nodes.map((n) => n.id);
710
- const removed = previous.filter((id2) => !ids.includes(id2));
711
- previous = ids;
712
- log2("update", {
713
- id,
714
- relation,
715
- ids,
716
- removed
717
- }, {
718
- F: __dxlog_file2,
719
- L: 338,
720
- S: this,
721
- C: (f, a) => f(...a)
722
- });
723
- const update = () => {
724
- Rx2.batch(() => {
725
- this._graph.removeEdges(removed.map((target) => ({
726
- source: id,
727
- target
728
- })), true);
729
- this._graph.addNodes(nodes);
730
- this._graph.addEdges(nodes.map((node) => relation === "outbound" ? {
731
- source: id,
732
- target: node.id
733
- } : {
734
- source: node.id,
735
- target: id
736
- }));
737
- this._graph.sortEdges(id, relation, nodes.map(({ id: id2 }) => id2));
738
- });
739
- };
740
- if (typeof requestAnimationFrame === "function") {
741
- requestAnimationFrame(update);
742
- } else {
743
- update();
744
- }
745
- }, {
746
- immediate: true
747
- });
748
- this._subscriptions.set(id, cancel);
749
- }
750
- // TODO(wittjosiah): If the same node is added by a connector, the resolver should probably cancel itself?
751
- async _onInitialize(id) {
752
- log2("onInitialize", {
753
- id
754
- }, {
755
- F: __dxlog_file2,
756
- L: 375,
757
- S: this,
758
- C: (f, a) => f(...a)
759
- });
760
- const resolver = this._resolvers(id);
761
- const cancel = this._registry.subscribe(resolver, (node) => {
762
- const trigger = this._initialized[id];
763
- Option2.match(node, {
764
- onSome: (node2) => {
765
- this._graph.addNodes([
766
- node2
767
- ]);
768
- trigger?.wake();
769
- },
770
- onNone: () => {
771
- trigger?.wake();
772
- this._graph.removeNodes([
773
- id
774
- ]);
775
- }
776
- });
777
- }, {
778
- immediate: true
779
- });
780
- this._subscriptions.set(id, cancel);
781
- }
782
- _onRemoveNode(id) {
783
- this._subscriptions.get(id)?.();
784
- this._subscriptions.delete(id);
785
- }
786
- constructor({ registry, ...params } = {}) {
787
- _define_property2(this, "_subscriptions", /* @__PURE__ */ new Map());
788
- _define_property2(this, "_extensions", Rx2.make(Record2.empty()).pipe(Rx2.keepAlive, Rx2.withLabel("graph-builder:extensions")));
789
- _define_property2(this, "_initialized", {});
790
- _define_property2(this, "_registry", void 0);
791
- _define_property2(this, "_graph", void 0);
792
- _define_property2(this, "_resolvers", Rx2.family((id) => {
793
- return Rx2.make((get2) => {
794
- return Function2.pipe(get2(this._extensions), Record2.values, Array.sortBy(byPosition), Array.map(({ resolver }) => resolver), Array.filter(isNonNullable2), Array.map((resolver) => get2(resolver(id))), Array.filter(isNonNullable2), Array.head);
795
- });
796
- }));
797
- _define_property2(this, "_connectors", Rx2.family((key) => {
798
- return Rx2.make((get2) => {
799
- const [id, relation] = key.split("+");
800
- const node = this._graph.node(id);
801
- return Function2.pipe(
802
- get2(this._extensions),
803
- Record2.values,
804
- // TODO(wittjosiah): Sort on write rather than read.
805
- Array.sortBy(byPosition),
806
- Array.filter(({ relation: _relation = "outbound" }) => _relation === relation),
807
- Array.map(({ connector }) => connector?.(node)),
808
- Array.filter(isNonNullable2),
809
- Array.flatMap((result) => get2(result))
810
- );
811
- }).pipe(Rx2.withLabel(`graph-builder:connectors:${key}`));
812
- }));
813
- this._registry = registry ?? Registry2.make();
814
- this._graph = new Graph({
815
- ...params,
816
- registry: this._registry,
817
- onExpand: (id, relation) => this._onExpand(id, relation),
818
- onInitialize: (id) => this._onInitialize(id),
819
- onRemoveNode: (id) => this._onRemoveNode(id)
820
- });
821
- }
822
- };
823
- var rxFromSignal = (cb) => {
824
- return Rx2.make((get2) => {
825
- const dispose = effect(() => {
826
- get2.setSelf(cb());
827
- });
828
- get2.addFinalizer(() => dispose());
829
- return cb();
830
- });
831
- };
832
- var observableFamily = Rx2.family((observable) => {
833
- return Rx2.make((get2) => {
834
- const subscription = observable.subscribe((value) => get2.setSelf(value));
835
- get2.addFinalizer(() => subscription.unsubscribe());
836
- return observable.get();
837
- });
838
- });
839
- var rxFromObservable = (observable) => {
840
- return observableFamily(observable);
841
- };
842
1516
  export {
843
- ACTION_GROUP_TYPE,
844
- ACTION_TYPE,
845
- Graph,
846
- GraphBuilder,
847
- ROOT_ID,
848
- ROOT_TYPE,
849
- actionGroupSymbol,
850
- createExtension,
851
- flattenExtensions,
852
- getGraph,
853
- isAction,
854
- isActionGroup,
855
- isActionLike,
856
- isGraphNode,
857
- rxFromObservable,
858
- rxFromSignal
1517
+ atoms_exports as CreateAtom,
1518
+ graph_exports as Graph,
1519
+ graph_builder_exports as GraphBuilder,
1520
+ node_exports as Node,
1521
+ node_matcher_exports as NodeMatcher
859
1522
  };
860
1523
  //# sourceMappingURL=index.mjs.map