@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
|
@@ -2,16 +2,21 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { Atom, Registry } from '@effect-atom/atom-react';
|
|
6
|
+
import * as Context from 'effect/Context';
|
|
7
|
+
import * as Effect from 'effect/Effect';
|
|
6
8
|
import * as Function from 'effect/Function';
|
|
7
9
|
import * as Option from 'effect/Option';
|
|
8
10
|
import { describe, expect, onTestFinished, test } from 'vitest';
|
|
9
11
|
|
|
10
12
|
import { Trigger, sleep } from '@dxos/async';
|
|
13
|
+
import { Obj } from '@dxos/echo';
|
|
14
|
+
import { TestSchema } from '@dxos/echo/testing';
|
|
11
15
|
|
|
12
|
-
import
|
|
13
|
-
import
|
|
14
|
-
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';
|
|
15
20
|
|
|
16
21
|
const exampleId = (id: number) => `dx:test:${id}`;
|
|
17
22
|
const EXAMPLE_ID = exampleId(1);
|
|
@@ -21,27 +26,29 @@ describe('GraphBuilder', () => {
|
|
|
21
26
|
describe('resolver', () => {
|
|
22
27
|
test('works', async () => {
|
|
23
28
|
const registry = Registry.make();
|
|
24
|
-
const builder =
|
|
29
|
+
const builder = GraphBuilder.make({ registry });
|
|
25
30
|
const graph = builder.graph;
|
|
26
31
|
|
|
27
32
|
{
|
|
28
|
-
const node =
|
|
33
|
+
const node = Graph.getNode(graph, EXAMPLE_ID).pipe(Option.getOrNull);
|
|
29
34
|
expect(node).to.be.null;
|
|
30
35
|
}
|
|
31
36
|
|
|
32
|
-
|
|
33
|
-
|
|
37
|
+
// Test direct API
|
|
38
|
+
GraphBuilder.addExtension(
|
|
39
|
+
builder,
|
|
40
|
+
GraphBuilder.createExtensionRaw({
|
|
34
41
|
id: 'resolver',
|
|
35
42
|
resolver: () => {
|
|
36
43
|
console.log('resolver');
|
|
37
|
-
return
|
|
44
|
+
return Atom.make({ id: EXAMPLE_ID, type: EXAMPLE_TYPE, data: 1 });
|
|
38
45
|
},
|
|
39
46
|
}),
|
|
40
47
|
);
|
|
41
|
-
await
|
|
48
|
+
await Graph.initialize(graph, EXAMPLE_ID);
|
|
42
49
|
|
|
43
50
|
{
|
|
44
|
-
const node =
|
|
51
|
+
const node = Graph.getNode(graph, EXAMPLE_ID).pipe(Option.getOrNull);
|
|
45
52
|
expect(node?.id).to.equal(EXAMPLE_ID);
|
|
46
53
|
expect(node?.type).to.equal(EXAMPLE_TYPE);
|
|
47
54
|
expect(node?.data).to.equal(1);
|
|
@@ -50,26 +57,27 @@ describe('GraphBuilder', () => {
|
|
|
50
57
|
|
|
51
58
|
test('updates', async () => {
|
|
52
59
|
const registry = Registry.make();
|
|
53
|
-
const builder =
|
|
54
|
-
const name =
|
|
55
|
-
|
|
56
|
-
|
|
60
|
+
const builder = GraphBuilder.make({ registry });
|
|
61
|
+
const name = Atom.make('default');
|
|
62
|
+
GraphBuilder.addExtension(
|
|
63
|
+
builder,
|
|
64
|
+
GraphBuilder.createExtensionRaw({
|
|
57
65
|
id: 'resolver',
|
|
58
|
-
resolver: () =>
|
|
66
|
+
resolver: () => Atom.make((get) => ({ id: EXAMPLE_ID, type: EXAMPLE_TYPE, data: get(name) })),
|
|
59
67
|
}),
|
|
60
68
|
);
|
|
61
69
|
const graph = builder.graph;
|
|
62
|
-
await
|
|
70
|
+
await Graph.initialize(graph, EXAMPLE_ID);
|
|
63
71
|
|
|
64
72
|
{
|
|
65
|
-
const node =
|
|
73
|
+
const node = Graph.getNode(graph, EXAMPLE_ID).pipe(Option.getOrNull);
|
|
66
74
|
expect(node?.data).to.equal('default');
|
|
67
75
|
}
|
|
68
76
|
|
|
69
77
|
registry.set(name, 'updated');
|
|
70
78
|
|
|
71
79
|
{
|
|
72
|
-
const node =
|
|
80
|
+
const node = Graph.getNode(graph, EXAMPLE_ID).pipe(Option.getOrNull);
|
|
73
81
|
expect(node?.data).to.equal('updated');
|
|
74
82
|
}
|
|
75
83
|
});
|
|
@@ -78,27 +86,29 @@ describe('GraphBuilder', () => {
|
|
|
78
86
|
describe('connector', () => {
|
|
79
87
|
test('works', () => {
|
|
80
88
|
const registry = Registry.make();
|
|
81
|
-
const builder =
|
|
82
|
-
|
|
83
|
-
|
|
89
|
+
const builder = GraphBuilder.make({ registry });
|
|
90
|
+
GraphBuilder.addExtension(
|
|
91
|
+
builder,
|
|
92
|
+
GraphBuilder.createExtensionRaw({
|
|
84
93
|
id: 'outbound-connector',
|
|
85
|
-
connector: () =>
|
|
94
|
+
connector: () => Atom.make([{ id: 'child', type: EXAMPLE_TYPE, data: 2 }]),
|
|
86
95
|
}),
|
|
87
96
|
);
|
|
88
|
-
|
|
89
|
-
|
|
97
|
+
GraphBuilder.addExtension(
|
|
98
|
+
builder,
|
|
99
|
+
GraphBuilder.createExtensionRaw({
|
|
90
100
|
id: 'inbound-connector',
|
|
91
101
|
relation: 'inbound',
|
|
92
|
-
connector: () =>
|
|
102
|
+
connector: () => Atom.make([{ id: 'parent', type: EXAMPLE_TYPE, data: 0 }]),
|
|
93
103
|
}),
|
|
94
104
|
);
|
|
95
105
|
|
|
96
106
|
const graph = builder.graph;
|
|
97
|
-
|
|
98
|
-
|
|
107
|
+
Graph.expand(graph, Node.RootId);
|
|
108
|
+
Graph.expand(graph, Node.RootId, 'inbound');
|
|
99
109
|
|
|
100
|
-
const outbound = registry.get(graph.connections(
|
|
101
|
-
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'));
|
|
102
112
|
|
|
103
113
|
expect(outbound).has.length(1);
|
|
104
114
|
expect(outbound[0].id).to.equal('child');
|
|
@@ -110,52 +120,54 @@ describe('GraphBuilder', () => {
|
|
|
110
120
|
|
|
111
121
|
test('updates', () => {
|
|
112
122
|
const registry = Registry.make();
|
|
113
|
-
const builder =
|
|
114
|
-
const state =
|
|
115
|
-
|
|
116
|
-
|
|
123
|
+
const builder = GraphBuilder.make({ registry });
|
|
124
|
+
const state = Atom.make(0);
|
|
125
|
+
GraphBuilder.addExtension(
|
|
126
|
+
builder,
|
|
127
|
+
GraphBuilder.createExtensionRaw({
|
|
117
128
|
id: 'connector',
|
|
118
|
-
connector: () =>
|
|
129
|
+
connector: () => Atom.make((get) => [{ id: EXAMPLE_ID, type: EXAMPLE_TYPE, data: get(state) }]),
|
|
119
130
|
}),
|
|
120
131
|
);
|
|
121
132
|
const graph = builder.graph;
|
|
122
|
-
|
|
133
|
+
Graph.expand(graph, Node.RootId);
|
|
123
134
|
|
|
124
135
|
{
|
|
125
|
-
const [node] = registry.get(graph.connections(
|
|
136
|
+
const [node] = registry.get(graph.connections(Node.RootId));
|
|
126
137
|
expect(node.data).to.equal(0);
|
|
127
138
|
}
|
|
128
139
|
|
|
129
140
|
{
|
|
130
141
|
registry.set(state, 1);
|
|
131
|
-
const [node] = registry.get(graph.connections(
|
|
142
|
+
const [node] = registry.get(graph.connections(Node.RootId));
|
|
132
143
|
expect(node.data).to.equal(1);
|
|
133
144
|
}
|
|
134
145
|
});
|
|
135
146
|
|
|
136
147
|
test('subscribes to updates', () => {
|
|
137
148
|
const registry = Registry.make();
|
|
138
|
-
const builder =
|
|
139
|
-
const state =
|
|
140
|
-
|
|
141
|
-
|
|
149
|
+
const builder = GraphBuilder.make({ registry });
|
|
150
|
+
const state = Atom.make(0);
|
|
151
|
+
GraphBuilder.addExtension(
|
|
152
|
+
builder,
|
|
153
|
+
GraphBuilder.createExtensionRaw({
|
|
142
154
|
id: 'connector',
|
|
143
|
-
connector: () =>
|
|
155
|
+
connector: () => Atom.make((get) => [{ id: EXAMPLE_ID, type: EXAMPLE_TYPE, data: get(state) }]),
|
|
144
156
|
}),
|
|
145
157
|
);
|
|
146
158
|
const graph = builder.graph;
|
|
147
159
|
|
|
148
160
|
let count = 0;
|
|
149
|
-
const cancel = registry.subscribe(graph.connections(
|
|
161
|
+
const cancel = registry.subscribe(graph.connections(Node.RootId), (_) => {
|
|
150
162
|
count++;
|
|
151
163
|
});
|
|
152
164
|
onTestFinished(() => cancel());
|
|
153
165
|
|
|
154
166
|
expect(count).to.equal(0);
|
|
155
|
-
expect(registry.get(graph.connections(
|
|
167
|
+
expect(registry.get(graph.connections(Node.RootId))).to.have.length(0);
|
|
156
168
|
expect(count).to.equal(1);
|
|
157
169
|
|
|
158
|
-
|
|
170
|
+
Graph.expand(graph, Node.RootId);
|
|
159
171
|
expect(count).to.equal(2);
|
|
160
172
|
registry.set(state, 1);
|
|
161
173
|
expect(count).to.equal(3);
|
|
@@ -163,19 +175,20 @@ describe('GraphBuilder', () => {
|
|
|
163
175
|
|
|
164
176
|
test('updates with new extensions', () => {
|
|
165
177
|
const registry = Registry.make();
|
|
166
|
-
const builder =
|
|
167
|
-
|
|
168
|
-
|
|
178
|
+
const builder = GraphBuilder.make({ registry });
|
|
179
|
+
GraphBuilder.addExtension(
|
|
180
|
+
builder,
|
|
181
|
+
GraphBuilder.createExtensionRaw({
|
|
169
182
|
id: 'connector',
|
|
170
|
-
connector: () =>
|
|
183
|
+
connector: () => Atom.make([{ id: EXAMPLE_ID, type: EXAMPLE_TYPE }]),
|
|
171
184
|
}),
|
|
172
185
|
);
|
|
173
186
|
const graph = builder.graph;
|
|
174
|
-
|
|
187
|
+
Graph.expand(graph, Node.RootId);
|
|
175
188
|
|
|
176
|
-
let nodes: Node[] = [];
|
|
189
|
+
let nodes: Node.Node[] = [];
|
|
177
190
|
let count = 0;
|
|
178
|
-
const cancel = registry.subscribe(graph.connections(
|
|
191
|
+
const cancel = registry.subscribe(graph.connections(Node.RootId), (_nodes) => {
|
|
179
192
|
count++;
|
|
180
193
|
nodes = _nodes;
|
|
181
194
|
});
|
|
@@ -183,14 +196,15 @@ describe('GraphBuilder', () => {
|
|
|
183
196
|
|
|
184
197
|
expect(nodes).has.length(0);
|
|
185
198
|
expect(count).to.equal(0);
|
|
186
|
-
registry.get(graph.connections(
|
|
199
|
+
registry.get(graph.connections(Node.RootId));
|
|
187
200
|
expect(nodes).has.length(1);
|
|
188
201
|
expect(count).to.equal(1);
|
|
189
202
|
|
|
190
|
-
|
|
191
|
-
|
|
203
|
+
GraphBuilder.addExtension(
|
|
204
|
+
builder,
|
|
205
|
+
GraphBuilder.createExtensionRaw({
|
|
192
206
|
id: 'connector-2',
|
|
193
|
-
connector: () =>
|
|
207
|
+
connector: () => Atom.make([{ id: exampleId(2), type: EXAMPLE_TYPE }]),
|
|
194
208
|
}),
|
|
195
209
|
);
|
|
196
210
|
expect(nodes).has.length(2);
|
|
@@ -199,22 +213,23 @@ describe('GraphBuilder', () => {
|
|
|
199
213
|
|
|
200
214
|
test('removes', () => {
|
|
201
215
|
const registry = Registry.make();
|
|
202
|
-
const builder =
|
|
203
|
-
const nodes =
|
|
216
|
+
const builder = GraphBuilder.make({ registry });
|
|
217
|
+
const nodes = Atom.make([
|
|
204
218
|
{ id: exampleId(1), type: EXAMPLE_TYPE },
|
|
205
219
|
{ id: exampleId(2), type: EXAMPLE_TYPE },
|
|
206
220
|
]);
|
|
207
|
-
|
|
208
|
-
|
|
221
|
+
GraphBuilder.addExtension(
|
|
222
|
+
builder,
|
|
223
|
+
GraphBuilder.createExtensionRaw({
|
|
209
224
|
id: 'connector',
|
|
210
|
-
connector: () =>
|
|
225
|
+
connector: () => Atom.make((get) => get(nodes)),
|
|
211
226
|
}),
|
|
212
227
|
);
|
|
213
228
|
const graph = builder.graph;
|
|
214
|
-
|
|
229
|
+
Graph.expand(graph, Node.RootId);
|
|
215
230
|
|
|
216
231
|
{
|
|
217
|
-
const nodes = registry.get(graph.connections(
|
|
232
|
+
const nodes = registry.get(graph.connections(Node.RootId));
|
|
218
233
|
expect(nodes).has.length(2);
|
|
219
234
|
expect(nodes[0].id).to.equal(exampleId(1));
|
|
220
235
|
expect(nodes[1].id).to.equal(exampleId(2));
|
|
@@ -223,7 +238,7 @@ describe('GraphBuilder', () => {
|
|
|
223
238
|
registry.set(nodes, [{ id: exampleId(3), type: EXAMPLE_TYPE }]);
|
|
224
239
|
|
|
225
240
|
{
|
|
226
|
-
const nodes = registry.get(graph.connections(
|
|
241
|
+
const nodes = registry.get(graph.connections(Node.RootId));
|
|
227
242
|
expect(nodes).has.length(1);
|
|
228
243
|
expect(nodes[0].id).to.equal(exampleId(3));
|
|
229
244
|
}
|
|
@@ -231,14 +246,14 @@ describe('GraphBuilder', () => {
|
|
|
231
246
|
|
|
232
247
|
test('nodes are updated when removed', () => {
|
|
233
248
|
const registry = Registry.make();
|
|
234
|
-
const builder =
|
|
235
|
-
const name =
|
|
249
|
+
const builder = GraphBuilder.make({ registry });
|
|
250
|
+
const name = Atom.make('removed');
|
|
236
251
|
|
|
237
|
-
|
|
238
|
-
|
|
252
|
+
GraphBuilder.addExtension(builder, [
|
|
253
|
+
GraphBuilder.createExtensionRaw({
|
|
239
254
|
id: 'root',
|
|
240
255
|
connector: (node) =>
|
|
241
|
-
|
|
256
|
+
Atom.make((get) =>
|
|
242
257
|
Function.pipe(
|
|
243
258
|
get(node),
|
|
244
259
|
Option.flatMap((node) => (node.id === 'root' ? Option.some(get(name)) : Option.none())),
|
|
@@ -260,7 +275,7 @@ describe('GraphBuilder', () => {
|
|
|
260
275
|
});
|
|
261
276
|
onTestFinished(() => cancel());
|
|
262
277
|
|
|
263
|
-
|
|
278
|
+
Graph.expand(graph, Node.RootId);
|
|
264
279
|
expect(count).to.equal(0);
|
|
265
280
|
expect(exists).to.be.false;
|
|
266
281
|
|
|
@@ -279,23 +294,24 @@ describe('GraphBuilder', () => {
|
|
|
279
294
|
|
|
280
295
|
test('sort edges', async () => {
|
|
281
296
|
const registry = Registry.make();
|
|
282
|
-
const builder =
|
|
283
|
-
const nodes =
|
|
297
|
+
const builder = GraphBuilder.make({ registry });
|
|
298
|
+
const nodes = Atom.make([
|
|
284
299
|
{ id: exampleId(1), type: EXAMPLE_TYPE, data: 1 },
|
|
285
300
|
{ id: exampleId(2), type: EXAMPLE_TYPE, data: 2 },
|
|
286
301
|
{ id: exampleId(3), type: EXAMPLE_TYPE, data: 3 },
|
|
287
302
|
]);
|
|
288
|
-
|
|
289
|
-
|
|
303
|
+
GraphBuilder.addExtension(
|
|
304
|
+
builder,
|
|
305
|
+
GraphBuilder.createExtensionRaw({
|
|
290
306
|
id: 'connector',
|
|
291
|
-
connector: () =>
|
|
307
|
+
connector: () => Atom.make((get) => get(nodes)),
|
|
292
308
|
}),
|
|
293
309
|
);
|
|
294
310
|
const graph = builder.graph;
|
|
295
|
-
|
|
311
|
+
Graph.expand(graph, Node.RootId);
|
|
296
312
|
|
|
297
313
|
{
|
|
298
|
-
const nodes = registry.get(graph.connections(
|
|
314
|
+
const nodes = registry.get(graph.connections(Node.RootId));
|
|
299
315
|
expect(nodes).has.length(3);
|
|
300
316
|
expect(nodes[0].id).to.equal(exampleId(1));
|
|
301
317
|
expect(nodes[1].id).to.equal(exampleId(2));
|
|
@@ -312,7 +328,7 @@ describe('GraphBuilder', () => {
|
|
|
312
328
|
await sleep(0);
|
|
313
329
|
|
|
314
330
|
{
|
|
315
|
-
const nodes = registry.get(graph.connections(
|
|
331
|
+
const nodes = registry.get(graph.connections(Node.RootId));
|
|
316
332
|
expect(nodes).has.length(3);
|
|
317
333
|
expect(nodes[0].id).to.equal(exampleId(3));
|
|
318
334
|
expect(nodes[1].id).to.equal(exampleId(1));
|
|
@@ -322,15 +338,15 @@ describe('GraphBuilder', () => {
|
|
|
322
338
|
|
|
323
339
|
test('updates are constrained', () => {
|
|
324
340
|
const registry = Registry.make();
|
|
325
|
-
const builder =
|
|
326
|
-
const name =
|
|
327
|
-
const sub =
|
|
341
|
+
const builder = GraphBuilder.make({ registry });
|
|
342
|
+
const name = Atom.make('default');
|
|
343
|
+
const sub = Atom.make('default');
|
|
328
344
|
|
|
329
|
-
|
|
330
|
-
|
|
345
|
+
GraphBuilder.addExtension(builder, [
|
|
346
|
+
GraphBuilder.createExtensionRaw({
|
|
331
347
|
id: 'root',
|
|
332
348
|
connector: (node) =>
|
|
333
|
-
|
|
349
|
+
Atom.make((get) =>
|
|
334
350
|
Function.pipe(
|
|
335
351
|
get(node),
|
|
336
352
|
Option.flatMap((node) => (node.id === 'root' ? Option.some(get(name)) : Option.none())),
|
|
@@ -340,10 +356,10 @@ describe('GraphBuilder', () => {
|
|
|
340
356
|
),
|
|
341
357
|
),
|
|
342
358
|
}),
|
|
343
|
-
|
|
359
|
+
GraphBuilder.createExtensionRaw({
|
|
344
360
|
id: 'connector1',
|
|
345
361
|
connector: (node) =>
|
|
346
|
-
|
|
362
|
+
Atom.make((get) =>
|
|
347
363
|
Function.pipe(
|
|
348
364
|
get(node),
|
|
349
365
|
Option.flatMap((node) => (node.id === EXAMPLE_ID ? Option.some(get(sub)) : Option.none())),
|
|
@@ -352,10 +368,10 @@ describe('GraphBuilder', () => {
|
|
|
352
368
|
),
|
|
353
369
|
),
|
|
354
370
|
}),
|
|
355
|
-
|
|
371
|
+
GraphBuilder.createExtensionRaw({
|
|
356
372
|
id: 'connector2',
|
|
357
373
|
connector: (node) =>
|
|
358
|
-
|
|
374
|
+
Atom.make((get) =>
|
|
359
375
|
Function.pipe(
|
|
360
376
|
get(node),
|
|
361
377
|
Option.flatMap((node) => (node.id === EXAMPLE_ID ? Option.some(node.data) : Option.none())),
|
|
@@ -387,13 +403,13 @@ describe('GraphBuilder', () => {
|
|
|
387
403
|
onTestFinished(() => dependentCancel());
|
|
388
404
|
|
|
389
405
|
// Counts should not increment until the node is expanded.
|
|
390
|
-
|
|
406
|
+
Graph.expand(graph, Node.RootId);
|
|
391
407
|
expect(parentCount).to.equal(1);
|
|
392
408
|
expect(independentCount).to.equal(0);
|
|
393
409
|
expect(dependentCount).to.equal(0);
|
|
394
410
|
|
|
395
411
|
// Counts should increment when the node is expanded.
|
|
396
|
-
|
|
412
|
+
Graph.expand(graph, EXAMPLE_ID);
|
|
397
413
|
expect(parentCount).to.equal(1);
|
|
398
414
|
expect(independentCount).to.equal(1);
|
|
399
415
|
expect(dependentCount).to.equal(1);
|
|
@@ -411,7 +427,7 @@ describe('GraphBuilder', () => {
|
|
|
411
427
|
expect(dependentCount).to.equal(2);
|
|
412
428
|
|
|
413
429
|
// Independent count should update if its state changes even if the parent is removed.
|
|
414
|
-
|
|
430
|
+
Atom.batch(() => {
|
|
415
431
|
registry.set(name, 'removed');
|
|
416
432
|
registry.set(sub, 'batch');
|
|
417
433
|
});
|
|
@@ -426,7 +442,7 @@ describe('GraphBuilder', () => {
|
|
|
426
442
|
expect(dependentCount).to.equal(3);
|
|
427
443
|
|
|
428
444
|
// Counts should not increment when the node is expanded again.
|
|
429
|
-
|
|
445
|
+
Graph.expand(graph, EXAMPLE_ID);
|
|
430
446
|
expect(parentCount).to.equal(3);
|
|
431
447
|
expect(independentCount).to.equal(3);
|
|
432
448
|
expect(dependentCount).to.equal(3);
|
|
@@ -434,12 +450,13 @@ describe('GraphBuilder', () => {
|
|
|
434
450
|
|
|
435
451
|
test('eager graph expansion', async () => {
|
|
436
452
|
const registry = Registry.make();
|
|
437
|
-
const builder =
|
|
438
|
-
|
|
439
|
-
|
|
453
|
+
const builder = GraphBuilder.make({ registry });
|
|
454
|
+
GraphBuilder.addExtension(
|
|
455
|
+
builder,
|
|
456
|
+
GraphBuilder.createExtensionRaw({
|
|
440
457
|
id: 'connector',
|
|
441
458
|
connector: (node) => {
|
|
442
|
-
return
|
|
459
|
+
return Atom.make((get) =>
|
|
443
460
|
Function.pipe(
|
|
444
461
|
get(node),
|
|
445
462
|
Option.map((node) => (node.data ? node.data + 1 : 1)),
|
|
@@ -455,14 +472,14 @@ describe('GraphBuilder', () => {
|
|
|
455
472
|
let count = 0;
|
|
456
473
|
const trigger = new Trigger();
|
|
457
474
|
builder.graph.onNodeChanged.on(({ id }) => {
|
|
458
|
-
builder.graph
|
|
475
|
+
Graph.expand(builder.graph, id);
|
|
459
476
|
count++;
|
|
460
477
|
if (count === 5) {
|
|
461
478
|
trigger.wake();
|
|
462
479
|
}
|
|
463
480
|
});
|
|
464
481
|
|
|
465
|
-
builder.graph.
|
|
482
|
+
Graph.expand(builder.graph, Node.RootId);
|
|
466
483
|
await trigger.wait();
|
|
467
484
|
expect(count).to.equal(5);
|
|
468
485
|
});
|
|
@@ -470,12 +487,13 @@ describe('GraphBuilder', () => {
|
|
|
470
487
|
|
|
471
488
|
describe('explore', () => {
|
|
472
489
|
test('works', async () => {
|
|
473
|
-
const builder =
|
|
474
|
-
|
|
475
|
-
|
|
490
|
+
const builder = GraphBuilder.make();
|
|
491
|
+
GraphBuilder.addExtension(
|
|
492
|
+
builder,
|
|
493
|
+
GraphBuilder.createExtensionRaw({
|
|
476
494
|
id: 'connector',
|
|
477
495
|
connector: (node) =>
|
|
478
|
-
|
|
496
|
+
Atom.make((get) =>
|
|
479
497
|
Function.pipe(
|
|
480
498
|
get(node),
|
|
481
499
|
Option.map((node) => (node.data ? node.data + 1 : 1)),
|
|
@@ -488,7 +506,7 @@ describe('GraphBuilder', () => {
|
|
|
488
506
|
);
|
|
489
507
|
|
|
490
508
|
let count = 0;
|
|
491
|
-
await
|
|
509
|
+
await GraphBuilder.explore(builder, {
|
|
492
510
|
visitor: () => {
|
|
493
511
|
count++;
|
|
494
512
|
},
|
|
@@ -497,4 +515,402 @@ describe('GraphBuilder', () => {
|
|
|
497
515
|
expect(count).to.equal(6);
|
|
498
516
|
});
|
|
499
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
|
+
});
|
|
500
916
|
});
|