@dxos/app-graph 0.6.3-main.d007b87 → 0.6.3-main.d76104f

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.
@@ -2,58 +2,49 @@ import { type MaybePromise, type MakeOptional } from '@dxos/util';
2
2
  /**
3
3
  * Represents a node in the graph.
4
4
  */
5
- export type NodeBase<TData = any, TProperties extends Record<string, any> = Record<string, any>> = {
5
+ export type Node<TData = any, TProperties extends Record<string, any> = Record<string, any>> = Readonly<{
6
6
  /**
7
7
  * Globally unique ID.
8
8
  */
9
9
  id: string;
10
+ /**
11
+ * Typename of the data the node represents.
12
+ */
13
+ type: string;
10
14
  /**
11
15
  * Properties of the node relevant to displaying the node.
12
16
  */
13
- properties: TProperties;
17
+ properties: Readonly<TProperties>;
14
18
  /**
15
19
  * Data the node represents.
16
20
  */
17
21
  data: TData;
18
- };
22
+ }>;
19
23
  export type NodeFilter<T = any, U extends Record<string, any> = Record<string, any>> = (node: Node<unknown, Record<string, any>>, connectedNode: Node) => node is Node<T, U>;
20
- export type EdgeDirection = 'outbound' | 'inbound';
21
- export type ConnectedNodes = {
24
+ export type Relation = 'outbound' | 'inbound';
25
+ export declare const isGraphNode: (data: unknown) => data is Readonly<{
22
26
  /**
23
- * Edges that this node is connected to in default order.
27
+ * Globally unique ID.
24
28
  */
25
- edges(params?: {
26
- direction?: EdgeDirection;
27
- }): Readonly<string[]>;
29
+ id: string;
28
30
  /**
29
- * Nodes that this node is connected to in default order.
31
+ * Typename of the data the node represents.
30
32
  */
31
- nodes<T = any, U extends Record<string, any> = Record<string, any>>(params?: {
32
- direction?: EdgeDirection;
33
- filter?: NodeFilter<T, U>;
34
- }): Node<T>[];
33
+ type: string;
35
34
  /**
36
- * Get a specific connected node by id.
35
+ * Properties of the node relevant to displaying the node.
37
36
  */
38
- node(id: string): Node | undefined;
39
- };
40
- export type ConnectedActions = {
37
+ properties: Readonly<Record<string, any>>;
41
38
  /**
42
- * Actions or action groups that this node is connected to in default order.
39
+ * Data the node represents.
43
40
  */
44
- actions(): ActionLike[];
45
- };
46
- export type Node<TData = any, TProperties extends Record<string, any> = Record<string, any>> = Readonly<Omit<NodeBase<TData, TProperties>, 'properties'> & {
47
- properties: Readonly<TProperties>;
48
- } & ConnectedNodes & ConnectedActions>;
49
- export declare const isGraphNode: (data: unknown) => data is Readonly<Omit<NodeBase<any, Record<string, any>>, "properties"> & {
50
- properties: Readonly<Record<string, any>>;
51
- } & ConnectedNodes & ConnectedActions>;
52
- export type NodeArg<TData, TProperties extends Record<string, any> = Record<string, any>> = MakeOptional<NodeBase<TData, TProperties>, 'data' | 'properties'> & {
41
+ data: any;
42
+ }>;
43
+ export type NodeArg<TData, TProperties extends Record<string, any> = Record<string, any>> = MakeOptional<Node<TData, TProperties>, 'data' | 'properties'> & {
53
44
  /** Will automatically add nodes with an edge from this node to each. */
54
45
  nodes?: NodeArg<unknown>[];
55
46
  /** Will automatically add specified edges. */
56
- edges?: [string, EdgeDirection][];
47
+ edges?: [string, Relation][];
57
48
  };
58
49
  export type InvokeParams = {
59
50
  /** Node the invoked action is connected to. */
@@ -61,23 +52,91 @@ export type InvokeParams = {
61
52
  caller?: string;
62
53
  };
63
54
  export type ActionData = (params: InvokeParams) => MaybePromise<void>;
64
- export type Action<TProperties extends Record<string, any> = Record<string, any>> = Readonly<Omit<NodeBase<ActionData, TProperties>, 'properties'> & {
55
+ export type Action<TProperties extends Record<string, any> = Record<string, any>> = Readonly<Omit<Node<ActionData, TProperties>, 'properties'> & {
65
56
  properties: Readonly<TProperties>;
66
- } & ConnectedNodes>;
67
- export declare const isAction: (data: unknown) => data is Readonly<Omit<NodeBase<ActionData, Record<string, any>>, "properties"> & {
57
+ }>;
58
+ export declare const isAction: (data: unknown) => data is Readonly<Omit<Readonly<{
59
+ /**
60
+ * Globally unique ID.
61
+ */
62
+ id: string;
63
+ /**
64
+ * Typename of the data the node represents.
65
+ */
66
+ type: string;
67
+ /**
68
+ * Properties of the node relevant to displaying the node.
69
+ */
70
+ properties: Readonly<Record<string, any>>;
71
+ /**
72
+ * Data the node represents.
73
+ */
74
+ data: ActionData;
75
+ }>, "properties"> & {
68
76
  properties: Readonly<Record<string, any>>;
69
- } & ConnectedNodes>;
77
+ }>;
70
78
  export declare const actionGroupSymbol: unique symbol;
71
- export type ActionGroup = Readonly<Omit<NodeBase<typeof actionGroupSymbol, Record<string, any>>, 'properties'> & {
79
+ export type ActionGroup = Readonly<Omit<Node<typeof actionGroupSymbol, Record<string, any>>, 'properties'> & {
72
80
  properties: Readonly<Record<string, any>>;
73
- } & ConnectedActions>;
74
- export declare const isActionGroup: (data: unknown) => data is Readonly<Omit<NodeBase<typeof actionGroupSymbol, Record<string, any>>, "properties"> & {
81
+ }>;
82
+ export declare const isActionGroup: (data: unknown) => data is Readonly<Omit<Readonly<{
83
+ /**
84
+ * Globally unique ID.
85
+ */
86
+ id: string;
87
+ /**
88
+ * Typename of the data the node represents.
89
+ */
90
+ type: string;
91
+ /**
92
+ * Properties of the node relevant to displaying the node.
93
+ */
94
+ properties: Readonly<Record<string, any>>;
95
+ /**
96
+ * Data the node represents.
97
+ */
98
+ data: typeof actionGroupSymbol;
99
+ }>, "properties"> & {
75
100
  properties: Readonly<Record<string, any>>;
76
- } & ConnectedActions>;
101
+ }>;
77
102
  export type ActionLike = Action | ActionGroup;
78
- export declare const isActionLike: (data: unknown) => data is Readonly<Omit<NodeBase<ActionData, Record<string, any>>, "properties"> & {
103
+ export declare const isActionLike: (data: unknown) => data is Readonly<Omit<Readonly<{
104
+ /**
105
+ * Globally unique ID.
106
+ */
107
+ id: string;
108
+ /**
109
+ * Typename of the data the node represents.
110
+ */
111
+ type: string;
112
+ /**
113
+ * Properties of the node relevant to displaying the node.
114
+ */
115
+ properties: Readonly<Record<string, any>>;
116
+ /**
117
+ * Data the node represents.
118
+ */
119
+ data: ActionData;
120
+ }>, "properties"> & {
121
+ properties: Readonly<Record<string, any>>;
122
+ }> | Readonly<Omit<Readonly<{
123
+ /**
124
+ * Globally unique ID.
125
+ */
126
+ id: string;
127
+ /**
128
+ * Typename of the data the node represents.
129
+ */
130
+ type: string;
131
+ /**
132
+ * Properties of the node relevant to displaying the node.
133
+ */
79
134
  properties: Readonly<Record<string, any>>;
80
- } & ConnectedNodes> | Readonly<Omit<NodeBase<typeof actionGroupSymbol, Record<string, any>>, "properties"> & {
135
+ /**
136
+ * Data the node represents.
137
+ */
138
+ data: typeof actionGroupSymbol;
139
+ }>, "properties"> & {
81
140
  properties: Readonly<Record<string, any>>;
82
- } & ConnectedActions>;
141
+ }>;
83
142
  //# sourceMappingURL=node.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../../../src/node.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../../../src/node.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"EchoGraph.stories.d.ts","sourceRoot":"","sources":["../../../../src/stories/EchoGraph.stories.tsx"],"names":[],"mappings":"AAIA,OAAO,YAAY,CAAC;;;;;AAsBpB,wBAGE;AA+OF,eAAO,MAAM,OAAO;;CAEnB,CAAC"}
1
+ {"version":3,"file":"EchoGraph.stories.d.ts","sourceRoot":"","sources":["../../../../src/stories/EchoGraph.stories.tsx"],"names":[],"mappings":"AAIA,OAAO,YAAY,CAAC;;;;;AA4BpB,wBAGE;AA6NF,eAAO,MAAM,OAAO;;CAEnB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/app-graph",
3
- "version": "0.6.3-main.d007b87",
3
+ "version": "0.6.3-main.d76104f",
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",
@@ -21,12 +21,14 @@
21
21
  ],
22
22
  "dependencies": {
23
23
  "@preact/signals-core": "^1.6.0",
24
- "@dxos/async": "0.6.3-main.d007b87",
25
- "@dxos/debug": "0.6.3-main.d007b87",
26
- "@dxos/echo-schema": "0.6.3-main.d007b87",
27
- "@dxos/echo-signals": "0.6.3-main.d007b87",
28
- "@dxos/invariant": "0.6.3-main.d007b87",
29
- "@dxos/util": "0.6.3-main.d007b87"
24
+ "main-thread-scheduling": "^14.1.1",
25
+ "@dxos/async": "0.6.3-main.d76104f",
26
+ "@dxos/debug": "0.6.3-main.d76104f",
27
+ "@dxos/echo-signals": "0.6.3-main.d76104f",
28
+ "@dxos/echo-schema": "0.6.3-main.d76104f",
29
+ "@dxos/invariant": "0.6.3-main.d76104f",
30
+ "@dxos/log": "0.6.3-main.d76104f",
31
+ "@dxos/util": "0.6.3-main.d76104f"
30
32
  },
31
33
  "devDependencies": {
32
34
  "@phosphor-icons/react": "^2.1.5",
@@ -35,11 +37,11 @@
35
37
  "react": "^18.2.0",
36
38
  "react-dom": "^18.2.0",
37
39
  "vite": "^5.2.9",
38
- "@dxos/random": "0.6.3-main.d007b87",
39
- "@dxos/react-client": "0.6.3-main.d007b87",
40
- "@dxos/react-ui": "0.6.3-main.d007b87",
41
- "@dxos/react-ui-theme": "0.6.3-main.d007b87",
42
- "@dxos/storybook-utils": "0.6.3-main.d007b87"
40
+ "@dxos/random": "0.6.3-main.d76104f",
41
+ "@dxos/react-client": "0.6.3-main.d76104f",
42
+ "@dxos/react-ui": "0.6.3-main.d76104f",
43
+ "@dxos/react-ui-theme": "0.6.3-main.d76104f",
44
+ "@dxos/storybook-utils": "0.6.3-main.d76104f"
43
45
  },
44
46
  "peerDependencies": {
45
47
  "react": "^18.2.0",
@@ -0,0 +1,322 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { signal } from '@preact/signals-core';
6
+ import chai, { expect } from 'chai';
7
+ import chaiAsPromised from 'chai-as-promised';
8
+
9
+ import { describe, test } from '@dxos/test';
10
+
11
+ import { ACTION_TYPE } from './graph';
12
+ import { GraphBuilder, createExtension, memoize } from './graph-builder';
13
+ import { type Node } from './node';
14
+
15
+ chai.use(chaiAsPromised);
16
+
17
+ const exampleId = (id: number) => `dx:test:${id}`;
18
+ const EXAMPLE_ID = exampleId(1);
19
+ const EXAMPLE_TYPE = 'dxos.org/type/example';
20
+
21
+ describe('GraphBuilder', () => {
22
+ describe('resolver', () => {
23
+ test('works', async () => {
24
+ const builder = new GraphBuilder();
25
+ const graph = builder.graph;
26
+
27
+ {
28
+ const node = graph.findNode(EXAMPLE_ID);
29
+ expect(node).to.be.undefined;
30
+ }
31
+
32
+ builder.addExtension(
33
+ createExtension({ id: 'resolver', resolver: () => ({ id: EXAMPLE_ID, type: EXAMPLE_TYPE, data: 1 }) }),
34
+ );
35
+
36
+ {
37
+ const node = await graph.waitForNode(EXAMPLE_ID);
38
+ expect(node?.id).to.equal(EXAMPLE_ID);
39
+ expect(node?.type).to.equal(EXAMPLE_TYPE);
40
+ expect(node?.data).to.equal(1);
41
+ }
42
+ });
43
+
44
+ test('updates', async () => {
45
+ const builder = new GraphBuilder();
46
+ const name = signal('default');
47
+ builder.addExtension(
48
+ createExtension({ id: 'resolver', resolver: () => ({ id: EXAMPLE_ID, type: EXAMPLE_TYPE, data: name.value }) }),
49
+ );
50
+ const graph = builder.graph;
51
+
52
+ const node = await graph.waitForNode(EXAMPLE_ID);
53
+ expect(node?.data).to.equal('default');
54
+
55
+ name.value = 'updated';
56
+ expect(node?.data).to.equal('updated');
57
+ });
58
+
59
+ test('memoize', async () => {
60
+ const builder = new GraphBuilder();
61
+ const name = signal('default');
62
+ let count = 0;
63
+ let memoizedCount = 0;
64
+ builder.addExtension(
65
+ createExtension({
66
+ id: 'resolver',
67
+ resolver: () => {
68
+ count++;
69
+ memoize(() => {
70
+ memoizedCount++;
71
+ });
72
+
73
+ return { id: EXAMPLE_ID, type: EXAMPLE_TYPE, data: name.value };
74
+ },
75
+ }),
76
+ );
77
+ const graph = builder.graph;
78
+
79
+ const node = await graph.waitForNode(EXAMPLE_ID);
80
+ expect(node?.data).to.equal('default');
81
+ expect(count).to.equal(1);
82
+ expect(memoizedCount).to.equal(1);
83
+
84
+ name!.value = 'one';
85
+ name!.value = 'two';
86
+ name!.value = 'three';
87
+
88
+ expect(node?.data).to.equal('three');
89
+ expect(count).to.equal(4);
90
+ expect(memoizedCount).to.equal(1);
91
+ });
92
+ });
93
+
94
+ describe('connector', () => {
95
+ test('works', async () => {
96
+ const builder = new GraphBuilder();
97
+ builder.addExtension(
98
+ createExtension({
99
+ id: 'outbound-connector',
100
+ connector: () => [{ id: 'child', type: EXAMPLE_TYPE, data: 2 }],
101
+ }),
102
+ );
103
+ builder.addExtension(
104
+ createExtension({
105
+ id: 'inbound-connector',
106
+ relation: 'inbound',
107
+ connector: () => [{ id: 'parent', type: EXAMPLE_TYPE, data: 0 }],
108
+ }),
109
+ );
110
+
111
+ const graph = builder.graph;
112
+ await graph.expand(graph.root);
113
+ await graph.expand(graph.root, 'inbound');
114
+
115
+ const outbound = graph.nodes(graph.root);
116
+ const inbound = graph.nodes(graph.root, { relation: 'inbound' });
117
+
118
+ expect(outbound).has.length(1);
119
+ expect(outbound?.[0].id).to.equal('child');
120
+ expect(outbound?.[0].data).to.equal(2);
121
+ expect(inbound).has.length(1);
122
+ expect(inbound?.[0].id).to.equal('parent');
123
+ expect(inbound?.[0].data).to.equal(0);
124
+ });
125
+
126
+ test('updates', async () => {
127
+ const name = signal('default');
128
+ const builder = new GraphBuilder();
129
+ builder.addExtension(
130
+ createExtension({
131
+ id: 'connector',
132
+ connector: () => [{ id: EXAMPLE_ID, type: EXAMPLE_TYPE, data: name, properties: { label: name.value } }],
133
+ }),
134
+ );
135
+ const graph = builder.graph;
136
+ await graph.expand(graph.root);
137
+
138
+ const [node] = graph.nodes(graph.root);
139
+ expect(node.properties.label).to.equal('default');
140
+
141
+ name.value = 'updated';
142
+ expect(node.properties.label).to.equal('updated');
143
+ });
144
+
145
+ test('removes', async () => {
146
+ const nodes = signal([
147
+ { id: exampleId(1), type: EXAMPLE_TYPE, data: 1 },
148
+ { id: exampleId(2), type: EXAMPLE_TYPE, data: 2 },
149
+ ]);
150
+
151
+ const builder = new GraphBuilder();
152
+ builder.addExtension(
153
+ createExtension({
154
+ id: 'connector',
155
+ connector: () => nodes.value,
156
+ }),
157
+ );
158
+ const graph = builder.graph;
159
+ await graph.expand(graph.root);
160
+
161
+ {
162
+ const nodes = graph.nodes(graph.root);
163
+ expect(nodes).has.length(2);
164
+ expect(nodes[0].id).to.equal(exampleId(1));
165
+ }
166
+
167
+ nodes.value = [{ id: exampleId(3), type: EXAMPLE_TYPE, data: 3 }];
168
+
169
+ {
170
+ const nodes = graph.nodes(graph.root);
171
+ expect(nodes).has.length(1);
172
+ expect(nodes[0].id).to.equal(exampleId(3));
173
+ expect(graph.findNode(exampleId(1))).to.be.undefined;
174
+ }
175
+ });
176
+
177
+ test('filters by type', async () => {
178
+ const builder = new GraphBuilder();
179
+ builder.addExtension(
180
+ createExtension({
181
+ id: 'actions',
182
+ connector: () => [{ id: 'not-action', type: EXAMPLE_TYPE, data: 1 }],
183
+ actions: () => [{ id: 'action', data: () => {} }],
184
+ }),
185
+ );
186
+ const graph = builder.graph;
187
+
188
+ await graph.expand(graph.root, 'outbound', ACTION_TYPE);
189
+ const actions = graph.actions(graph.root);
190
+ expect(actions).has.length(1);
191
+ expect(actions?.[0].id).to.equal('action');
192
+ expect(actions?.[0].type).to.equal(ACTION_TYPE);
193
+
194
+ await expect(graph.waitForNode('not-action', 10)).to.be.rejected;
195
+
196
+ await graph.expand(graph.root);
197
+ const nodes = graph.nodes(graph.root);
198
+ expect(nodes).has.length(1);
199
+ expect(nodes?.[0].id).to.equal('not-action');
200
+ expect(nodes?.[0].data).to.equal(1);
201
+ });
202
+
203
+ test('filters by callback', async () => {
204
+ const builder = new GraphBuilder();
205
+ builder.addExtension(
206
+ createExtension({
207
+ id: 'filtered-connector',
208
+ filter: (node): node is Node<null> => node.id === 'root',
209
+ connector: () => [{ id: EXAMPLE_ID, type: EXAMPLE_TYPE, data: 1 }],
210
+ }),
211
+ );
212
+ const graph = builder.graph;
213
+ await graph.expand(graph.root);
214
+
215
+ const [node1] = graph.nodes(graph.root);
216
+ expect(node1?.id).to.equal(EXAMPLE_ID);
217
+
218
+ const nodes = graph.nodes(node1);
219
+ expect(nodes).has.length(0);
220
+ });
221
+
222
+ test('memoize', async () => {
223
+ const builder = new GraphBuilder();
224
+ const name = signal('default');
225
+ let count = 0;
226
+ let memoizedCount = 0;
227
+ builder.addExtension(
228
+ createExtension({
229
+ id: 'connector',
230
+ connector: () => {
231
+ count++;
232
+ memoize(() => {
233
+ memoizedCount++;
234
+ });
235
+
236
+ return [{ id: EXAMPLE_ID, type: EXAMPLE_TYPE, data: name, properties: { label: name.value } }];
237
+ },
238
+ }),
239
+ );
240
+ const graph = builder.graph;
241
+ await graph.expand(graph.root);
242
+
243
+ const [node] = graph.nodes(graph.root);
244
+ expect(node.properties.label).to.equal('default');
245
+ expect(count).to.equal(1);
246
+ expect(memoizedCount).to.equal(1);
247
+
248
+ name!.value = 'one';
249
+ name!.value = 'two';
250
+ name!.value = 'three';
251
+
252
+ expect(node.properties.label).to.equal('three');
253
+ expect(count).to.equal(4);
254
+ expect(memoizedCount).to.equal(1);
255
+ });
256
+ });
257
+
258
+ describe('traverse', () => {
259
+ test('works', async () => {
260
+ const builder = new GraphBuilder();
261
+ builder.addExtension(
262
+ createExtension({
263
+ id: 'connector',
264
+ connector: ({ node }) => {
265
+ const data = node.data ? node.data + 1 : 1;
266
+ return data > 5 ? [] : [{ id: `node-${data}`, type: EXAMPLE_TYPE, data }];
267
+ },
268
+ }),
269
+ );
270
+ const graph = builder.graph;
271
+
272
+ let count = 0;
273
+ await builder.traverse({
274
+ node: graph.root,
275
+ visitor: () => {
276
+ count++;
277
+ },
278
+ });
279
+
280
+ expect(count).to.equal(6);
281
+ });
282
+ });
283
+
284
+ describe('multiples', () => {
285
+ test('one of each with multiple memos', async () => {
286
+ const name = signal('default');
287
+ const builder = new GraphBuilder();
288
+ builder.addExtension(
289
+ createExtension({
290
+ id: 'extension',
291
+ resolver: () => {
292
+ const data = memoize(() => Math.random());
293
+ return { id: EXAMPLE_ID, type: EXAMPLE_TYPE, data, properties: { name: name.value } };
294
+ },
295
+ connector: () => {
296
+ const a = memoize(() => Math.random());
297
+ const b = memoize(() => Math.random());
298
+ const c = Math.random();
299
+ return [{ id: `${EXAMPLE_ID}-child`, type: EXAMPLE_TYPE, data: { a, b, c } }];
300
+ },
301
+ }),
302
+ );
303
+ const graph = builder.graph;
304
+
305
+ const one = await graph.waitForNode(EXAMPLE_ID);
306
+ const initialData = one!.data;
307
+ await graph.expand(one!);
308
+ const two = graph.nodes(one!)[0];
309
+ const initialA = two?.data.a;
310
+ const initialB = two?.data.b;
311
+ const initialC = two?.data.c;
312
+
313
+ name.value = 'updated';
314
+
315
+ expect(one?.properties.name).to.equal('updated');
316
+ expect(one?.data).to.equal(initialData);
317
+ expect(two?.data.a).to.equal(initialA);
318
+ expect(two?.data.b).to.equal(initialB);
319
+ expect(two?.data.c).not.to.equal(initialC);
320
+ });
321
+ });
322
+ });