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