@dxos/app-graph 0.8.4-main.dedc0f3 → 0.8.4-main.e8ec1fe
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 +216 -233
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +216 -233
- package/dist/lib/node-esm/index.mjs.map +3 -3
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/types/src/graph-builder.d.ts +16 -16
- package/dist/types/src/graph-builder.d.ts.map +1 -1
- package/dist/types/src/graph.d.ts +19 -19
- package/dist/types/src/graph.d.ts.map +1 -1
- package/dist/types/src/stories/EchoGraph.stories.d.ts +0 -1
- package/dist/types/src/stories/EchoGraph.stories.d.ts.map +1 -1
- package/dist/types/src/testing.d.ts +3 -3
- package/dist/types/src/testing.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +31 -32
- package/src/graph-builder.test.ts +34 -33
- package/src/graph-builder.ts +50 -45
- package/src/graph.test.ts +3 -3
- package/src/graph.ts +115 -73
- package/src/signals-integration.test.ts +29 -28
- package/src/stories/EchoGraph.stories.tsx +28 -22
- package/src/stories/Tree.tsx +1 -1
- package/src/testing.ts +4 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/app-graph",
|
|
3
|
-
"version": "0.8.4-main.
|
|
3
|
+
"version": "0.8.4-main.e8ec1fe",
|
|
4
4
|
"description": "Constructs knowledge graphs for the purpose of building applications on top of",
|
|
5
5
|
"homepage": "https://dxos.org",
|
|
6
6
|
"bugs": "https://github.com/dxos/dxos/issues",
|
|
@@ -25,44 +25,43 @@
|
|
|
25
25
|
"src"
|
|
26
26
|
],
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@preact/signals-core": "^1.
|
|
28
|
+
"@preact/signals-core": "^1.12.1",
|
|
29
29
|
"main-thread-scheduling": "^14.1.1",
|
|
30
|
-
"@dxos/async": "0.8.4-main.
|
|
31
|
-
"@dxos/debug": "0.8.4-main.
|
|
32
|
-
"@dxos/echo": "0.8.4-main.
|
|
33
|
-
"@dxos/
|
|
34
|
-
"@dxos/
|
|
35
|
-
"@dxos/
|
|
36
|
-
"@dxos/
|
|
37
|
-
"@dxos/
|
|
38
|
-
"@dxos/util": "0.8.4-main.dedc0f3"
|
|
30
|
+
"@dxos/async": "0.8.4-main.e8ec1fe",
|
|
31
|
+
"@dxos/debug": "0.8.4-main.e8ec1fe",
|
|
32
|
+
"@dxos/echo": "0.8.4-main.e8ec1fe",
|
|
33
|
+
"@dxos/invariant": "0.8.4-main.e8ec1fe",
|
|
34
|
+
"@dxos/live-object": "0.8.4-main.e8ec1fe",
|
|
35
|
+
"@dxos/log": "0.8.4-main.e8ec1fe",
|
|
36
|
+
"@dxos/echo-signals": "0.8.4-main.e8ec1fe",
|
|
37
|
+
"@dxos/util": "0.8.4-main.e8ec1fe"
|
|
39
38
|
},
|
|
40
39
|
"devDependencies": {
|
|
41
|
-
"@effect-
|
|
42
|
-
"@effect/platform": "0.
|
|
43
|
-
"@types/react": "~
|
|
44
|
-
"@types/react-dom": "~
|
|
45
|
-
"effect": "3.
|
|
46
|
-
"react": "~
|
|
47
|
-
"react-dom": "~
|
|
48
|
-
"vite": "7.1.
|
|
49
|
-
"@dxos/echo-db": "0.8.4-main.
|
|
50
|
-
"@dxos/random": "0.8.4-main.
|
|
51
|
-
"@dxos/react-client": "0.8.4-main.
|
|
52
|
-
"@dxos/react-ui": "0.8.4-main.
|
|
53
|
-
"@dxos/react-ui-list": "0.8.4-main.
|
|
54
|
-
"@dxos/react-ui-tabs": "0.8.4-main.
|
|
55
|
-
"@dxos/react-ui-theme": "0.8.4-main.
|
|
56
|
-
"@dxos/storybook-utils": "0.8.4-main.
|
|
40
|
+
"@effect-atom/atom-react": "^0.3.4",
|
|
41
|
+
"@effect/platform": "0.92.1",
|
|
42
|
+
"@types/react": "~19.2.2",
|
|
43
|
+
"@types/react-dom": "~19.2.2",
|
|
44
|
+
"effect": "3.18.3",
|
|
45
|
+
"react": "~19.2.0",
|
|
46
|
+
"react-dom": "~19.2.0",
|
|
47
|
+
"vite": "7.1.9",
|
|
48
|
+
"@dxos/echo-db": "0.8.4-main.e8ec1fe",
|
|
49
|
+
"@dxos/random": "0.8.4-main.e8ec1fe",
|
|
50
|
+
"@dxos/react-client": "0.8.4-main.e8ec1fe",
|
|
51
|
+
"@dxos/react-ui": "0.8.4-main.e8ec1fe",
|
|
52
|
+
"@dxos/react-ui-list": "0.8.4-main.e8ec1fe",
|
|
53
|
+
"@dxos/react-ui-tabs": "0.8.4-main.e8ec1fe",
|
|
54
|
+
"@dxos/react-ui-theme": "0.8.4-main.e8ec1fe",
|
|
55
|
+
"@dxos/storybook-utils": "0.8.4-main.e8ec1fe"
|
|
57
56
|
},
|
|
58
57
|
"peerDependencies": {
|
|
59
|
-
"@effect-
|
|
58
|
+
"@effect-atom/atom-react": "^0.3.4",
|
|
60
59
|
"@effect/platform": "^0.80.12",
|
|
61
60
|
"effect": "3.14.21",
|
|
62
|
-
"react": "
|
|
63
|
-
"react-dom": "
|
|
64
|
-
"@dxos/react-ui": "0.8.4-main.
|
|
65
|
-
"@dxos/react-ui-theme": "0.8.4-main.
|
|
61
|
+
"react": "^19.0.0",
|
|
62
|
+
"react-dom": "^19.0.0",
|
|
63
|
+
"@dxos/react-ui": "0.8.4-main.e8ec1fe",
|
|
64
|
+
"@dxos/react-ui-theme": "0.8.4-main.e8ec1fe"
|
|
66
65
|
},
|
|
67
66
|
"publishConfig": {
|
|
68
67
|
"access": "public"
|
|
@@ -2,8 +2,9 @@
|
|
|
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 Function from 'effect/Function';
|
|
7
|
+
import * as Option from 'effect/Option';
|
|
7
8
|
import { describe, expect, onTestFinished, test } from 'vitest';
|
|
8
9
|
|
|
9
10
|
import { Trigger, sleep } from '@dxos/async';
|
|
@@ -33,7 +34,7 @@ describe('GraphBuilder', () => {
|
|
|
33
34
|
id: 'resolver',
|
|
34
35
|
resolver: () => {
|
|
35
36
|
console.log('resolver');
|
|
36
|
-
return
|
|
37
|
+
return Atom.make({ id: EXAMPLE_ID, type: EXAMPLE_TYPE, data: 1 });
|
|
37
38
|
},
|
|
38
39
|
}),
|
|
39
40
|
);
|
|
@@ -50,11 +51,11 @@ describe('GraphBuilder', () => {
|
|
|
50
51
|
test('updates', async () => {
|
|
51
52
|
const registry = Registry.make();
|
|
52
53
|
const builder = new GraphBuilder({ registry });
|
|
53
|
-
const name =
|
|
54
|
+
const name = Atom.make('default');
|
|
54
55
|
builder.addExtension(
|
|
55
56
|
createExtension({
|
|
56
57
|
id: 'resolver',
|
|
57
|
-
resolver: () =>
|
|
58
|
+
resolver: () => Atom.make((get) => ({ id: EXAMPLE_ID, type: EXAMPLE_TYPE, data: get(name) })),
|
|
58
59
|
}),
|
|
59
60
|
);
|
|
60
61
|
const graph = builder.graph;
|
|
@@ -81,14 +82,14 @@ describe('GraphBuilder', () => {
|
|
|
81
82
|
builder.addExtension(
|
|
82
83
|
createExtension({
|
|
83
84
|
id: 'outbound-connector',
|
|
84
|
-
connector: () =>
|
|
85
|
+
connector: () => Atom.make([{ id: 'child', type: EXAMPLE_TYPE, data: 2 }]),
|
|
85
86
|
}),
|
|
86
87
|
);
|
|
87
88
|
builder.addExtension(
|
|
88
89
|
createExtension({
|
|
89
90
|
id: 'inbound-connector',
|
|
90
91
|
relation: 'inbound',
|
|
91
|
-
connector: () =>
|
|
92
|
+
connector: () => Atom.make([{ id: 'parent', type: EXAMPLE_TYPE, data: 0 }]),
|
|
92
93
|
}),
|
|
93
94
|
);
|
|
94
95
|
|
|
@@ -110,11 +111,11 @@ describe('GraphBuilder', () => {
|
|
|
110
111
|
test('updates', () => {
|
|
111
112
|
const registry = Registry.make();
|
|
112
113
|
const builder = new GraphBuilder({ registry });
|
|
113
|
-
const state =
|
|
114
|
+
const state = Atom.make(0);
|
|
114
115
|
builder.addExtension(
|
|
115
116
|
createExtension({
|
|
116
117
|
id: 'connector',
|
|
117
|
-
connector: () =>
|
|
118
|
+
connector: () => Atom.make((get) => [{ id: EXAMPLE_ID, type: EXAMPLE_TYPE, data: get(state) }]),
|
|
118
119
|
}),
|
|
119
120
|
);
|
|
120
121
|
const graph = builder.graph;
|
|
@@ -135,11 +136,11 @@ describe('GraphBuilder', () => {
|
|
|
135
136
|
test('subscribes to updates', () => {
|
|
136
137
|
const registry = Registry.make();
|
|
137
138
|
const builder = new GraphBuilder({ registry });
|
|
138
|
-
const state =
|
|
139
|
+
const state = Atom.make(0);
|
|
139
140
|
builder.addExtension(
|
|
140
141
|
createExtension({
|
|
141
142
|
id: 'connector',
|
|
142
|
-
connector: () =>
|
|
143
|
+
connector: () => Atom.make((get) => [{ id: EXAMPLE_ID, type: EXAMPLE_TYPE, data: get(state) }]),
|
|
143
144
|
}),
|
|
144
145
|
);
|
|
145
146
|
const graph = builder.graph;
|
|
@@ -166,7 +167,7 @@ describe('GraphBuilder', () => {
|
|
|
166
167
|
builder.addExtension(
|
|
167
168
|
createExtension({
|
|
168
169
|
id: 'connector',
|
|
169
|
-
connector: () =>
|
|
170
|
+
connector: () => Atom.make([{ id: EXAMPLE_ID, type: EXAMPLE_TYPE }]),
|
|
170
171
|
}),
|
|
171
172
|
);
|
|
172
173
|
const graph = builder.graph;
|
|
@@ -189,7 +190,7 @@ describe('GraphBuilder', () => {
|
|
|
189
190
|
builder.addExtension(
|
|
190
191
|
createExtension({
|
|
191
192
|
id: 'connector-2',
|
|
192
|
-
connector: () =>
|
|
193
|
+
connector: () => Atom.make([{ id: exampleId(2), type: EXAMPLE_TYPE }]),
|
|
193
194
|
}),
|
|
194
195
|
);
|
|
195
196
|
expect(nodes).has.length(2);
|
|
@@ -199,14 +200,14 @@ describe('GraphBuilder', () => {
|
|
|
199
200
|
test('removes', () => {
|
|
200
201
|
const registry = Registry.make();
|
|
201
202
|
const builder = new GraphBuilder({ registry });
|
|
202
|
-
const nodes =
|
|
203
|
+
const nodes = Atom.make([
|
|
203
204
|
{ id: exampleId(1), type: EXAMPLE_TYPE },
|
|
204
205
|
{ id: exampleId(2), type: EXAMPLE_TYPE },
|
|
205
206
|
]);
|
|
206
207
|
builder.addExtension(
|
|
207
208
|
createExtension({
|
|
208
209
|
id: 'connector',
|
|
209
|
-
connector: () =>
|
|
210
|
+
connector: () => Atom.make((get) => get(nodes)),
|
|
210
211
|
}),
|
|
211
212
|
);
|
|
212
213
|
const graph = builder.graph;
|
|
@@ -231,14 +232,14 @@ describe('GraphBuilder', () => {
|
|
|
231
232
|
test('nodes are updated when removed', () => {
|
|
232
233
|
const registry = Registry.make();
|
|
233
234
|
const builder = new GraphBuilder({ registry });
|
|
234
|
-
const name =
|
|
235
|
+
const name = Atom.make('removed');
|
|
235
236
|
|
|
236
237
|
builder.addExtension([
|
|
237
238
|
createExtension({
|
|
238
239
|
id: 'root',
|
|
239
240
|
connector: (node) =>
|
|
240
|
-
|
|
241
|
-
pipe(
|
|
241
|
+
Atom.make((get) =>
|
|
242
|
+
Function.pipe(
|
|
242
243
|
get(node),
|
|
243
244
|
Option.flatMap((node) => (node.id === 'root' ? Option.some(get(name)) : Option.none())),
|
|
244
245
|
Option.filter((name) => name !== 'removed'),
|
|
@@ -279,7 +280,7 @@ describe('GraphBuilder', () => {
|
|
|
279
280
|
test('sort edges', async () => {
|
|
280
281
|
const registry = Registry.make();
|
|
281
282
|
const builder = new GraphBuilder({ registry });
|
|
282
|
-
const nodes =
|
|
283
|
+
const nodes = Atom.make([
|
|
283
284
|
{ id: exampleId(1), type: EXAMPLE_TYPE, data: 1 },
|
|
284
285
|
{ id: exampleId(2), type: EXAMPLE_TYPE, data: 2 },
|
|
285
286
|
{ id: exampleId(3), type: EXAMPLE_TYPE, data: 3 },
|
|
@@ -287,7 +288,7 @@ describe('GraphBuilder', () => {
|
|
|
287
288
|
builder.addExtension(
|
|
288
289
|
createExtension({
|
|
289
290
|
id: 'connector',
|
|
290
|
-
connector: () =>
|
|
291
|
+
connector: () => Atom.make((get) => get(nodes)),
|
|
291
292
|
}),
|
|
292
293
|
);
|
|
293
294
|
const graph = builder.graph;
|
|
@@ -322,15 +323,15 @@ describe('GraphBuilder', () => {
|
|
|
322
323
|
test('updates are constrained', () => {
|
|
323
324
|
const registry = Registry.make();
|
|
324
325
|
const builder = new GraphBuilder({ registry });
|
|
325
|
-
const name =
|
|
326
|
-
const sub =
|
|
326
|
+
const name = Atom.make('default');
|
|
327
|
+
const sub = Atom.make('default');
|
|
327
328
|
|
|
328
329
|
builder.addExtension([
|
|
329
330
|
createExtension({
|
|
330
331
|
id: 'root',
|
|
331
332
|
connector: (node) =>
|
|
332
|
-
|
|
333
|
-
pipe(
|
|
333
|
+
Atom.make((get) =>
|
|
334
|
+
Function.pipe(
|
|
334
335
|
get(node),
|
|
335
336
|
Option.flatMap((node) => (node.id === 'root' ? Option.some(get(name)) : Option.none())),
|
|
336
337
|
Option.filter((name) => name !== 'removed'),
|
|
@@ -342,8 +343,8 @@ describe('GraphBuilder', () => {
|
|
|
342
343
|
createExtension({
|
|
343
344
|
id: 'connector1',
|
|
344
345
|
connector: (node) =>
|
|
345
|
-
|
|
346
|
-
pipe(
|
|
346
|
+
Atom.make((get) =>
|
|
347
|
+
Function.pipe(
|
|
347
348
|
get(node),
|
|
348
349
|
Option.flatMap((node) => (node.id === EXAMPLE_ID ? Option.some(get(sub)) : Option.none())),
|
|
349
350
|
Option.map((sub) => [{ id: exampleId(2), type: EXAMPLE_TYPE, data: sub }]),
|
|
@@ -354,8 +355,8 @@ describe('GraphBuilder', () => {
|
|
|
354
355
|
createExtension({
|
|
355
356
|
id: 'connector2',
|
|
356
357
|
connector: (node) =>
|
|
357
|
-
|
|
358
|
-
pipe(
|
|
358
|
+
Atom.make((get) =>
|
|
359
|
+
Function.pipe(
|
|
359
360
|
get(node),
|
|
360
361
|
Option.flatMap((node) => (node.id === EXAMPLE_ID ? Option.some(node.data) : Option.none())),
|
|
361
362
|
Option.map((data) => [{ id: exampleId(3), type: EXAMPLE_TYPE, data }]),
|
|
@@ -410,7 +411,7 @@ describe('GraphBuilder', () => {
|
|
|
410
411
|
expect(dependentCount).to.equal(2);
|
|
411
412
|
|
|
412
413
|
// Independent count should update if its state changes even if the parent is removed.
|
|
413
|
-
|
|
414
|
+
Atom.batch(() => {
|
|
414
415
|
registry.set(name, 'removed');
|
|
415
416
|
registry.set(sub, 'batch');
|
|
416
417
|
});
|
|
@@ -438,8 +439,8 @@ describe('GraphBuilder', () => {
|
|
|
438
439
|
createExtension({
|
|
439
440
|
id: 'connector',
|
|
440
441
|
connector: (node) => {
|
|
441
|
-
return
|
|
442
|
-
pipe(
|
|
442
|
+
return Atom.make((get) =>
|
|
443
|
+
Function.pipe(
|
|
443
444
|
get(node),
|
|
444
445
|
Option.map((node) => (node.data ? node.data + 1 : 1)),
|
|
445
446
|
Option.filter((data) => data <= 5),
|
|
@@ -474,8 +475,8 @@ describe('GraphBuilder', () => {
|
|
|
474
475
|
createExtension({
|
|
475
476
|
id: 'connector',
|
|
476
477
|
connector: (node) =>
|
|
477
|
-
|
|
478
|
-
pipe(
|
|
478
|
+
Atom.make((get) =>
|
|
479
|
+
Function.pipe(
|
|
479
480
|
get(node),
|
|
480
481
|
Option.map((node) => (node.data ? node.data + 1 : 1)),
|
|
481
482
|
Option.filter((data) => data <= 5),
|
package/src/graph-builder.ts
CHANGED
|
@@ -2,9 +2,12 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { Atom, Registry } from '@effect-atom/atom-react';
|
|
6
6
|
import { effect } from '@preact/signals-core';
|
|
7
|
-
import
|
|
7
|
+
import * as Array from 'effect/Array';
|
|
8
|
+
import * as Function from 'effect/Function';
|
|
9
|
+
import * as Option from 'effect/Option';
|
|
10
|
+
import * as Record from 'effect/Record';
|
|
8
11
|
|
|
9
12
|
import { type CleanupFn, type MulticastObservable, type Trigger } from '@dxos/async';
|
|
10
13
|
import { log } from '@dxos/log';
|
|
@@ -16,28 +19,28 @@ import { type ActionData, type Node, type NodeArg, type Relation, actionGroupSym
|
|
|
16
19
|
/**
|
|
17
20
|
* Graph builder extension for adding nodes to the graph based on a node id.
|
|
18
21
|
*/
|
|
19
|
-
export type ResolverExtension = (id: string) =>
|
|
22
|
+
export type ResolverExtension = (id: string) => Atom.Atom<NodeArg<any> | null>;
|
|
20
23
|
|
|
21
24
|
/**
|
|
22
25
|
* Graph builder extension for adding nodes to the graph based on a connection to an existing node.
|
|
23
26
|
*
|
|
24
27
|
* @param params.node The existing node the returned nodes will be connected to.
|
|
25
28
|
*/
|
|
26
|
-
export type ConnectorExtension = (node:
|
|
29
|
+
export type ConnectorExtension = (node: Atom.Atom<Option.Option<Node>>) => Atom.Atom<NodeArg<any>[]>;
|
|
27
30
|
|
|
28
31
|
/**
|
|
29
32
|
* Constrained case of the connector extension for more easily adding actions to the graph.
|
|
30
33
|
*/
|
|
31
34
|
export type ActionsExtension = (
|
|
32
|
-
node:
|
|
33
|
-
) =>
|
|
35
|
+
node: Atom.Atom<Option.Option<Node>>,
|
|
36
|
+
) => Atom.Atom<Omit<NodeArg<ActionData>, 'type' | 'nodes' | 'edges'>[]>;
|
|
34
37
|
|
|
35
38
|
/**
|
|
36
39
|
* Constrained case of the connector extension for more easily adding action groups to the graph.
|
|
37
40
|
*/
|
|
38
41
|
export type ActionGroupsExtension = (
|
|
39
|
-
node:
|
|
40
|
-
) =>
|
|
42
|
+
node: Atom.Atom<Option.Option<Node>>,
|
|
43
|
+
) => Atom.Atom<Omit<NodeArg<typeof actionGroupSymbol>, 'type' | 'data' | 'nodes' | 'edges'>[]>;
|
|
41
44
|
|
|
42
45
|
/**
|
|
43
46
|
* A graph builder extension is used to add nodes to the graph.
|
|
@@ -76,23 +79,25 @@ export const createExtension = (extension: CreateExtensionOptions): BuilderExten
|
|
|
76
79
|
const getId = (key: string) => `${id}/${key}`;
|
|
77
80
|
|
|
78
81
|
const resolver =
|
|
79
|
-
_resolver &&
|
|
82
|
+
_resolver && Atom.family((id: string) => _resolver(id).pipe(Atom.withLabel(`graph-builder:_resolver:${id}`)));
|
|
80
83
|
|
|
81
84
|
const connector =
|
|
82
85
|
_connector &&
|
|
83
|
-
|
|
84
|
-
_connector(node).pipe(
|
|
86
|
+
Atom.family((node: Atom.Atom<Option.Option<Node>>) =>
|
|
87
|
+
_connector(node).pipe(Atom.withLabel(`graph-builder:_connector:${id}`)),
|
|
85
88
|
);
|
|
86
89
|
|
|
87
90
|
const actionGroups =
|
|
88
91
|
_actionGroups &&
|
|
89
|
-
|
|
90
|
-
_actionGroups(node).pipe(
|
|
92
|
+
Atom.family((node: Atom.Atom<Option.Option<Node>>) =>
|
|
93
|
+
_actionGroups(node).pipe(Atom.withLabel(`graph-builder:_actionGroups:${id}`)),
|
|
91
94
|
);
|
|
92
95
|
|
|
93
96
|
const actions =
|
|
94
97
|
_actions &&
|
|
95
|
-
|
|
98
|
+
Atom.family((node: Atom.Atom<Option.Option<Node>>) =>
|
|
99
|
+
_actions(node).pipe(Atom.withLabel(`graph-builder:_actions:${id}`)),
|
|
100
|
+
);
|
|
96
101
|
|
|
97
102
|
return [
|
|
98
103
|
resolver ? { id: getId('resolver'), position, resolver } : undefined,
|
|
@@ -101,15 +106,15 @@ export const createExtension = (extension: CreateExtensionOptions): BuilderExten
|
|
|
101
106
|
id: getId('connector'),
|
|
102
107
|
position,
|
|
103
108
|
relation,
|
|
104
|
-
connector:
|
|
105
|
-
|
|
109
|
+
connector: Atom.family((node) =>
|
|
110
|
+
Atom.make((get) => {
|
|
106
111
|
try {
|
|
107
112
|
return get(connector(node));
|
|
108
113
|
} catch {
|
|
109
114
|
log.warn('Error in connector', { id: getId('connector'), node });
|
|
110
115
|
return [];
|
|
111
116
|
}
|
|
112
|
-
}).pipe(
|
|
117
|
+
}).pipe(Atom.withLabel(`graph-builder:connector:${id}`)),
|
|
113
118
|
),
|
|
114
119
|
} satisfies BuilderExtension)
|
|
115
120
|
: undefined,
|
|
@@ -118,8 +123,8 @@ export const createExtension = (extension: CreateExtensionOptions): BuilderExten
|
|
|
118
123
|
id: getId('actionGroups'),
|
|
119
124
|
position,
|
|
120
125
|
relation: 'outbound',
|
|
121
|
-
connector:
|
|
122
|
-
|
|
126
|
+
connector: Atom.family((node) =>
|
|
127
|
+
Atom.make((get) => {
|
|
123
128
|
try {
|
|
124
129
|
return get(actionGroups(node)).map((arg) => ({
|
|
125
130
|
...arg,
|
|
@@ -130,7 +135,7 @@ export const createExtension = (extension: CreateExtensionOptions): BuilderExten
|
|
|
130
135
|
log.warn('Error in actionGroups', { id: getId('actionGroups'), node });
|
|
131
136
|
return [];
|
|
132
137
|
}
|
|
133
|
-
}).pipe(
|
|
138
|
+
}).pipe(Atom.withLabel(`graph-builder:connector:actionGroups:${id}`)),
|
|
134
139
|
),
|
|
135
140
|
} satisfies BuilderExtension)
|
|
136
141
|
: undefined,
|
|
@@ -139,15 +144,15 @@ export const createExtension = (extension: CreateExtensionOptions): BuilderExten
|
|
|
139
144
|
id: getId('actions'),
|
|
140
145
|
position,
|
|
141
146
|
relation: 'outbound',
|
|
142
|
-
connector:
|
|
143
|
-
|
|
147
|
+
connector: Atom.family((node) =>
|
|
148
|
+
Atom.make((get) => {
|
|
144
149
|
try {
|
|
145
150
|
return get(actions(node)).map((arg) => ({ ...arg, type: ACTION_TYPE }));
|
|
146
151
|
} catch {
|
|
147
152
|
log.warn('Error in actions', { id: getId('actions'), node });
|
|
148
153
|
return [];
|
|
149
154
|
}
|
|
150
|
-
}).pipe(
|
|
155
|
+
}).pipe(Atom.withLabel(`graph-builder:connector:actions:${id}`)),
|
|
151
156
|
),
|
|
152
157
|
} satisfies BuilderExtension)
|
|
153
158
|
: undefined,
|
|
@@ -166,7 +171,7 @@ export type BuilderExtension = Readonly<{
|
|
|
166
171
|
position: Position;
|
|
167
172
|
relation?: Relation; // Only for connector.
|
|
168
173
|
resolver?: ResolverExtension;
|
|
169
|
-
connector?: (node:
|
|
174
|
+
connector?: (node: Atom.Atom<Option.Option<Node>>) => Atom.Atom<NodeArg<any>[]>;
|
|
170
175
|
}>;
|
|
171
176
|
|
|
172
177
|
export type BuilderExtensions = BuilderExtension | BuilderExtension[] | BuilderExtensions[];
|
|
@@ -188,9 +193,9 @@ export const flattenExtensions = (extension: BuilderExtensions, acc: BuilderExte
|
|
|
188
193
|
export class GraphBuilder {
|
|
189
194
|
// TODO(wittjosiah): Use Context.
|
|
190
195
|
private readonly _subscriptions = new Map<string, CleanupFn>();
|
|
191
|
-
private readonly _extensions =
|
|
192
|
-
|
|
193
|
-
|
|
196
|
+
private readonly _extensions = Atom.make(Record.empty<string, BuilderExtension>()).pipe(
|
|
197
|
+
Atom.keepAlive,
|
|
198
|
+
Atom.withLabel('graph-builder:extensions'),
|
|
194
199
|
);
|
|
195
200
|
private readonly _initialized: Record<string, Trigger> = {};
|
|
196
201
|
private readonly _registry: Registry.Registry;
|
|
@@ -287,9 +292,9 @@ export class GraphBuilder {
|
|
|
287
292
|
this._subscriptions.clear();
|
|
288
293
|
}
|
|
289
294
|
|
|
290
|
-
private readonly _resolvers =
|
|
291
|
-
return
|
|
292
|
-
return pipe(
|
|
295
|
+
private readonly _resolvers = Atom.family<string, Atom.Atom<Option.Option<NodeArg<any>>>>((id) => {
|
|
296
|
+
return Atom.make((get) => {
|
|
297
|
+
return Function.pipe(
|
|
293
298
|
get(this._extensions),
|
|
294
299
|
Record.values,
|
|
295
300
|
Array.sortBy(byPosition),
|
|
@@ -302,12 +307,12 @@ export class GraphBuilder {
|
|
|
302
307
|
});
|
|
303
308
|
});
|
|
304
309
|
|
|
305
|
-
private readonly _connectors =
|
|
306
|
-
return
|
|
310
|
+
private readonly _connectors = Atom.family<string, Atom.Atom<NodeArg<any>[]>>((key) => {
|
|
311
|
+
return Atom.make((get) => {
|
|
307
312
|
const [id, relation] = key.split('+');
|
|
308
313
|
const node = this._graph.node(id);
|
|
309
314
|
|
|
310
|
-
return pipe(
|
|
315
|
+
return Function.pipe(
|
|
311
316
|
get(this._extensions),
|
|
312
317
|
Record.values,
|
|
313
318
|
// TODO(wittjosiah): Sort on write rather than read.
|
|
@@ -317,7 +322,7 @@ export class GraphBuilder {
|
|
|
317
322
|
Array.filter(isNonNullable),
|
|
318
323
|
Array.flatMap((result) => get(result)),
|
|
319
324
|
);
|
|
320
|
-
}).pipe(
|
|
325
|
+
}).pipe(Atom.withLabel(`graph-builder:connectors:${key}`));
|
|
321
326
|
});
|
|
322
327
|
|
|
323
328
|
private _onExpand(id: string, relation: Relation): void {
|
|
@@ -334,7 +339,7 @@ export class GraphBuilder {
|
|
|
334
339
|
|
|
335
340
|
log('update', { id, relation, ids, removed });
|
|
336
341
|
const update = () => {
|
|
337
|
-
|
|
342
|
+
Atom.batch(() => {
|
|
338
343
|
this._graph.removeEdges(
|
|
339
344
|
removed.map((target) => ({ source: id, target })),
|
|
340
345
|
true,
|
|
@@ -400,11 +405,11 @@ export class GraphBuilder {
|
|
|
400
405
|
}
|
|
401
406
|
|
|
402
407
|
/**
|
|
403
|
-
* Creates an
|
|
404
|
-
* Will return a new
|
|
408
|
+
* Creates an Atom.Atom<T> from a callback which accesses signals.
|
|
409
|
+
* Will return a new atom instance each time.
|
|
405
410
|
*/
|
|
406
|
-
export const
|
|
407
|
-
return
|
|
411
|
+
export const atomFromSignal = <T>(cb: () => T): Atom.Atom<T> => {
|
|
412
|
+
return Atom.make((get) => {
|
|
408
413
|
const dispose = effect(() => {
|
|
409
414
|
get.setSelf(cb());
|
|
410
415
|
});
|
|
@@ -415,8 +420,8 @@ export const rxFromSignal = <T>(cb: () => T): Rx.Rx<T> => {
|
|
|
415
420
|
});
|
|
416
421
|
};
|
|
417
422
|
|
|
418
|
-
const observableFamily =
|
|
419
|
-
return
|
|
423
|
+
const observableFamily = Atom.family((observable: MulticastObservable<any>) => {
|
|
424
|
+
return Atom.make((get) => {
|
|
420
425
|
const subscription = observable.subscribe((value) => get.setSelf(value));
|
|
421
426
|
|
|
422
427
|
get.addFinalizer(() => subscription.unsubscribe());
|
|
@@ -426,9 +431,9 @@ const observableFamily = Rx.family((observable: MulticastObservable<any>) => {
|
|
|
426
431
|
});
|
|
427
432
|
|
|
428
433
|
/**
|
|
429
|
-
* Creates an
|
|
430
|
-
* Will return the same
|
|
434
|
+
* Creates an Atom.Atom<T> from a MulticastObservable<T>
|
|
435
|
+
* Will return the same atom instance for the same observable.
|
|
431
436
|
*/
|
|
432
|
-
export const
|
|
433
|
-
return observableFamily(observable) as
|
|
437
|
+
export const atomFromObservable = <T>(observable: MulticastObservable<T>): Atom.Atom<T> => {
|
|
438
|
+
return observableFamily(observable) as Atom.Atom<T>;
|
|
434
439
|
};
|
package/src/graph.test.ts
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
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 Option from 'effect/Option';
|
|
7
7
|
import { assert, describe, expect, onTestFinished, test } from 'vitest';
|
|
8
8
|
|
|
9
9
|
import { Graph, ROOT_ID, ROOT_TYPE, getGraph } from './graph';
|
|
@@ -238,7 +238,7 @@ describe('Graph', () => {
|
|
|
238
238
|
expect(count).toEqual(5);
|
|
239
239
|
|
|
240
240
|
// Batching the edge and node updates fires a single update.
|
|
241
|
-
|
|
241
|
+
Atom.batch(() => {
|
|
242
242
|
graph.addEdge({ source: exampleId(1), target: exampleId(6) });
|
|
243
243
|
graph.addNode({ id: exampleId(6), type: EXAMPLE_TYPE });
|
|
244
244
|
});
|