@dxos/app-graph 0.8.4-main.69d29f4 → 0.8.4-main.6fa680abb7
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 +480 -198
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +480 -198
- 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 +11 -7
- package/dist/types/src/graph-builder.d.ts.map +1 -1
- package/dist/types/src/graph.d.ts +13 -17
- package/dist/types/src/graph.d.ts.map +1 -1
- package/dist/types/src/node-matcher.d.ts +4 -4
- package/dist/types/src/node-matcher.d.ts.map +1 -1
- package/dist/types/src/node.d.ts +15 -5
- 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/src/util.d.ts +34 -0
- package/dist/types/src/util.d.ts.map +1 -0
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +25 -25
- package/src/graph-builder.test.ts +444 -102
- package/src/graph-builder.ts +191 -72
- package/src/graph.test.ts +187 -52
- package/src/graph.ts +179 -98
- package/src/node-matcher.ts +8 -10
- package/src/node.ts +20 -5
- package/src/stories/EchoGraph.stories.tsx +86 -56
- package/src/stories/Tree.tsx +1 -1
- package/src/util.ts +71 -0
|
@@ -9,7 +9,7 @@ import * as Function from 'effect/Function';
|
|
|
9
9
|
import * as Option from 'effect/Option';
|
|
10
10
|
import { describe, expect, onTestFinished, test } from 'vitest';
|
|
11
11
|
|
|
12
|
-
import { Trigger
|
|
12
|
+
import { Trigger } from '@dxos/async';
|
|
13
13
|
import { Obj } from '@dxos/echo';
|
|
14
14
|
import { TestSchema } from '@dxos/echo/testing';
|
|
15
15
|
|
|
@@ -20,7 +20,9 @@ import * as NodeMatcher from './node-matcher';
|
|
|
20
20
|
|
|
21
21
|
const exampleId = (id: number) => `dx:test:${id}`;
|
|
22
22
|
const EXAMPLE_ID = exampleId(1);
|
|
23
|
-
const EXAMPLE_TYPE = 'dxos.
|
|
23
|
+
const EXAMPLE_TYPE = 'org.dxos.type.example';
|
|
24
|
+
|
|
25
|
+
const qualifiedId = (...segments: string[]) => segments.join('/');
|
|
24
26
|
|
|
25
27
|
describe('GraphBuilder', () => {
|
|
26
28
|
describe('resolver', () => {
|
|
@@ -39,10 +41,7 @@ describe('GraphBuilder', () => {
|
|
|
39
41
|
builder,
|
|
40
42
|
GraphBuilder.createExtensionRaw({
|
|
41
43
|
id: 'resolver',
|
|
42
|
-
resolver: () => {
|
|
43
|
-
console.log('resolver');
|
|
44
|
-
return Atom.make({ id: EXAMPLE_ID, type: EXAMPLE_TYPE, data: 1 });
|
|
45
|
-
},
|
|
44
|
+
resolver: () => Atom.make({ id: EXAMPLE_ID, type: EXAMPLE_TYPE, data: 1 }),
|
|
46
45
|
}),
|
|
47
46
|
);
|
|
48
47
|
await Graph.initialize(graph, EXAMPLE_ID);
|
|
@@ -81,10 +80,65 @@ describe('GraphBuilder', () => {
|
|
|
81
80
|
expect(node?.data).to.equal('updated');
|
|
82
81
|
}
|
|
83
82
|
});
|
|
83
|
+
|
|
84
|
+
test('does not overwrite connector-produced node', async () => {
|
|
85
|
+
const registry = Registry.make();
|
|
86
|
+
const builder = GraphBuilder.make({ registry });
|
|
87
|
+
const resolverData = Atom.make('from-resolver');
|
|
88
|
+
|
|
89
|
+
GraphBuilder.addExtension(builder, [
|
|
90
|
+
GraphBuilder.createExtensionRaw({
|
|
91
|
+
id: 'resolver',
|
|
92
|
+
resolver: (id) =>
|
|
93
|
+
id === qualifiedId('root', 'shared')
|
|
94
|
+
? Atom.make((get) => ({ id: qualifiedId('root', 'shared'), type: EXAMPLE_TYPE, data: get(resolverData) }))
|
|
95
|
+
: Atom.make(null),
|
|
96
|
+
}),
|
|
97
|
+
GraphBuilder.createExtensionRaw({
|
|
98
|
+
id: 'connector',
|
|
99
|
+
connector: (node) =>
|
|
100
|
+
Atom.make((get) =>
|
|
101
|
+
Function.pipe(
|
|
102
|
+
get(node),
|
|
103
|
+
Option.filter((n) => n.id === 'root'),
|
|
104
|
+
Option.map(() => [{ id: 'shared', type: EXAMPLE_TYPE, data: 'from-connector' }]),
|
|
105
|
+
Option.getOrElse(() => []),
|
|
106
|
+
),
|
|
107
|
+
),
|
|
108
|
+
}),
|
|
109
|
+
]);
|
|
110
|
+
|
|
111
|
+
const graph = builder.graph;
|
|
112
|
+
|
|
113
|
+
// Connector produces root/shared.
|
|
114
|
+
Graph.expand(graph, Node.RootId, 'child');
|
|
115
|
+
await GraphBuilder.flush(builder);
|
|
116
|
+
|
|
117
|
+
{
|
|
118
|
+
const node = Graph.getNode(graph, qualifiedId('root', 'shared')).pipe(Option.getOrNull);
|
|
119
|
+
expect(node?.data).to.equal('from-connector');
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Resolver fires for the same ID but should not overwrite.
|
|
123
|
+
await Graph.initialize(graph, qualifiedId('root', 'shared'));
|
|
124
|
+
|
|
125
|
+
{
|
|
126
|
+
const node = Graph.getNode(graph, qualifiedId('root', 'shared')).pipe(Option.getOrNull);
|
|
127
|
+
expect(node?.data).to.equal('from-connector');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Updating the resolver's atom should still not overwrite.
|
|
131
|
+
registry.set(resolverData, 'updated-resolver');
|
|
132
|
+
|
|
133
|
+
{
|
|
134
|
+
const node = Graph.getNode(graph, qualifiedId('root', 'shared')).pipe(Option.getOrNull);
|
|
135
|
+
expect(node?.data).to.equal('from-connector');
|
|
136
|
+
}
|
|
137
|
+
});
|
|
84
138
|
});
|
|
85
139
|
|
|
86
140
|
describe('connector', () => {
|
|
87
|
-
test('works', () => {
|
|
141
|
+
test('works', async () => {
|
|
88
142
|
const registry = Registry.make();
|
|
89
143
|
const builder = GraphBuilder.make({ registry });
|
|
90
144
|
GraphBuilder.addExtension(
|
|
@@ -98,27 +152,28 @@ describe('GraphBuilder', () => {
|
|
|
98
152
|
builder,
|
|
99
153
|
GraphBuilder.createExtensionRaw({
|
|
100
154
|
id: 'inbound-connector',
|
|
101
|
-
relation: 'inbound',
|
|
155
|
+
relation: Node.childRelation('inbound'),
|
|
102
156
|
connector: () => Atom.make([{ id: 'parent', type: EXAMPLE_TYPE, data: 0 }]),
|
|
103
157
|
}),
|
|
104
158
|
);
|
|
105
159
|
|
|
106
160
|
const graph = builder.graph;
|
|
107
|
-
Graph.expand(graph, Node.RootId);
|
|
108
|
-
Graph.expand(graph, Node.RootId, 'inbound');
|
|
161
|
+
Graph.expand(graph, Node.RootId, 'child');
|
|
162
|
+
Graph.expand(graph, Node.RootId, Node.childRelation('inbound'));
|
|
163
|
+
await GraphBuilder.flush(builder);
|
|
109
164
|
|
|
110
|
-
const outbound = registry.get(graph.connections(Node.RootId));
|
|
111
|
-
const inbound = registry.get(graph.connections(Node.RootId, 'inbound'));
|
|
165
|
+
const outbound = registry.get(graph.connections(Node.RootId, 'child'));
|
|
166
|
+
const inbound = registry.get(graph.connections(Node.RootId, Node.childRelation('inbound')));
|
|
112
167
|
|
|
113
168
|
expect(outbound).has.length(1);
|
|
114
|
-
expect(outbound[0].id).to.equal('child');
|
|
169
|
+
expect(outbound[0].id).to.equal('root/child');
|
|
115
170
|
expect(outbound[0].data).to.equal(2);
|
|
116
171
|
expect(inbound).has.length(1);
|
|
117
|
-
expect(inbound[0].id).to.equal('parent');
|
|
172
|
+
expect(inbound[0].id).to.equal('root/parent');
|
|
118
173
|
expect(inbound[0].data).to.equal(0);
|
|
119
174
|
});
|
|
120
175
|
|
|
121
|
-
test('updates', () => {
|
|
176
|
+
test('updates', async () => {
|
|
122
177
|
const registry = Registry.make();
|
|
123
178
|
const builder = GraphBuilder.make({ registry });
|
|
124
179
|
const state = Atom.make(0);
|
|
@@ -130,21 +185,23 @@ describe('GraphBuilder', () => {
|
|
|
130
185
|
}),
|
|
131
186
|
);
|
|
132
187
|
const graph = builder.graph;
|
|
133
|
-
Graph.expand(graph, Node.RootId);
|
|
188
|
+
Graph.expand(graph, Node.RootId, 'child');
|
|
189
|
+
await GraphBuilder.flush(builder);
|
|
134
190
|
|
|
135
191
|
{
|
|
136
|
-
const [node] = registry.get(graph.connections(Node.RootId));
|
|
192
|
+
const [node] = registry.get(graph.connections(Node.RootId, 'child'));
|
|
137
193
|
expect(node.data).to.equal(0);
|
|
138
194
|
}
|
|
139
195
|
|
|
140
196
|
{
|
|
141
197
|
registry.set(state, 1);
|
|
142
|
-
|
|
198
|
+
await GraphBuilder.flush(builder);
|
|
199
|
+
const [node] = registry.get(graph.connections(Node.RootId, 'child'));
|
|
143
200
|
expect(node.data).to.equal(1);
|
|
144
201
|
}
|
|
145
202
|
});
|
|
146
203
|
|
|
147
|
-
test('subscribes to updates', () => {
|
|
204
|
+
test('subscribes to updates', async () => {
|
|
148
205
|
const registry = Registry.make();
|
|
149
206
|
const builder = GraphBuilder.make({ registry });
|
|
150
207
|
const state = Atom.make(0);
|
|
@@ -158,22 +215,25 @@ describe('GraphBuilder', () => {
|
|
|
158
215
|
const graph = builder.graph;
|
|
159
216
|
|
|
160
217
|
let count = 0;
|
|
161
|
-
const cancel = registry.subscribe(graph.connections(Node.RootId), (_) => {
|
|
218
|
+
const cancel = registry.subscribe(graph.connections(Node.RootId, 'child'), (_) => {
|
|
162
219
|
count++;
|
|
163
220
|
});
|
|
164
221
|
onTestFinished(() => cancel());
|
|
165
222
|
|
|
166
223
|
expect(count).to.equal(0);
|
|
167
|
-
expect(registry.get(graph.connections(Node.RootId))).to.have.length(0);
|
|
224
|
+
expect(registry.get(graph.connections(Node.RootId, 'child'))).to.have.length(0);
|
|
168
225
|
expect(count).to.equal(1);
|
|
169
226
|
|
|
170
|
-
Graph.expand(graph, Node.RootId);
|
|
227
|
+
Graph.expand(graph, Node.RootId, 'child');
|
|
228
|
+
await GraphBuilder.flush(builder);
|
|
171
229
|
expect(count).to.equal(2);
|
|
230
|
+
|
|
172
231
|
registry.set(state, 1);
|
|
232
|
+
await GraphBuilder.flush(builder);
|
|
173
233
|
expect(count).to.equal(3);
|
|
174
234
|
});
|
|
175
235
|
|
|
176
|
-
test('updates with new extensions', () => {
|
|
236
|
+
test('updates with new extensions', async () => {
|
|
177
237
|
const registry = Registry.make();
|
|
178
238
|
const builder = GraphBuilder.make({ registry });
|
|
179
239
|
GraphBuilder.addExtension(
|
|
@@ -184,11 +244,12 @@ describe('GraphBuilder', () => {
|
|
|
184
244
|
}),
|
|
185
245
|
);
|
|
186
246
|
const graph = builder.graph;
|
|
187
|
-
Graph.expand(graph, Node.RootId);
|
|
247
|
+
Graph.expand(graph, Node.RootId, 'child');
|
|
248
|
+
await GraphBuilder.flush(builder);
|
|
188
249
|
|
|
189
250
|
let nodes: Node.Node[] = [];
|
|
190
251
|
let count = 0;
|
|
191
|
-
const cancel = registry.subscribe(graph.connections(Node.RootId), (_nodes) => {
|
|
252
|
+
const cancel = registry.subscribe(graph.connections(Node.RootId, 'child'), (_nodes) => {
|
|
192
253
|
count++;
|
|
193
254
|
nodes = _nodes;
|
|
194
255
|
});
|
|
@@ -196,7 +257,7 @@ describe('GraphBuilder', () => {
|
|
|
196
257
|
|
|
197
258
|
expect(nodes).has.length(0);
|
|
198
259
|
expect(count).to.equal(0);
|
|
199
|
-
registry.get(graph.connections(Node.RootId));
|
|
260
|
+
registry.get(graph.connections(Node.RootId, 'child'));
|
|
200
261
|
expect(nodes).has.length(1);
|
|
201
262
|
expect(count).to.equal(1);
|
|
202
263
|
|
|
@@ -207,11 +268,12 @@ describe('GraphBuilder', () => {
|
|
|
207
268
|
connector: () => Atom.make([{ id: exampleId(2), type: EXAMPLE_TYPE }]),
|
|
208
269
|
}),
|
|
209
270
|
);
|
|
271
|
+
await GraphBuilder.flush(builder);
|
|
210
272
|
expect(nodes).has.length(2);
|
|
211
273
|
expect(count).to.equal(2);
|
|
212
274
|
});
|
|
213
275
|
|
|
214
|
-
test('removes', () => {
|
|
276
|
+
test('removes', async () => {
|
|
215
277
|
const registry = Registry.make();
|
|
216
278
|
const builder = GraphBuilder.make({ registry });
|
|
217
279
|
const nodes = Atom.make([
|
|
@@ -226,25 +288,27 @@ describe('GraphBuilder', () => {
|
|
|
226
288
|
}),
|
|
227
289
|
);
|
|
228
290
|
const graph = builder.graph;
|
|
229
|
-
Graph.expand(graph, Node.RootId);
|
|
291
|
+
Graph.expand(graph, Node.RootId, 'child');
|
|
292
|
+
await GraphBuilder.flush(builder);
|
|
230
293
|
|
|
231
294
|
{
|
|
232
|
-
const nodes = registry.get(graph.connections(Node.RootId));
|
|
295
|
+
const nodes = registry.get(graph.connections(Node.RootId, 'child'));
|
|
233
296
|
expect(nodes).has.length(2);
|
|
234
|
-
expect(nodes[0].id).to.equal(exampleId(1));
|
|
235
|
-
expect(nodes[1].id).to.equal(exampleId(2));
|
|
297
|
+
expect(nodes[0].id).to.equal(qualifiedId('root', exampleId(1)));
|
|
298
|
+
expect(nodes[1].id).to.equal(qualifiedId('root', exampleId(2)));
|
|
236
299
|
}
|
|
237
300
|
|
|
238
301
|
registry.set(nodes, [{ id: exampleId(3), type: EXAMPLE_TYPE }]);
|
|
302
|
+
await GraphBuilder.flush(builder);
|
|
239
303
|
|
|
240
304
|
{
|
|
241
|
-
const nodes = registry.get(graph.connections(Node.RootId));
|
|
305
|
+
const nodes = registry.get(graph.connections(Node.RootId, 'child'));
|
|
242
306
|
expect(nodes).has.length(1);
|
|
243
|
-
expect(nodes[0].id).to.equal(exampleId(3));
|
|
307
|
+
expect(nodes[0].id).to.equal(qualifiedId('root', exampleId(3)));
|
|
244
308
|
}
|
|
245
309
|
});
|
|
246
310
|
|
|
247
|
-
test('nodes are updated when removed', () => {
|
|
311
|
+
test('nodes are updated when removed', async () => {
|
|
248
312
|
const registry = Registry.make();
|
|
249
313
|
const builder = GraphBuilder.make({ registry });
|
|
250
314
|
const name = Atom.make('removed');
|
|
@@ -269,25 +333,29 @@ describe('GraphBuilder', () => {
|
|
|
269
333
|
|
|
270
334
|
let count = 0;
|
|
271
335
|
let exists = false;
|
|
272
|
-
const cancel = registry.subscribe(graph.node(EXAMPLE_ID), (node) => {
|
|
336
|
+
const cancel = registry.subscribe(graph.node(qualifiedId('root', EXAMPLE_ID)), (node) => {
|
|
273
337
|
count++;
|
|
274
338
|
exists = Option.isSome(node);
|
|
275
339
|
});
|
|
276
340
|
onTestFinished(() => cancel());
|
|
277
341
|
|
|
278
|
-
Graph.expand(graph, Node.RootId);
|
|
342
|
+
Graph.expand(graph, Node.RootId, 'child');
|
|
343
|
+
await GraphBuilder.flush(builder);
|
|
279
344
|
expect(count).to.equal(0);
|
|
280
345
|
expect(exists).to.be.false;
|
|
281
346
|
|
|
282
347
|
registry.set(name, 'default');
|
|
348
|
+
await GraphBuilder.flush(builder);
|
|
283
349
|
expect(count).to.equal(1);
|
|
284
350
|
expect(exists).to.be.true;
|
|
285
351
|
|
|
286
352
|
registry.set(name, 'removed');
|
|
353
|
+
await GraphBuilder.flush(builder);
|
|
287
354
|
expect(count).to.equal(2);
|
|
288
355
|
expect(exists).to.be.false;
|
|
289
356
|
|
|
290
357
|
registry.set(name, 'added');
|
|
358
|
+
await GraphBuilder.flush(builder);
|
|
291
359
|
expect(count).to.equal(3);
|
|
292
360
|
expect(exists).to.be.true;
|
|
293
361
|
});
|
|
@@ -308,14 +376,15 @@ describe('GraphBuilder', () => {
|
|
|
308
376
|
}),
|
|
309
377
|
);
|
|
310
378
|
const graph = builder.graph;
|
|
311
|
-
Graph.expand(graph, Node.RootId);
|
|
379
|
+
Graph.expand(graph, Node.RootId, 'child');
|
|
380
|
+
await GraphBuilder.flush(builder);
|
|
312
381
|
|
|
313
382
|
{
|
|
314
|
-
const nodes = registry.get(graph.connections(Node.RootId));
|
|
383
|
+
const nodes = registry.get(graph.connections(Node.RootId, 'child'));
|
|
315
384
|
expect(nodes).has.length(3);
|
|
316
|
-
expect(nodes[0].id).to.equal(exampleId(1));
|
|
317
|
-
expect(nodes[1].id).to.equal(exampleId(2));
|
|
318
|
-
expect(nodes[2].id).to.equal(exampleId(3));
|
|
385
|
+
expect(nodes[0].id).to.equal(qualifiedId('root', exampleId(1)));
|
|
386
|
+
expect(nodes[1].id).to.equal(qualifiedId('root', exampleId(2)));
|
|
387
|
+
expect(nodes[2].id).to.equal(qualifiedId('root', exampleId(3)));
|
|
319
388
|
}
|
|
320
389
|
|
|
321
390
|
registry.set(nodes, [
|
|
@@ -323,20 +392,18 @@ describe('GraphBuilder', () => {
|
|
|
323
392
|
{ id: exampleId(1), type: EXAMPLE_TYPE, data: 1 },
|
|
324
393
|
{ id: exampleId(2), type: EXAMPLE_TYPE, data: 2 },
|
|
325
394
|
]);
|
|
326
|
-
|
|
327
|
-
// TODO(wittjosiah): Why is this needed for the following conditions to pass?
|
|
328
|
-
await sleep(0);
|
|
395
|
+
await GraphBuilder.flush(builder);
|
|
329
396
|
|
|
330
397
|
{
|
|
331
|
-
const nodes = registry.get(graph.connections(Node.RootId));
|
|
398
|
+
const nodes = registry.get(graph.connections(Node.RootId, 'child'));
|
|
332
399
|
expect(nodes).has.length(3);
|
|
333
|
-
expect(nodes[0].id).to.equal(exampleId(3));
|
|
334
|
-
expect(nodes[1].id).to.equal(exampleId(1));
|
|
335
|
-
expect(nodes[2].id).to.equal(exampleId(2));
|
|
400
|
+
expect(nodes[0].id).to.equal(qualifiedId('root', exampleId(3)));
|
|
401
|
+
expect(nodes[1].id).to.equal(qualifiedId('root', exampleId(1)));
|
|
402
|
+
expect(nodes[2].id).to.equal(qualifiedId('root', exampleId(2)));
|
|
336
403
|
}
|
|
337
404
|
});
|
|
338
405
|
|
|
339
|
-
test('updates are constrained', () => {
|
|
406
|
+
test('updates are constrained', async () => {
|
|
340
407
|
const registry = Registry.make();
|
|
341
408
|
const builder = GraphBuilder.make({ registry });
|
|
342
409
|
const name = Atom.make('default');
|
|
@@ -362,7 +429,9 @@ describe('GraphBuilder', () => {
|
|
|
362
429
|
Atom.make((get) =>
|
|
363
430
|
Function.pipe(
|
|
364
431
|
get(node),
|
|
365
|
-
Option.flatMap((node) =>
|
|
432
|
+
Option.flatMap((node) =>
|
|
433
|
+
node.id === qualifiedId('root', EXAMPLE_ID) ? Option.some(get(sub)) : Option.none(),
|
|
434
|
+
),
|
|
366
435
|
Option.map((sub) => [{ id: exampleId(2), type: EXAMPLE_TYPE, data: sub }]),
|
|
367
436
|
Option.getOrElse(() => []),
|
|
368
437
|
),
|
|
@@ -374,7 +443,9 @@ describe('GraphBuilder', () => {
|
|
|
374
443
|
Atom.make((get) =>
|
|
375
444
|
Function.pipe(
|
|
376
445
|
get(node),
|
|
377
|
-
Option.flatMap((node) =>
|
|
446
|
+
Option.flatMap((node) =>
|
|
447
|
+
node.id === qualifiedId('root', EXAMPLE_ID) ? Option.some(node.data) : Option.none(),
|
|
448
|
+
),
|
|
378
449
|
Option.map((data) => [{ id: exampleId(3), type: EXAMPLE_TYPE, data }]),
|
|
379
450
|
Option.getOrElse(() => []),
|
|
380
451
|
),
|
|
@@ -385,43 +456,47 @@ describe('GraphBuilder', () => {
|
|
|
385
456
|
const graph = builder.graph;
|
|
386
457
|
|
|
387
458
|
let parentCount = 0;
|
|
388
|
-
const parentCancel = registry.subscribe(graph.node(EXAMPLE_ID), (_) => {
|
|
459
|
+
const parentCancel = registry.subscribe(graph.node(qualifiedId('root', EXAMPLE_ID)), (_) => {
|
|
389
460
|
parentCount++;
|
|
390
461
|
});
|
|
391
462
|
onTestFinished(() => parentCancel());
|
|
392
463
|
|
|
393
464
|
let independentCount = 0;
|
|
394
|
-
const independentCancel = registry.subscribe(graph.node(exampleId(2)), (_) => {
|
|
465
|
+
const independentCancel = registry.subscribe(graph.node(qualifiedId('root', EXAMPLE_ID, exampleId(2))), (_) => {
|
|
395
466
|
independentCount++;
|
|
396
467
|
});
|
|
397
468
|
onTestFinished(() => independentCancel());
|
|
398
469
|
|
|
399
470
|
let dependentCount = 0;
|
|
400
|
-
const dependentCancel = registry.subscribe(graph.node(exampleId(3)), (_) => {
|
|
471
|
+
const dependentCancel = registry.subscribe(graph.node(qualifiedId('root', EXAMPLE_ID, exampleId(3))), (_) => {
|
|
401
472
|
dependentCount++;
|
|
402
473
|
});
|
|
403
474
|
onTestFinished(() => dependentCancel());
|
|
404
475
|
|
|
405
476
|
// Counts should not increment until the node is expanded.
|
|
406
|
-
Graph.expand(graph, Node.RootId);
|
|
477
|
+
Graph.expand(graph, Node.RootId, 'child');
|
|
478
|
+
await GraphBuilder.flush(builder);
|
|
407
479
|
expect(parentCount).to.equal(1);
|
|
408
480
|
expect(independentCount).to.equal(0);
|
|
409
481
|
expect(dependentCount).to.equal(0);
|
|
410
482
|
|
|
411
483
|
// Counts should increment when the node is expanded.
|
|
412
|
-
Graph.expand(graph, EXAMPLE_ID);
|
|
484
|
+
Graph.expand(graph, qualifiedId('root', EXAMPLE_ID), 'child');
|
|
485
|
+
await GraphBuilder.flush(builder);
|
|
413
486
|
expect(parentCount).to.equal(1);
|
|
414
487
|
expect(independentCount).to.equal(1);
|
|
415
488
|
expect(dependentCount).to.equal(1);
|
|
416
489
|
|
|
417
490
|
// Only dependent count should increment when the parent changes.
|
|
418
491
|
registry.set(name, 'updated');
|
|
492
|
+
await GraphBuilder.flush(builder);
|
|
419
493
|
expect(parentCount).to.equal(2);
|
|
420
494
|
expect(independentCount).to.equal(1);
|
|
421
495
|
expect(dependentCount).to.equal(2);
|
|
422
496
|
|
|
423
497
|
// Only independent count should increment when its state changes.
|
|
424
498
|
registry.set(sub, 'updated');
|
|
499
|
+
await GraphBuilder.flush(builder);
|
|
425
500
|
expect(parentCount).to.equal(2);
|
|
426
501
|
expect(independentCount).to.equal(2);
|
|
427
502
|
expect(dependentCount).to.equal(2);
|
|
@@ -431,18 +506,21 @@ describe('GraphBuilder', () => {
|
|
|
431
506
|
registry.set(name, 'removed');
|
|
432
507
|
registry.set(sub, 'batch');
|
|
433
508
|
});
|
|
509
|
+
await GraphBuilder.flush(builder);
|
|
434
510
|
expect(parentCount).to.equal(2);
|
|
435
511
|
expect(independentCount).to.equal(3);
|
|
436
512
|
expect(dependentCount).to.equal(2);
|
|
437
513
|
|
|
438
514
|
// Dependent count should increment when the node is added back.
|
|
439
515
|
registry.set(name, 'added');
|
|
516
|
+
await GraphBuilder.flush(builder);
|
|
440
517
|
expect(parentCount).to.equal(3);
|
|
441
518
|
expect(independentCount).to.equal(3);
|
|
442
519
|
expect(dependentCount).to.equal(3);
|
|
443
520
|
|
|
444
521
|
// Counts should not increment when the node is expanded again.
|
|
445
|
-
Graph.expand(graph, EXAMPLE_ID);
|
|
522
|
+
Graph.expand(graph, qualifiedId('root', EXAMPLE_ID), 'child');
|
|
523
|
+
await GraphBuilder.flush(builder);
|
|
446
524
|
expect(parentCount).to.equal(3);
|
|
447
525
|
expect(independentCount).to.equal(3);
|
|
448
526
|
expect(dependentCount).to.equal(3);
|
|
@@ -472,14 +550,14 @@ describe('GraphBuilder', () => {
|
|
|
472
550
|
let count = 0;
|
|
473
551
|
const trigger = new Trigger();
|
|
474
552
|
builder.graph.onNodeChanged.on(({ id }) => {
|
|
475
|
-
Graph.expand(builder.graph, id);
|
|
553
|
+
Graph.expand(builder.graph, id, 'child');
|
|
476
554
|
count++;
|
|
477
555
|
if (count === 5) {
|
|
478
556
|
trigger.wake();
|
|
479
557
|
}
|
|
480
558
|
});
|
|
481
559
|
|
|
482
|
-
Graph.expand(builder.graph, Node.RootId);
|
|
560
|
+
Graph.expand(builder.graph, Node.RootId, 'child');
|
|
483
561
|
await trigger.wait();
|
|
484
562
|
expect(count).to.equal(5);
|
|
485
563
|
});
|
|
@@ -507,6 +585,7 @@ describe('GraphBuilder', () => {
|
|
|
507
585
|
|
|
508
586
|
let count = 0;
|
|
509
587
|
await GraphBuilder.explore(builder, {
|
|
588
|
+
relation: 'child',
|
|
510
589
|
visitor: () => {
|
|
511
590
|
count++;
|
|
512
591
|
},
|
|
@@ -518,7 +597,7 @@ describe('GraphBuilder', () => {
|
|
|
518
597
|
|
|
519
598
|
describe('helpers', () => {
|
|
520
599
|
describe('createConnector', () => {
|
|
521
|
-
test('creates connector with type inference', () => {
|
|
600
|
+
test('creates connector with type inference', async () => {
|
|
522
601
|
const registry = Registry.make();
|
|
523
602
|
const builder = GraphBuilder.make({ registry });
|
|
524
603
|
const graph = builder.graph;
|
|
@@ -536,16 +615,17 @@ describe('GraphBuilder', () => {
|
|
|
536
615
|
}),
|
|
537
616
|
);
|
|
538
617
|
|
|
539
|
-
Graph.expand(graph, Node.RootId);
|
|
618
|
+
Graph.expand(graph, Node.RootId, 'child');
|
|
619
|
+
await GraphBuilder.flush(builder);
|
|
540
620
|
|
|
541
|
-
const connections = registry.get(graph.connections(Node.RootId));
|
|
621
|
+
const connections = registry.get(graph.connections(Node.RootId, 'child'));
|
|
542
622
|
expect(connections).has.length(1);
|
|
543
|
-
expect(connections[0].id).to.equal('child');
|
|
623
|
+
expect(connections[0].id).to.equal('root/child');
|
|
544
624
|
});
|
|
545
625
|
});
|
|
546
626
|
|
|
547
627
|
describe('createExtension', () => {
|
|
548
|
-
test('works with Effect connector', () => {
|
|
628
|
+
test('works with Effect connector', async () => {
|
|
549
629
|
const registry = Registry.make();
|
|
550
630
|
const builder = GraphBuilder.make({ registry });
|
|
551
631
|
const graph = builder.graph;
|
|
@@ -562,15 +642,16 @@ describe('GraphBuilder', () => {
|
|
|
562
642
|
|
|
563
643
|
const writableGraph = graph as Graph.WritableGraph;
|
|
564
644
|
Graph.addNode(writableGraph, { id: 'parent', type: EXAMPLE_TYPE, properties: {}, data: 'test' });
|
|
565
|
-
Graph.expand(graph, 'parent');
|
|
645
|
+
Graph.expand(graph, 'parent', 'child');
|
|
646
|
+
await GraphBuilder.flush(builder);
|
|
566
647
|
|
|
567
|
-
const connections = registry.get(graph.connections('parent'));
|
|
648
|
+
const connections = registry.get(graph.connections('parent', 'child'));
|
|
568
649
|
expect(connections).has.length(1);
|
|
569
|
-
expect(connections[0].id).to.equal('child');
|
|
650
|
+
expect(connections[0].id).to.equal('parent/child');
|
|
570
651
|
expect(connections[0].data).to.equal('test');
|
|
571
652
|
});
|
|
572
653
|
|
|
573
|
-
test('works with Effect actions', () => {
|
|
654
|
+
test('works with Effect actions', async () => {
|
|
574
655
|
const registry = Registry.make();
|
|
575
656
|
const builder = GraphBuilder.make({ registry });
|
|
576
657
|
const graph = builder.graph;
|
|
@@ -583,10 +664,7 @@ describe('GraphBuilder', () => {
|
|
|
583
664
|
Effect.succeed([
|
|
584
665
|
{
|
|
585
666
|
id: 'test-action',
|
|
586
|
-
data: () =>
|
|
587
|
-
Effect.sync(() => {
|
|
588
|
-
console.log('TestAction');
|
|
589
|
-
}),
|
|
667
|
+
data: () => Effect.void,
|
|
590
668
|
properties: { label: 'Test' },
|
|
591
669
|
},
|
|
592
670
|
]),
|
|
@@ -597,14 +675,83 @@ describe('GraphBuilder', () => {
|
|
|
597
675
|
|
|
598
676
|
const writableGraph = graph as Graph.WritableGraph;
|
|
599
677
|
Graph.addNode(writableGraph, { id: 'parent', type: EXAMPLE_TYPE, properties: {}, data: 'test' });
|
|
600
|
-
Graph.expand(graph, 'parent');
|
|
678
|
+
Graph.expand(graph, 'parent', 'child');
|
|
679
|
+
await GraphBuilder.flush(builder);
|
|
601
680
|
|
|
681
|
+
const edges = registry.get(graph.edges('parent'));
|
|
682
|
+
expect(edges[Graph.relationKey('action')] ?? []).to.have.length(1);
|
|
683
|
+
expect(edges[Graph.relationKey('action')] ?? []).to.include('parent/test-action');
|
|
684
|
+
expect(edges[Graph.relationKey('child')] ?? []).to.have.length(0);
|
|
602
685
|
const actions = registry.get(graph.actions('parent'));
|
|
603
686
|
expect(actions).has.length(1);
|
|
604
|
-
expect(actions[0].id).to.equal('test-action');
|
|
687
|
+
expect(actions[0].id).to.equal('parent/test-action');
|
|
605
688
|
});
|
|
606
689
|
|
|
607
|
-
test('
|
|
690
|
+
test('actions expand automatically with child relation', async ({ expect }) => {
|
|
691
|
+
const registry = Registry.make();
|
|
692
|
+
const builder = GraphBuilder.make({ registry });
|
|
693
|
+
const graph = builder.graph;
|
|
694
|
+
|
|
695
|
+
const extensions = Effect.runSync(
|
|
696
|
+
GraphBuilder.createExtension({
|
|
697
|
+
id: 'test-extension',
|
|
698
|
+
match: NodeMatcher.whenNodeType(EXAMPLE_TYPE),
|
|
699
|
+
connector: (node, get) => Effect.succeed([{ id: 'child', type: EXAMPLE_TYPE, data: 'c' }]),
|
|
700
|
+
actions: (node, get) =>
|
|
701
|
+
Effect.succeed([{ id: 'act1', data: () => Effect.void, properties: { label: 'A' } }]),
|
|
702
|
+
}),
|
|
703
|
+
);
|
|
704
|
+
|
|
705
|
+
GraphBuilder.addExtension(builder, extensions);
|
|
706
|
+
|
|
707
|
+
const writableGraph = graph as Graph.WritableGraph;
|
|
708
|
+
Graph.addNode(writableGraph, { id: 'parent', type: EXAMPLE_TYPE, properties: {}, data: 'test' });
|
|
709
|
+
Graph.expand(graph, 'parent', 'child');
|
|
710
|
+
await GraphBuilder.flush(builder);
|
|
711
|
+
|
|
712
|
+
const edges = registry.get(graph.edges('parent'));
|
|
713
|
+
expect(edges[Graph.relationKey('child')] ?? []).to.include('parent/child');
|
|
714
|
+
expect(edges[Graph.relationKey('action')] ?? []).to.include('parent/act1');
|
|
715
|
+
const actions = registry.get(graph.actions('parent'));
|
|
716
|
+
expect(actions).has.length(1);
|
|
717
|
+
expect(actions[0].id).to.equal('parent/act1');
|
|
718
|
+
const connections = registry.get(graph.connections('parent', 'child'));
|
|
719
|
+
expect(connections).has.length(1);
|
|
720
|
+
expect(connections[0].id).to.equal('parent/child');
|
|
721
|
+
});
|
|
722
|
+
|
|
723
|
+
test('actions appear when extension registered after expand', async ({ expect }) => {
|
|
724
|
+
const registry = Registry.make();
|
|
725
|
+
const builder = GraphBuilder.make({ registry });
|
|
726
|
+
const graph = builder.graph;
|
|
727
|
+
const writableGraph = graph as Graph.WritableGraph;
|
|
728
|
+
|
|
729
|
+
Graph.addNode(writableGraph, { id: 'parent', type: EXAMPLE_TYPE, properties: {}, data: 'test' });
|
|
730
|
+
Graph.expand(graph, 'parent', 'child');
|
|
731
|
+
await GraphBuilder.flush(builder);
|
|
732
|
+
|
|
733
|
+
expect(registry.get(graph.actions('parent'))).to.have.length(0);
|
|
734
|
+
|
|
735
|
+
const extensions = Effect.runSync(
|
|
736
|
+
GraphBuilder.createExtension({
|
|
737
|
+
id: 'late-extension',
|
|
738
|
+
match: NodeMatcher.whenNodeType(EXAMPLE_TYPE),
|
|
739
|
+
actions: (node, get) =>
|
|
740
|
+
Effect.succeed([{ id: 'late-act', data: () => Effect.void, properties: { label: 'Late' } }]),
|
|
741
|
+
}),
|
|
742
|
+
);
|
|
743
|
+
|
|
744
|
+
GraphBuilder.addExtension(builder, extensions);
|
|
745
|
+
await GraphBuilder.flush(builder);
|
|
746
|
+
|
|
747
|
+
const edges = registry.get(graph.edges('parent'));
|
|
748
|
+
expect(edges[Graph.relationKey('action')] ?? []).to.include('parent/late-act');
|
|
749
|
+
const actions = registry.get(graph.actions('parent'));
|
|
750
|
+
expect(actions).has.length(1);
|
|
751
|
+
expect(actions[0].id).to.equal('parent/late-act');
|
|
752
|
+
});
|
|
753
|
+
|
|
754
|
+
test('_actionContext captures and provides services to action execution', async () => {
|
|
608
755
|
const registry = Registry.make();
|
|
609
756
|
const builder = GraphBuilder.make({ registry });
|
|
610
757
|
const graph = builder.graph;
|
|
@@ -648,7 +795,8 @@ describe('GraphBuilder', () => {
|
|
|
648
795
|
|
|
649
796
|
const writableGraph = graph as Graph.WritableGraph;
|
|
650
797
|
Graph.addNode(writableGraph, { id: 'parent', type: EXAMPLE_TYPE, properties: {}, data: 'test' });
|
|
651
|
-
Graph.expand(graph, 'parent');
|
|
798
|
+
Graph.expand(graph, 'parent', 'child');
|
|
799
|
+
await GraphBuilder.flush(builder);
|
|
652
800
|
|
|
653
801
|
const actions = registry.get(graph.actions('parent'));
|
|
654
802
|
expect(actions).has.length(1);
|
|
@@ -691,7 +839,7 @@ describe('GraphBuilder', () => {
|
|
|
691
839
|
expect(node?.data).to.equal('resolved');
|
|
692
840
|
});
|
|
693
841
|
|
|
694
|
-
test('works with connector and actions together', () => {
|
|
842
|
+
test('works with connector and actions together', async () => {
|
|
695
843
|
const registry = Registry.make();
|
|
696
844
|
const builder = GraphBuilder.make({ registry });
|
|
697
845
|
const graph = builder.graph;
|
|
@@ -705,10 +853,7 @@ describe('GraphBuilder', () => {
|
|
|
705
853
|
Effect.succeed([
|
|
706
854
|
{
|
|
707
855
|
id: 'test-action',
|
|
708
|
-
data: () =>
|
|
709
|
-
Effect.sync(() => {
|
|
710
|
-
console.log('TestAction');
|
|
711
|
-
}),
|
|
856
|
+
data: () => Effect.void,
|
|
712
857
|
properties: { label: 'Test' },
|
|
713
858
|
},
|
|
714
859
|
]),
|
|
@@ -719,21 +864,22 @@ describe('GraphBuilder', () => {
|
|
|
719
864
|
|
|
720
865
|
const writableGraph = graph as Graph.WritableGraph;
|
|
721
866
|
Graph.addNode(writableGraph, { id: 'parent', type: EXAMPLE_TYPE, properties: {}, data: 'test' });
|
|
722
|
-
Graph.expand(graph, 'parent');
|
|
867
|
+
Graph.expand(graph, 'parent', 'child');
|
|
868
|
+
await GraphBuilder.flush(builder);
|
|
723
869
|
|
|
724
|
-
const connections = registry.get(graph.connections('parent'));
|
|
870
|
+
const connections = registry.get(graph.connections('parent', 'child'));
|
|
725
871
|
// Should have both the child node and the action node.
|
|
726
872
|
expect(connections.length).to.be.greaterThanOrEqual(1);
|
|
727
|
-
const childNode = connections.find((n) => n.id === 'child');
|
|
873
|
+
const childNode = connections.find((n) => n.id === 'parent/child');
|
|
728
874
|
expect(childNode).to.not.be.undefined;
|
|
729
875
|
expect(childNode?.data).to.equal('test');
|
|
730
876
|
|
|
731
877
|
const actions = registry.get(graph.actions('parent'));
|
|
732
878
|
expect(actions).has.length(1);
|
|
733
|
-
expect(actions[0].id).to.equal('test-action');
|
|
879
|
+
expect(actions[0].id).to.equal('parent/test-action');
|
|
734
880
|
});
|
|
735
881
|
|
|
736
|
-
test('works with reactive connector using get context', () => {
|
|
882
|
+
test('works with reactive connector using get context', async () => {
|
|
737
883
|
const registry = Registry.make();
|
|
738
884
|
const builder = GraphBuilder.make({ registry });
|
|
739
885
|
const graph = builder.graph;
|
|
@@ -752,18 +898,20 @@ describe('GraphBuilder', () => {
|
|
|
752
898
|
|
|
753
899
|
const writableGraph = graph as Graph.WritableGraph;
|
|
754
900
|
Graph.addNode(writableGraph, { id: 'parent', type: EXAMPLE_TYPE, properties: {}, data: 'test' });
|
|
755
|
-
Graph.expand(graph, 'parent');
|
|
901
|
+
Graph.expand(graph, 'parent', 'child');
|
|
902
|
+
await GraphBuilder.flush(builder);
|
|
756
903
|
|
|
757
904
|
{
|
|
758
|
-
const connections = registry.get(graph.connections('parent'));
|
|
905
|
+
const connections = registry.get(graph.connections('parent', 'child'));
|
|
759
906
|
expect(connections).has.length(1);
|
|
760
907
|
expect(connections[0].data).to.equal('initial');
|
|
761
908
|
}
|
|
762
909
|
|
|
763
910
|
registry.set(state, 'updated');
|
|
911
|
+
await GraphBuilder.flush(builder);
|
|
764
912
|
|
|
765
913
|
{
|
|
766
|
-
const connections = registry.get(graph.connections('parent'));
|
|
914
|
+
const connections = registry.get(graph.connections('parent', 'child'));
|
|
767
915
|
expect(connections).has.length(1);
|
|
768
916
|
expect(connections[0].data).to.equal('updated');
|
|
769
917
|
}
|
|
@@ -771,7 +919,7 @@ describe('GraphBuilder', () => {
|
|
|
771
919
|
});
|
|
772
920
|
|
|
773
921
|
describe('extension error handling', () => {
|
|
774
|
-
test('connector failure is caught and logged, returns empty array', () => {
|
|
922
|
+
test('connector failure is caught and logged, returns empty array', async () => {
|
|
775
923
|
const registry = Registry.make();
|
|
776
924
|
const builder = GraphBuilder.make({ registry });
|
|
777
925
|
const graph = builder.graph;
|
|
@@ -790,14 +938,15 @@ describe('GraphBuilder', () => {
|
|
|
790
938
|
Graph.addNode(writableGraph, { id: 'parent', type: EXAMPLE_TYPE, properties: {}, data: 'test' });
|
|
791
939
|
|
|
792
940
|
// Should not throw, error is caught internally.
|
|
793
|
-
Graph.expand(graph, 'parent');
|
|
941
|
+
Graph.expand(graph, 'parent', 'child');
|
|
942
|
+
await GraphBuilder.flush(builder);
|
|
794
943
|
|
|
795
944
|
// Should return empty connections since the connector failed.
|
|
796
|
-
const connections = registry.get(graph.connections('parent'));
|
|
945
|
+
const connections = registry.get(graph.connections('parent', 'child'));
|
|
797
946
|
expect(connections).has.length(0);
|
|
798
947
|
});
|
|
799
948
|
|
|
800
|
-
test('actions failure is caught and logged, returns empty array', () => {
|
|
949
|
+
test('actions failure is caught and logged, returns empty array', async () => {
|
|
801
950
|
const registry = Registry.make();
|
|
802
951
|
const builder = GraphBuilder.make({ registry });
|
|
803
952
|
const graph = builder.graph;
|
|
@@ -816,7 +965,8 @@ describe('GraphBuilder', () => {
|
|
|
816
965
|
Graph.addNode(writableGraph, { id: 'parent', type: EXAMPLE_TYPE, properties: {}, data: 'test' });
|
|
817
966
|
|
|
818
967
|
// Should not throw, error is caught internally.
|
|
819
|
-
Graph.expand(graph, 'parent');
|
|
968
|
+
Graph.expand(graph, 'parent', 'child');
|
|
969
|
+
await GraphBuilder.flush(builder);
|
|
820
970
|
|
|
821
971
|
// Should return empty actions since the actions callback failed.
|
|
822
972
|
const actions = registry.get(graph.actions('parent'));
|
|
@@ -846,7 +996,7 @@ describe('GraphBuilder', () => {
|
|
|
846
996
|
expect(node).to.be.null;
|
|
847
997
|
});
|
|
848
998
|
|
|
849
|
-
test('failing extension does not affect other extensions', () => {
|
|
999
|
+
test('failing extension does not affect other extensions', async () => {
|
|
850
1000
|
const registry = Registry.make();
|
|
851
1001
|
const builder = GraphBuilder.make({ registry });
|
|
852
1002
|
const graph = builder.graph;
|
|
@@ -875,18 +1025,19 @@ describe('GraphBuilder', () => {
|
|
|
875
1025
|
|
|
876
1026
|
const writableGraph = graph as Graph.WritableGraph;
|
|
877
1027
|
Graph.addNode(writableGraph, { id: 'parent', type: EXAMPLE_TYPE, properties: {}, data: 'test' });
|
|
878
|
-
Graph.expand(graph, 'parent');
|
|
1028
|
+
Graph.expand(graph, 'parent', 'child');
|
|
1029
|
+
await GraphBuilder.flush(builder);
|
|
879
1030
|
|
|
880
1031
|
// The working extension should still produce its node.
|
|
881
|
-
const connections = registry.get(graph.connections('parent'));
|
|
1032
|
+
const connections = registry.get(graph.connections('parent', 'child'));
|
|
882
1033
|
expect(connections).has.length(1);
|
|
883
|
-
expect(connections[0].id).to.equal('child-from-working');
|
|
1034
|
+
expect(connections[0].id).to.equal('parent/child-from-working');
|
|
884
1035
|
expect(connections[0].data).to.equal('success');
|
|
885
1036
|
});
|
|
886
1037
|
});
|
|
887
1038
|
|
|
888
1039
|
describe('createTypeExtension', () => {
|
|
889
|
-
test('creates extension matching by schema type with inferred object type', () => {
|
|
1040
|
+
test('creates extension matching by schema type with inferred object type', async () => {
|
|
890
1041
|
const registry = Registry.make();
|
|
891
1042
|
const builder = GraphBuilder.make({ registry });
|
|
892
1043
|
const graph = builder.graph;
|
|
@@ -904,13 +1055,204 @@ describe('GraphBuilder', () => {
|
|
|
904
1055
|
const writableGraph = graph as Graph.WritableGraph;
|
|
905
1056
|
const testObject = Obj.make(TestSchema.Person, { name: 'Test' });
|
|
906
1057
|
Graph.addNode(writableGraph, { id: 'parent', type: EXAMPLE_TYPE, properties: {}, data: testObject });
|
|
907
|
-
Graph.expand(graph, 'parent');
|
|
1058
|
+
Graph.expand(graph, 'parent', 'child');
|
|
1059
|
+
await GraphBuilder.flush(builder);
|
|
908
1060
|
|
|
909
|
-
const connections = registry.get(graph.connections('parent'));
|
|
1061
|
+
const connections = registry.get(graph.connections('parent', 'child'));
|
|
910
1062
|
expect(connections).has.length(1);
|
|
911
|
-
expect(connections[0].id).to.equal('child');
|
|
1063
|
+
expect(connections[0].id).to.equal('parent/child');
|
|
912
1064
|
expect(connections[0].data).to.equal(testObject);
|
|
913
1065
|
});
|
|
914
1066
|
});
|
|
915
1067
|
});
|
|
1068
|
+
describe('path-based ID qualification', () => {
|
|
1069
|
+
test('rejects segment IDs containing slash', async () => {
|
|
1070
|
+
const registry = Registry.make();
|
|
1071
|
+
const builder = GraphBuilder.make({ registry });
|
|
1072
|
+
GraphBuilder.addExtension(
|
|
1073
|
+
builder,
|
|
1074
|
+
GraphBuilder.createExtensionRaw({
|
|
1075
|
+
id: 'bad-connector',
|
|
1076
|
+
connector: () => Atom.make([{ id: 'foo/bar', type: EXAMPLE_TYPE, data: null }]),
|
|
1077
|
+
}),
|
|
1078
|
+
);
|
|
1079
|
+
|
|
1080
|
+
expect(() => Graph.expand(builder.graph, Node.RootId, 'child')).toThrow(/must not contain/);
|
|
1081
|
+
});
|
|
1082
|
+
|
|
1083
|
+
test('multi-level path qualification', async () => {
|
|
1084
|
+
const registry = Registry.make();
|
|
1085
|
+
const builder = GraphBuilder.make({ registry });
|
|
1086
|
+
GraphBuilder.addExtension(builder, [
|
|
1087
|
+
GraphBuilder.createExtensionRaw({
|
|
1088
|
+
id: 'level1',
|
|
1089
|
+
connector: (node) =>
|
|
1090
|
+
Atom.make((get) =>
|
|
1091
|
+
Function.pipe(
|
|
1092
|
+
get(node),
|
|
1093
|
+
Option.filter((n) => n.id === 'root'),
|
|
1094
|
+
Option.map(() => [{ id: 'A', type: EXAMPLE_TYPE, data: 'a' }]),
|
|
1095
|
+
Option.getOrElse(() => []),
|
|
1096
|
+
),
|
|
1097
|
+
),
|
|
1098
|
+
}),
|
|
1099
|
+
GraphBuilder.createExtensionRaw({
|
|
1100
|
+
id: 'level2',
|
|
1101
|
+
connector: (node) =>
|
|
1102
|
+
Atom.make((get) =>
|
|
1103
|
+
Function.pipe(
|
|
1104
|
+
get(node),
|
|
1105
|
+
Option.filter((n) => n.id === 'root/A'),
|
|
1106
|
+
Option.map(() => [{ id: 'B', type: EXAMPLE_TYPE, data: 'b' }]),
|
|
1107
|
+
Option.getOrElse(() => []),
|
|
1108
|
+
),
|
|
1109
|
+
),
|
|
1110
|
+
}),
|
|
1111
|
+
]);
|
|
1112
|
+
|
|
1113
|
+
const graph = builder.graph;
|
|
1114
|
+
|
|
1115
|
+
Graph.expand(graph, Node.RootId, 'child');
|
|
1116
|
+
await GraphBuilder.flush(builder);
|
|
1117
|
+
|
|
1118
|
+
const level1 = registry.get(graph.connections(Node.RootId, 'child'));
|
|
1119
|
+
expect(level1).has.length(1);
|
|
1120
|
+
expect(level1[0].id).to.equal('root/A');
|
|
1121
|
+
|
|
1122
|
+
Graph.expand(graph, 'root/A', 'child');
|
|
1123
|
+
await GraphBuilder.flush(builder);
|
|
1124
|
+
|
|
1125
|
+
const level2 = registry.get(graph.connections('root/A', 'child'));
|
|
1126
|
+
expect(level2).has.length(1);
|
|
1127
|
+
expect(level2[0].id).to.equal('root/A/B');
|
|
1128
|
+
});
|
|
1129
|
+
|
|
1130
|
+
test('inline nodes are recursively qualified', async () => {
|
|
1131
|
+
const registry = Registry.make();
|
|
1132
|
+
const builder = GraphBuilder.make({ registry });
|
|
1133
|
+
GraphBuilder.addExtension(
|
|
1134
|
+
builder,
|
|
1135
|
+
GraphBuilder.createExtensionRaw({
|
|
1136
|
+
id: 'inline-connector',
|
|
1137
|
+
connector: () =>
|
|
1138
|
+
Atom.make([
|
|
1139
|
+
{
|
|
1140
|
+
id: 'parent-node',
|
|
1141
|
+
type: EXAMPLE_TYPE,
|
|
1142
|
+
data: null,
|
|
1143
|
+
nodes: [
|
|
1144
|
+
{
|
|
1145
|
+
id: 'inline-child',
|
|
1146
|
+
type: EXAMPLE_TYPE,
|
|
1147
|
+
data: null,
|
|
1148
|
+
nodes: [{ id: 'deep-child', type: EXAMPLE_TYPE, data: null }],
|
|
1149
|
+
},
|
|
1150
|
+
],
|
|
1151
|
+
},
|
|
1152
|
+
]),
|
|
1153
|
+
}),
|
|
1154
|
+
);
|
|
1155
|
+
|
|
1156
|
+
const graph = builder.graph;
|
|
1157
|
+
Graph.expand(graph, Node.RootId, 'child');
|
|
1158
|
+
await GraphBuilder.flush(builder);
|
|
1159
|
+
|
|
1160
|
+
const connections = registry.get(graph.connections(Node.RootId, 'child'));
|
|
1161
|
+
expect(connections).has.length(1);
|
|
1162
|
+
expect(connections[0].id).to.equal('root/parent-node');
|
|
1163
|
+
|
|
1164
|
+
const inlineNode = Graph.getNode(graph, 'root/parent-node/inline-child').pipe(Option.getOrNull);
|
|
1165
|
+
expect(inlineNode).to.not.be.null;
|
|
1166
|
+
expect(inlineNode?.id).to.equal('root/parent-node/inline-child');
|
|
1167
|
+
|
|
1168
|
+
const deepNode = Graph.getNode(graph, 'root/parent-node/inline-child/deep-child').pipe(Option.getOrNull);
|
|
1169
|
+
expect(deepNode).to.not.be.null;
|
|
1170
|
+
expect(deepNode?.id).to.equal('root/parent-node/inline-child/deep-child');
|
|
1171
|
+
});
|
|
1172
|
+
|
|
1173
|
+
test('constant connector produces distinct nodes under different parents', async () => {
|
|
1174
|
+
const registry = Registry.make();
|
|
1175
|
+
const builder = GraphBuilder.make({ registry });
|
|
1176
|
+
|
|
1177
|
+
GraphBuilder.addExtension(builder, [
|
|
1178
|
+
GraphBuilder.createExtensionRaw({
|
|
1179
|
+
id: 'parents',
|
|
1180
|
+
connector: (node) =>
|
|
1181
|
+
Atom.make((get) =>
|
|
1182
|
+
Function.pipe(
|
|
1183
|
+
get(node),
|
|
1184
|
+
Option.filter((n) => n.id === 'root'),
|
|
1185
|
+
Option.map(() => [
|
|
1186
|
+
{ id: 'A', type: EXAMPLE_TYPE, data: 'a' },
|
|
1187
|
+
{ id: 'B', type: EXAMPLE_TYPE, data: 'b' },
|
|
1188
|
+
]),
|
|
1189
|
+
Option.getOrElse(() => []),
|
|
1190
|
+
),
|
|
1191
|
+
),
|
|
1192
|
+
}),
|
|
1193
|
+
GraphBuilder.createExtensionRaw({
|
|
1194
|
+
id: 'constant-child',
|
|
1195
|
+
connector: () => Atom.make([{ id: 'shared', type: EXAMPLE_TYPE, data: 'constant' }]),
|
|
1196
|
+
}),
|
|
1197
|
+
]);
|
|
1198
|
+
|
|
1199
|
+
const graph = builder.graph;
|
|
1200
|
+
|
|
1201
|
+
Graph.expand(graph, Node.RootId, 'child');
|
|
1202
|
+
await GraphBuilder.flush(builder);
|
|
1203
|
+
|
|
1204
|
+
Graph.expand(graph, 'root/A', 'child');
|
|
1205
|
+
Graph.expand(graph, 'root/B', 'child');
|
|
1206
|
+
await GraphBuilder.flush(builder);
|
|
1207
|
+
|
|
1208
|
+
const childrenOfA = registry.get(graph.connections('root/A', 'child'));
|
|
1209
|
+
const childrenOfB = registry.get(graph.connections('root/B', 'child'));
|
|
1210
|
+
|
|
1211
|
+
expect(childrenOfA).has.length(1);
|
|
1212
|
+
expect(childrenOfB).has.length(1);
|
|
1213
|
+
expect(childrenOfA[0].id).to.equal('root/A/shared');
|
|
1214
|
+
expect(childrenOfB[0].id).to.equal('root/B/shared');
|
|
1215
|
+
|
|
1216
|
+
const nodeA = Graph.getNode(graph, 'root/A/shared').pipe(Option.getOrNull);
|
|
1217
|
+
const nodeB = Graph.getNode(graph, 'root/B/shared').pipe(Option.getOrNull);
|
|
1218
|
+
expect(nodeA).to.not.be.null;
|
|
1219
|
+
expect(nodeB).to.not.be.null;
|
|
1220
|
+
expect(nodeA?.id).to.not.equal(nodeB?.id);
|
|
1221
|
+
});
|
|
1222
|
+
|
|
1223
|
+
test('explore qualifies node IDs', async () => {
|
|
1224
|
+
const builder = GraphBuilder.make();
|
|
1225
|
+
GraphBuilder.addExtension(
|
|
1226
|
+
builder,
|
|
1227
|
+
GraphBuilder.createExtensionRaw({
|
|
1228
|
+
id: 'connector',
|
|
1229
|
+
connector: (node) =>
|
|
1230
|
+
Atom.make((get) =>
|
|
1231
|
+
Function.pipe(
|
|
1232
|
+
get(node),
|
|
1233
|
+
Option.filter((n) => n.id === 'root'),
|
|
1234
|
+
Option.map(() => [
|
|
1235
|
+
{ id: 'first', type: EXAMPLE_TYPE, data: 1 },
|
|
1236
|
+
{ id: 'second', type: EXAMPLE_TYPE, data: 2 },
|
|
1237
|
+
]),
|
|
1238
|
+
Option.getOrElse(() => []),
|
|
1239
|
+
),
|
|
1240
|
+
),
|
|
1241
|
+
}),
|
|
1242
|
+
);
|
|
1243
|
+
|
|
1244
|
+
const visited: Array<{ id: string; path: string[] }> = [];
|
|
1245
|
+
await GraphBuilder.explore(builder, {
|
|
1246
|
+
relation: 'child',
|
|
1247
|
+
visitor: (node, path) => {
|
|
1248
|
+
visited.push({ id: node.id, path });
|
|
1249
|
+
},
|
|
1250
|
+
});
|
|
1251
|
+
|
|
1252
|
+
expect(visited).has.length(3);
|
|
1253
|
+
expect(visited[0].id).to.equal('root');
|
|
1254
|
+
expect(visited[1].id).to.equal('root/first');
|
|
1255
|
+
expect(visited[2].id).to.equal('root/second');
|
|
1256
|
+
});
|
|
1257
|
+
});
|
|
916
1258
|
});
|