@dxos/app-graph 0.8.4-main.f9ba587 → 0.8.4-main.fd6878d

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,6 @@
1
1
  // src/graph.ts
2
2
  import { Registry, Rx } from "@effect-rx/rx-react";
3
- import { Option, pipe, Record } from "effect";
3
+ import { Option, Record, pipe } from "effect";
4
4
  import { Event, Trigger } from "@dxos/async";
5
5
  import { todo } from "@dxos/debug";
6
6
  import { invariant } from "@dxos/invariant";
@@ -26,90 +26,95 @@ var ROOT_TYPE = "dxos.org/type/GraphRoot";
26
26
  var ACTION_TYPE = "dxos.org/type/GraphAction";
27
27
  var ACTION_GROUP_TYPE = "dxos.org/type/GraphActionGroup";
28
28
  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;
29
+ onNodeChanged = new Event();
30
+ _onExpand;
31
+ _onInitialize;
32
+ _onRemoveNode;
33
+ _registry;
34
+ _expanded = Record.empty();
35
+ _initialized = Record.empty();
36
+ _initialEdges = Record.empty();
37
+ _initialNodes = Record.fromEntries([
38
+ [
39
+ ROOT_ID,
40
+ this._constructNode({
41
+ id: ROOT_ID,
42
+ type: ROOT_TYPE,
43
+ data: null,
44
+ properties: {}
45
+ })
46
+ ]
47
+ ]);
48
+ /** @internal */
49
+ _node = Rx.family((id) => {
50
+ const initial = Option.flatten(Record.get(this._initialNodes, id));
51
+ return Rx.make(initial).pipe(Rx.keepAlive, Rx.withLabel(`graph:node:${id}`));
52
+ });
53
+ _nodeOrThrow = Rx.family((id) => {
54
+ return Rx.make((get) => {
55
+ const node = get(this._node(id));
56
+ invariant(Option.isSome(node), `Node not available: ${id}`, {
57
+ F: __dxlog_file,
58
+ L: 252,
59
+ S: this,
60
+ A: [
61
+ "Option.isSome(node)",
62
+ "`Node not available: ${id}`"
63
+ ]
63
64
  });
65
+ return node.value;
64
66
  });
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;
67
+ });
68
+ _edges = Rx.family((id) => {
69
+ const initial = Record.get(this._initialEdges, id).pipe(Option.getOrElse(() => ({
70
+ inbound: [],
71
+ outbound: []
72
+ })));
73
+ return Rx.make(initial).pipe(Rx.keepAlive, Rx.withLabel(`graph:edges:${id}`));
74
+ });
75
+ // NOTE: Currently the argument to the family needs to be referentially stable for the rx to be referentially stable.
76
+ // TODO(wittjosiah): Rx feature request, support for something akin to `ComplexMap` to allow for complex arguments.
77
+ _connections = Rx.family((key) => {
78
+ return Rx.make((get) => {
79
+ const [id, relation] = key.split("$");
80
+ const edges = get(this._edges(id));
81
+ return edges[relation].map((id2) => get(this._node(id2))).filter(Option.isSome).map((o) => o.value);
82
+ }).pipe(Rx.withLabel(`graph:connections:${key}`));
83
+ });
84
+ _actions = Rx.family((id) => {
85
+ return Rx.make((get) => {
86
+ return get(this._connections(`${id}$outbound`)).filter((node) => node.type === ACTION_TYPE || node.type === ACTION_GROUP_TYPE);
87
+ }).pipe(Rx.withLabel(`graph:actions:${id}`));
88
+ });
89
+ _json = Rx.family((id) => {
90
+ return Rx.make((get) => {
91
+ const toJSON = (node, seen = []) => {
92
+ const nodes = get(this.connections(node.id));
93
+ const obj = {
94
+ id: node.id.length > 32 ? `${node.id.slice(0, 32)}...` : node.id,
95
+ type: node.type
107
96
  };
108
- const root = get(this.nodeOrThrow(id));
109
- return toJSON(root);
110
- }).pipe(Rx.withLabel(`graph:json:${id}`));
111
- });
97
+ if (node.properties.label) {
98
+ obj.label = node.properties.label;
99
+ }
100
+ if (nodes.length) {
101
+ obj.nodes = nodes.map((n) => {
102
+ const nextSeen = [
103
+ ...seen,
104
+ node.id
105
+ ];
106
+ return nextSeen.includes(n.id) ? void 0 : toJSON(n, nextSeen);
107
+ }).filter(isNonNullable);
108
+ }
109
+ return obj;
110
+ };
111
+ const root = get(this.nodeOrThrow(id));
112
+ return toJSON(root);
113
+ }).pipe(Rx.withLabel(`graph:json:${id}`));
114
+ });
115
+ constructor({ registry, nodes, edges, onInitialize, onExpand, onRemoveNode } = {}) {
112
116
  this._registry = registry ?? Registry.make();
117
+ this._onInitialize = onInitialize;
113
118
  this._onExpand = onExpand;
114
119
  this._onRemoveNode = onRemoveNode;
115
120
  if (nodes) {
@@ -162,15 +167,22 @@ var Graph = class {
162
167
  getEdges(id) {
163
168
  return this._registry.get(this.edges(id));
164
169
  }
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
- // }
170
+ async initialize(id) {
171
+ const initialized = Record.get(this._initialized, id).pipe(Option.getOrElse(() => false));
172
+ log("initialize", {
173
+ id,
174
+ initialized
175
+ }, {
176
+ F: __dxlog_file,
177
+ L: 384,
178
+ S: this,
179
+ C: (f, a) => f(...a)
180
+ });
181
+ if (!initialized) {
182
+ await this._onInitialize?.(id);
183
+ Record.set(this._initialized, id, true);
184
+ }
185
+ }
174
186
  expand(id, relation = "outbound") {
175
187
  const key = `${id}$${relation}`;
176
188
  const expanded = Record.get(this._expanded, key).pipe(Option.getOrElse(() => false));
@@ -179,7 +191,7 @@ var Graph = class {
179
191
  expanded
180
192
  }, {
181
193
  F: __dxlog_file,
182
- L: 395,
194
+ L: 394,
183
195
  S: this,
184
196
  C: (f, a) => f(...a)
185
197
  });
@@ -209,7 +221,7 @@ var Graph = class {
209
221
  propertiesChanged
210
222
  }, {
211
223
  F: __dxlog_file,
212
- L: 417,
224
+ L: 416,
213
225
  S: this,
214
226
  C: (f, a) => f(...a)
215
227
  });
@@ -221,7 +233,7 @@ var Graph = class {
221
233
  properties
222
234
  }, {
223
235
  F: __dxlog_file,
224
- L: 419,
236
+ L: 418,
225
237
  S: this,
226
238
  C: (f, a) => f(...a)
227
239
  });
@@ -249,7 +261,7 @@ var Graph = class {
249
261
  properties
250
262
  }, {
251
263
  F: __dxlog_file,
252
- L: 426,
264
+ L: 425,
253
265
  S: this,
254
266
  C: (f, a) => f(...a)
255
267
  });
@@ -320,7 +332,7 @@ var Graph = class {
320
332
  target: edgeArg.target
321
333
  }, {
322
334
  F: __dxlog_file,
323
- L: 481,
335
+ L: 480,
324
336
  S: this,
325
337
  C: (f, a) => f(...a)
326
338
  });
@@ -340,7 +352,7 @@ var Graph = class {
340
352
  target: edgeArg.target
341
353
  }, {
342
354
  F: __dxlog_file,
343
- L: 488,
355
+ L: 487,
344
356
  S: this,
345
357
  C: (f, a) => f(...a)
346
358
  });
@@ -469,27 +481,32 @@ var Graph = class {
469
481
  // src/graph-builder.ts
470
482
  import { Registry as Registry2, Rx as Rx2 } from "@effect-rx/rx-react";
471
483
  import { effect } from "@preact/signals-core";
472
- import { Array, pipe as pipe2, Record as Record2 } from "effect";
484
+ import { Array, Option as Option2, Record as Record2, pipe as pipe2 } from "effect";
473
485
  import { log as log2 } from "@dxos/log";
474
486
  import { byPosition, getDebugName, isNode, isNonNullable as isNonNullable2 } from "@dxos/util";
475
487
 
476
488
  // src/node.ts
477
489
  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;
490
+ var isAction = (data) => isGraphNode(data) ? typeof data.data === "function" && data.type === ACTION_TYPE : false;
479
491
  var actionGroupSymbol = Symbol("ActionGroup");
480
- var isActionGroup = (data) => isGraphNode(data) ? data.data === actionGroupSymbol : false;
492
+ var isActionGroup = (data) => isGraphNode(data) ? data.data === actionGroupSymbol && data.type === ACTION_GROUP_TYPE : false;
481
493
  var isActionLike = (data) => isAction(data) || isActionGroup(data);
482
494
 
483
495
  // src/graph-builder.ts
484
496
  var __dxlog_file2 = "/__w/dxos/dxos/packages/sdk/app-graph/src/graph-builder.ts";
485
497
  var createExtension = (extension) => {
486
- const { id, position = "static", relation = "outbound", connector: _connector, actions: _actions, actionGroups: _actionGroups } = extension;
498
+ const { id, position = "static", relation = "outbound", resolver: _resolver, connector: _connector, actions: _actions, actionGroups: _actionGroups } = extension;
487
499
  const getId = (key) => `${id}/${key}`;
500
+ const resolver = _resolver && Rx2.family((id2) => _resolver(id2).pipe(Rx2.withLabel(`graph-builder:_resolver:${id2}`)));
488
501
  const connector = _connector && Rx2.family((node) => _connector(node).pipe(Rx2.withLabel(`graph-builder:_connector:${id}`)));
489
502
  const actionGroups = _actionGroups && Rx2.family((node) => _actionGroups(node).pipe(Rx2.withLabel(`graph-builder:_actionGroups:${id}`)));
490
503
  const actions = _actions && Rx2.family((node) => _actions(node).pipe(Rx2.withLabel(`graph-builder:_actions:${id}`)));
491
504
  return [
492
- // resolver ? { id: getId('resolver'), position, resolver } : undefined,
505
+ resolver ? {
506
+ id: getId("resolver"),
507
+ position,
508
+ resolver
509
+ } : void 0,
493
510
  connector ? {
494
511
  id: getId("connector"),
495
512
  position,
@@ -503,7 +520,7 @@ var createExtension = (extension) => {
503
520
  node
504
521
  }, {
505
522
  F: __dxlog_file2,
506
- L: 101,
523
+ L: 109,
507
524
  S: void 0,
508
525
  C: (f, a) => f(...a)
509
526
  });
@@ -528,7 +545,7 @@ var createExtension = (extension) => {
528
545
  node
529
546
  }, {
530
547
  F: __dxlog_file2,
531
- L: 122,
548
+ L: 130,
532
549
  S: void 0,
533
550
  C: (f, a) => f(...a)
534
551
  });
@@ -552,7 +569,7 @@ var createExtension = (extension) => {
552
569
  node
553
570
  }, {
554
571
  F: __dxlog_file2,
555
- L: 139,
572
+ L: 147,
556
573
  S: void 0,
557
574
  C: (f, a) => f(...a)
558
575
  });
@@ -576,32 +593,19 @@ var flattenExtensions = (extension, acc = []) => {
576
593
  }
577
594
  };
578
595
  var GraphBuilder = class _GraphBuilder {
596
+ // TODO(wittjosiah): Use Context.
597
+ _subscriptions = /* @__PURE__ */ new Map();
598
+ _extensions = Rx2.make(Record2.empty()).pipe(Rx2.keepAlive, Rx2.withLabel("graph-builder:extensions"));
599
+ _initialized = {};
600
+ _registry;
601
+ _graph;
579
602
  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
603
  this._registry = registry ?? Registry2.make();
600
604
  this._graph = new Graph({
601
605
  ...params,
602
606
  registry: this._registry,
603
607
  onExpand: (id, relation) => this._onExpand(id, relation),
604
- // onInitialize: (id) => this._onInitialize(id),
608
+ onInitialize: (id) => this._onInitialize(id),
605
609
  onRemoveNode: (id) => this._onRemoveNode(id)
606
610
  });
607
611
  }
@@ -671,9 +675,30 @@ var GraphBuilder = class _GraphBuilder {
671
675
  }
672
676
  }
673
677
  destroy() {
674
- this._connectorSubscriptions.forEach((unsubscribe) => unsubscribe());
675
- this._connectorSubscriptions.clear();
678
+ this._subscriptions.forEach((unsubscribe) => unsubscribe());
679
+ this._subscriptions.clear();
676
680
  }
681
+ _resolvers = Rx2.family((id) => {
682
+ return Rx2.make((get) => {
683
+ return pipe2(get(this._extensions), Record2.values, Array.sortBy(byPosition), Array.map(({ resolver }) => resolver), Array.filter(isNonNullable2), Array.map((resolver) => get(resolver(id))), Array.filter(isNonNullable2), Array.head);
684
+ });
685
+ });
686
+ _connectors = Rx2.family((key) => {
687
+ return Rx2.make((get) => {
688
+ const [id, relation] = key.split("+");
689
+ const node = this._graph.node(id);
690
+ return pipe2(
691
+ get(this._extensions),
692
+ Record2.values,
693
+ // TODO(wittjosiah): Sort on write rather than read.
694
+ Array.sortBy(byPosition),
695
+ Array.filter(({ relation: _relation = "outbound" }) => _relation === relation),
696
+ Array.map(({ connector }) => connector?.(node)),
697
+ Array.filter(isNonNullable2),
698
+ Array.flatMap((result) => get(result))
699
+ );
700
+ }).pipe(Rx2.withLabel(`graph-builder:connectors:${key}`));
701
+ });
677
702
  _onExpand(id, relation) {
678
703
  log2("onExpand", {
679
704
  id,
@@ -681,7 +706,7 @@ var GraphBuilder = class _GraphBuilder {
681
706
  registry: getDebugName(this._registry)
682
707
  }, {
683
708
  F: __dxlog_file2,
684
- L: 301,
709
+ L: 324,
685
710
  S: this,
686
711
  C: (f, a) => f(...a)
687
712
  });
@@ -698,7 +723,7 @@ var GraphBuilder = class _GraphBuilder {
698
723
  removed
699
724
  }, {
700
725
  F: __dxlog_file2,
701
- L: 312,
726
+ L: 335,
702
727
  S: this,
703
728
  C: (f, a) => f(...a)
704
729
  });
@@ -727,15 +752,43 @@ var GraphBuilder = class _GraphBuilder {
727
752
  }, {
728
753
  immediate: true
729
754
  });
730
- this._connectorSubscriptions.set(id, cancel);
755
+ this._subscriptions.set(id, cancel);
756
+ }
757
+ // TODO(wittjosiah): If the same node is added by a connector, the resolver should probably cancel itself?
758
+ async _onInitialize(id) {
759
+ log2("onInitialize", {
760
+ id
761
+ }, {
762
+ F: __dxlog_file2,
763
+ L: 372,
764
+ S: this,
765
+ C: (f, a) => f(...a)
766
+ });
767
+ const resolver = this._resolvers(id);
768
+ const cancel = this._registry.subscribe(resolver, (node) => {
769
+ const trigger = this._initialized[id];
770
+ Option2.match(node, {
771
+ onSome: (node2) => {
772
+ this._graph.addNodes([
773
+ node2
774
+ ]);
775
+ trigger?.wake();
776
+ },
777
+ onNone: () => {
778
+ trigger?.wake();
779
+ this._graph.removeNodes([
780
+ id
781
+ ]);
782
+ }
783
+ });
784
+ }, {
785
+ immediate: true
786
+ });
787
+ this._subscriptions.set(id, cancel);
731
788
  }
732
- // TODO(wittjosiah): On initialize to restore state from cache.
733
- // private async _onInitialize(id: string) {
734
- // log('onInitialize', { id });
735
- // }
736
789
  _onRemoveNode(id) {
737
- this._connectorSubscriptions.get(id)?.();
738
- this._connectorSubscriptions.delete(id);
790
+ this._subscriptions.get(id)?.();
791
+ this._subscriptions.delete(id);
739
792
  }
740
793
  };
741
794
  var rxFromSignal = (cb) => {