@dxos/app-graph 0.8.2-main.fbd8ed0 → 0.8.2-staging.7ac8446
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 +789 -541
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node/index.cjs +780 -533
- package/dist/lib/node/index.cjs.map +4 -4
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +789 -541
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/types/src/graph-builder.d.ts +91 -48
- package/dist/types/src/graph-builder.d.ts.map +1 -1
- package/dist/types/src/graph.d.ts +98 -191
- package/dist/types/src/graph.d.ts.map +1 -1
- package/dist/types/src/node.d.ts +3 -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 +16 -23
- package/src/graph-builder.test.ts +310 -293
- package/src/graph-builder.ts +317 -209
- package/src/graph.test.ts +463 -314
- package/src/graph.ts +455 -452
- package/src/node.ts +4 -4
- package/src/stories/EchoGraph.stories.tsx +78 -57
- 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
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
//
|
|
2
|
-
// Copyright
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { describe, expect, onTestFinished, test } from 'vitest';
|
|
5
|
+
import { batch, signal } from '@preact/signals-core';
|
|
6
|
+
import { describe, expect, test } from 'vitest';
|
|
8
7
|
|
|
9
|
-
import {
|
|
8
|
+
import { updateCounter } from '@dxos/echo-schema/testing';
|
|
10
9
|
|
|
11
|
-
import { ROOT_ID } from './graph';
|
|
12
|
-
import { createExtension,
|
|
10
|
+
import { ACTION_TYPE, ROOT_ID, ROOT_TYPE } from './graph';
|
|
11
|
+
import { GraphBuilder, createExtension, memoize } from './graph-builder';
|
|
13
12
|
import { type Node } from './node';
|
|
14
13
|
|
|
15
14
|
const exampleId = (id: number) => `dx:test:${id}`;
|
|
@@ -17,396 +16,379 @@ const EXAMPLE_ID = exampleId(1);
|
|
|
17
16
|
const EXAMPLE_TYPE = 'dxos.org/type/example';
|
|
18
17
|
|
|
19
18
|
describe('GraphBuilder', () => {
|
|
20
|
-
describe('
|
|
21
|
-
test('works', () => {
|
|
22
|
-
const
|
|
23
|
-
const
|
|
19
|
+
describe('resolver', () => {
|
|
20
|
+
test('works', async () => {
|
|
21
|
+
const builder = new GraphBuilder();
|
|
22
|
+
const graph = builder.graph;
|
|
23
|
+
|
|
24
|
+
{
|
|
25
|
+
const node = graph.findNode(EXAMPLE_ID);
|
|
26
|
+
expect(node).to.be.undefined;
|
|
27
|
+
}
|
|
28
|
+
|
|
24
29
|
builder.addExtension(
|
|
25
|
-
createExtension({
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
30
|
+
createExtension({ id: 'resolver', resolver: () => ({ id: EXAMPLE_ID, type: EXAMPLE_TYPE, data: 1 }) }),
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
{
|
|
34
|
+
const node = await graph.waitForNode(EXAMPLE_ID);
|
|
35
|
+
expect(node?.id).to.equal(EXAMPLE_ID);
|
|
36
|
+
expect(node?.type).to.equal(EXAMPLE_TYPE);
|
|
37
|
+
expect(node?.data).to.equal(1);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test('updates', async () => {
|
|
42
|
+
const builder = new GraphBuilder();
|
|
43
|
+
const name = signal('default');
|
|
44
|
+
builder.addExtension(
|
|
45
|
+
createExtension({ id: 'resolver', resolver: () => ({ id: EXAMPLE_ID, type: EXAMPLE_TYPE, data: name.value }) }),
|
|
29
46
|
);
|
|
47
|
+
const graph = builder.graph;
|
|
48
|
+
|
|
49
|
+
const node = await graph.waitForNode(EXAMPLE_ID);
|
|
50
|
+
expect(node?.data).to.equal('default');
|
|
51
|
+
|
|
52
|
+
name.value = 'updated';
|
|
53
|
+
expect(node?.data).to.equal('updated');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test('memoize', async () => {
|
|
57
|
+
const builder = new GraphBuilder();
|
|
58
|
+
const name = signal('default');
|
|
59
|
+
let count = 0;
|
|
60
|
+
let memoizedCount = 0;
|
|
30
61
|
builder.addExtension(
|
|
31
62
|
createExtension({
|
|
32
|
-
id: '
|
|
33
|
-
|
|
34
|
-
|
|
63
|
+
id: 'resolver',
|
|
64
|
+
resolver: () => {
|
|
65
|
+
count++;
|
|
66
|
+
memoize(() => {
|
|
67
|
+
memoizedCount++;
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
return { id: EXAMPLE_ID, type: EXAMPLE_TYPE, data: name.value };
|
|
71
|
+
},
|
|
35
72
|
}),
|
|
36
73
|
);
|
|
37
|
-
|
|
38
74
|
const graph = builder.graph;
|
|
39
|
-
graph.expand(ROOT_ID);
|
|
40
|
-
graph.expand(ROOT_ID, 'inbound');
|
|
41
75
|
|
|
42
|
-
const
|
|
43
|
-
|
|
76
|
+
const node = await graph.waitForNode(EXAMPLE_ID);
|
|
77
|
+
expect(node?.data).to.equal('default');
|
|
78
|
+
expect(count).to.equal(1);
|
|
79
|
+
expect(memoizedCount).to.equal(1);
|
|
44
80
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
expect(
|
|
50
|
-
expect(
|
|
81
|
+
name!.value = 'one';
|
|
82
|
+
name!.value = 'two';
|
|
83
|
+
name!.value = 'three';
|
|
84
|
+
|
|
85
|
+
expect(node?.data).to.equal('three');
|
|
86
|
+
expect(count).to.equal(4);
|
|
87
|
+
expect(memoizedCount).to.equal(1);
|
|
51
88
|
});
|
|
52
89
|
|
|
53
|
-
test('
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
const
|
|
90
|
+
test('resolving pickled graph', async () => {
|
|
91
|
+
const pickle =
|
|
92
|
+
'{"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":[]}}';
|
|
93
|
+
const builder = GraphBuilder.from(pickle);
|
|
94
|
+
const graph = builder.graph;
|
|
95
|
+
|
|
57
96
|
builder.addExtension(
|
|
58
97
|
createExtension({
|
|
59
|
-
id: '
|
|
60
|
-
|
|
98
|
+
id: 'resolver',
|
|
99
|
+
resolver: ({ id }) => {
|
|
100
|
+
if (id === ROOT_ID) {
|
|
101
|
+
return { id: ROOT_ID, type: ROOT_TYPE };
|
|
102
|
+
} else {
|
|
103
|
+
return { id, type: EXAMPLE_TYPE, data: id, properties: { value: parseInt(id.replace('test', '')) } };
|
|
104
|
+
}
|
|
105
|
+
},
|
|
61
106
|
}),
|
|
62
107
|
);
|
|
63
|
-
const graph = builder.graph;
|
|
64
|
-
graph.expand(ROOT_ID);
|
|
65
108
|
|
|
66
109
|
{
|
|
67
|
-
|
|
68
|
-
expect(
|
|
110
|
+
expect(graph.findNode('test1', false)).toBeDefined();
|
|
111
|
+
expect(graph.findNode('test1', false)?.data).to.equal(null);
|
|
112
|
+
expect(graph.findNode('test1', false)?.properties.value).to.equal(1);
|
|
113
|
+
expect(graph.findNode('test2', false)).toBeDefined();
|
|
114
|
+
expect(graph.findNode('test2', false)?.data).to.equal(null);
|
|
115
|
+
expect(graph.findNode('test2', false)?.properties.value).to.equal(2);
|
|
69
116
|
}
|
|
70
117
|
|
|
118
|
+
await builder.initialize();
|
|
119
|
+
|
|
71
120
|
{
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
expect(
|
|
121
|
+
expect(graph.findNode('test1', false)?.data).to.equal('test1');
|
|
122
|
+
expect(graph.findNode('test1', false)?.properties.value).to.equal(1);
|
|
123
|
+
expect(graph.findNode('test2', false)?.data).to.equal('test2');
|
|
124
|
+
expect(graph.findNode('test2', false)?.properties.value).to.equal(2);
|
|
75
125
|
}
|
|
76
126
|
});
|
|
127
|
+
});
|
|
77
128
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
const builder = new GraphBuilder(
|
|
81
|
-
const state = Rx.make(0);
|
|
129
|
+
describe('connector', () => {
|
|
130
|
+
test('works', async () => {
|
|
131
|
+
const builder = new GraphBuilder();
|
|
82
132
|
builder.addExtension(
|
|
83
133
|
createExtension({
|
|
84
|
-
id: 'connector',
|
|
85
|
-
connector: () =>
|
|
134
|
+
id: 'outbound-connector',
|
|
135
|
+
connector: () => [{ id: 'child', type: EXAMPLE_TYPE, data: 2 }],
|
|
136
|
+
}),
|
|
137
|
+
);
|
|
138
|
+
builder.addExtension(
|
|
139
|
+
createExtension({
|
|
140
|
+
id: 'inbound-connector',
|
|
141
|
+
relation: 'inbound',
|
|
142
|
+
connector: () => [{ id: 'parent', type: EXAMPLE_TYPE, data: 0 }],
|
|
86
143
|
}),
|
|
87
144
|
);
|
|
145
|
+
|
|
88
146
|
const graph = builder.graph;
|
|
147
|
+
await graph.expand(graph.root);
|
|
148
|
+
await graph.expand(graph.root, 'inbound');
|
|
89
149
|
|
|
90
|
-
|
|
91
|
-
const
|
|
92
|
-
count++;
|
|
93
|
-
});
|
|
94
|
-
onTestFinished(() => cancel());
|
|
150
|
+
const outbound = graph.nodes(graph.root);
|
|
151
|
+
const inbound = graph.nodes(graph.root, { relation: 'inbound' });
|
|
95
152
|
|
|
96
|
-
expect(
|
|
97
|
-
expect(
|
|
98
|
-
expect(
|
|
153
|
+
expect(outbound).has.length(1);
|
|
154
|
+
expect(outbound?.[0].id).to.equal('child');
|
|
155
|
+
expect(outbound?.[0].data).to.equal(2);
|
|
156
|
+
expect(inbound).has.length(1);
|
|
157
|
+
expect(inbound?.[0].id).to.equal('parent');
|
|
158
|
+
expect(inbound?.[0].data).to.equal(0);
|
|
159
|
+
});
|
|
99
160
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
161
|
+
test('updates', async () => {
|
|
162
|
+
const name = signal('default');
|
|
163
|
+
const builder = new GraphBuilder();
|
|
164
|
+
builder.addExtension(
|
|
165
|
+
createExtension({
|
|
166
|
+
id: 'connector',
|
|
167
|
+
connector: () => [{ id: EXAMPLE_ID, type: EXAMPLE_TYPE, data: name, properties: { label: name.value } }],
|
|
168
|
+
}),
|
|
169
|
+
);
|
|
170
|
+
const graph = builder.graph;
|
|
171
|
+
await graph.expand(graph.root);
|
|
172
|
+
|
|
173
|
+
const [node] = graph.nodes(graph.root);
|
|
174
|
+
expect(node.properties.label).to.equal('default');
|
|
175
|
+
|
|
176
|
+
name.value = 'updated';
|
|
177
|
+
expect(node.properties.label).to.equal('updated');
|
|
104
178
|
});
|
|
105
179
|
|
|
106
|
-
test('updates with new extensions', () => {
|
|
107
|
-
const
|
|
108
|
-
const builder = new GraphBuilder(
|
|
180
|
+
test('updates with new extensions', async () => {
|
|
181
|
+
const name = signal('default');
|
|
182
|
+
const builder = new GraphBuilder();
|
|
109
183
|
builder.addExtension(
|
|
110
184
|
createExtension({
|
|
111
185
|
id: 'connector',
|
|
112
|
-
connector: () =>
|
|
186
|
+
connector: () => [{ id: EXAMPLE_ID, type: EXAMPLE_TYPE, data: name, properties: { label: name.value } }],
|
|
113
187
|
}),
|
|
114
188
|
);
|
|
115
189
|
const graph = builder.graph;
|
|
116
|
-
graph.expand(
|
|
190
|
+
await graph.expand(graph.root);
|
|
117
191
|
|
|
118
192
|
let nodes: Node[] = [];
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
count++;
|
|
122
|
-
nodes = _nodes;
|
|
193
|
+
using updates = updateCounter(() => {
|
|
194
|
+
nodes = graph.nodes(graph.root);
|
|
123
195
|
});
|
|
124
|
-
onTestFinished(() => cancel());
|
|
125
196
|
|
|
126
|
-
expect(
|
|
127
|
-
expect(count).to.equal(0);
|
|
128
|
-
registry.get(graph.connections(ROOT_ID));
|
|
197
|
+
expect(updates.count).to.equal(0);
|
|
129
198
|
expect(nodes).has.length(1);
|
|
130
|
-
expect(
|
|
199
|
+
expect(nodes[0].id).to.equal(EXAMPLE_ID);
|
|
131
200
|
|
|
132
201
|
builder.addExtension(
|
|
133
202
|
createExtension({
|
|
134
203
|
id: 'connector-2',
|
|
135
|
-
connector: () =>
|
|
204
|
+
connector: () => [{ id: exampleId(2), type: EXAMPLE_TYPE, data: 0 }],
|
|
136
205
|
}),
|
|
137
206
|
);
|
|
207
|
+
|
|
208
|
+
expect(updates.count).to.equal(1);
|
|
138
209
|
expect(nodes).has.length(2);
|
|
139
|
-
expect(
|
|
210
|
+
expect(nodes[0].id).to.equal(EXAMPLE_ID);
|
|
211
|
+
expect(nodes[1].id).to.equal(exampleId(2));
|
|
140
212
|
});
|
|
141
213
|
|
|
142
|
-
test('removes', () => {
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
{ id: exampleId(1), type: EXAMPLE_TYPE },
|
|
147
|
-
{ id: exampleId(2), type: EXAMPLE_TYPE },
|
|
214
|
+
test('removes', async () => {
|
|
215
|
+
const nodes = signal([
|
|
216
|
+
{ id: exampleId(1), type: EXAMPLE_TYPE, data: 1 },
|
|
217
|
+
{ id: exampleId(2), type: EXAMPLE_TYPE, data: 2 },
|
|
148
218
|
]);
|
|
219
|
+
|
|
220
|
+
const builder = new GraphBuilder();
|
|
149
221
|
builder.addExtension(
|
|
150
222
|
createExtension({
|
|
151
223
|
id: 'connector',
|
|
152
|
-
connector: () =>
|
|
224
|
+
connector: () => nodes.value,
|
|
153
225
|
}),
|
|
154
226
|
);
|
|
155
227
|
const graph = builder.graph;
|
|
156
|
-
graph.expand(
|
|
228
|
+
await graph.expand(graph.root);
|
|
157
229
|
|
|
158
230
|
{
|
|
159
|
-
const nodes =
|
|
231
|
+
const nodes = graph.nodes(graph.root);
|
|
160
232
|
expect(nodes).has.length(2);
|
|
161
233
|
expect(nodes[0].id).to.equal(exampleId(1));
|
|
162
|
-
expect(nodes[1].id).to.equal(exampleId(2));
|
|
163
234
|
}
|
|
164
235
|
|
|
165
|
-
|
|
236
|
+
nodes.value = [{ id: exampleId(3), type: EXAMPLE_TYPE, data: 3 }];
|
|
166
237
|
|
|
167
238
|
{
|
|
168
|
-
const nodes =
|
|
239
|
+
const nodes = graph.nodes(graph.root);
|
|
169
240
|
expect(nodes).has.length(1);
|
|
170
241
|
expect(nodes[0].id).to.equal(exampleId(3));
|
|
242
|
+
expect(graph.findNode(exampleId(1))).to.be.undefined;
|
|
171
243
|
}
|
|
172
244
|
});
|
|
173
245
|
|
|
174
|
-
test('
|
|
175
|
-
|
|
176
|
-
const
|
|
177
|
-
const
|
|
178
|
-
|
|
246
|
+
test('unsubscribes', async () => {
|
|
247
|
+
let count = 0;
|
|
248
|
+
const name = signal('default');
|
|
249
|
+
const sub = signal('default');
|
|
250
|
+
const builder = new GraphBuilder();
|
|
179
251
|
builder.addExtension([
|
|
180
252
|
createExtension({
|
|
181
253
|
id: 'root',
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
254
|
+
filter: (node): node is Node<null> => node.id === 'root',
|
|
255
|
+
connector: () =>
|
|
256
|
+
name.value === 'removed'
|
|
257
|
+
? []
|
|
258
|
+
: [{ id: EXAMPLE_ID, type: EXAMPLE_TYPE, data: name, properties: { label: name.value } }],
|
|
259
|
+
}),
|
|
260
|
+
createExtension({
|
|
261
|
+
id: 'connector',
|
|
262
|
+
filter: (node): node is Node<string> => node.id === EXAMPLE_ID,
|
|
263
|
+
connector: () => {
|
|
264
|
+
count++;
|
|
265
|
+
sub.value;
|
|
266
|
+
|
|
267
|
+
return [];
|
|
268
|
+
},
|
|
192
269
|
}),
|
|
193
270
|
]);
|
|
194
271
|
|
|
272
|
+
// Count should not increment until the node is expanded.
|
|
195
273
|
const graph = builder.graph;
|
|
196
|
-
|
|
197
|
-
let count = 0;
|
|
198
|
-
let exists = false;
|
|
199
|
-
const cancel = registry.subscribe(graph.node(EXAMPLE_ID), (node) => {
|
|
200
|
-
count++;
|
|
201
|
-
exists = Option.isSome(node);
|
|
202
|
-
});
|
|
203
|
-
onTestFinished(() => cancel());
|
|
204
|
-
|
|
205
|
-
graph.expand(ROOT_ID);
|
|
274
|
+
await graph.expand(graph.root);
|
|
206
275
|
expect(count).to.equal(0);
|
|
207
|
-
expect(exists).to.be.false;
|
|
208
276
|
|
|
209
|
-
|
|
277
|
+
// Count should increment when the node is expanded.
|
|
278
|
+
const [node] = graph.nodes(graph.root);
|
|
279
|
+
await graph.expand(node!);
|
|
210
280
|
expect(count).to.equal(1);
|
|
211
|
-
expect(exists).to.be.true;
|
|
212
281
|
|
|
213
|
-
|
|
282
|
+
// Count should increment when the parent changes.
|
|
283
|
+
name.value = 'updated';
|
|
214
284
|
expect(count).to.equal(2);
|
|
215
|
-
expect(exists).to.be.false;
|
|
216
285
|
|
|
217
|
-
|
|
286
|
+
// Count should increment when the signal changes.
|
|
287
|
+
sub.value = 'updated';
|
|
218
288
|
expect(count).to.equal(3);
|
|
219
|
-
|
|
289
|
+
|
|
290
|
+
// Count will still increment if the node is removed in a batch.
|
|
291
|
+
batch(() => {
|
|
292
|
+
name.value = 'removed';
|
|
293
|
+
sub.value = 'batch';
|
|
294
|
+
});
|
|
295
|
+
expect(count).to.equal(4);
|
|
296
|
+
|
|
297
|
+
// Count should not increment after the node is removed.
|
|
298
|
+
sub.value = 'removed';
|
|
299
|
+
expect(count).to.equal(4);
|
|
300
|
+
|
|
301
|
+
// Count will not increment when node is added back.
|
|
302
|
+
name.value = 'added';
|
|
303
|
+
expect(count).to.equal(4);
|
|
304
|
+
|
|
305
|
+
// Count should increment when the node is expanded again.
|
|
306
|
+
await graph.expand(node!);
|
|
307
|
+
expect(count).to.equal(5);
|
|
308
|
+
|
|
309
|
+
// Count should increment when signal changes again.
|
|
310
|
+
sub.value = 'added';
|
|
311
|
+
expect(count).to.equal(6);
|
|
220
312
|
});
|
|
221
313
|
|
|
222
|
-
test('
|
|
223
|
-
const
|
|
224
|
-
const builder = new GraphBuilder({ registry });
|
|
225
|
-
const nodes = Rx.make([
|
|
226
|
-
{ id: exampleId(1), type: EXAMPLE_TYPE, data: 1 },
|
|
227
|
-
{ id: exampleId(2), type: EXAMPLE_TYPE, data: 2 },
|
|
228
|
-
{ id: exampleId(3), type: EXAMPLE_TYPE, data: 3 },
|
|
229
|
-
]);
|
|
314
|
+
test('filters by type', async () => {
|
|
315
|
+
const builder = new GraphBuilder();
|
|
230
316
|
builder.addExtension(
|
|
231
317
|
createExtension({
|
|
232
|
-
id: '
|
|
233
|
-
connector: () =>
|
|
318
|
+
id: 'actions',
|
|
319
|
+
connector: () => [{ id: 'not-action', type: EXAMPLE_TYPE, data: 1 }],
|
|
320
|
+
actions: () => [{ id: 'action', data: () => {} }],
|
|
234
321
|
}),
|
|
235
322
|
);
|
|
236
323
|
const graph = builder.graph;
|
|
237
|
-
graph.expand(ROOT_ID);
|
|
238
|
-
|
|
239
|
-
{
|
|
240
|
-
const nodes = registry.get(graph.connections(ROOT_ID));
|
|
241
|
-
expect(nodes).has.length(3);
|
|
242
|
-
expect(nodes[0].id).to.equal(exampleId(1));
|
|
243
|
-
expect(nodes[1].id).to.equal(exampleId(2));
|
|
244
|
-
expect(nodes[2].id).to.equal(exampleId(3));
|
|
245
|
-
}
|
|
246
324
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
]);
|
|
325
|
+
await graph.expand(graph.root, 'outbound', ACTION_TYPE);
|
|
326
|
+
const actions = graph.actions(graph.root);
|
|
327
|
+
expect(actions).has.length(1);
|
|
328
|
+
expect(actions?.[0].id).to.equal('action');
|
|
329
|
+
expect(actions?.[0].type).to.equal(ACTION_TYPE);
|
|
252
330
|
|
|
253
|
-
|
|
254
|
-
await sleep(0);
|
|
331
|
+
await expect(graph.waitForNode('not-action', 10)).rejects.toBeInstanceOf(Error);
|
|
255
332
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
expect(nodes[2].id).to.equal(exampleId(2));
|
|
262
|
-
}
|
|
333
|
+
await graph.expand(graph.root);
|
|
334
|
+
const nodes = graph.nodes(graph.root);
|
|
335
|
+
expect(nodes).has.length(1);
|
|
336
|
+
expect(nodes?.[0].id).to.equal('not-action');
|
|
337
|
+
expect(nodes?.[0].data).to.equal(1);
|
|
263
338
|
});
|
|
264
339
|
|
|
265
|
-
test('
|
|
266
|
-
const
|
|
267
|
-
|
|
268
|
-
const name = Rx.make('default');
|
|
269
|
-
const sub = Rx.make('default');
|
|
270
|
-
|
|
271
|
-
builder.addExtension([
|
|
272
|
-
createExtension({
|
|
273
|
-
id: 'root',
|
|
274
|
-
connector: (node) =>
|
|
275
|
-
Rx.make((get) =>
|
|
276
|
-
pipe(
|
|
277
|
-
get(node),
|
|
278
|
-
Option.flatMap((node) => (node.id === 'root' ? Option.some(get(name)) : Option.none())),
|
|
279
|
-
Option.filter((name) => name !== 'removed'),
|
|
280
|
-
Option.map((name) => [{ id: EXAMPLE_ID, type: EXAMPLE_TYPE, data: name }]),
|
|
281
|
-
Option.getOrElse(() => []),
|
|
282
|
-
),
|
|
283
|
-
),
|
|
284
|
-
}),
|
|
285
|
-
createExtension({
|
|
286
|
-
id: 'connector1',
|
|
287
|
-
connector: (node) =>
|
|
288
|
-
Rx.make((get) =>
|
|
289
|
-
pipe(
|
|
290
|
-
get(node),
|
|
291
|
-
Option.flatMap((node) => (node.id === EXAMPLE_ID ? Option.some(get(sub)) : Option.none())),
|
|
292
|
-
Option.map((sub) => [{ id: exampleId(2), type: EXAMPLE_TYPE, data: sub }]),
|
|
293
|
-
Option.getOrElse(() => []),
|
|
294
|
-
),
|
|
295
|
-
),
|
|
296
|
-
}),
|
|
340
|
+
test('filters by callback', async () => {
|
|
341
|
+
const builder = new GraphBuilder();
|
|
342
|
+
builder.addExtension(
|
|
297
343
|
createExtension({
|
|
298
|
-
id: '
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
pipe(
|
|
302
|
-
get(node),
|
|
303
|
-
Option.flatMap((node) => (node.id === EXAMPLE_ID ? Option.some(node.data) : Option.none())),
|
|
304
|
-
Option.map((data) => [{ id: exampleId(3), type: EXAMPLE_TYPE, data }]),
|
|
305
|
-
Option.getOrElse(() => []),
|
|
306
|
-
),
|
|
307
|
-
),
|
|
344
|
+
id: 'filtered-connector',
|
|
345
|
+
filter: (node): node is Node<null> => node.id === 'root',
|
|
346
|
+
connector: () => [{ id: EXAMPLE_ID, type: EXAMPLE_TYPE, data: 1 }],
|
|
308
347
|
}),
|
|
309
|
-
|
|
310
|
-
|
|
348
|
+
);
|
|
311
349
|
const graph = builder.graph;
|
|
350
|
+
await graph.expand(graph.root);
|
|
312
351
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
parentCount++;
|
|
316
|
-
});
|
|
317
|
-
onTestFinished(() => parentCancel());
|
|
318
|
-
|
|
319
|
-
let independentCount = 0;
|
|
320
|
-
const independentCancel = registry.subscribe(graph.node(exampleId(2)), (_) => {
|
|
321
|
-
independentCount++;
|
|
322
|
-
});
|
|
323
|
-
onTestFinished(() => independentCancel());
|
|
352
|
+
const [node1] = graph.nodes(graph.root);
|
|
353
|
+
expect(node1?.id).to.equal(EXAMPLE_ID);
|
|
324
354
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
dependentCount++;
|
|
328
|
-
});
|
|
329
|
-
onTestFinished(() => dependentCancel());
|
|
330
|
-
|
|
331
|
-
// Counts should not increment until the node is expanded.
|
|
332
|
-
graph.expand(ROOT_ID);
|
|
333
|
-
expect(parentCount).to.equal(1);
|
|
334
|
-
expect(independentCount).to.equal(0);
|
|
335
|
-
expect(dependentCount).to.equal(0);
|
|
336
|
-
|
|
337
|
-
// Counts should increment when the node is expanded.
|
|
338
|
-
graph.expand(EXAMPLE_ID);
|
|
339
|
-
expect(parentCount).to.equal(1);
|
|
340
|
-
expect(independentCount).to.equal(1);
|
|
341
|
-
expect(dependentCount).to.equal(1);
|
|
342
|
-
|
|
343
|
-
// Only dependent count should increment when the parent changes.
|
|
344
|
-
registry.set(name, 'updated');
|
|
345
|
-
expect(parentCount).to.equal(2);
|
|
346
|
-
expect(independentCount).to.equal(1);
|
|
347
|
-
expect(dependentCount).to.equal(2);
|
|
348
|
-
|
|
349
|
-
// Only independent count should increment when its state changes.
|
|
350
|
-
registry.set(sub, 'updated');
|
|
351
|
-
expect(parentCount).to.equal(2);
|
|
352
|
-
expect(independentCount).to.equal(2);
|
|
353
|
-
expect(dependentCount).to.equal(2);
|
|
354
|
-
|
|
355
|
-
// Independent count should update if its state changes even if the parent is removed.
|
|
356
|
-
Rx.batch(() => {
|
|
357
|
-
registry.set(name, 'removed');
|
|
358
|
-
registry.set(sub, 'batch');
|
|
359
|
-
});
|
|
360
|
-
expect(parentCount).to.equal(2);
|
|
361
|
-
expect(independentCount).to.equal(3);
|
|
362
|
-
expect(dependentCount).to.equal(2);
|
|
363
|
-
|
|
364
|
-
// Dependent count should increment when the node is added back.
|
|
365
|
-
registry.set(name, 'added');
|
|
366
|
-
expect(parentCount).to.equal(3);
|
|
367
|
-
expect(independentCount).to.equal(3);
|
|
368
|
-
expect(dependentCount).to.equal(3);
|
|
369
|
-
|
|
370
|
-
// Counts should not increment when the node is expanded again.
|
|
371
|
-
graph.expand(EXAMPLE_ID);
|
|
372
|
-
expect(parentCount).to.equal(3);
|
|
373
|
-
expect(independentCount).to.equal(3);
|
|
374
|
-
expect(dependentCount).to.equal(3);
|
|
355
|
+
const nodes = graph.nodes(node1);
|
|
356
|
+
expect(nodes).has.length(0);
|
|
375
357
|
});
|
|
376
358
|
|
|
377
|
-
test('
|
|
378
|
-
const
|
|
379
|
-
const
|
|
359
|
+
test('memoize', async () => {
|
|
360
|
+
const builder = new GraphBuilder();
|
|
361
|
+
const name = signal('default');
|
|
362
|
+
let count = 0;
|
|
363
|
+
let memoizedCount = 0;
|
|
380
364
|
builder.addExtension(
|
|
381
365
|
createExtension({
|
|
382
366
|
id: 'connector',
|
|
383
|
-
connector: (
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
Option.getOrElse(() => []),
|
|
391
|
-
),
|
|
392
|
-
);
|
|
367
|
+
connector: () => {
|
|
368
|
+
count++;
|
|
369
|
+
memoize(() => {
|
|
370
|
+
memoizedCount++;
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
return [{ id: EXAMPLE_ID, type: EXAMPLE_TYPE, data: name, properties: { label: name.value } }];
|
|
393
374
|
},
|
|
394
375
|
}),
|
|
395
376
|
);
|
|
377
|
+
const graph = builder.graph;
|
|
378
|
+
await graph.expand(graph.root);
|
|
396
379
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
count++;
|
|
402
|
-
if (count === 5) {
|
|
403
|
-
trigger.wake();
|
|
404
|
-
}
|
|
405
|
-
});
|
|
380
|
+
const [node] = graph.nodes(graph.root);
|
|
381
|
+
expect(node.properties.label).to.equal('default');
|
|
382
|
+
expect(count).to.equal(1);
|
|
383
|
+
expect(memoizedCount).to.equal(1);
|
|
406
384
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
385
|
+
name!.value = 'one';
|
|
386
|
+
name!.value = 'two';
|
|
387
|
+
name!.value = 'three';
|
|
388
|
+
|
|
389
|
+
expect(node.properties.label).to.equal('three');
|
|
390
|
+
expect(count).to.equal(4);
|
|
391
|
+
expect(memoizedCount).to.equal(1);
|
|
410
392
|
});
|
|
411
393
|
});
|
|
412
394
|
|
|
@@ -416,21 +398,17 @@ describe('GraphBuilder', () => {
|
|
|
416
398
|
builder.addExtension(
|
|
417
399
|
createExtension({
|
|
418
400
|
id: 'connector',
|
|
419
|
-
connector: (node) =>
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
Option.map((node) => (node.data ? node.data + 1 : 1)),
|
|
424
|
-
Option.filter((data) => data <= 5),
|
|
425
|
-
Option.map((data) => [{ id: `node-${data}`, type: EXAMPLE_TYPE, data }]),
|
|
426
|
-
Option.getOrElse(() => []),
|
|
427
|
-
),
|
|
428
|
-
),
|
|
401
|
+
connector: ({ node }) => {
|
|
402
|
+
const data = node.data ? node.data + 1 : 1;
|
|
403
|
+
return data > 5 ? [] : [{ id: `node-${data}`, type: EXAMPLE_TYPE, data }];
|
|
404
|
+
},
|
|
429
405
|
}),
|
|
430
406
|
);
|
|
407
|
+
const graph = builder.graph;
|
|
431
408
|
|
|
432
409
|
let count = 0;
|
|
433
410
|
await builder.explore({
|
|
411
|
+
node: graph.root,
|
|
434
412
|
visitor: () => {
|
|
435
413
|
count++;
|
|
436
414
|
},
|
|
@@ -439,4 +417,43 @@ describe('GraphBuilder', () => {
|
|
|
439
417
|
expect(count).to.equal(6);
|
|
440
418
|
});
|
|
441
419
|
});
|
|
420
|
+
|
|
421
|
+
describe('multiples', () => {
|
|
422
|
+
test('one of each with multiple memos', async () => {
|
|
423
|
+
const name = signal('default');
|
|
424
|
+
const builder = new GraphBuilder();
|
|
425
|
+
builder.addExtension(
|
|
426
|
+
createExtension({
|
|
427
|
+
id: 'extension',
|
|
428
|
+
resolver: () => {
|
|
429
|
+
const data = memoize(() => Math.random());
|
|
430
|
+
return { id: EXAMPLE_ID, type: EXAMPLE_TYPE, data, properties: { name: name.value } };
|
|
431
|
+
},
|
|
432
|
+
connector: () => {
|
|
433
|
+
const a = memoize(() => Math.random());
|
|
434
|
+
const b = memoize(() => Math.random());
|
|
435
|
+
const c = Math.random();
|
|
436
|
+
return [{ id: `${EXAMPLE_ID}-child`, type: EXAMPLE_TYPE, data: { a, b, c } }];
|
|
437
|
+
},
|
|
438
|
+
}),
|
|
439
|
+
);
|
|
440
|
+
const graph = builder.graph;
|
|
441
|
+
|
|
442
|
+
const one = await graph.waitForNode(EXAMPLE_ID);
|
|
443
|
+
const initialData = one!.data;
|
|
444
|
+
await graph.expand(one!);
|
|
445
|
+
const two = graph.nodes(one!)[0];
|
|
446
|
+
const initialA = two?.data.a;
|
|
447
|
+
const initialB = two?.data.b;
|
|
448
|
+
const initialC = two?.data.c;
|
|
449
|
+
|
|
450
|
+
name.value = 'updated';
|
|
451
|
+
|
|
452
|
+
expect(one?.properties.name).to.equal('updated');
|
|
453
|
+
expect(one?.data).to.equal(initialData);
|
|
454
|
+
expect(two?.data.a).to.equal(initialA);
|
|
455
|
+
expect(two?.data.b).to.equal(initialB);
|
|
456
|
+
expect(two?.data.c).not.to.equal(initialC);
|
|
457
|
+
});
|
|
458
|
+
});
|
|
442
459
|
});
|