@dxos/app-graph 0.8.4-main.ae835ea → 0.8.4-main.bc674ce
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.
- package/dist/lib/browser/index.mjs +1014 -553
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +1013 -553
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/types/src/atoms.d.ts +8 -0
- package/dist/types/src/atoms.d.ts.map +1 -0
- package/dist/types/src/graph-builder.d.ts +108 -66
- package/dist/types/src/graph-builder.d.ts.map +1 -1
- package/dist/types/src/graph.d.ts +182 -212
- package/dist/types/src/graph.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +6 -3
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/node-matcher.d.ts +218 -0
- package/dist/types/src/node-matcher.d.ts.map +1 -0
- package/dist/types/src/node-matcher.test.d.ts +2 -0
- package/dist/types/src/node-matcher.test.d.ts.map +1 -0
- package/dist/types/src/node.d.ts +32 -3
- package/dist/types/src/node.d.ts.map +1 -1
- package/dist/types/src/stories/EchoGraph.stories.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +35 -33
- package/src/atoms.ts +25 -0
- package/src/graph-builder.test.ts +520 -104
- package/src/graph-builder.ts +550 -255
- package/src/graph.test.ts +299 -106
- package/src/graph.ts +964 -394
- package/src/index.ts +9 -3
- package/src/node-matcher.test.ts +301 -0
- package/src/node-matcher.ts +284 -0
- package/src/node.ts +39 -6
- package/src/stories/EchoGraph.stories.tsx +104 -95
- package/src/stories/Tree.tsx +2 -2
- package/dist/types/src/experimental/graph-projections.test.d.ts +0 -25
- package/dist/types/src/experimental/graph-projections.test.d.ts.map +0 -1
- package/dist/types/src/signals-integration.test.d.ts +0 -2
- package/dist/types/src/signals-integration.test.d.ts.map +0 -1
- package/dist/types/src/testing.d.ts +0 -5
- package/dist/types/src/testing.d.ts.map +0 -1
- package/src/experimental/graph-projections.test.ts +0 -56
- package/src/signals-integration.test.ts +0 -218
- package/src/testing.ts +0 -20
package/src/graph.test.ts
CHANGED
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { Atom, Registry } from '@effect-atom/atom-react';
|
|
6
6
|
import * as Option from 'effect/Option';
|
|
7
7
|
import { assert, describe, expect, onTestFinished, test } from 'vitest';
|
|
8
8
|
|
|
9
|
-
import
|
|
10
|
-
import
|
|
9
|
+
import * as Graph from './graph';
|
|
10
|
+
import * as GraphBuilder from './graph-builder';
|
|
11
|
+
import * as Node from './node';
|
|
11
12
|
|
|
12
13
|
const exampleId = (id: number) => `dx:test:${id}`;
|
|
13
14
|
const EXAMPLE_ID = exampleId(1);
|
|
@@ -16,18 +17,18 @@ const EXAMPLE_TYPE = 'dxos.org/type/example';
|
|
|
16
17
|
describe('Graph', () => {
|
|
17
18
|
test('getGraph', () => {
|
|
18
19
|
const registry = Registry.make();
|
|
19
|
-
const graph =
|
|
20
|
-
const root = registry.get(graph.node(
|
|
20
|
+
const graph = Graph.make({ registry });
|
|
21
|
+
const root = registry.get(graph.node(Node.RootId));
|
|
21
22
|
assert.ok(Option.isSome(root));
|
|
22
|
-
expect(root.value.id).toEqual(
|
|
23
|
-
expect(root.value.type).toEqual(
|
|
24
|
-
expect(getGraph(root.value)).toEqual(graph);
|
|
23
|
+
expect(root.value.id).toEqual(Node.RootId);
|
|
24
|
+
expect(root.value.type).toEqual(Node.RootType);
|
|
25
|
+
expect(Graph.getGraph(root.value)).toEqual(graph);
|
|
25
26
|
});
|
|
26
27
|
|
|
27
28
|
test('add node', () => {
|
|
28
29
|
const registry = Registry.make();
|
|
29
|
-
const graph =
|
|
30
|
-
|
|
30
|
+
const graph = Graph.make({ registry });
|
|
31
|
+
Graph.addNode(graph, { id: EXAMPLE_ID, type: EXAMPLE_TYPE });
|
|
31
32
|
const node = registry.get(graph.node(EXAMPLE_ID));
|
|
32
33
|
assert.ok(Option.isSome(node));
|
|
33
34
|
expect(node.value.id).toEqual(EXAMPLE_ID);
|
|
@@ -36,9 +37,20 @@ describe('Graph', () => {
|
|
|
36
37
|
expect(node.value.properties).toEqual({});
|
|
37
38
|
});
|
|
38
39
|
|
|
40
|
+
test('add node curried', () => {
|
|
41
|
+
const registry = Registry.make();
|
|
42
|
+
const graph = Graph.make({ registry });
|
|
43
|
+
const result = graph.pipe(Graph.addNode({ id: EXAMPLE_ID, type: EXAMPLE_TYPE }));
|
|
44
|
+
expect(result).toEqual(graph);
|
|
45
|
+
const node = registry.get(graph.node(EXAMPLE_ID));
|
|
46
|
+
assert.ok(Option.isSome(node));
|
|
47
|
+
expect(node.value.id).toEqual(EXAMPLE_ID);
|
|
48
|
+
expect(node.value.type).toEqual(EXAMPLE_TYPE);
|
|
49
|
+
});
|
|
50
|
+
|
|
39
51
|
test('add nodes updates existing nodes', () => {
|
|
40
52
|
const registry = Registry.make();
|
|
41
|
-
const graph =
|
|
53
|
+
const graph = Graph.make({ registry });
|
|
42
54
|
const nodeKey = graph.node(EXAMPLE_ID);
|
|
43
55
|
|
|
44
56
|
let count = 0;
|
|
@@ -53,7 +65,7 @@ describe('Graph', () => {
|
|
|
53
65
|
expect(registry.get(nodeKey)).toEqual(Option.none());
|
|
54
66
|
expect(count).toEqual(1);
|
|
55
67
|
|
|
56
|
-
|
|
68
|
+
Graph.addNode(graph, { id: EXAMPLE_ID, type: EXAMPLE_TYPE });
|
|
57
69
|
const node = registry.get(nodeKey);
|
|
58
70
|
assert.ok(Option.isSome(node));
|
|
59
71
|
expect(node.value.id).toEqual(EXAMPLE_ID);
|
|
@@ -62,13 +74,13 @@ describe('Graph', () => {
|
|
|
62
74
|
expect(node.value.properties).toEqual({});
|
|
63
75
|
expect(count).toEqual(2);
|
|
64
76
|
|
|
65
|
-
|
|
77
|
+
Graph.addNode(graph, { id: EXAMPLE_ID, type: EXAMPLE_TYPE });
|
|
66
78
|
expect(count).toEqual(2);
|
|
67
79
|
});
|
|
68
80
|
|
|
69
81
|
test('remove node', () => {
|
|
70
82
|
const registry = Registry.make();
|
|
71
|
-
const graph =
|
|
83
|
+
const graph = Graph.make({ registry });
|
|
72
84
|
|
|
73
85
|
{
|
|
74
86
|
const node = registry.get(graph.node(EXAMPLE_ID));
|
|
@@ -76,39 +88,49 @@ describe('Graph', () => {
|
|
|
76
88
|
}
|
|
77
89
|
|
|
78
90
|
{
|
|
79
|
-
|
|
91
|
+
Graph.addNode(graph, { id: EXAMPLE_ID, type: EXAMPLE_TYPE });
|
|
80
92
|
const node = registry.get(graph.node(EXAMPLE_ID));
|
|
81
93
|
expect(Option.isSome(node)).toEqual(true);
|
|
82
94
|
}
|
|
83
95
|
|
|
84
96
|
{
|
|
85
|
-
|
|
97
|
+
Graph.removeNode(graph, EXAMPLE_ID);
|
|
86
98
|
const node = registry.get(graph.node(EXAMPLE_ID));
|
|
87
99
|
expect(Option.isNone(node)).toEqual(true);
|
|
88
100
|
}
|
|
89
101
|
});
|
|
90
102
|
|
|
103
|
+
test('remove node curried', () => {
|
|
104
|
+
const registry = Registry.make();
|
|
105
|
+
const graph = Graph.make({ registry });
|
|
106
|
+
Graph.addNode(graph, { id: EXAMPLE_ID, type: EXAMPLE_TYPE });
|
|
107
|
+
const result = graph.pipe(Graph.removeNode(EXAMPLE_ID));
|
|
108
|
+
expect(result).toEqual(graph);
|
|
109
|
+
const node = registry.get(graph.node(EXAMPLE_ID));
|
|
110
|
+
assert.ok(Option.isNone(node));
|
|
111
|
+
});
|
|
112
|
+
|
|
91
113
|
test('onNodeChanged', () => {
|
|
92
|
-
const graph =
|
|
114
|
+
const graph = Graph.make();
|
|
93
115
|
|
|
94
|
-
let node: Option.Option<Node> = Option.none();
|
|
116
|
+
let node: Option.Option<Node.Node> = Option.none();
|
|
95
117
|
graph.onNodeChanged.on(({ node: newNode }) => {
|
|
96
118
|
node = newNode;
|
|
97
119
|
});
|
|
98
120
|
|
|
99
|
-
|
|
121
|
+
Graph.addNode(graph, { id: EXAMPLE_ID, type: EXAMPLE_TYPE });
|
|
100
122
|
assert.ok(Option.isSome(node));
|
|
101
123
|
expect(node.value.id).toEqual(EXAMPLE_ID);
|
|
102
124
|
expect(node.value.type).toEqual(EXAMPLE_TYPE);
|
|
103
125
|
|
|
104
|
-
|
|
126
|
+
Graph.removeNode(graph, EXAMPLE_ID);
|
|
105
127
|
expect(node.pipe(Option.getOrNull)).toEqual(null);
|
|
106
128
|
});
|
|
107
129
|
|
|
108
130
|
test('add edge', () => {
|
|
109
131
|
const registry = Registry.make();
|
|
110
|
-
const graph =
|
|
111
|
-
|
|
132
|
+
const graph = Graph.make({ registry });
|
|
133
|
+
Graph.addEdge(graph, { source: exampleId(1), target: exampleId(2) });
|
|
112
134
|
const edges = registry.get(graph.edges(exampleId(1)));
|
|
113
135
|
expect(edges.inbound).toEqual([]);
|
|
114
136
|
expect(edges.outbound).toEqual([exampleId(2)]);
|
|
@@ -116,9 +138,9 @@ describe('Graph', () => {
|
|
|
116
138
|
|
|
117
139
|
test('add edges is idempotent', () => {
|
|
118
140
|
const registry = Registry.make();
|
|
119
|
-
const graph =
|
|
120
|
-
|
|
121
|
-
|
|
141
|
+
const graph = Graph.make({ registry });
|
|
142
|
+
Graph.addEdge(graph, { source: exampleId(1), target: exampleId(2) });
|
|
143
|
+
Graph.addEdge(graph, { source: exampleId(1), target: exampleId(2) });
|
|
122
144
|
const edges = registry.get(graph.edges(exampleId(1)));
|
|
123
145
|
expect(edges.inbound).toEqual([]);
|
|
124
146
|
expect(edges.outbound).toEqual([exampleId(2)]);
|
|
@@ -126,18 +148,18 @@ describe('Graph', () => {
|
|
|
126
148
|
|
|
127
149
|
test('sort edges', () => {
|
|
128
150
|
const registry = Registry.make();
|
|
129
|
-
const graph =
|
|
151
|
+
const graph = Graph.make({ registry });
|
|
130
152
|
|
|
131
153
|
{
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
154
|
+
Graph.addEdge(graph, { source: exampleId(1), target: exampleId(2) });
|
|
155
|
+
Graph.addEdge(graph, { source: exampleId(1), target: exampleId(3) });
|
|
156
|
+
Graph.addEdge(graph, { source: exampleId(1), target: exampleId(4) });
|
|
135
157
|
const edges = registry.get(graph.edges(exampleId(1)));
|
|
136
158
|
expect(edges.outbound).toEqual([exampleId(2), exampleId(3), exampleId(4)]);
|
|
137
159
|
}
|
|
138
160
|
|
|
139
161
|
{
|
|
140
|
-
|
|
162
|
+
Graph.sortEdges(graph, exampleId(1), 'outbound', [exampleId(3), exampleId(2)]);
|
|
141
163
|
const edges = registry.get(graph.edges(exampleId(1)));
|
|
142
164
|
expect(edges.outbound).toEqual([exampleId(3), exampleId(2), exampleId(4)]);
|
|
143
165
|
}
|
|
@@ -145,29 +167,42 @@ describe('Graph', () => {
|
|
|
145
167
|
|
|
146
168
|
test('remove edge', () => {
|
|
147
169
|
const registry = Registry.make();
|
|
148
|
-
const graph =
|
|
170
|
+
const graph = Graph.make({ registry });
|
|
149
171
|
|
|
150
172
|
{
|
|
151
|
-
|
|
173
|
+
Graph.addEdge(graph, { source: exampleId(1), target: exampleId(2) });
|
|
152
174
|
const edges = registry.get(graph.edges(exampleId(1)));
|
|
153
175
|
expect(edges.inbound).toEqual([]);
|
|
154
176
|
expect(edges.outbound).toEqual([exampleId(2)]);
|
|
155
177
|
}
|
|
156
178
|
|
|
157
179
|
{
|
|
158
|
-
|
|
180
|
+
Graph.removeEdge(graph, { source: exampleId(1), target: exampleId(2) });
|
|
159
181
|
const edges = registry.get(graph.edges(exampleId(1)));
|
|
160
182
|
expect(edges.inbound).toEqual([]);
|
|
161
183
|
expect(edges.outbound).toEqual([]);
|
|
162
184
|
}
|
|
163
185
|
});
|
|
164
186
|
|
|
187
|
+
test('remove edge curried', () => {
|
|
188
|
+
const registry = Registry.make();
|
|
189
|
+
const graph = Graph.make({ registry });
|
|
190
|
+
Graph.addNode(graph, { id: exampleId(1), type: EXAMPLE_TYPE });
|
|
191
|
+
Graph.addNode(graph, { id: exampleId(2), type: EXAMPLE_TYPE });
|
|
192
|
+
Graph.addEdge(graph, { source: exampleId(1), target: exampleId(2) });
|
|
193
|
+
const result = graph.pipe(Graph.removeEdge({ source: exampleId(1), target: exampleId(2) }));
|
|
194
|
+
expect(result).toEqual(graph);
|
|
195
|
+
const edges = registry.get(graph.edges(exampleId(1)));
|
|
196
|
+
expect(edges.inbound).toEqual([]);
|
|
197
|
+
expect(edges.outbound).toEqual([]);
|
|
198
|
+
});
|
|
199
|
+
|
|
165
200
|
test('get connections', () => {
|
|
166
201
|
const registry = Registry.make();
|
|
167
|
-
const graph =
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
202
|
+
const graph = Graph.make({ registry });
|
|
203
|
+
Graph.addNode(graph, { id: exampleId(1), type: EXAMPLE_TYPE });
|
|
204
|
+
Graph.addNode(graph, { id: exampleId(2), type: EXAMPLE_TYPE });
|
|
205
|
+
Graph.addEdge(graph, { source: exampleId(1), target: exampleId(2) });
|
|
171
206
|
const nodes = registry.get(graph.connections(exampleId(1)));
|
|
172
207
|
expect(nodes).has.length(1);
|
|
173
208
|
expect(nodes[0].id).toEqual(exampleId(2));
|
|
@@ -175,24 +210,24 @@ describe('Graph', () => {
|
|
|
175
210
|
|
|
176
211
|
test('can subscribe to a node before it exists', async () => {
|
|
177
212
|
const registry = Registry.make();
|
|
178
|
-
const graph =
|
|
213
|
+
const graph = Graph.make({ registry });
|
|
179
214
|
const nodeKey = graph.node(exampleId(1));
|
|
180
215
|
|
|
181
|
-
let node: Option.Option<Node> = Option.none();
|
|
216
|
+
let node: Option.Option<Node.Node> = Option.none();
|
|
182
217
|
const cancel = registry.subscribe(nodeKey, (n) => {
|
|
183
218
|
node = n;
|
|
184
219
|
});
|
|
185
220
|
onTestFinished(() => cancel());
|
|
186
221
|
|
|
187
222
|
expect(node).toEqual(Option.none());
|
|
188
|
-
|
|
223
|
+
Graph.addNode(graph, { id: exampleId(1), type: EXAMPLE_TYPE });
|
|
189
224
|
assert.ok(Option.isSome(node));
|
|
190
225
|
expect(node.value.id).toEqual(exampleId(1));
|
|
191
226
|
});
|
|
192
227
|
|
|
193
228
|
test('connections updates', () => {
|
|
194
229
|
const registry = Registry.make();
|
|
195
|
-
const graph =
|
|
230
|
+
const graph = Graph.make({ registry });
|
|
196
231
|
assert.strictEqual(graph.connections(exampleId(1)), graph.connections(exampleId(1)));
|
|
197
232
|
const childrenKey = graph.connections(exampleId(1));
|
|
198
233
|
|
|
@@ -202,9 +237,11 @@ describe('Graph', () => {
|
|
|
202
237
|
});
|
|
203
238
|
onTestFinished(() => cancel());
|
|
204
239
|
|
|
205
|
-
graph.
|
|
206
|
-
|
|
207
|
-
|
|
240
|
+
graph.pipe(
|
|
241
|
+
Graph.addNode({ id: exampleId(1), type: EXAMPLE_TYPE }),
|
|
242
|
+
Graph.addNode({ id: exampleId(2), type: EXAMPLE_TYPE }),
|
|
243
|
+
Graph.addEdge({ source: exampleId(1), target: exampleId(2) }),
|
|
244
|
+
);
|
|
208
245
|
|
|
209
246
|
expect(count).toEqual(0);
|
|
210
247
|
const children = registry.get(childrenKey);
|
|
@@ -213,55 +250,57 @@ describe('Graph', () => {
|
|
|
213
250
|
expect(count).toEqual(1);
|
|
214
251
|
|
|
215
252
|
// Updating an existing node fires an update.
|
|
216
|
-
|
|
253
|
+
Graph.addNode(graph, { id: exampleId(2), type: EXAMPLE_TYPE, data: 'updated' });
|
|
217
254
|
expect(count).toEqual(2);
|
|
218
255
|
|
|
219
256
|
// Adding a node with no changes does not fire an update.
|
|
220
|
-
|
|
257
|
+
Graph.addNode(graph, { id: exampleId(2), type: EXAMPLE_TYPE, data: 'updated' });
|
|
221
258
|
expect(count).toEqual(2);
|
|
222
259
|
|
|
223
260
|
// Adding an unconnected node does not fire an update.
|
|
224
|
-
|
|
261
|
+
Graph.addNode(graph, { id: exampleId(3), type: EXAMPLE_TYPE });
|
|
225
262
|
expect(count).toEqual(2);
|
|
226
263
|
|
|
227
264
|
// Connecting a node fires an update.
|
|
228
|
-
|
|
265
|
+
Graph.addEdge(graph, { source: exampleId(1), target: exampleId(3) });
|
|
229
266
|
expect(count).toEqual(3);
|
|
230
267
|
|
|
231
268
|
// Adding an edge connected to nothing fires an update.
|
|
232
269
|
// TODO(wittjosiah): Is there a way to avoid this?
|
|
233
|
-
|
|
270
|
+
Graph.addEdge(graph, { source: exampleId(1), target: exampleId(4) });
|
|
234
271
|
expect(count).toEqual(4);
|
|
235
272
|
|
|
236
273
|
// Adding a node to an existing edge fires an update.
|
|
237
|
-
|
|
274
|
+
Graph.addNode(graph, { id: exampleId(4), type: EXAMPLE_TYPE });
|
|
238
275
|
expect(count).toEqual(5);
|
|
239
276
|
|
|
240
277
|
// Batching the edge and node updates fires a single update.
|
|
241
|
-
|
|
242
|
-
graph.
|
|
243
|
-
|
|
278
|
+
Atom.batch(() => {
|
|
279
|
+
graph.pipe(
|
|
280
|
+
Graph.addEdge({ source: exampleId(1), target: exampleId(6) }),
|
|
281
|
+
Graph.addNode({ id: exampleId(6), type: EXAMPLE_TYPE }),
|
|
282
|
+
);
|
|
244
283
|
});
|
|
245
284
|
expect(count).toEqual(6);
|
|
246
285
|
});
|
|
247
286
|
|
|
248
287
|
test('toJSON', () => {
|
|
249
|
-
const graph =
|
|
288
|
+
const graph = Graph.make();
|
|
250
289
|
|
|
251
|
-
|
|
252
|
-
id:
|
|
253
|
-
type:
|
|
290
|
+
Graph.addNode(graph, {
|
|
291
|
+
id: Node.RootId,
|
|
292
|
+
type: Node.RootType,
|
|
254
293
|
nodes: [
|
|
255
294
|
{ id: 'test1', type: 'test' },
|
|
256
295
|
{ id: 'test2', type: 'test' },
|
|
257
296
|
],
|
|
258
297
|
});
|
|
259
|
-
|
|
298
|
+
Graph.addEdge(graph, { source: 'test1', target: 'test2' });
|
|
260
299
|
|
|
261
|
-
const json =
|
|
300
|
+
const json = Graph.toJSON(graph);
|
|
262
301
|
expect(json).to.deep.equal({
|
|
263
|
-
id:
|
|
264
|
-
type:
|
|
302
|
+
id: Node.RootId,
|
|
303
|
+
type: Node.RootType,
|
|
265
304
|
nodes: [
|
|
266
305
|
{ id: 'test1', type: 'test', nodes: [{ id: 'test2', type: 'test' }] },
|
|
267
306
|
{ id: 'test2', type: 'test' },
|
|
@@ -271,17 +310,17 @@ describe('Graph', () => {
|
|
|
271
310
|
|
|
272
311
|
test('subscribe to json', () => {
|
|
273
312
|
const registry = Registry.make();
|
|
274
|
-
const graph =
|
|
313
|
+
const graph = Graph.make({ registry });
|
|
275
314
|
|
|
276
|
-
|
|
277
|
-
id:
|
|
278
|
-
type:
|
|
315
|
+
Graph.addNode(graph, {
|
|
316
|
+
id: Node.RootId,
|
|
317
|
+
type: Node.RootType,
|
|
279
318
|
nodes: [
|
|
280
319
|
{ id: 'test1', type: 'test' },
|
|
281
320
|
{ id: 'test2', type: 'test' },
|
|
282
321
|
],
|
|
283
322
|
});
|
|
284
|
-
|
|
323
|
+
Graph.addEdge(graph, { source: 'test1', target: 'test2' });
|
|
285
324
|
|
|
286
325
|
let json: any;
|
|
287
326
|
const cancel = registry.subscribe(graph.json(), (_) => {
|
|
@@ -291,19 +330,19 @@ describe('Graph', () => {
|
|
|
291
330
|
|
|
292
331
|
registry.get(graph.json());
|
|
293
332
|
expect(json).to.deep.equal({
|
|
294
|
-
id:
|
|
295
|
-
type:
|
|
333
|
+
id: Node.RootId,
|
|
334
|
+
type: Node.RootType,
|
|
296
335
|
nodes: [
|
|
297
336
|
{ id: 'test1', type: 'test', nodes: [{ id: 'test2', type: 'test' }] },
|
|
298
337
|
{ id: 'test2', type: 'test' },
|
|
299
338
|
],
|
|
300
339
|
});
|
|
301
340
|
|
|
302
|
-
|
|
303
|
-
|
|
341
|
+
Graph.addNode(graph, { id: 'test3', type: 'test' });
|
|
342
|
+
Graph.addEdge(graph, { source: 'root', target: 'test3' });
|
|
304
343
|
expect(json).to.deep.equal({
|
|
305
|
-
id:
|
|
306
|
-
type:
|
|
344
|
+
id: Node.RootId,
|
|
345
|
+
type: Node.RootType,
|
|
307
346
|
nodes: [
|
|
308
347
|
{ id: 'test1', type: 'test', nodes: [{ id: 'test2', type: 'test' }] },
|
|
309
348
|
{ id: 'test2', type: 'test' },
|
|
@@ -313,35 +352,50 @@ describe('Graph', () => {
|
|
|
313
352
|
});
|
|
314
353
|
|
|
315
354
|
test('get path', () => {
|
|
316
|
-
const graph =
|
|
317
|
-
|
|
318
|
-
id:
|
|
319
|
-
type:
|
|
355
|
+
const graph = Graph.make();
|
|
356
|
+
Graph.addNode(graph, {
|
|
357
|
+
id: Node.RootId,
|
|
358
|
+
type: Node.RootType,
|
|
320
359
|
nodes: [
|
|
321
360
|
{ id: exampleId(1), type: EXAMPLE_TYPE },
|
|
322
361
|
{ id: exampleId(2), type: EXAMPLE_TYPE },
|
|
323
362
|
],
|
|
324
363
|
});
|
|
325
|
-
|
|
364
|
+
Graph.addEdge(graph, { source: exampleId(1), target: exampleId(2) });
|
|
326
365
|
|
|
327
|
-
expect(
|
|
366
|
+
expect(Graph.getPath(graph, { target: exampleId(2) }).pipe(Option.getOrNull)).to.deep.equal([
|
|
328
367
|
'root',
|
|
329
368
|
exampleId(1),
|
|
330
369
|
exampleId(2),
|
|
331
370
|
]);
|
|
332
|
-
expect(
|
|
371
|
+
expect(Graph.getPath(graph, { source: exampleId(1), target: exampleId(2) }).pipe(Option.getOrNull)).to.deep.equal([
|
|
333
372
|
exampleId(1),
|
|
334
373
|
exampleId(2),
|
|
335
374
|
]);
|
|
336
|
-
expect(
|
|
375
|
+
expect(Graph.getPath(graph, { source: exampleId(2), target: exampleId(1) }).pipe(Option.getOrNull)).to.be.null;
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
test('get path curried', () => {
|
|
379
|
+
const graph = Graph.make();
|
|
380
|
+
Graph.addNode(graph, {
|
|
381
|
+
id: Node.RootId,
|
|
382
|
+
type: Node.RootType,
|
|
383
|
+
nodes: [
|
|
384
|
+
{ id: exampleId(1), type: EXAMPLE_TYPE },
|
|
385
|
+
{ id: exampleId(2), type: EXAMPLE_TYPE },
|
|
386
|
+
],
|
|
387
|
+
});
|
|
388
|
+
Graph.addEdge(graph, { source: exampleId(1), target: exampleId(2) });
|
|
389
|
+
const path = graph.pipe(Graph.getPath({ target: exampleId(2) }));
|
|
390
|
+
expect(path.pipe(Option.getOrNull)).to.deep.equal(['root', exampleId(1), exampleId(2)]);
|
|
337
391
|
});
|
|
338
392
|
|
|
339
393
|
describe('traverse', () => {
|
|
340
394
|
test('can be traversed', () => {
|
|
341
|
-
const graph =
|
|
342
|
-
|
|
343
|
-
id:
|
|
344
|
-
type:
|
|
395
|
+
const graph = Graph.make();
|
|
396
|
+
Graph.addNode(graph, {
|
|
397
|
+
id: Node.RootId,
|
|
398
|
+
type: Node.RootType,
|
|
345
399
|
nodes: [
|
|
346
400
|
{ id: 'test1', type: 'test' },
|
|
347
401
|
{ id: 'test2', type: 'test' },
|
|
@@ -349,7 +403,7 @@ describe('Graph', () => {
|
|
|
349
403
|
});
|
|
350
404
|
|
|
351
405
|
const nodes: string[] = [];
|
|
352
|
-
|
|
406
|
+
Graph.traverse(graph, {
|
|
353
407
|
visitor: (node) => {
|
|
354
408
|
nodes.push(node.id);
|
|
355
409
|
},
|
|
@@ -358,19 +412,19 @@ describe('Graph', () => {
|
|
|
358
412
|
});
|
|
359
413
|
|
|
360
414
|
test('traversal breaks cycles', () => {
|
|
361
|
-
const graph =
|
|
362
|
-
|
|
363
|
-
id:
|
|
364
|
-
type:
|
|
415
|
+
const graph = Graph.make();
|
|
416
|
+
Graph.addNode(graph, {
|
|
417
|
+
id: Node.RootId,
|
|
418
|
+
type: Node.RootType,
|
|
365
419
|
nodes: [
|
|
366
420
|
{ id: 'test1', type: 'test' },
|
|
367
421
|
{ id: 'test2', type: 'test' },
|
|
368
422
|
],
|
|
369
423
|
});
|
|
370
|
-
|
|
424
|
+
Graph.addEdge(graph, { source: 'test1', target: 'root' });
|
|
371
425
|
|
|
372
426
|
const nodes: string[] = [];
|
|
373
|
-
|
|
427
|
+
Graph.traverse(graph, {
|
|
374
428
|
visitor: (node) => {
|
|
375
429
|
nodes.push(node.id);
|
|
376
430
|
},
|
|
@@ -379,10 +433,10 @@ describe('Graph', () => {
|
|
|
379
433
|
});
|
|
380
434
|
|
|
381
435
|
test('traversal can be started from any node', () => {
|
|
382
|
-
const graph =
|
|
383
|
-
|
|
384
|
-
id:
|
|
385
|
-
type:
|
|
436
|
+
const graph = Graph.make();
|
|
437
|
+
Graph.addNode(graph, {
|
|
438
|
+
id: Node.RootId,
|
|
439
|
+
type: Node.RootType,
|
|
386
440
|
nodes: [
|
|
387
441
|
{
|
|
388
442
|
id: 'test1',
|
|
@@ -393,7 +447,7 @@ describe('Graph', () => {
|
|
|
393
447
|
});
|
|
394
448
|
|
|
395
449
|
const nodes: string[] = [];
|
|
396
|
-
|
|
450
|
+
Graph.traverse(graph, {
|
|
397
451
|
source: 'test2',
|
|
398
452
|
visitor: (node) => {
|
|
399
453
|
nodes.push(node.id);
|
|
@@ -403,10 +457,10 @@ describe('Graph', () => {
|
|
|
403
457
|
});
|
|
404
458
|
|
|
405
459
|
test('traversal can follow inbound edges', () => {
|
|
406
|
-
const graph =
|
|
407
|
-
|
|
408
|
-
id:
|
|
409
|
-
type:
|
|
460
|
+
const graph = Graph.make();
|
|
461
|
+
Graph.addNode(graph, {
|
|
462
|
+
id: Node.RootId,
|
|
463
|
+
type: Node.RootType,
|
|
410
464
|
nodes: [
|
|
411
465
|
{
|
|
412
466
|
id: 'test1',
|
|
@@ -417,7 +471,7 @@ describe('Graph', () => {
|
|
|
417
471
|
});
|
|
418
472
|
|
|
419
473
|
const nodes: string[] = [];
|
|
420
|
-
|
|
474
|
+
Graph.traverse(graph, {
|
|
421
475
|
source: 'test2',
|
|
422
476
|
relation: 'inbound',
|
|
423
477
|
visitor: (node) => {
|
|
@@ -428,10 +482,10 @@ describe('Graph', () => {
|
|
|
428
482
|
});
|
|
429
483
|
|
|
430
484
|
test('traversal can be terminated early', () => {
|
|
431
|
-
const graph =
|
|
432
|
-
|
|
433
|
-
id:
|
|
434
|
-
type:
|
|
485
|
+
const graph = Graph.make();
|
|
486
|
+
Graph.addNode(graph, {
|
|
487
|
+
id: Node.RootId,
|
|
488
|
+
type: Node.RootType,
|
|
435
489
|
nodes: [
|
|
436
490
|
{ id: 'test1', type: 'test' },
|
|
437
491
|
{ id: 'test2', type: 'test' },
|
|
@@ -439,7 +493,7 @@ describe('Graph', () => {
|
|
|
439
493
|
});
|
|
440
494
|
|
|
441
495
|
const nodes: string[] = [];
|
|
442
|
-
|
|
496
|
+
Graph.traverse(graph, {
|
|
443
497
|
visitor: (node) => {
|
|
444
498
|
if (nodes.length === 2) {
|
|
445
499
|
return false;
|
|
@@ -450,5 +504,144 @@ describe('Graph', () => {
|
|
|
450
504
|
});
|
|
451
505
|
expect(nodes).to.deep.equal(['root', 'test1']);
|
|
452
506
|
});
|
|
507
|
+
|
|
508
|
+
test('traverse curried', () => {
|
|
509
|
+
const graph = Graph.make();
|
|
510
|
+
Graph.addNode(graph, {
|
|
511
|
+
id: Node.RootId,
|
|
512
|
+
type: Node.RootType,
|
|
513
|
+
nodes: [{ id: 'test1', type: 'test' }],
|
|
514
|
+
});
|
|
515
|
+
Graph.addNode(graph, { id: 'test2', type: 'test' });
|
|
516
|
+
Graph.addEdge(graph, { source: 'test1', target: 'test2' });
|
|
517
|
+
const nodes: string[] = [];
|
|
518
|
+
graph.pipe(
|
|
519
|
+
Graph.traverse({
|
|
520
|
+
source: Node.RootId,
|
|
521
|
+
visitor: (node, _path) => {
|
|
522
|
+
nodes.push(node.id);
|
|
523
|
+
},
|
|
524
|
+
}),
|
|
525
|
+
);
|
|
526
|
+
expect(nodes).to.deep.equal(['root', 'test1', 'test2']);
|
|
527
|
+
});
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
test('add nodes curried', () => {
|
|
531
|
+
const registry = Registry.make();
|
|
532
|
+
const graph = Graph.make({ registry });
|
|
533
|
+
const result = graph.pipe(
|
|
534
|
+
Graph.addNodes([
|
|
535
|
+
{ id: exampleId(1), type: EXAMPLE_TYPE },
|
|
536
|
+
{ id: exampleId(2), type: EXAMPLE_TYPE },
|
|
537
|
+
]),
|
|
538
|
+
);
|
|
539
|
+
expect(result).toEqual(graph);
|
|
540
|
+
const node1 = registry.get(graph.node(exampleId(1)));
|
|
541
|
+
const node2 = registry.get(graph.node(exampleId(2)));
|
|
542
|
+
assert.ok(Option.isSome(node1));
|
|
543
|
+
assert.ok(Option.isSome(node2));
|
|
544
|
+
expect(node1.value.id).toEqual(exampleId(1));
|
|
545
|
+
expect(node2.value.id).toEqual(exampleId(2));
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
test('add edges curried', () => {
|
|
549
|
+
const registry = Registry.make();
|
|
550
|
+
const graph = Graph.make({ registry });
|
|
551
|
+
Graph.addNode(graph, { id: exampleId(1), type: EXAMPLE_TYPE });
|
|
552
|
+
Graph.addNode(graph, { id: exampleId(2), type: EXAMPLE_TYPE });
|
|
553
|
+
Graph.addNode(graph, { id: exampleId(3), type: EXAMPLE_TYPE });
|
|
554
|
+
const result = graph.pipe(
|
|
555
|
+
Graph.addEdges([
|
|
556
|
+
{ source: exampleId(1), target: exampleId(2) },
|
|
557
|
+
{ source: exampleId(1), target: exampleId(3) },
|
|
558
|
+
]),
|
|
559
|
+
);
|
|
560
|
+
expect(result).toEqual(graph);
|
|
561
|
+
const edges = registry.get(graph.edges(exampleId(1)));
|
|
562
|
+
expect(edges.outbound).to.have.length(2);
|
|
563
|
+
expect(edges.outbound).to.include(exampleId(2));
|
|
564
|
+
expect(edges.outbound).to.include(exampleId(3));
|
|
565
|
+
});
|
|
566
|
+
|
|
567
|
+
test('remove nodes curried', () => {
|
|
568
|
+
const registry = Registry.make();
|
|
569
|
+
const graph = Graph.make({ registry });
|
|
570
|
+
Graph.addNode(graph, { id: exampleId(1), type: EXAMPLE_TYPE });
|
|
571
|
+
Graph.addNode(graph, { id: exampleId(2), type: EXAMPLE_TYPE });
|
|
572
|
+
const result = graph.pipe(Graph.removeNodes([exampleId(1), exampleId(2)]));
|
|
573
|
+
expect(result).toEqual(graph);
|
|
574
|
+
const node1 = registry.get(graph.node(exampleId(1)));
|
|
575
|
+
const node2 = registry.get(graph.node(exampleId(2)));
|
|
576
|
+
assert.ok(Option.isNone(node1));
|
|
577
|
+
assert.ok(Option.isNone(node2));
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
test('remove edges curried', () => {
|
|
581
|
+
const registry = Registry.make();
|
|
582
|
+
const graph = Graph.make({ registry });
|
|
583
|
+
Graph.addNode(graph, { id: exampleId(1), type: EXAMPLE_TYPE });
|
|
584
|
+
Graph.addNode(graph, { id: exampleId(2), type: EXAMPLE_TYPE });
|
|
585
|
+
Graph.addNode(graph, { id: exampleId(3), type: EXAMPLE_TYPE });
|
|
586
|
+
Graph.addEdge(graph, { source: exampleId(1), target: exampleId(2) });
|
|
587
|
+
Graph.addEdge(graph, { source: exampleId(1), target: exampleId(3) });
|
|
588
|
+
const result = graph.pipe(
|
|
589
|
+
Graph.removeEdges([
|
|
590
|
+
{ source: exampleId(1), target: exampleId(2) },
|
|
591
|
+
{ source: exampleId(1), target: exampleId(3) },
|
|
592
|
+
]),
|
|
593
|
+
);
|
|
594
|
+
expect(result).toEqual(graph);
|
|
595
|
+
const edges = registry.get(graph.edges(exampleId(1)));
|
|
596
|
+
expect(edges.outbound).to.have.length(0);
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
test('expand curried', async () => {
|
|
600
|
+
const registry = Registry.make();
|
|
601
|
+
const builder = GraphBuilder.make({ registry });
|
|
602
|
+
const graph = builder.graph;
|
|
603
|
+
let expandCalled = false;
|
|
604
|
+
GraphBuilder.addExtension(
|
|
605
|
+
builder,
|
|
606
|
+
GraphBuilder.createExtensionRaw({
|
|
607
|
+
id: 'test',
|
|
608
|
+
connector: () => {
|
|
609
|
+
expandCalled = true;
|
|
610
|
+
return Atom.make([]);
|
|
611
|
+
},
|
|
612
|
+
}),
|
|
613
|
+
);
|
|
614
|
+
await graph.pipe(Graph.expand(Node.RootId));
|
|
615
|
+
expect(expandCalled).to.be.true;
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
test('initialize curried', async () => {
|
|
619
|
+
const registry = Registry.make();
|
|
620
|
+
const builder = GraphBuilder.make({ registry });
|
|
621
|
+
const graph = builder.graph;
|
|
622
|
+
let initializeCalled = false;
|
|
623
|
+
GraphBuilder.addExtension(
|
|
624
|
+
builder,
|
|
625
|
+
GraphBuilder.createExtensionRaw({
|
|
626
|
+
id: 'test',
|
|
627
|
+
resolver: () => {
|
|
628
|
+
initializeCalled = true;
|
|
629
|
+
return Atom.make({ id: EXAMPLE_ID, type: EXAMPLE_TYPE });
|
|
630
|
+
},
|
|
631
|
+
}),
|
|
632
|
+
);
|
|
633
|
+
await graph.pipe(Graph.initialize(EXAMPLE_ID));
|
|
634
|
+
expect(initializeCalled).to.be.true;
|
|
635
|
+
});
|
|
636
|
+
|
|
637
|
+
test('waitForPath curried', async () => {
|
|
638
|
+
const graph = Graph.make();
|
|
639
|
+
Graph.addNode(graph, {
|
|
640
|
+
id: Node.RootId,
|
|
641
|
+
type: Node.RootType,
|
|
642
|
+
nodes: [{ id: exampleId(1), type: EXAMPLE_TYPE }],
|
|
643
|
+
});
|
|
644
|
+
const path = await graph.pipe(Graph.waitForPath({ target: exampleId(1) }, { timeout: 1000 }));
|
|
645
|
+
expect(path).to.deep.equal(['root', exampleId(1)]);
|
|
453
646
|
});
|
|
454
647
|
});
|