@dxos/app-graph 0.6.14-staging.e15392e → 0.7.0

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.
@@ -34,6 +34,13 @@ export type GraphTraversalOptions = {
34
34
  */
35
35
  expansion?: boolean;
36
36
  };
37
+ export type GraphParams = {
38
+ nodes?: Omit<Node, 'data'>[];
39
+ edges?: Record<string, string[]>;
40
+ onInitialNode?: Graph['_onInitialNode'];
41
+ onInitialNodes?: Graph['_onInitialNodes'];
42
+ onRemoveNode?: Graph['_onRemoveNode'];
43
+ };
37
44
  /**
38
45
  * The Graph represents the structure of the application constructed via plugins.
39
46
  */
@@ -43,11 +50,8 @@ export declare class Graph {
43
50
  private readonly _onRemoveNode?;
44
51
  private readonly _waitingForNodes;
45
52
  private readonly _initialized;
46
- constructor({ onInitialNode, onInitialNodes, onRemoveNode, }?: {
47
- onInitialNode?: Graph['_onInitialNode'];
48
- onInitialNodes?: Graph['_onInitialNodes'];
49
- onRemoveNode?: Graph['_onRemoveNode'];
50
- });
53
+ constructor({ nodes, edges, onInitialNode, onInitialNodes, onRemoveNode }?: GraphParams);
54
+ static from(pickle: string, options?: Omit<GraphParams, 'nodes' | 'edges'>): Graph;
51
55
  /**
52
56
  * Alias for `findNode('root')`.
53
57
  */
@@ -64,6 +68,7 @@ export declare class Graph {
64
68
  id?: string;
65
69
  maxLength?: number;
66
70
  }): any;
71
+ pickle(): string;
67
72
  /**
68
73
  * Find the node with the given id in the graph.
69
74
  *
@@ -131,6 +136,16 @@ export declare class Graph {
131
136
  source?: string;
132
137
  target: string;
133
138
  }): string[] | undefined;
139
+ /**
140
+ * Wait for the path between two nodes in the graph to be established.
141
+ */
142
+ waitForPath(params: {
143
+ source?: string;
144
+ target: string;
145
+ }, { timeout, interval }?: {
146
+ timeout?: number;
147
+ interval?: number;
148
+ }): Promise<string[]>;
134
149
  private _addNode;
135
150
  private _removeNode;
136
151
  private _addEdge;
@@ -1 +1 @@
1
- {"version":3,"file":"graph.d.ts","sourceRoot":"","sources":["../../../src/graph.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,KAAK,QAAQ,EAAE,KAAK,IAAI,EAAgB,KAAK,UAAU,EAAgB,MAAM,QAAQ,CAAC;AAM/F,eAAO,MAAM,QAAQ,SAAU,IAAI,KAAG,KAIrC,CAAC;AAEF,eAAO,MAAM,OAAO,SAAS,CAAC;AAC9B,eAAO,MAAM,SAAS,4BAA4B,CAAC;AACnD,eAAO,MAAM,WAAW,8BAA8B,CAAC;AACvD,eAAO,MAAM,iBAAiB,mCAAmC,CAAC;AAElE,MAAM,MAAM,YAAY,CAAC,CAAC,GAAG,GAAG,EAAE,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,IAAI;IACvF,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,MAAM,CAAC,EAAE,UAAU,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAC1B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAKF,MAAM,MAAM,qBAAqB,GAAG;IAClC;;;;OAIG;IACH,OAAO,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,OAAO,GAAG,IAAI,CAAC;IAExD;;;;OAIG;IACH,IAAI,CAAC,EAAE,IAAI,CAAC;IAEZ;;;;OAIG;IACH,QAAQ,CAAC,EAAE,QAAQ,CAAC;IAEpB;;OAEG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB,CAAC;AAEF;;GAEG;AACH,qBAAa,KAAK;IAChB,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAgC;IAChE,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAC,CAAmE;IACpG,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAgC;IAE/D,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAqC;IACtE,OAAO,CAAC,QAAQ,CAAC,YAAY,CAA+B;gBAYhD,EACV,aAAa,EACb,cAAc,EACd,YAAY,GACb,GAAE;QACD,aAAa,CAAC,EAAE,KAAK,CAAC,gBAAgB,CAAC,CAAC;QACxC,cAAc,CAAC,EAAE,KAAK,CAAC,iBAAiB,CAAC,CAAC;QAC1C,YAAY,CAAC,EAAE,KAAK,CAAC,eAAe,CAAC,CAAC;KAClC;IAQN;;OAEG;IACH,IAAI,IAAI;;;;;OAEP;IAED;;OAEG;IACH,MAAM,CAAC,EAAE,EAAY,EAAE,SAAc,EAAE,GAAE;QAAE,EAAE,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAO;IA2BjF;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,UAAO,GAAG,IAAI,GAAG,SAAS;IASxD;;;;;;;OAOG;IACG,WAAW,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAe9D;;OAEG;IACH,KAAK,CAAC,CAAC,GAAG,GAAG,EAAE,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,GAAE,YAAY,CAAC,CAAC,EAAE,CAAC,CAAM;;;;;;IAMhH;;OAEG;IACH,KAAK,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,QAAqB,EAAE,GAAE;QAAE,QAAQ,CAAC,EAAE,QAAQ,CAAA;KAAO;IAIzE;;OAEG;IACH,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,SAAS,EAAE,GAAE;QAAE,SAAS,CAAC,EAAE,OAAO,CAAA;KAAO;;;;;;IAOzD,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,QAAQ,GAAE,QAAqB,EAAE,IAAI,CAAC,EAAE,MAAM;IASvE,OAAO,CAAC,IAAI;IAIZ;;;;;;OAMG;IACH,QAAQ,CACN,EAAE,OAAO,EAAE,IAAgB,EAAE,QAAqB,EAAE,SAAS,EAAE,EAAE,qBAAqB,EACtF,IAAI,GAAE,MAAM,EAAO,GAClB,IAAI;IAgBP;;;;;;OAMG;IACH,iBAAiB,CACf,EAAE,OAAO,EAAE,IAAgB,EAAE,QAAqB,EAAE,SAAS,EAAE,EAAE,qBAAqB,EACtF,WAAW,GAAE,MAAM,EAAO;IAkB5B;;OAEG;IACH,OAAO,CAAC,EAAE,MAAe,EAAE,MAAM,EAAE,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,MAAM,EAAE,GAAG,SAAS;IAkC/F,OAAO,CAAC,QAAQ;IAgEhB,OAAO,CAAC,WAAW;IAwCnB,OAAO,CAAC,QAAQ;IA6BhB,OAAO,CAAC,WAAW;IAiCnB;;;;;;;;;OASG;IACH,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE;IAa9D,OAAO,CAAC,cAAc,CAEpB;IAEF,OAAO,CAAC,SAAS;CAyBlB"}
1
+ {"version":3,"file":"graph.d.ts","sourceRoot":"","sources":["../../../src/graph.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/app-graph",
3
- "version": "0.6.14-staging.e15392e",
3
+ "version": "0.7.0",
4
4
  "description": "Constructs knowledge graphs for the purpose of building applications on top of",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
@@ -25,13 +25,13 @@
25
25
  "dependencies": {
26
26
  "@preact/signals-core": "^1.6.0",
27
27
  "main-thread-scheduling": "^14.1.1",
28
- "@dxos/async": "0.6.14-staging.e15392e",
29
- "@dxos/debug": "0.6.14-staging.e15392e",
30
- "@dxos/echo-schema": "0.6.14-staging.e15392e",
31
- "@dxos/echo-signals": "0.6.14-staging.e15392e",
32
- "@dxos/log": "0.6.14-staging.e15392e",
33
- "@dxos/invariant": "0.6.14-staging.e15392e",
34
- "@dxos/util": "0.6.14-staging.e15392e"
28
+ "@dxos/async": "0.7.0",
29
+ "@dxos/debug": "0.7.0",
30
+ "@dxos/echo-schema": "0.7.0",
31
+ "@dxos/echo-signals": "0.7.0",
32
+ "@dxos/invariant": "0.7.0",
33
+ "@dxos/log": "0.7.0",
34
+ "@dxos/util": "0.7.0"
35
35
  },
36
36
  "devDependencies": {
37
37
  "@phosphor-icons/react": "^2.1.5",
@@ -40,18 +40,18 @@
40
40
  "react": "~18.2.0",
41
41
  "react-dom": "~18.2.0",
42
42
  "vite": "5.4.7",
43
- "@dxos/react-client": "0.6.14-staging.e15392e",
44
- "@dxos/random": "0.6.14-staging.e15392e",
45
- "@dxos/react-ui-theme": "0.6.14-staging.e15392e",
46
- "@dxos/storybook-utils": "0.6.14-staging.e15392e",
47
- "@dxos/react-ui": "0.6.14-staging.e15392e"
43
+ "@dxos/random": "0.7.0",
44
+ "@dxos/react-client": "0.7.0",
45
+ "@dxos/react-ui": "0.7.0",
46
+ "@dxos/react-ui-theme": "0.7.0",
47
+ "@dxos/storybook-utils": "0.7.0"
48
48
  },
49
49
  "peerDependencies": {
50
50
  "@phosphor-icons/react": "^2.1.5",
51
51
  "react": "~18.2.0",
52
52
  "react-dom": "~18.2.0",
53
- "@dxos/react-ui": "0.6.14-staging.e15392e",
54
- "@dxos/react-ui-theme": "0.6.14-staging.e15392e"
53
+ "@dxos/react-ui": "0.7.0",
54
+ "@dxos/react-ui-theme": "0.7.0"
55
55
  },
56
56
  "publishConfig": {
57
57
  "access": "public"
@@ -5,7 +5,7 @@
5
5
  import { batch, signal } from '@preact/signals-core';
6
6
  import { describe, expect, test } from 'vitest';
7
7
 
8
- import { ACTION_TYPE } from './graph';
8
+ import { ACTION_TYPE, ROOT_ID, ROOT_TYPE } from './graph';
9
9
  import { GraphBuilder, createExtension, memoize } from './graph-builder';
10
10
  import { type Node } from './node';
11
11
 
@@ -84,6 +84,44 @@ describe('GraphBuilder', () => {
84
84
  expect(count).to.equal(4);
85
85
  expect(memoizedCount).to.equal(1);
86
86
  });
87
+
88
+ test('resolving pickled graph', async () => {
89
+ const pickle =
90
+ '{"nodes":[{"id":"root","type":"dxos.org/type/GraphRoot","properties":{}},{"id":"test1","type":"test","properties":{"value":1}},{"id":"test2","type":"test","properties":{"value":2}}],"edges":{"root":["test1","test2"],"test1":["test2"],"test2":[]}}';
91
+ const builder = GraphBuilder.from(pickle);
92
+ const graph = builder.graph;
93
+
94
+ builder.addExtension(
95
+ createExtension({
96
+ id: 'resolver',
97
+ resolver: ({ id }) => {
98
+ if (id === ROOT_ID) {
99
+ return { id: ROOT_ID, type: ROOT_TYPE };
100
+ } else {
101
+ return { id, type: EXAMPLE_TYPE, data: id, properties: { value: parseInt(id.replace('test', '')) } };
102
+ }
103
+ },
104
+ }),
105
+ );
106
+
107
+ {
108
+ expect(graph.findNode('test1', false)).toBeDefined();
109
+ expect(graph.findNode('test1', false)?.data).to.equal(null);
110
+ expect(graph.findNode('test1', false)?.properties.value).to.equal(1);
111
+ expect(graph.findNode('test2', false)).toBeDefined();
112
+ expect(graph.findNode('test2', false)?.data).to.equal(null);
113
+ expect(graph.findNode('test2', false)?.properties.value).to.equal(2);
114
+ }
115
+
116
+ await builder.initialize();
117
+
118
+ {
119
+ expect(graph.findNode('test1', false)?.data).to.equal('test1');
120
+ expect(graph.findNode('test1', false)?.properties.value).to.equal(1);
121
+ expect(graph.findNode('test2', false)?.data).to.equal('test2');
122
+ expect(graph.findNode('test2', false)?.properties.value).to.equal(2);
123
+ }
124
+ });
87
125
  });
88
126
 
89
127
  describe('connector', () => {
@@ -10,7 +10,7 @@ import { invariant } from '@dxos/invariant';
10
10
  import { log } from '@dxos/log';
11
11
  import { isNode, type MaybePromise, nonNullable } from '@dxos/util';
12
12
 
13
- import { ACTION_GROUP_TYPE, ACTION_TYPE, Graph } from './graph';
13
+ import { ACTION_GROUP_TYPE, ACTION_TYPE, Graph, type GraphParams } from './graph';
14
14
  import { type Relation, type NodeArg, type Node, type ActionData, actionGroupSymbol } from './node';
15
15
 
16
16
  /**
@@ -192,14 +192,32 @@ export class GraphBuilder {
192
192
  private readonly _nodeChanged: Record<string, Signal<{}>> = {};
193
193
  private _graph: Graph;
194
194
 
195
- constructor() {
195
+ constructor(params: Pick<GraphParams, 'nodes' | 'edges'> = {}) {
196
196
  this._graph = new Graph({
197
+ ...params,
197
198
  onInitialNode: (id) => this._onInitialNode(id),
198
199
  onInitialNodes: (node, relation, type) => this._onInitialNodes(node, relation, type),
199
200
  onRemoveNode: (id) => this._onRemoveNode(id),
200
201
  });
201
202
  }
202
203
 
204
+ static from(pickle?: string) {
205
+ if (!pickle) {
206
+ return new GraphBuilder();
207
+ }
208
+
209
+ const { nodes, edges } = JSON.parse(pickle);
210
+ return new GraphBuilder({ nodes, edges });
211
+ }
212
+
213
+ /**
214
+ * If graph is being restored from a pickle, the data will be null.
215
+ * Initialize the data of each node by calling resolvers.
216
+ */
217
+ async initialize() {
218
+ return Promise.all(Object.keys(this._graph._nodes).map((id) => this._onInitialNode(id)));
219
+ }
220
+
203
221
  get graph() {
204
222
  return this._graph;
205
223
  }
package/src/graph.test.ts CHANGED
@@ -249,6 +249,13 @@ describe('Graph', () => {
249
249
  });
250
250
  });
251
251
 
252
+ test('pickle', () => {
253
+ const pickle =
254
+ '{"nodes":[{"id":"root","type":"dxos.org/type/GraphRoot","properties":{}},{"id":"test1","type":"test","properties":{"value":1}},{"id":"test2","type":"test","properties":{"value":2}}],"edges":{"root":["test1","test2"],"test1":["test2"],"test2":[]}}';
255
+ const graph = Graph.from(pickle);
256
+ expect(graph.pickle()).to.equal(pickle);
257
+ });
258
+
252
259
  test('waitForNode', async () => {
253
260
  const graph = new Graph();
254
261
  const promise = graph.waitForNode('test1');
package/src/graph.ts CHANGED
@@ -7,9 +7,10 @@ import { batch, effect, untracked } from '@preact/signals-core';
7
7
  import { asyncTimeout, Trigger } from '@dxos/async';
8
8
  import { type ReactiveObject, create } from '@dxos/echo-schema';
9
9
  import { invariant } from '@dxos/invariant';
10
+ import { log } from '@dxos/log';
10
11
  import { nonNullable } from '@dxos/util';
11
12
 
12
- import { type Relation, type Node, type NodeArg, type NodeFilter, isActionLike } from './node';
13
+ import { type Relation, type Node, type NodeArg, type NodeFilter, isActionLike, actionGroupSymbol } from './node';
13
14
 
14
15
  const graphSymbol = Symbol('graph');
15
16
  type DeepWriteable<T> = { -readonly [K in keyof T]: DeepWriteable<T[K]> };
@@ -64,6 +65,15 @@ export type GraphTraversalOptions = {
64
65
  expansion?: boolean;
65
66
  };
66
67
 
68
+ export type GraphParams = {
69
+ // TODO(wittjosiah): Make data optional instead of omitting.
70
+ nodes?: Omit<Node, 'data'>[];
71
+ edges?: Record<string, string[]>;
72
+ onInitialNode?: Graph['_onInitialNode'];
73
+ onInitialNodes?: Graph['_onInitialNodes'];
74
+ onRemoveNode?: Graph['_onRemoveNode'];
75
+ };
76
+
67
77
  /**
68
78
  * The Graph represents the structure of the application constructed via plugins.
69
79
  */
@@ -85,20 +95,38 @@ export class Graph {
85
95
  */
86
96
  readonly _edges: Record<string, ReactiveObject<{ inbound: string[]; outbound: string[] }>> = {};
87
97
 
88
- constructor({
89
- onInitialNode,
90
- onInitialNodes,
91
- onRemoveNode,
92
- }: {
93
- onInitialNode?: Graph['_onInitialNode'];
94
- onInitialNodes?: Graph['_onInitialNodes'];
95
- onRemoveNode?: Graph['_onRemoveNode'];
96
- } = {}) {
98
+ constructor({ nodes, edges, onInitialNode, onInitialNodes, onRemoveNode }: GraphParams = {}) {
99
+ this._nodes[ROOT_ID] = this._constructNode({ id: ROOT_ID, type: ROOT_TYPE, properties: {}, data: null });
100
+ if (nodes) {
101
+ nodes.forEach((node) => {
102
+ if (node.type === ACTION_TYPE) {
103
+ this._addNode({ ...node, data: () => log.warn('Pickled action invocation') });
104
+ } else if (node.type === ACTION_GROUP_TYPE) {
105
+ this._addNode({ ...node, data: actionGroupSymbol });
106
+ } else {
107
+ this._addNode(node);
108
+ }
109
+ });
110
+ }
111
+
112
+ this._edges[ROOT_ID] = create({ inbound: [], outbound: [] });
113
+ if (edges) {
114
+ Object.entries(edges).forEach(([source, edges]) => {
115
+ edges.forEach((target) => {
116
+ this._addEdge({ source, target });
117
+ });
118
+ this._sortEdges(source, 'outbound', edges);
119
+ });
120
+ }
121
+
97
122
  this._onInitialNode = onInitialNode;
98
123
  this._onInitialNodes = onInitialNodes;
99
124
  this._onRemoveNode = onRemoveNode;
100
- this._nodes[ROOT_ID] = this._constructNode({ id: ROOT_ID, type: ROOT_TYPE, properties: {}, data: null });
101
- this._edges[ROOT_ID] = create({ inbound: [], outbound: [] });
125
+ }
126
+
127
+ static from(pickle: string, options: Omit<GraphParams, 'nodes' | 'edges'> = {}) {
128
+ const { nodes, edges } = JSON.parse(pickle);
129
+ return new Graph({ nodes, edges, ...options });
102
130
  }
103
131
 
104
132
  /**
@@ -138,6 +166,24 @@ export class Graph {
138
166
  return toJSON(root);
139
167
  }
140
168
 
169
+ pickle() {
170
+ const nodes = Object.values(this._nodes).map((node) => {
171
+ return {
172
+ id: node.id,
173
+ type: node.type,
174
+ properties: node.properties,
175
+ };
176
+ });
177
+
178
+ const edges = Object.fromEntries(
179
+ Object.entries(this._edges)
180
+ .map(([id, { outbound }]): [string, string[]] => [id, outbound])
181
+ .toSorted(([a], [b]) => a.localeCompare(b)),
182
+ );
183
+
184
+ return JSON.stringify({ nodes, edges });
185
+ }
186
+
141
187
  /**
142
188
  * Find the node with the given id in the graph.
143
189
  *
@@ -294,6 +340,29 @@ export class Graph {
294
340
  return found;
295
341
  }
296
342
 
343
+ /**
344
+ * Wait for the path between two nodes in the graph to be established.
345
+ */
346
+ async waitForPath(
347
+ params: { source?: string; target: string },
348
+ { timeout = 5_000, interval = 500 }: { timeout?: number; interval?: number } = {},
349
+ ) {
350
+ const path = this.getPath(params);
351
+ if (path) {
352
+ return path;
353
+ }
354
+
355
+ const trigger = new Trigger<string[]>();
356
+ const i = setInterval(() => {
357
+ const path = this.getPath(params);
358
+ if (path) {
359
+ trigger.wake(path);
360
+ }
361
+ }, interval);
362
+
363
+ return trigger.wait({ timeout }).finally(() => clearInterval(i));
364
+ }
365
+
297
366
  /**
298
367
  * Add nodes to the graph.
299
368
  *