@dxos/app-graph 0.8.4-main.84f28bd → 0.8.4-main.ae835ea

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.
@@ -1,6 +1,8 @@
1
1
  // src/graph.ts
2
2
  import { Registry, Rx } from "@effect-rx/rx-react";
3
- import { Option, pipe, Record } from "effect";
3
+ import * as Function from "effect/Function";
4
+ import * as Option from "effect/Option";
5
+ import * as Record from "effect/Record";
4
6
  import { Event, Trigger } from "@dxos/async";
5
7
  import { todo } from "@dxos/debug";
6
8
  import { invariant } from "@dxos/invariant";
@@ -12,7 +14,7 @@ var getGraph = (node) => {
12
14
  const graph = node[graphSymbol];
13
15
  invariant(graph, "Node is not associated with a graph.", {
14
16
  F: __dxlog_file,
15
- L: 25,
17
+ L: 27,
16
18
  S: void 0,
17
19
  A: [
18
20
  "graph",
@@ -26,90 +28,95 @@ var ROOT_TYPE = "dxos.org/type/GraphRoot";
26
28
  var ACTION_TYPE = "dxos.org/type/GraphAction";
27
29
  var ACTION_GROUP_TYPE = "dxos.org/type/GraphActionGroup";
28
30
  var Graph = class {
29
- constructor({ registry, nodes, edges, onExpand, onRemoveNode } = {}) {
30
- this.onNodeChanged = new Event();
31
- this._expanded = Record.empty();
32
- this._initialized = Record.empty();
33
- this._initialEdges = Record.empty();
34
- this._initialNodes = Record.fromEntries([
35
- [
36
- ROOT_ID,
37
- this._constructNode({
38
- id: ROOT_ID,
39
- type: ROOT_TYPE,
40
- data: null,
41
- properties: {}
42
- })
43
- ]
44
- ]);
45
- /** @internal */
46
- this._node = Rx.family((id) => {
47
- const initial = Option.flatten(Record.get(this._initialNodes, id));
48
- return Rx.make(initial).pipe(Rx.keepAlive, Rx.withLabel(`graph:node:${id}`));
49
- });
50
- this._nodeOrThrow = Rx.family((id) => {
51
- return Rx.make((get) => {
52
- const node = get(this._node(id));
53
- invariant(Option.isSome(node), `Node not available: ${id}`, {
54
- F: __dxlog_file,
55
- L: 253,
56
- S: this,
57
- A: [
58
- "Option.isSome(node)",
59
- "`Node not available: ${id}`"
60
- ]
61
- });
62
- return node.value;
31
+ onNodeChanged = new Event();
32
+ _onExpand;
33
+ _onInitialize;
34
+ _onRemoveNode;
35
+ _registry;
36
+ _expanded = Record.empty();
37
+ _initialized = Record.empty();
38
+ _initialEdges = Record.empty();
39
+ _initialNodes = Record.fromEntries([
40
+ [
41
+ ROOT_ID,
42
+ this._constructNode({
43
+ id: ROOT_ID,
44
+ type: ROOT_TYPE,
45
+ data: null,
46
+ properties: {}
47
+ })
48
+ ]
49
+ ]);
50
+ /** @internal */
51
+ _node = Rx.family((id) => {
52
+ const initial = Option.flatten(Record.get(this._initialNodes, id));
53
+ return Rx.make(initial).pipe(Rx.keepAlive, Rx.withLabel(`graph:node:${id}`));
54
+ });
55
+ _nodeOrThrow = Rx.family((id) => {
56
+ return Rx.make((get2) => {
57
+ const node = get2(this._node(id));
58
+ invariant(Option.isSome(node), `Node not available: ${id}`, {
59
+ F: __dxlog_file,
60
+ L: 254,
61
+ S: this,
62
+ A: [
63
+ "Option.isSome(node)",
64
+ "`Node not available: ${id}`"
65
+ ]
63
66
  });
67
+ return node.value;
64
68
  });
65
- this._edges = Rx.family((id) => {
66
- const initial = Record.get(this._initialEdges, id).pipe(Option.getOrElse(() => ({
67
- inbound: [],
68
- outbound: []
69
- })));
70
- return Rx.make(initial).pipe(Rx.keepAlive, Rx.withLabel(`graph:edges:${id}`));
71
- });
72
- // NOTE: Currently the argument to the family needs to be referentially stable for the rx to be referentially stable.
73
- // TODO(wittjosiah): Rx feature request, support for something akin to `ComplexMap` to allow for complex arguments.
74
- this._connections = Rx.family((key) => {
75
- return Rx.make((get) => {
76
- const [id, relation] = key.split("$");
77
- const edges = get(this._edges(id));
78
- return edges[relation].map((id2) => get(this._node(id2))).filter(Option.isSome).map((o) => o.value);
79
- }).pipe(Rx.withLabel(`graph:connections:${key}`));
80
- });
81
- this._actions = Rx.family((id) => {
82
- return Rx.make((get) => {
83
- return get(this._connections(`${id}$outbound`)).filter((node) => node.type === ACTION_TYPE || node.type === ACTION_GROUP_TYPE);
84
- }).pipe(Rx.withLabel(`graph:actions:${id}`));
85
- });
86
- this._json = Rx.family((id) => {
87
- return Rx.make((get) => {
88
- const toJSON = (node, seen = []) => {
89
- const nodes = get(this.connections(node.id));
90
- const obj = {
91
- id: node.id.length > 32 ? `${node.id.slice(0, 32)}...` : node.id,
92
- type: node.type
93
- };
94
- if (node.properties.label) {
95
- obj.label = node.properties.label;
96
- }
97
- if (nodes.length) {
98
- obj.nodes = nodes.map((n) => {
99
- const nextSeen = [
100
- ...seen,
101
- node.id
102
- ];
103
- return nextSeen.includes(n.id) ? void 0 : toJSON(n, nextSeen);
104
- }).filter(isNonNullable);
105
- }
106
- return obj;
69
+ });
70
+ _edges = Rx.family((id) => {
71
+ const initial = Record.get(this._initialEdges, id).pipe(Option.getOrElse(() => ({
72
+ inbound: [],
73
+ outbound: []
74
+ })));
75
+ return Rx.make(initial).pipe(Rx.keepAlive, Rx.withLabel(`graph:edges:${id}`));
76
+ });
77
+ // NOTE: Currently the argument to the family needs to be referentially stable for the rx to be referentially stable.
78
+ // TODO(wittjosiah): Rx feature request, support for something akin to `ComplexMap` to allow for complex arguments.
79
+ _connections = Rx.family((key) => {
80
+ return Rx.make((get2) => {
81
+ const [id, relation] = key.split("$");
82
+ const edges = get2(this._edges(id));
83
+ return edges[relation].map((id2) => get2(this._node(id2))).filter(Option.isSome).map((o) => o.value);
84
+ }).pipe(Rx.withLabel(`graph:connections:${key}`));
85
+ });
86
+ _actions = Rx.family((id) => {
87
+ return Rx.make((get2) => {
88
+ return get2(this._connections(`${id}$outbound`)).filter((node) => node.type === ACTION_TYPE || node.type === ACTION_GROUP_TYPE);
89
+ }).pipe(Rx.withLabel(`graph:actions:${id}`));
90
+ });
91
+ _json = Rx.family((id) => {
92
+ return Rx.make((get2) => {
93
+ const toJSON = (node, seen = []) => {
94
+ const nodes = get2(this.connections(node.id));
95
+ const obj = {
96
+ id: node.id,
97
+ type: node.type
107
98
  };
108
- const root = get(this.nodeOrThrow(id));
109
- return toJSON(root);
110
- }).pipe(Rx.withLabel(`graph:json:${id}`));
111
- });
99
+ if (node.properties.label) {
100
+ obj.label = node.properties.label;
101
+ }
102
+ if (nodes.length) {
103
+ obj.nodes = nodes.map((n) => {
104
+ const nextSeen = [
105
+ ...seen,
106
+ node.id
107
+ ];
108
+ return nextSeen.includes(n.id) ? void 0 : toJSON(n, nextSeen);
109
+ }).filter(isNonNullable);
110
+ }
111
+ return obj;
112
+ };
113
+ const root = get2(this.nodeOrThrow(id));
114
+ return toJSON(root);
115
+ }).pipe(Rx.withLabel(`graph:json:${id}`));
116
+ });
117
+ constructor({ registry, nodes, edges, onInitialize, onExpand, onRemoveNode } = {}) {
112
118
  this._registry = registry ?? Registry.make();
119
+ this._onInitialize = onInitialize;
113
120
  this._onExpand = onExpand;
114
121
  this._onRemoveNode = onRemoveNode;
115
122
  if (nodes) {
@@ -162,15 +169,22 @@ var Graph = class {
162
169
  getEdges(id) {
163
170
  return this._registry.get(this.edges(id));
164
171
  }
165
- // TODO(wittjosiah): On initialize to restore state from cache.
166
- // async initialize(id: string) {
167
- // const initialized = Record.get(this._initialized, id).pipe(Option.getOrElse(() => false));
168
- // log('initialize', { id, initialized });
169
- // if (!initialized) {
170
- // await this._onInitialize?.(id);
171
- // Record.set(this._initialized, id, true);
172
- // }
173
- // }
172
+ async initialize(id) {
173
+ const initialized = Record.get(this._initialized, id).pipe(Option.getOrElse(() => false));
174
+ log("initialize", {
175
+ id,
176
+ initialized
177
+ }, {
178
+ F: __dxlog_file,
179
+ L: 386,
180
+ S: this,
181
+ C: (f, a) => f(...a)
182
+ });
183
+ if (!initialized) {
184
+ await this._onInitialize?.(id);
185
+ Record.set(this._initialized, id, true);
186
+ }
187
+ }
174
188
  expand(id, relation = "outbound") {
175
189
  const key = `${id}$${relation}`;
176
190
  const expanded = Record.get(this._expanded, key).pipe(Option.getOrElse(() => false));
@@ -179,7 +193,7 @@ var Graph = class {
179
193
  expanded
180
194
  }, {
181
195
  F: __dxlog_file,
182
- L: 395,
196
+ L: 396,
183
197
  S: this,
184
198
  C: (f, a) => f(...a)
185
199
  });
@@ -209,7 +223,7 @@ var Graph = class {
209
223
  propertiesChanged
210
224
  }, {
211
225
  F: __dxlog_file,
212
- L: 417,
226
+ L: 418,
213
227
  S: this,
214
228
  C: (f, a) => f(...a)
215
229
  });
@@ -221,7 +235,7 @@ var Graph = class {
221
235
  properties
222
236
  }, {
223
237
  F: __dxlog_file,
224
- L: 419,
238
+ L: 420,
225
239
  S: this,
226
240
  C: (f, a) => f(...a)
227
241
  });
@@ -249,7 +263,7 @@ var Graph = class {
249
263
  properties
250
264
  }, {
251
265
  F: __dxlog_file,
252
- L: 426,
266
+ L: 427,
253
267
  S: this,
254
268
  C: (f, a) => f(...a)
255
269
  });
@@ -320,7 +334,7 @@ var Graph = class {
320
334
  target: edgeArg.target
321
335
  }, {
322
336
  F: __dxlog_file,
323
- L: 481,
337
+ L: 482,
324
338
  S: this,
325
339
  C: (f, a) => f(...a)
326
340
  });
@@ -340,7 +354,7 @@ var Graph = class {
340
354
  target: edgeArg.target
341
355
  }, {
342
356
  F: __dxlog_file,
343
- L: 488,
357
+ L: 489,
344
358
  S: this,
345
359
  C: (f, a) => f(...a)
346
360
  });
@@ -423,7 +437,7 @@ var Graph = class {
423
437
  ]));
424
438
  }
425
439
  getPath({ source = "root", target }) {
426
- return pipe(this.getNode(source), Option.flatMap((node) => {
440
+ return Function.pipe(this.getNode(source), Option.flatMap((node) => {
427
441
  let found = Option.none();
428
442
  this.traverse({
429
443
  source: node.id,
@@ -469,41 +483,49 @@ var Graph = class {
469
483
  // src/graph-builder.ts
470
484
  import { Registry as Registry2, Rx as Rx2 } from "@effect-rx/rx-react";
471
485
  import { effect } from "@preact/signals-core";
472
- import { Array, pipe as pipe2, Record as Record2 } from "effect";
486
+ import * as Array from "effect/Array";
487
+ import * as Function2 from "effect/Function";
488
+ import * as Option2 from "effect/Option";
489
+ import * as Record2 from "effect/Record";
473
490
  import { log as log2 } from "@dxos/log";
474
491
  import { byPosition, getDebugName, isNode, isNonNullable as isNonNullable2 } from "@dxos/util";
475
492
 
476
493
  // src/node.ts
477
494
  var isGraphNode = (data) => data && typeof data === "object" && "id" in data && "properties" in data && data.properties ? typeof data.properties === "object" && "data" in data : false;
478
- var isAction = (data) => isGraphNode(data) ? typeof data.data === "function" : false;
495
+ var isAction = (data) => isGraphNode(data) ? typeof data.data === "function" && data.type === ACTION_TYPE : false;
479
496
  var actionGroupSymbol = Symbol("ActionGroup");
480
- var isActionGroup = (data) => isGraphNode(data) ? data.data === actionGroupSymbol : false;
497
+ var isActionGroup = (data) => isGraphNode(data) ? data.data === actionGroupSymbol && data.type === ACTION_GROUP_TYPE : false;
481
498
  var isActionLike = (data) => isAction(data) || isActionGroup(data);
482
499
 
483
500
  // src/graph-builder.ts
484
501
  var __dxlog_file2 = "/__w/dxos/dxos/packages/sdk/app-graph/src/graph-builder.ts";
485
502
  var createExtension = (extension) => {
486
- const { id, position = "static", relation = "outbound", connector: _connector, actions: _actions, actionGroups: _actionGroups } = extension;
503
+ const { id, position = "static", relation = "outbound", resolver: _resolver, connector: _connector, actions: _actions, actionGroups: _actionGroups } = extension;
487
504
  const getId = (key) => `${id}/${key}`;
505
+ const resolver = _resolver && Rx2.family((id2) => _resolver(id2).pipe(Rx2.withLabel(`graph-builder:_resolver:${id2}`)));
488
506
  const connector = _connector && Rx2.family((node) => _connector(node).pipe(Rx2.withLabel(`graph-builder:_connector:${id}`)));
489
507
  const actionGroups = _actionGroups && Rx2.family((node) => _actionGroups(node).pipe(Rx2.withLabel(`graph-builder:_actionGroups:${id}`)));
490
508
  const actions = _actions && Rx2.family((node) => _actions(node).pipe(Rx2.withLabel(`graph-builder:_actions:${id}`)));
491
509
  return [
492
- // resolver ? { id: getId('resolver'), position, resolver } : undefined,
510
+ resolver ? {
511
+ id: getId("resolver"),
512
+ position,
513
+ resolver
514
+ } : void 0,
493
515
  connector ? {
494
516
  id: getId("connector"),
495
517
  position,
496
518
  relation,
497
- connector: Rx2.family((node) => Rx2.make((get) => {
519
+ connector: Rx2.family((node) => Rx2.make((get2) => {
498
520
  try {
499
- return get(connector(node));
521
+ return get2(connector(node));
500
522
  } catch {
501
523
  log2.warn("Error in connector", {
502
524
  id: getId("connector"),
503
525
  node
504
526
  }, {
505
527
  F: __dxlog_file2,
506
- L: 101,
528
+ L: 112,
507
529
  S: void 0,
508
530
  C: (f, a) => f(...a)
509
531
  });
@@ -515,9 +537,9 @@ var createExtension = (extension) => {
515
537
  id: getId("actionGroups"),
516
538
  position,
517
539
  relation: "outbound",
518
- connector: Rx2.family((node) => Rx2.make((get) => {
540
+ connector: Rx2.family((node) => Rx2.make((get2) => {
519
541
  try {
520
- return get(actionGroups(node)).map((arg) => ({
542
+ return get2(actionGroups(node)).map((arg) => ({
521
543
  ...arg,
522
544
  data: actionGroupSymbol,
523
545
  type: ACTION_GROUP_TYPE
@@ -528,7 +550,7 @@ var createExtension = (extension) => {
528
550
  node
529
551
  }, {
530
552
  F: __dxlog_file2,
531
- L: 122,
553
+ L: 133,
532
554
  S: void 0,
533
555
  C: (f, a) => f(...a)
534
556
  });
@@ -540,9 +562,9 @@ var createExtension = (extension) => {
540
562
  id: getId("actions"),
541
563
  position,
542
564
  relation: "outbound",
543
- connector: Rx2.family((node) => Rx2.make((get) => {
565
+ connector: Rx2.family((node) => Rx2.make((get2) => {
544
566
  try {
545
- return get(actions(node)).map((arg) => ({
567
+ return get2(actions(node)).map((arg) => ({
546
568
  ...arg,
547
569
  type: ACTION_TYPE
548
570
  }));
@@ -552,7 +574,7 @@ var createExtension = (extension) => {
552
574
  node
553
575
  }, {
554
576
  F: __dxlog_file2,
555
- L: 139,
577
+ L: 150,
556
578
  S: void 0,
557
579
  C: (f, a) => f(...a)
558
580
  });
@@ -576,32 +598,19 @@ var flattenExtensions = (extension, acc = []) => {
576
598
  }
577
599
  };
578
600
  var GraphBuilder = class _GraphBuilder {
601
+ // TODO(wittjosiah): Use Context.
602
+ _subscriptions = /* @__PURE__ */ new Map();
603
+ _extensions = Rx2.make(Record2.empty()).pipe(Rx2.keepAlive, Rx2.withLabel("graph-builder:extensions"));
604
+ _initialized = {};
605
+ _registry;
606
+ _graph;
579
607
  constructor({ registry, ...params } = {}) {
580
- // TODO(wittjosiah): Use Context.
581
- this._connectorSubscriptions = /* @__PURE__ */ new Map();
582
- this._extensions = Rx2.make(Record2.empty()).pipe(Rx2.keepAlive, Rx2.withLabel("graph-builder:extensions"));
583
- this._connectors = Rx2.family((key) => {
584
- return Rx2.make((get) => {
585
- const [id, relation] = key.split("+");
586
- const node = this._graph.node(id);
587
- return pipe2(
588
- get(this._extensions),
589
- Record2.values,
590
- // TODO(wittjosiah): Sort on write rather than read.
591
- Array.sortBy(byPosition),
592
- Array.filter(({ relation: _relation = "outbound" }) => _relation === relation),
593
- Array.map(({ connector }) => connector?.(node)),
594
- Array.filter(isNonNullable2),
595
- Array.flatMap((result) => get(result))
596
- );
597
- }).pipe(Rx2.withLabel(`graph-builder:connectors:${key}`));
598
- });
599
608
  this._registry = registry ?? Registry2.make();
600
609
  this._graph = new Graph({
601
610
  ...params,
602
611
  registry: this._registry,
603
612
  onExpand: (id, relation) => this._onExpand(id, relation),
604
- // onInitialize: (id) => this._onInitialize(id),
613
+ onInitialize: (id) => this._onInitialize(id),
605
614
  onRemoveNode: (id) => this._onRemoveNode(id)
606
615
  });
607
616
  }
@@ -671,9 +680,30 @@ var GraphBuilder = class _GraphBuilder {
671
680
  }
672
681
  }
673
682
  destroy() {
674
- this._connectorSubscriptions.forEach((unsubscribe) => unsubscribe());
675
- this._connectorSubscriptions.clear();
683
+ this._subscriptions.forEach((unsubscribe) => unsubscribe());
684
+ this._subscriptions.clear();
676
685
  }
686
+ _resolvers = Rx2.family((id) => {
687
+ return Rx2.make((get2) => {
688
+ 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);
689
+ });
690
+ });
691
+ _connectors = Rx2.family((key) => {
692
+ return Rx2.make((get2) => {
693
+ const [id, relation] = key.split("+");
694
+ const node = this._graph.node(id);
695
+ return Function2.pipe(
696
+ get2(this._extensions),
697
+ Record2.values,
698
+ // TODO(wittjosiah): Sort on write rather than read.
699
+ Array.sortBy(byPosition),
700
+ Array.filter(({ relation: _relation = "outbound" }) => _relation === relation),
701
+ Array.map(({ connector }) => connector?.(node)),
702
+ Array.filter(isNonNullable2),
703
+ Array.flatMap((result) => get2(result))
704
+ );
705
+ }).pipe(Rx2.withLabel(`graph-builder:connectors:${key}`));
706
+ });
677
707
  _onExpand(id, relation) {
678
708
  log2("onExpand", {
679
709
  id,
@@ -681,7 +711,7 @@ var GraphBuilder = class _GraphBuilder {
681
711
  registry: getDebugName(this._registry)
682
712
  }, {
683
713
  F: __dxlog_file2,
684
- L: 301,
714
+ L: 327,
685
715
  S: this,
686
716
  C: (f, a) => f(...a)
687
717
  });
@@ -698,7 +728,7 @@ var GraphBuilder = class _GraphBuilder {
698
728
  removed
699
729
  }, {
700
730
  F: __dxlog_file2,
701
- L: 312,
731
+ L: 338,
702
732
  S: this,
703
733
  C: (f, a) => f(...a)
704
734
  });
@@ -727,30 +757,58 @@ var GraphBuilder = class _GraphBuilder {
727
757
  }, {
728
758
  immediate: true
729
759
  });
730
- this._connectorSubscriptions.set(id, cancel);
760
+ this._subscriptions.set(id, cancel);
761
+ }
762
+ // TODO(wittjosiah): If the same node is added by a connector, the resolver should probably cancel itself?
763
+ async _onInitialize(id) {
764
+ log2("onInitialize", {
765
+ id
766
+ }, {
767
+ F: __dxlog_file2,
768
+ L: 375,
769
+ S: this,
770
+ C: (f, a) => f(...a)
771
+ });
772
+ const resolver = this._resolvers(id);
773
+ const cancel = this._registry.subscribe(resolver, (node) => {
774
+ const trigger = this._initialized[id];
775
+ Option2.match(node, {
776
+ onSome: (node2) => {
777
+ this._graph.addNodes([
778
+ node2
779
+ ]);
780
+ trigger?.wake();
781
+ },
782
+ onNone: () => {
783
+ trigger?.wake();
784
+ this._graph.removeNodes([
785
+ id
786
+ ]);
787
+ }
788
+ });
789
+ }, {
790
+ immediate: true
791
+ });
792
+ this._subscriptions.set(id, cancel);
731
793
  }
732
- // TODO(wittjosiah): On initialize to restore state from cache.
733
- // private async _onInitialize(id: string) {
734
- // log('onInitialize', { id });
735
- // }
736
794
  _onRemoveNode(id) {
737
- this._connectorSubscriptions.get(id)?.();
738
- this._connectorSubscriptions.delete(id);
795
+ this._subscriptions.get(id)?.();
796
+ this._subscriptions.delete(id);
739
797
  }
740
798
  };
741
799
  var rxFromSignal = (cb) => {
742
- return Rx2.make((get) => {
800
+ return Rx2.make((get2) => {
743
801
  const dispose = effect(() => {
744
- get.setSelf(cb());
802
+ get2.setSelf(cb());
745
803
  });
746
- get.addFinalizer(() => dispose());
804
+ get2.addFinalizer(() => dispose());
747
805
  return cb();
748
806
  });
749
807
  };
750
808
  var observableFamily = Rx2.family((observable) => {
751
- return Rx2.make((get) => {
752
- const subscription = observable.subscribe((value) => get.setSelf(value));
753
- get.addFinalizer(() => subscription.unsubscribe());
809
+ return Rx2.make((get2) => {
810
+ const subscription = observable.subscribe((value) => get2.setSelf(value));
811
+ get2.addFinalizer(() => subscription.unsubscribe());
754
812
  return observable.get();
755
813
  });
756
814
  });