@dxos/app-graph 0.8.3 → 0.8.4-main.1068cf700f
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 +1135 -616
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +1134 -616
- 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 +113 -60
- package/dist/types/src/graph-builder.d.ts.map +1 -1
- package/dist/types/src/graph.d.ts +183 -209
- 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 +6 -13
- package/dist/types/src/stories/EchoGraph.stories.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +37 -37
- package/src/atoms.ts +25 -0
- package/src/graph-builder.test.ts +571 -97
- package/src/graph-builder.ts +600 -258
- package/src/graph.test.ts +300 -107
- package/src/graph.ts +971 -400
- 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 +40 -5
- package/src/stories/EchoGraph.stories.tsx +128 -233
- package/src/stories/Tree.tsx +2 -2
- package/dist/lib/node/index.cjs +0 -816
- package/dist/lib/node/index.cjs.map +0 -7
- package/dist/lib/node/meta.json +0 -1
- 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
|
@@ -2,45 +2,113 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
import
|
|
5
|
+
import { Atom, Registry } from '@effect-atom/atom-react';
|
|
6
|
+
import * as Context from 'effect/Context';
|
|
7
|
+
import * as Effect from 'effect/Effect';
|
|
8
|
+
import * as Function from 'effect/Function';
|
|
9
|
+
import * as Option from 'effect/Option';
|
|
7
10
|
import { describe, expect, onTestFinished, test } from 'vitest';
|
|
8
11
|
|
|
9
|
-
import {
|
|
12
|
+
import { Trigger, sleep } from '@dxos/async';
|
|
13
|
+
import { Obj } from '@dxos/echo';
|
|
14
|
+
import { TestSchema } from '@dxos/echo/testing';
|
|
10
15
|
|
|
11
|
-
import
|
|
12
|
-
import
|
|
13
|
-
import
|
|
16
|
+
import * as Graph from './graph';
|
|
17
|
+
import * as GraphBuilder from './graph-builder';
|
|
18
|
+
import * as Node from './node';
|
|
19
|
+
import * as NodeMatcher from './node-matcher';
|
|
14
20
|
|
|
15
21
|
const exampleId = (id: number) => `dx:test:${id}`;
|
|
16
22
|
const EXAMPLE_ID = exampleId(1);
|
|
17
23
|
const EXAMPLE_TYPE = 'dxos.org/type/example';
|
|
18
24
|
|
|
19
25
|
describe('GraphBuilder', () => {
|
|
26
|
+
describe('resolver', () => {
|
|
27
|
+
test('works', async () => {
|
|
28
|
+
const registry = Registry.make();
|
|
29
|
+
const builder = GraphBuilder.make({ registry });
|
|
30
|
+
const graph = builder.graph;
|
|
31
|
+
|
|
32
|
+
{
|
|
33
|
+
const node = Graph.getNode(graph, EXAMPLE_ID).pipe(Option.getOrNull);
|
|
34
|
+
expect(node).to.be.null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Test direct API
|
|
38
|
+
GraphBuilder.addExtension(
|
|
39
|
+
builder,
|
|
40
|
+
GraphBuilder.createExtensionRaw({
|
|
41
|
+
id: 'resolver',
|
|
42
|
+
resolver: () => {
|
|
43
|
+
console.log('resolver');
|
|
44
|
+
return Atom.make({ id: EXAMPLE_ID, type: EXAMPLE_TYPE, data: 1 });
|
|
45
|
+
},
|
|
46
|
+
}),
|
|
47
|
+
);
|
|
48
|
+
await Graph.initialize(graph, EXAMPLE_ID);
|
|
49
|
+
|
|
50
|
+
{
|
|
51
|
+
const node = Graph.getNode(graph, EXAMPLE_ID).pipe(Option.getOrNull);
|
|
52
|
+
expect(node?.id).to.equal(EXAMPLE_ID);
|
|
53
|
+
expect(node?.type).to.equal(EXAMPLE_TYPE);
|
|
54
|
+
expect(node?.data).to.equal(1);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test('updates', async () => {
|
|
59
|
+
const registry = Registry.make();
|
|
60
|
+
const builder = GraphBuilder.make({ registry });
|
|
61
|
+
const name = Atom.make('default');
|
|
62
|
+
GraphBuilder.addExtension(
|
|
63
|
+
builder,
|
|
64
|
+
GraphBuilder.createExtensionRaw({
|
|
65
|
+
id: 'resolver',
|
|
66
|
+
resolver: () => Atom.make((get) => ({ id: EXAMPLE_ID, type: EXAMPLE_TYPE, data: get(name) })),
|
|
67
|
+
}),
|
|
68
|
+
);
|
|
69
|
+
const graph = builder.graph;
|
|
70
|
+
await Graph.initialize(graph, EXAMPLE_ID);
|
|
71
|
+
|
|
72
|
+
{
|
|
73
|
+
const node = Graph.getNode(graph, EXAMPLE_ID).pipe(Option.getOrNull);
|
|
74
|
+
expect(node?.data).to.equal('default');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
registry.set(name, 'updated');
|
|
78
|
+
|
|
79
|
+
{
|
|
80
|
+
const node = Graph.getNode(graph, EXAMPLE_ID).pipe(Option.getOrNull);
|
|
81
|
+
expect(node?.data).to.equal('updated');
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
20
86
|
describe('connector', () => {
|
|
21
87
|
test('works', () => {
|
|
22
88
|
const registry = Registry.make();
|
|
23
|
-
const builder =
|
|
24
|
-
|
|
25
|
-
|
|
89
|
+
const builder = GraphBuilder.make({ registry });
|
|
90
|
+
GraphBuilder.addExtension(
|
|
91
|
+
builder,
|
|
92
|
+
GraphBuilder.createExtensionRaw({
|
|
26
93
|
id: 'outbound-connector',
|
|
27
|
-
connector: () =>
|
|
94
|
+
connector: () => Atom.make([{ id: 'child', type: EXAMPLE_TYPE, data: 2 }]),
|
|
28
95
|
}),
|
|
29
96
|
);
|
|
30
|
-
|
|
31
|
-
|
|
97
|
+
GraphBuilder.addExtension(
|
|
98
|
+
builder,
|
|
99
|
+
GraphBuilder.createExtensionRaw({
|
|
32
100
|
id: 'inbound-connector',
|
|
33
101
|
relation: 'inbound',
|
|
34
|
-
connector: () =>
|
|
102
|
+
connector: () => Atom.make([{ id: 'parent', type: EXAMPLE_TYPE, data: 0 }]),
|
|
35
103
|
}),
|
|
36
104
|
);
|
|
37
105
|
|
|
38
106
|
const graph = builder.graph;
|
|
39
|
-
|
|
40
|
-
|
|
107
|
+
Graph.expand(graph, Node.RootId);
|
|
108
|
+
Graph.expand(graph, Node.RootId, 'inbound');
|
|
41
109
|
|
|
42
|
-
const outbound = registry.get(graph.connections(
|
|
43
|
-
const inbound = registry.get(graph.connections(
|
|
110
|
+
const outbound = registry.get(graph.connections(Node.RootId));
|
|
111
|
+
const inbound = registry.get(graph.connections(Node.RootId, 'inbound'));
|
|
44
112
|
|
|
45
113
|
expect(outbound).has.length(1);
|
|
46
114
|
expect(outbound[0].id).to.equal('child');
|
|
@@ -52,52 +120,54 @@ describe('GraphBuilder', () => {
|
|
|
52
120
|
|
|
53
121
|
test('updates', () => {
|
|
54
122
|
const registry = Registry.make();
|
|
55
|
-
const builder =
|
|
56
|
-
const state =
|
|
57
|
-
|
|
58
|
-
|
|
123
|
+
const builder = GraphBuilder.make({ registry });
|
|
124
|
+
const state = Atom.make(0);
|
|
125
|
+
GraphBuilder.addExtension(
|
|
126
|
+
builder,
|
|
127
|
+
GraphBuilder.createExtensionRaw({
|
|
59
128
|
id: 'connector',
|
|
60
|
-
connector: () =>
|
|
129
|
+
connector: () => Atom.make((get) => [{ id: EXAMPLE_ID, type: EXAMPLE_TYPE, data: get(state) }]),
|
|
61
130
|
}),
|
|
62
131
|
);
|
|
63
132
|
const graph = builder.graph;
|
|
64
|
-
|
|
133
|
+
Graph.expand(graph, Node.RootId);
|
|
65
134
|
|
|
66
135
|
{
|
|
67
|
-
const [node] = registry.get(graph.connections(
|
|
136
|
+
const [node] = registry.get(graph.connections(Node.RootId));
|
|
68
137
|
expect(node.data).to.equal(0);
|
|
69
138
|
}
|
|
70
139
|
|
|
71
140
|
{
|
|
72
141
|
registry.set(state, 1);
|
|
73
|
-
const [node] = registry.get(graph.connections(
|
|
142
|
+
const [node] = registry.get(graph.connections(Node.RootId));
|
|
74
143
|
expect(node.data).to.equal(1);
|
|
75
144
|
}
|
|
76
145
|
});
|
|
77
146
|
|
|
78
147
|
test('subscribes to updates', () => {
|
|
79
148
|
const registry = Registry.make();
|
|
80
|
-
const builder =
|
|
81
|
-
const state =
|
|
82
|
-
|
|
83
|
-
|
|
149
|
+
const builder = GraphBuilder.make({ registry });
|
|
150
|
+
const state = Atom.make(0);
|
|
151
|
+
GraphBuilder.addExtension(
|
|
152
|
+
builder,
|
|
153
|
+
GraphBuilder.createExtensionRaw({
|
|
84
154
|
id: 'connector',
|
|
85
|
-
connector: () =>
|
|
155
|
+
connector: () => Atom.make((get) => [{ id: EXAMPLE_ID, type: EXAMPLE_TYPE, data: get(state) }]),
|
|
86
156
|
}),
|
|
87
157
|
);
|
|
88
158
|
const graph = builder.graph;
|
|
89
159
|
|
|
90
160
|
let count = 0;
|
|
91
|
-
const cancel = registry.subscribe(graph.connections(
|
|
161
|
+
const cancel = registry.subscribe(graph.connections(Node.RootId), (_) => {
|
|
92
162
|
count++;
|
|
93
163
|
});
|
|
94
164
|
onTestFinished(() => cancel());
|
|
95
165
|
|
|
96
166
|
expect(count).to.equal(0);
|
|
97
|
-
expect(registry.get(graph.connections(
|
|
167
|
+
expect(registry.get(graph.connections(Node.RootId))).to.have.length(0);
|
|
98
168
|
expect(count).to.equal(1);
|
|
99
169
|
|
|
100
|
-
|
|
170
|
+
Graph.expand(graph, Node.RootId);
|
|
101
171
|
expect(count).to.equal(2);
|
|
102
172
|
registry.set(state, 1);
|
|
103
173
|
expect(count).to.equal(3);
|
|
@@ -105,19 +175,20 @@ describe('GraphBuilder', () => {
|
|
|
105
175
|
|
|
106
176
|
test('updates with new extensions', () => {
|
|
107
177
|
const registry = Registry.make();
|
|
108
|
-
const builder =
|
|
109
|
-
|
|
110
|
-
|
|
178
|
+
const builder = GraphBuilder.make({ registry });
|
|
179
|
+
GraphBuilder.addExtension(
|
|
180
|
+
builder,
|
|
181
|
+
GraphBuilder.createExtensionRaw({
|
|
111
182
|
id: 'connector',
|
|
112
|
-
connector: () =>
|
|
183
|
+
connector: () => Atom.make([{ id: EXAMPLE_ID, type: EXAMPLE_TYPE }]),
|
|
113
184
|
}),
|
|
114
185
|
);
|
|
115
186
|
const graph = builder.graph;
|
|
116
|
-
|
|
187
|
+
Graph.expand(graph, Node.RootId);
|
|
117
188
|
|
|
118
|
-
let nodes: Node[] = [];
|
|
189
|
+
let nodes: Node.Node[] = [];
|
|
119
190
|
let count = 0;
|
|
120
|
-
const cancel = registry.subscribe(graph.connections(
|
|
191
|
+
const cancel = registry.subscribe(graph.connections(Node.RootId), (_nodes) => {
|
|
121
192
|
count++;
|
|
122
193
|
nodes = _nodes;
|
|
123
194
|
});
|
|
@@ -125,14 +196,15 @@ describe('GraphBuilder', () => {
|
|
|
125
196
|
|
|
126
197
|
expect(nodes).has.length(0);
|
|
127
198
|
expect(count).to.equal(0);
|
|
128
|
-
registry.get(graph.connections(
|
|
199
|
+
registry.get(graph.connections(Node.RootId));
|
|
129
200
|
expect(nodes).has.length(1);
|
|
130
201
|
expect(count).to.equal(1);
|
|
131
202
|
|
|
132
|
-
|
|
133
|
-
|
|
203
|
+
GraphBuilder.addExtension(
|
|
204
|
+
builder,
|
|
205
|
+
GraphBuilder.createExtensionRaw({
|
|
134
206
|
id: 'connector-2',
|
|
135
|
-
connector: () =>
|
|
207
|
+
connector: () => Atom.make([{ id: exampleId(2), type: EXAMPLE_TYPE }]),
|
|
136
208
|
}),
|
|
137
209
|
);
|
|
138
210
|
expect(nodes).has.length(2);
|
|
@@ -141,22 +213,23 @@ describe('GraphBuilder', () => {
|
|
|
141
213
|
|
|
142
214
|
test('removes', () => {
|
|
143
215
|
const registry = Registry.make();
|
|
144
|
-
const builder =
|
|
145
|
-
const nodes =
|
|
216
|
+
const builder = GraphBuilder.make({ registry });
|
|
217
|
+
const nodes = Atom.make([
|
|
146
218
|
{ id: exampleId(1), type: EXAMPLE_TYPE },
|
|
147
219
|
{ id: exampleId(2), type: EXAMPLE_TYPE },
|
|
148
220
|
]);
|
|
149
|
-
|
|
150
|
-
|
|
221
|
+
GraphBuilder.addExtension(
|
|
222
|
+
builder,
|
|
223
|
+
GraphBuilder.createExtensionRaw({
|
|
151
224
|
id: 'connector',
|
|
152
|
-
connector: () =>
|
|
225
|
+
connector: () => Atom.make((get) => get(nodes)),
|
|
153
226
|
}),
|
|
154
227
|
);
|
|
155
228
|
const graph = builder.graph;
|
|
156
|
-
|
|
229
|
+
Graph.expand(graph, Node.RootId);
|
|
157
230
|
|
|
158
231
|
{
|
|
159
|
-
const nodes = registry.get(graph.connections(
|
|
232
|
+
const nodes = registry.get(graph.connections(Node.RootId));
|
|
160
233
|
expect(nodes).has.length(2);
|
|
161
234
|
expect(nodes[0].id).to.equal(exampleId(1));
|
|
162
235
|
expect(nodes[1].id).to.equal(exampleId(2));
|
|
@@ -165,7 +238,7 @@ describe('GraphBuilder', () => {
|
|
|
165
238
|
registry.set(nodes, [{ id: exampleId(3), type: EXAMPLE_TYPE }]);
|
|
166
239
|
|
|
167
240
|
{
|
|
168
|
-
const nodes = registry.get(graph.connections(
|
|
241
|
+
const nodes = registry.get(graph.connections(Node.RootId));
|
|
169
242
|
expect(nodes).has.length(1);
|
|
170
243
|
expect(nodes[0].id).to.equal(exampleId(3));
|
|
171
244
|
}
|
|
@@ -173,15 +246,15 @@ describe('GraphBuilder', () => {
|
|
|
173
246
|
|
|
174
247
|
test('nodes are updated when removed', () => {
|
|
175
248
|
const registry = Registry.make();
|
|
176
|
-
const builder =
|
|
177
|
-
const name =
|
|
249
|
+
const builder = GraphBuilder.make({ registry });
|
|
250
|
+
const name = Atom.make('removed');
|
|
178
251
|
|
|
179
|
-
|
|
180
|
-
|
|
252
|
+
GraphBuilder.addExtension(builder, [
|
|
253
|
+
GraphBuilder.createExtensionRaw({
|
|
181
254
|
id: 'root',
|
|
182
255
|
connector: (node) =>
|
|
183
|
-
|
|
184
|
-
pipe(
|
|
256
|
+
Atom.make((get) =>
|
|
257
|
+
Function.pipe(
|
|
185
258
|
get(node),
|
|
186
259
|
Option.flatMap((node) => (node.id === 'root' ? Option.some(get(name)) : Option.none())),
|
|
187
260
|
Option.filter((name) => name !== 'removed'),
|
|
@@ -202,7 +275,7 @@ describe('GraphBuilder', () => {
|
|
|
202
275
|
});
|
|
203
276
|
onTestFinished(() => cancel());
|
|
204
277
|
|
|
205
|
-
|
|
278
|
+
Graph.expand(graph, Node.RootId);
|
|
206
279
|
expect(count).to.equal(0);
|
|
207
280
|
expect(exists).to.be.false;
|
|
208
281
|
|
|
@@ -221,23 +294,24 @@ describe('GraphBuilder', () => {
|
|
|
221
294
|
|
|
222
295
|
test('sort edges', async () => {
|
|
223
296
|
const registry = Registry.make();
|
|
224
|
-
const builder =
|
|
225
|
-
const nodes =
|
|
297
|
+
const builder = GraphBuilder.make({ registry });
|
|
298
|
+
const nodes = Atom.make([
|
|
226
299
|
{ id: exampleId(1), type: EXAMPLE_TYPE, data: 1 },
|
|
227
300
|
{ id: exampleId(2), type: EXAMPLE_TYPE, data: 2 },
|
|
228
301
|
{ id: exampleId(3), type: EXAMPLE_TYPE, data: 3 },
|
|
229
302
|
]);
|
|
230
|
-
|
|
231
|
-
|
|
303
|
+
GraphBuilder.addExtension(
|
|
304
|
+
builder,
|
|
305
|
+
GraphBuilder.createExtensionRaw({
|
|
232
306
|
id: 'connector',
|
|
233
|
-
connector: () =>
|
|
307
|
+
connector: () => Atom.make((get) => get(nodes)),
|
|
234
308
|
}),
|
|
235
309
|
);
|
|
236
310
|
const graph = builder.graph;
|
|
237
|
-
|
|
311
|
+
Graph.expand(graph, Node.RootId);
|
|
238
312
|
|
|
239
313
|
{
|
|
240
|
-
const nodes = registry.get(graph.connections(
|
|
314
|
+
const nodes = registry.get(graph.connections(Node.RootId));
|
|
241
315
|
expect(nodes).has.length(3);
|
|
242
316
|
expect(nodes[0].id).to.equal(exampleId(1));
|
|
243
317
|
expect(nodes[1].id).to.equal(exampleId(2));
|
|
@@ -254,7 +328,7 @@ describe('GraphBuilder', () => {
|
|
|
254
328
|
await sleep(0);
|
|
255
329
|
|
|
256
330
|
{
|
|
257
|
-
const nodes = registry.get(graph.connections(
|
|
331
|
+
const nodes = registry.get(graph.connections(Node.RootId));
|
|
258
332
|
expect(nodes).has.length(3);
|
|
259
333
|
expect(nodes[0].id).to.equal(exampleId(3));
|
|
260
334
|
expect(nodes[1].id).to.equal(exampleId(1));
|
|
@@ -264,16 +338,16 @@ describe('GraphBuilder', () => {
|
|
|
264
338
|
|
|
265
339
|
test('updates are constrained', () => {
|
|
266
340
|
const registry = Registry.make();
|
|
267
|
-
const builder =
|
|
268
|
-
const name =
|
|
269
|
-
const sub =
|
|
341
|
+
const builder = GraphBuilder.make({ registry });
|
|
342
|
+
const name = Atom.make('default');
|
|
343
|
+
const sub = Atom.make('default');
|
|
270
344
|
|
|
271
|
-
|
|
272
|
-
|
|
345
|
+
GraphBuilder.addExtension(builder, [
|
|
346
|
+
GraphBuilder.createExtensionRaw({
|
|
273
347
|
id: 'root',
|
|
274
348
|
connector: (node) =>
|
|
275
|
-
|
|
276
|
-
pipe(
|
|
349
|
+
Atom.make((get) =>
|
|
350
|
+
Function.pipe(
|
|
277
351
|
get(node),
|
|
278
352
|
Option.flatMap((node) => (node.id === 'root' ? Option.some(get(name)) : Option.none())),
|
|
279
353
|
Option.filter((name) => name !== 'removed'),
|
|
@@ -282,11 +356,11 @@ describe('GraphBuilder', () => {
|
|
|
282
356
|
),
|
|
283
357
|
),
|
|
284
358
|
}),
|
|
285
|
-
|
|
359
|
+
GraphBuilder.createExtensionRaw({
|
|
286
360
|
id: 'connector1',
|
|
287
361
|
connector: (node) =>
|
|
288
|
-
|
|
289
|
-
pipe(
|
|
362
|
+
Atom.make((get) =>
|
|
363
|
+
Function.pipe(
|
|
290
364
|
get(node),
|
|
291
365
|
Option.flatMap((node) => (node.id === EXAMPLE_ID ? Option.some(get(sub)) : Option.none())),
|
|
292
366
|
Option.map((sub) => [{ id: exampleId(2), type: EXAMPLE_TYPE, data: sub }]),
|
|
@@ -294,11 +368,11 @@ describe('GraphBuilder', () => {
|
|
|
294
368
|
),
|
|
295
369
|
),
|
|
296
370
|
}),
|
|
297
|
-
|
|
371
|
+
GraphBuilder.createExtensionRaw({
|
|
298
372
|
id: 'connector2',
|
|
299
373
|
connector: (node) =>
|
|
300
|
-
|
|
301
|
-
pipe(
|
|
374
|
+
Atom.make((get) =>
|
|
375
|
+
Function.pipe(
|
|
302
376
|
get(node),
|
|
303
377
|
Option.flatMap((node) => (node.id === EXAMPLE_ID ? Option.some(node.data) : Option.none())),
|
|
304
378
|
Option.map((data) => [{ id: exampleId(3), type: EXAMPLE_TYPE, data }]),
|
|
@@ -329,13 +403,13 @@ describe('GraphBuilder', () => {
|
|
|
329
403
|
onTestFinished(() => dependentCancel());
|
|
330
404
|
|
|
331
405
|
// Counts should not increment until the node is expanded.
|
|
332
|
-
|
|
406
|
+
Graph.expand(graph, Node.RootId);
|
|
333
407
|
expect(parentCount).to.equal(1);
|
|
334
408
|
expect(independentCount).to.equal(0);
|
|
335
409
|
expect(dependentCount).to.equal(0);
|
|
336
410
|
|
|
337
411
|
// Counts should increment when the node is expanded.
|
|
338
|
-
|
|
412
|
+
Graph.expand(graph, EXAMPLE_ID);
|
|
339
413
|
expect(parentCount).to.equal(1);
|
|
340
414
|
expect(independentCount).to.equal(1);
|
|
341
415
|
expect(dependentCount).to.equal(1);
|
|
@@ -353,7 +427,7 @@ describe('GraphBuilder', () => {
|
|
|
353
427
|
expect(dependentCount).to.equal(2);
|
|
354
428
|
|
|
355
429
|
// Independent count should update if its state changes even if the parent is removed.
|
|
356
|
-
|
|
430
|
+
Atom.batch(() => {
|
|
357
431
|
registry.set(name, 'removed');
|
|
358
432
|
registry.set(sub, 'batch');
|
|
359
433
|
});
|
|
@@ -368,7 +442,7 @@ describe('GraphBuilder', () => {
|
|
|
368
442
|
expect(dependentCount).to.equal(3);
|
|
369
443
|
|
|
370
444
|
// Counts should not increment when the node is expanded again.
|
|
371
|
-
|
|
445
|
+
Graph.expand(graph, EXAMPLE_ID);
|
|
372
446
|
expect(parentCount).to.equal(3);
|
|
373
447
|
expect(independentCount).to.equal(3);
|
|
374
448
|
expect(dependentCount).to.equal(3);
|
|
@@ -376,13 +450,14 @@ describe('GraphBuilder', () => {
|
|
|
376
450
|
|
|
377
451
|
test('eager graph expansion', async () => {
|
|
378
452
|
const registry = Registry.make();
|
|
379
|
-
const builder =
|
|
380
|
-
|
|
381
|
-
|
|
453
|
+
const builder = GraphBuilder.make({ registry });
|
|
454
|
+
GraphBuilder.addExtension(
|
|
455
|
+
builder,
|
|
456
|
+
GraphBuilder.createExtensionRaw({
|
|
382
457
|
id: 'connector',
|
|
383
458
|
connector: (node) => {
|
|
384
|
-
return
|
|
385
|
-
pipe(
|
|
459
|
+
return Atom.make((get) =>
|
|
460
|
+
Function.pipe(
|
|
386
461
|
get(node),
|
|
387
462
|
Option.map((node) => (node.data ? node.data + 1 : 1)),
|
|
388
463
|
Option.filter((data) => data <= 5),
|
|
@@ -397,14 +472,14 @@ describe('GraphBuilder', () => {
|
|
|
397
472
|
let count = 0;
|
|
398
473
|
const trigger = new Trigger();
|
|
399
474
|
builder.graph.onNodeChanged.on(({ id }) => {
|
|
400
|
-
builder.graph
|
|
475
|
+
Graph.expand(builder.graph, id);
|
|
401
476
|
count++;
|
|
402
477
|
if (count === 5) {
|
|
403
478
|
trigger.wake();
|
|
404
479
|
}
|
|
405
480
|
});
|
|
406
481
|
|
|
407
|
-
builder.graph.
|
|
482
|
+
Graph.expand(builder.graph, Node.RootId);
|
|
408
483
|
await trigger.wait();
|
|
409
484
|
expect(count).to.equal(5);
|
|
410
485
|
});
|
|
@@ -412,13 +487,14 @@ describe('GraphBuilder', () => {
|
|
|
412
487
|
|
|
413
488
|
describe('explore', () => {
|
|
414
489
|
test('works', async () => {
|
|
415
|
-
const builder =
|
|
416
|
-
|
|
417
|
-
|
|
490
|
+
const builder = GraphBuilder.make();
|
|
491
|
+
GraphBuilder.addExtension(
|
|
492
|
+
builder,
|
|
493
|
+
GraphBuilder.createExtensionRaw({
|
|
418
494
|
id: 'connector',
|
|
419
495
|
connector: (node) =>
|
|
420
|
-
|
|
421
|
-
pipe(
|
|
496
|
+
Atom.make((get) =>
|
|
497
|
+
Function.pipe(
|
|
422
498
|
get(node),
|
|
423
499
|
Option.map((node) => (node.data ? node.data + 1 : 1)),
|
|
424
500
|
Option.filter((data) => data <= 5),
|
|
@@ -430,7 +506,7 @@ describe('GraphBuilder', () => {
|
|
|
430
506
|
);
|
|
431
507
|
|
|
432
508
|
let count = 0;
|
|
433
|
-
await
|
|
509
|
+
await GraphBuilder.explore(builder, {
|
|
434
510
|
visitor: () => {
|
|
435
511
|
count++;
|
|
436
512
|
},
|
|
@@ -439,4 +515,402 @@ describe('GraphBuilder', () => {
|
|
|
439
515
|
expect(count).to.equal(6);
|
|
440
516
|
});
|
|
441
517
|
});
|
|
518
|
+
|
|
519
|
+
describe('helpers', () => {
|
|
520
|
+
describe('createConnector', () => {
|
|
521
|
+
test('creates connector with type inference', () => {
|
|
522
|
+
const registry = Registry.make();
|
|
523
|
+
const builder = GraphBuilder.make({ registry });
|
|
524
|
+
const graph = builder.graph;
|
|
525
|
+
|
|
526
|
+
const matcher = (node: Node.Node) => NodeMatcher.whenId('root')(node);
|
|
527
|
+
const factory = (node: Node.Node) => [{ id: 'child', type: EXAMPLE_TYPE, data: node.id }];
|
|
528
|
+
|
|
529
|
+
const connector = GraphBuilder.createConnector(matcher, factory);
|
|
530
|
+
|
|
531
|
+
GraphBuilder.addExtension(
|
|
532
|
+
builder,
|
|
533
|
+
GraphBuilder.createExtensionRaw({
|
|
534
|
+
id: 'test-connector',
|
|
535
|
+
connector,
|
|
536
|
+
}),
|
|
537
|
+
);
|
|
538
|
+
|
|
539
|
+
Graph.expand(graph, Node.RootId);
|
|
540
|
+
|
|
541
|
+
const connections = registry.get(graph.connections(Node.RootId));
|
|
542
|
+
expect(connections).has.length(1);
|
|
543
|
+
expect(connections[0].id).to.equal('child');
|
|
544
|
+
});
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
describe('createExtension', () => {
|
|
548
|
+
test('works with Effect connector', () => {
|
|
549
|
+
const registry = Registry.make();
|
|
550
|
+
const builder = GraphBuilder.make({ registry });
|
|
551
|
+
const graph = builder.graph;
|
|
552
|
+
|
|
553
|
+
const extensions = Effect.runSync(
|
|
554
|
+
GraphBuilder.createExtension({
|
|
555
|
+
id: 'test-extension',
|
|
556
|
+
match: NodeMatcher.whenNodeType(EXAMPLE_TYPE),
|
|
557
|
+
connector: (node, get) => Effect.succeed([{ id: 'child', type: EXAMPLE_TYPE, data: node.data }]),
|
|
558
|
+
}),
|
|
559
|
+
);
|
|
560
|
+
|
|
561
|
+
GraphBuilder.addExtension(builder, extensions);
|
|
562
|
+
|
|
563
|
+
const writableGraph = graph as Graph.WritableGraph;
|
|
564
|
+
Graph.addNode(writableGraph, { id: 'parent', type: EXAMPLE_TYPE, properties: {}, data: 'test' });
|
|
565
|
+
Graph.expand(graph, 'parent');
|
|
566
|
+
|
|
567
|
+
const connections = registry.get(graph.connections('parent'));
|
|
568
|
+
expect(connections).has.length(1);
|
|
569
|
+
expect(connections[0].id).to.equal('child');
|
|
570
|
+
expect(connections[0].data).to.equal('test');
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
test('works with Effect actions', () => {
|
|
574
|
+
const registry = Registry.make();
|
|
575
|
+
const builder = GraphBuilder.make({ registry });
|
|
576
|
+
const graph = builder.graph;
|
|
577
|
+
|
|
578
|
+
const extensions = Effect.runSync(
|
|
579
|
+
GraphBuilder.createExtension({
|
|
580
|
+
id: 'test-extension',
|
|
581
|
+
match: NodeMatcher.whenNodeType(EXAMPLE_TYPE),
|
|
582
|
+
actions: (node, get) =>
|
|
583
|
+
Effect.succeed([
|
|
584
|
+
{
|
|
585
|
+
id: 'test-action',
|
|
586
|
+
data: () =>
|
|
587
|
+
Effect.sync(() => {
|
|
588
|
+
console.log('TestAction');
|
|
589
|
+
}),
|
|
590
|
+
properties: { label: 'Test' },
|
|
591
|
+
},
|
|
592
|
+
]),
|
|
593
|
+
}),
|
|
594
|
+
);
|
|
595
|
+
|
|
596
|
+
GraphBuilder.addExtension(builder, extensions);
|
|
597
|
+
|
|
598
|
+
const writableGraph = graph as Graph.WritableGraph;
|
|
599
|
+
Graph.addNode(writableGraph, { id: 'parent', type: EXAMPLE_TYPE, properties: {}, data: 'test' });
|
|
600
|
+
Graph.expand(graph, 'parent');
|
|
601
|
+
|
|
602
|
+
const actions = registry.get(graph.actions('parent'));
|
|
603
|
+
expect(actions).has.length(1);
|
|
604
|
+
expect(actions[0].id).to.equal('test-action');
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
test('_actionContext captures and provides services to action execution', () => {
|
|
608
|
+
const registry = Registry.make();
|
|
609
|
+
const builder = GraphBuilder.make({ registry });
|
|
610
|
+
const graph = builder.graph;
|
|
611
|
+
|
|
612
|
+
// Define a test service using Context.GenericTag pattern.
|
|
613
|
+
interface TestServiceInterface {
|
|
614
|
+
getValue(): number;
|
|
615
|
+
}
|
|
616
|
+
const TestService = Context.GenericTag<TestServiceInterface>('TestService');
|
|
617
|
+
|
|
618
|
+
// Track whether the action was executed with the correct context.
|
|
619
|
+
let executionResult: number | null = null;
|
|
620
|
+
|
|
621
|
+
// Create extension with service requirement.
|
|
622
|
+
// Note: The actions callback must USE the service for R to be inferred correctly.
|
|
623
|
+
const extensions = Effect.runSync(
|
|
624
|
+
GraphBuilder.createExtension({
|
|
625
|
+
id: 'test-extension',
|
|
626
|
+
match: NodeMatcher.whenNodeType(EXAMPLE_TYPE),
|
|
627
|
+
actions: (node, get) =>
|
|
628
|
+
// Use TestService in the callback to include it in R.
|
|
629
|
+
Effect.gen(function* () {
|
|
630
|
+
const service = yield* TestService;
|
|
631
|
+
return [
|
|
632
|
+
{
|
|
633
|
+
id: 'test-action',
|
|
634
|
+
data: () =>
|
|
635
|
+
Effect.gen(function* () {
|
|
636
|
+
// Action can use the same service from captured context.
|
|
637
|
+
const svc = yield* TestService;
|
|
638
|
+
executionResult = svc.getValue();
|
|
639
|
+
}).pipe(Effect.asVoid),
|
|
640
|
+
properties: { label: `Test ${service.getValue()}` },
|
|
641
|
+
},
|
|
642
|
+
];
|
|
643
|
+
}),
|
|
644
|
+
}).pipe(Effect.provideService(TestService, { getValue: () => 42 })),
|
|
645
|
+
);
|
|
646
|
+
|
|
647
|
+
GraphBuilder.addExtension(builder, extensions);
|
|
648
|
+
|
|
649
|
+
const writableGraph = graph as Graph.WritableGraph;
|
|
650
|
+
Graph.addNode(writableGraph, { id: 'parent', type: EXAMPLE_TYPE, properties: {}, data: 'test' });
|
|
651
|
+
Graph.expand(graph, 'parent');
|
|
652
|
+
|
|
653
|
+
const actions = registry.get(graph.actions('parent'));
|
|
654
|
+
expect(actions).has.length(1);
|
|
655
|
+
|
|
656
|
+
// Verify _actionContext is captured.
|
|
657
|
+
const action = actions[0] as Node.Action;
|
|
658
|
+
expect(action._actionContext).to.not.be.undefined;
|
|
659
|
+
|
|
660
|
+
// Execute the action with the captured context.
|
|
661
|
+
const actionEffect = action.data();
|
|
662
|
+
const effectWithContext = action._actionContext
|
|
663
|
+
? actionEffect.pipe(Effect.provide(action._actionContext))
|
|
664
|
+
: actionEffect;
|
|
665
|
+
|
|
666
|
+
Effect.runSync(effectWithContext);
|
|
667
|
+
|
|
668
|
+
// Verify the service was accessible during execution.
|
|
669
|
+
expect(executionResult).to.equal(42);
|
|
670
|
+
});
|
|
671
|
+
|
|
672
|
+
test('works with resolver', async () => {
|
|
673
|
+
const registry = Registry.make();
|
|
674
|
+
const builder = GraphBuilder.make({ registry });
|
|
675
|
+
const graph = builder.graph;
|
|
676
|
+
|
|
677
|
+
const extensions = Effect.runSync(
|
|
678
|
+
GraphBuilder.createExtension({
|
|
679
|
+
id: 'test-extension',
|
|
680
|
+
match: NodeMatcher.whenNodeType(EXAMPLE_TYPE),
|
|
681
|
+
resolver: (id, get) => Effect.succeed({ id, type: EXAMPLE_TYPE, properties: {}, data: 'resolved' }),
|
|
682
|
+
}),
|
|
683
|
+
);
|
|
684
|
+
|
|
685
|
+
GraphBuilder.addExtension(builder, extensions);
|
|
686
|
+
await Graph.initialize(graph, EXAMPLE_ID);
|
|
687
|
+
|
|
688
|
+
const node = Graph.getNode(graph, EXAMPLE_ID).pipe(Option.getOrNull);
|
|
689
|
+
expect(node).to.not.be.null;
|
|
690
|
+
expect(node?.id).to.equal(EXAMPLE_ID);
|
|
691
|
+
expect(node?.data).to.equal('resolved');
|
|
692
|
+
});
|
|
693
|
+
|
|
694
|
+
test('works with connector and actions together', () => {
|
|
695
|
+
const registry = Registry.make();
|
|
696
|
+
const builder = GraphBuilder.make({ registry });
|
|
697
|
+
const graph = builder.graph;
|
|
698
|
+
|
|
699
|
+
const extensions = Effect.runSync(
|
|
700
|
+
GraphBuilder.createExtension({
|
|
701
|
+
id: 'test-extension',
|
|
702
|
+
match: NodeMatcher.whenNodeType(EXAMPLE_TYPE),
|
|
703
|
+
connector: (node, get) => Effect.succeed([{ id: 'child', type: EXAMPLE_TYPE, data: node.data }]),
|
|
704
|
+
actions: (node, get) =>
|
|
705
|
+
Effect.succeed([
|
|
706
|
+
{
|
|
707
|
+
id: 'test-action',
|
|
708
|
+
data: () =>
|
|
709
|
+
Effect.sync(() => {
|
|
710
|
+
console.log('TestAction');
|
|
711
|
+
}),
|
|
712
|
+
properties: { label: 'Test' },
|
|
713
|
+
},
|
|
714
|
+
]),
|
|
715
|
+
}),
|
|
716
|
+
);
|
|
717
|
+
|
|
718
|
+
GraphBuilder.addExtension(builder, extensions);
|
|
719
|
+
|
|
720
|
+
const writableGraph = graph as Graph.WritableGraph;
|
|
721
|
+
Graph.addNode(writableGraph, { id: 'parent', type: EXAMPLE_TYPE, properties: {}, data: 'test' });
|
|
722
|
+
Graph.expand(graph, 'parent');
|
|
723
|
+
|
|
724
|
+
const connections = registry.get(graph.connections('parent'));
|
|
725
|
+
// Should have both the child node and the action node.
|
|
726
|
+
expect(connections.length).to.be.greaterThanOrEqual(1);
|
|
727
|
+
const childNode = connections.find((n) => n.id === 'child');
|
|
728
|
+
expect(childNode).to.not.be.undefined;
|
|
729
|
+
expect(childNode?.data).to.equal('test');
|
|
730
|
+
|
|
731
|
+
const actions = registry.get(graph.actions('parent'));
|
|
732
|
+
expect(actions).has.length(1);
|
|
733
|
+
expect(actions[0].id).to.equal('test-action');
|
|
734
|
+
});
|
|
735
|
+
|
|
736
|
+
test('works with reactive connector using get context', () => {
|
|
737
|
+
const registry = Registry.make();
|
|
738
|
+
const builder = GraphBuilder.make({ registry });
|
|
739
|
+
const graph = builder.graph;
|
|
740
|
+
|
|
741
|
+
const state = Atom.make('initial');
|
|
742
|
+
|
|
743
|
+
const extensions = Effect.runSync(
|
|
744
|
+
GraphBuilder.createExtension({
|
|
745
|
+
id: 'test-extension',
|
|
746
|
+
match: NodeMatcher.whenNodeType(EXAMPLE_TYPE),
|
|
747
|
+
connector: (node, get) => Effect.succeed([{ id: 'child', type: EXAMPLE_TYPE, data: get(state) }]),
|
|
748
|
+
}),
|
|
749
|
+
);
|
|
750
|
+
|
|
751
|
+
GraphBuilder.addExtension(builder, extensions);
|
|
752
|
+
|
|
753
|
+
const writableGraph = graph as Graph.WritableGraph;
|
|
754
|
+
Graph.addNode(writableGraph, { id: 'parent', type: EXAMPLE_TYPE, properties: {}, data: 'test' });
|
|
755
|
+
Graph.expand(graph, 'parent');
|
|
756
|
+
|
|
757
|
+
{
|
|
758
|
+
const connections = registry.get(graph.connections('parent'));
|
|
759
|
+
expect(connections).has.length(1);
|
|
760
|
+
expect(connections[0].data).to.equal('initial');
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
registry.set(state, 'updated');
|
|
764
|
+
|
|
765
|
+
{
|
|
766
|
+
const connections = registry.get(graph.connections('parent'));
|
|
767
|
+
expect(connections).has.length(1);
|
|
768
|
+
expect(connections[0].data).to.equal('updated');
|
|
769
|
+
}
|
|
770
|
+
});
|
|
771
|
+
});
|
|
772
|
+
|
|
773
|
+
describe('extension error handling', () => {
|
|
774
|
+
test('connector failure is caught and logged, returns empty array', () => {
|
|
775
|
+
const registry = Registry.make();
|
|
776
|
+
const builder = GraphBuilder.make({ registry });
|
|
777
|
+
const graph = builder.graph;
|
|
778
|
+
|
|
779
|
+
const extensions = Effect.runSync(
|
|
780
|
+
GraphBuilder.createExtension({
|
|
781
|
+
id: 'failing-extension',
|
|
782
|
+
match: NodeMatcher.whenNodeType(EXAMPLE_TYPE),
|
|
783
|
+
connector: (node, get) => Effect.fail(new Error('Connector failed intentionally')),
|
|
784
|
+
}),
|
|
785
|
+
);
|
|
786
|
+
|
|
787
|
+
GraphBuilder.addExtension(builder, extensions);
|
|
788
|
+
|
|
789
|
+
const writableGraph = graph as Graph.WritableGraph;
|
|
790
|
+
Graph.addNode(writableGraph, { id: 'parent', type: EXAMPLE_TYPE, properties: {}, data: 'test' });
|
|
791
|
+
|
|
792
|
+
// Should not throw, error is caught internally.
|
|
793
|
+
Graph.expand(graph, 'parent');
|
|
794
|
+
|
|
795
|
+
// Should return empty connections since the connector failed.
|
|
796
|
+
const connections = registry.get(graph.connections('parent'));
|
|
797
|
+
expect(connections).has.length(0);
|
|
798
|
+
});
|
|
799
|
+
|
|
800
|
+
test('actions failure is caught and logged, returns empty array', () => {
|
|
801
|
+
const registry = Registry.make();
|
|
802
|
+
const builder = GraphBuilder.make({ registry });
|
|
803
|
+
const graph = builder.graph;
|
|
804
|
+
|
|
805
|
+
const extensions = Effect.runSync(
|
|
806
|
+
GraphBuilder.createExtension({
|
|
807
|
+
id: 'failing-actions-extension',
|
|
808
|
+
match: NodeMatcher.whenNodeType(EXAMPLE_TYPE),
|
|
809
|
+
actions: (node, get) => Effect.fail(new Error('Actions failed intentionally')),
|
|
810
|
+
}),
|
|
811
|
+
);
|
|
812
|
+
|
|
813
|
+
GraphBuilder.addExtension(builder, extensions);
|
|
814
|
+
|
|
815
|
+
const writableGraph = graph as Graph.WritableGraph;
|
|
816
|
+
Graph.addNode(writableGraph, { id: 'parent', type: EXAMPLE_TYPE, properties: {}, data: 'test' });
|
|
817
|
+
|
|
818
|
+
// Should not throw, error is caught internally.
|
|
819
|
+
Graph.expand(graph, 'parent');
|
|
820
|
+
|
|
821
|
+
// Should return empty actions since the actions callback failed.
|
|
822
|
+
const actions = registry.get(graph.actions('parent'));
|
|
823
|
+
expect(actions).has.length(0);
|
|
824
|
+
});
|
|
825
|
+
|
|
826
|
+
test('resolver failure is caught and logged, returns null', async () => {
|
|
827
|
+
const registry = Registry.make();
|
|
828
|
+
const builder = GraphBuilder.make({ registry });
|
|
829
|
+
const graph = builder.graph;
|
|
830
|
+
|
|
831
|
+
const extensions = Effect.runSync(
|
|
832
|
+
GraphBuilder.createExtension({
|
|
833
|
+
id: 'failing-resolver-extension',
|
|
834
|
+
match: NodeMatcher.whenNodeType(EXAMPLE_TYPE),
|
|
835
|
+
resolver: (id, get) => Effect.fail(new Error('Resolver failed intentionally')),
|
|
836
|
+
}),
|
|
837
|
+
);
|
|
838
|
+
|
|
839
|
+
GraphBuilder.addExtension(builder, extensions);
|
|
840
|
+
|
|
841
|
+
// Should not throw, error is caught internally.
|
|
842
|
+
await Graph.initialize(graph, EXAMPLE_ID);
|
|
843
|
+
|
|
844
|
+
// Should return null/none since the resolver failed.
|
|
845
|
+
const node = Graph.getNode(graph, EXAMPLE_ID).pipe(Option.getOrNull);
|
|
846
|
+
expect(node).to.be.null;
|
|
847
|
+
});
|
|
848
|
+
|
|
849
|
+
test('failing extension does not affect other extensions', () => {
|
|
850
|
+
const registry = Registry.make();
|
|
851
|
+
const builder = GraphBuilder.make({ registry });
|
|
852
|
+
const graph = builder.graph;
|
|
853
|
+
|
|
854
|
+
// Add a failing extension.
|
|
855
|
+
const failingExtensions = Effect.runSync(
|
|
856
|
+
GraphBuilder.createExtension({
|
|
857
|
+
id: 'failing-extension',
|
|
858
|
+
match: NodeMatcher.whenNodeType(EXAMPLE_TYPE),
|
|
859
|
+
connector: (node, get) => Effect.fail(new Error('This one fails')),
|
|
860
|
+
}),
|
|
861
|
+
);
|
|
862
|
+
|
|
863
|
+
// Add a working extension.
|
|
864
|
+
const workingExtensions = Effect.runSync(
|
|
865
|
+
GraphBuilder.createExtension({
|
|
866
|
+
id: 'working-extension',
|
|
867
|
+
match: NodeMatcher.whenNodeType(EXAMPLE_TYPE),
|
|
868
|
+
connector: (node, get) =>
|
|
869
|
+
Effect.succeed([{ id: 'child-from-working', type: EXAMPLE_TYPE, data: 'success' }]),
|
|
870
|
+
}),
|
|
871
|
+
);
|
|
872
|
+
|
|
873
|
+
GraphBuilder.addExtension(builder, failingExtensions);
|
|
874
|
+
GraphBuilder.addExtension(builder, workingExtensions);
|
|
875
|
+
|
|
876
|
+
const writableGraph = graph as Graph.WritableGraph;
|
|
877
|
+
Graph.addNode(writableGraph, { id: 'parent', type: EXAMPLE_TYPE, properties: {}, data: 'test' });
|
|
878
|
+
Graph.expand(graph, 'parent');
|
|
879
|
+
|
|
880
|
+
// The working extension should still produce its node.
|
|
881
|
+
const connections = registry.get(graph.connections('parent'));
|
|
882
|
+
expect(connections).has.length(1);
|
|
883
|
+
expect(connections[0].id).to.equal('child-from-working');
|
|
884
|
+
expect(connections[0].data).to.equal('success');
|
|
885
|
+
});
|
|
886
|
+
});
|
|
887
|
+
|
|
888
|
+
describe('createTypeExtension', () => {
|
|
889
|
+
test('creates extension matching by schema type with inferred object type', () => {
|
|
890
|
+
const registry = Registry.make();
|
|
891
|
+
const builder = GraphBuilder.make({ registry });
|
|
892
|
+
const graph = builder.graph;
|
|
893
|
+
|
|
894
|
+
const extensions = Effect.runSync(
|
|
895
|
+
GraphBuilder.createTypeExtension({
|
|
896
|
+
id: 'type-extension',
|
|
897
|
+
type: TestSchema.Person,
|
|
898
|
+
connector: (object) => Effect.succeed([{ id: 'child', type: EXAMPLE_TYPE, data: object }]),
|
|
899
|
+
}),
|
|
900
|
+
);
|
|
901
|
+
|
|
902
|
+
GraphBuilder.addExtension(builder, extensions);
|
|
903
|
+
|
|
904
|
+
const writableGraph = graph as Graph.WritableGraph;
|
|
905
|
+
const testObject = Obj.make(TestSchema.Person, { name: 'Test' });
|
|
906
|
+
Graph.addNode(writableGraph, { id: 'parent', type: EXAMPLE_TYPE, properties: {}, data: testObject });
|
|
907
|
+
Graph.expand(graph, 'parent');
|
|
908
|
+
|
|
909
|
+
const connections = registry.get(graph.connections('parent'));
|
|
910
|
+
expect(connections).has.length(1);
|
|
911
|
+
expect(connections[0].id).to.equal('child');
|
|
912
|
+
expect(connections[0].data).to.equal(testObject);
|
|
913
|
+
});
|
|
914
|
+
});
|
|
915
|
+
});
|
|
442
916
|
});
|