@dxos/app-graph 0.8.4-main.67995b8 → 0.8.4-main.6fa680abb7

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