@dxos/app-graph 0.7.5-main.9d2a38b → 0.7.5-main.e9bb01b
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 +44 -21
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node/index.cjs +43 -19
- package/dist/lib/node/index.cjs.map +3 -3
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +44 -21
- 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 -3
- package/dist/types/src/graph-builder.d.ts.map +1 -1
- package/package.json +16 -16
- package/src/graph-builder.test.ts +36 -0
- package/src/graph-builder.ts +36 -19
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/app-graph",
|
|
3
|
-
"version": "0.7.5-main.
|
|
3
|
+
"version": "0.7.5-main.e9bb01b",
|
|
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,14 +25,14 @@
|
|
|
25
25
|
"dependencies": {
|
|
26
26
|
"@preact/signals-core": "^1.6.0",
|
|
27
27
|
"main-thread-scheduling": "^14.1.1",
|
|
28
|
-
"@dxos/
|
|
29
|
-
"@dxos/
|
|
30
|
-
"@dxos/
|
|
31
|
-
"@dxos/echo-signals": "0.7.5-main.
|
|
32
|
-
"@dxos/
|
|
33
|
-
"@dxos/
|
|
34
|
-
"@dxos/
|
|
35
|
-
"@dxos/
|
|
28
|
+
"@dxos/async": "0.7.5-main.e9bb01b",
|
|
29
|
+
"@dxos/debug": "0.7.5-main.e9bb01b",
|
|
30
|
+
"@dxos/echo-schema": "0.7.5-main.e9bb01b",
|
|
31
|
+
"@dxos/echo-signals": "0.7.5-main.e9bb01b",
|
|
32
|
+
"@dxos/invariant": "0.7.5-main.e9bb01b",
|
|
33
|
+
"@dxos/log": "0.7.5-main.e9bb01b",
|
|
34
|
+
"@dxos/util": "0.7.5-main.e9bb01b",
|
|
35
|
+
"@dxos/live-object": "0.7.5-main.e9bb01b"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@phosphor-icons/react": "^2.1.5",
|
|
@@ -41,18 +41,18 @@
|
|
|
41
41
|
"react": "~18.2.0",
|
|
42
42
|
"react-dom": "~18.2.0",
|
|
43
43
|
"vite": "5.4.7",
|
|
44
|
-
"@dxos/random": "0.7.5-main.
|
|
45
|
-
"@dxos/react-
|
|
46
|
-
"@dxos/react-ui": "0.7.5-main.
|
|
47
|
-
"@dxos/react-
|
|
48
|
-
"@dxos/storybook-utils": "0.7.5-main.
|
|
44
|
+
"@dxos/random": "0.7.5-main.e9bb01b",
|
|
45
|
+
"@dxos/react-client": "0.7.5-main.e9bb01b",
|
|
46
|
+
"@dxos/react-ui": "0.7.5-main.e9bb01b",
|
|
47
|
+
"@dxos/react-ui-theme": "0.7.5-main.e9bb01b",
|
|
48
|
+
"@dxos/storybook-utils": "0.7.5-main.e9bb01b"
|
|
49
49
|
},
|
|
50
50
|
"peerDependencies": {
|
|
51
51
|
"@phosphor-icons/react": "^2.1.5",
|
|
52
52
|
"react": "~18.2.0",
|
|
53
53
|
"react-dom": "~18.2.0",
|
|
54
|
-
"@dxos/react-ui": "0.7.5-main.
|
|
55
|
-
"@dxos/react-ui-theme": "0.7.5-main.
|
|
54
|
+
"@dxos/react-ui": "0.7.5-main.e9bb01b",
|
|
55
|
+
"@dxos/react-ui-theme": "0.7.5-main.e9bb01b"
|
|
56
56
|
},
|
|
57
57
|
"publishConfig": {
|
|
58
58
|
"access": "public"
|
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
import { batch, signal } from '@preact/signals-core';
|
|
6
6
|
import { describe, expect, test } from 'vitest';
|
|
7
7
|
|
|
8
|
+
import { updateCounter } from '@dxos/echo-schema/testing';
|
|
9
|
+
|
|
8
10
|
import { ACTION_TYPE, ROOT_ID, ROOT_TYPE } from './graph';
|
|
9
11
|
import { GraphBuilder, createExtension, memoize } from './graph-builder';
|
|
10
12
|
import { type Node } from './node';
|
|
@@ -175,6 +177,40 @@ describe('GraphBuilder', () => {
|
|
|
175
177
|
expect(node.properties.label).to.equal('updated');
|
|
176
178
|
});
|
|
177
179
|
|
|
180
|
+
test('updates with new extensions', async () => {
|
|
181
|
+
const name = signal('default');
|
|
182
|
+
const builder = new GraphBuilder();
|
|
183
|
+
builder.addExtension(
|
|
184
|
+
createExtension({
|
|
185
|
+
id: 'connector',
|
|
186
|
+
connector: () => [{ id: EXAMPLE_ID, type: EXAMPLE_TYPE, data: name, properties: { label: name.value } }],
|
|
187
|
+
}),
|
|
188
|
+
);
|
|
189
|
+
const graph = builder.graph;
|
|
190
|
+
await graph.expand(graph.root);
|
|
191
|
+
|
|
192
|
+
let nodes: Node[] = [];
|
|
193
|
+
using updates = updateCounter(() => {
|
|
194
|
+
nodes = graph.nodes(graph.root);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
expect(updates.count).to.equal(0);
|
|
198
|
+
expect(nodes).has.length(1);
|
|
199
|
+
expect(nodes[0].id).to.equal(EXAMPLE_ID);
|
|
200
|
+
|
|
201
|
+
builder.addExtension(
|
|
202
|
+
createExtension({
|
|
203
|
+
id: 'connector-2',
|
|
204
|
+
connector: () => [{ id: exampleId(2), type: EXAMPLE_TYPE, data: 0 }],
|
|
205
|
+
}),
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
expect(updates.count).to.equal(1);
|
|
209
|
+
expect(nodes).has.length(2);
|
|
210
|
+
expect(nodes[0].id).to.equal(EXAMPLE_ID);
|
|
211
|
+
expect(nodes[1].id).to.equal(exampleId(2));
|
|
212
|
+
});
|
|
213
|
+
|
|
178
214
|
test('removes', async () => {
|
|
179
215
|
const nodes = signal([
|
|
180
216
|
{ id: exampleId(1), type: EXAMPLE_TYPE, data: 1 },
|
package/src/graph-builder.ts
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { effect, type Signal, signal } from '@preact/signals-core';
|
|
5
|
+
import { effect, type Signal, signal, untracked } from '@preact/signals-core';
|
|
6
6
|
|
|
7
7
|
import { Trigger, type UnsubscribeCallback } from '@dxos/async';
|
|
8
8
|
import { invariant } from '@dxos/invariant';
|
|
9
9
|
import { create } from '@dxos/live-object';
|
|
10
10
|
import { log } from '@dxos/log';
|
|
11
|
-
import {
|
|
11
|
+
import { byPosition, type Position, isNode, type MaybePromise, nonNullable } from '@dxos/util';
|
|
12
12
|
|
|
13
13
|
import { ACTION_GROUP_TYPE, ACTION_TYPE, Graph, ROOT_ID, type GraphParams } from './graph';
|
|
14
14
|
import { type ActionData, actionGroupSymbol, type Node, type NodeArg, type Relation } from './node';
|
|
@@ -63,7 +63,7 @@ export type CreateExtensionOptions<T = any> = {
|
|
|
63
63
|
id: string;
|
|
64
64
|
relation?: Relation;
|
|
65
65
|
type?: string;
|
|
66
|
-
|
|
66
|
+
position?: Position;
|
|
67
67
|
filter?: (node: Node) => node is Node<T>;
|
|
68
68
|
resolver?: ResolverExtension;
|
|
69
69
|
connector?: ConnectorExtension<GuardedNodeType<CreateExtensionOptions<T>['filter']>>;
|
|
@@ -75,16 +75,16 @@ export type CreateExtensionOptions<T = any> = {
|
|
|
75
75
|
* Create a graph builder extension.
|
|
76
76
|
*/
|
|
77
77
|
export const createExtension = <T = any>(extension: CreateExtensionOptions<T>): BuilderExtension[] => {
|
|
78
|
-
const { id,
|
|
78
|
+
const { id, position = 'static', resolver, connector, actions, actionGroups, ...rest } = extension;
|
|
79
79
|
const getId = (key: string) => `${id}/${key}`;
|
|
80
80
|
return [
|
|
81
|
-
resolver ? { id: getId('resolver'),
|
|
82
|
-
connector ? { ...rest, id: getId('connector'),
|
|
81
|
+
resolver ? { id: getId('resolver'), position, resolver } : undefined,
|
|
82
|
+
connector ? { ...rest, id: getId('connector'), position, connector } : undefined,
|
|
83
83
|
actionGroups
|
|
84
84
|
? ({
|
|
85
85
|
...rest,
|
|
86
86
|
id: getId('actionGroups'),
|
|
87
|
-
|
|
87
|
+
position,
|
|
88
88
|
type: ACTION_GROUP_TYPE,
|
|
89
89
|
relation: 'outbound',
|
|
90
90
|
connector: ({ node }) =>
|
|
@@ -95,7 +95,7 @@ export const createExtension = <T = any>(extension: CreateExtensionOptions<T>):
|
|
|
95
95
|
? ({
|
|
96
96
|
...rest,
|
|
97
97
|
id: getId('actions'),
|
|
98
|
-
|
|
98
|
+
position,
|
|
99
99
|
type: ACTION_TYPE,
|
|
100
100
|
relation: 'outbound',
|
|
101
101
|
connector: ({ node }) => actions({ node })?.map((arg) => ({ ...arg, type: ACTION_TYPE })),
|
|
@@ -174,7 +174,7 @@ export const toSignal = <T>(
|
|
|
174
174
|
|
|
175
175
|
export type BuilderExtension = Readonly<{
|
|
176
176
|
id: string;
|
|
177
|
-
|
|
177
|
+
position: Position;
|
|
178
178
|
resolver?: ResolverExtension;
|
|
179
179
|
connector?: ConnectorExtension;
|
|
180
180
|
// Only for connector.
|
|
@@ -185,6 +185,14 @@ export type BuilderExtension = Readonly<{
|
|
|
185
185
|
|
|
186
186
|
type ExtensionArg = BuilderExtension | BuilderExtension[] | ExtensionArg[];
|
|
187
187
|
|
|
188
|
+
export const flattenExtensions = (extension: ExtensionArg, acc: BuilderExtension[] = []): BuilderExtension[] => {
|
|
189
|
+
if (Array.isArray(extension)) {
|
|
190
|
+
return [...acc, ...extension.flatMap((ext) => flattenExtensions(ext, acc))];
|
|
191
|
+
} else {
|
|
192
|
+
return [...acc, extension];
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
|
|
188
196
|
/**
|
|
189
197
|
* The builder provides an extensible way to compose the construction of the graph.
|
|
190
198
|
*/
|
|
@@ -244,17 +252,24 @@ export class GraphBuilder {
|
|
|
244
252
|
return this._graph;
|
|
245
253
|
}
|
|
246
254
|
|
|
255
|
+
/**
|
|
256
|
+
* @reactive
|
|
257
|
+
*/
|
|
258
|
+
get extensions() {
|
|
259
|
+
return Object.values(this._extensions);
|
|
260
|
+
}
|
|
261
|
+
|
|
247
262
|
/**
|
|
248
263
|
* Register a node builder which will be called in order to construct the graph.
|
|
249
264
|
*/
|
|
250
265
|
addExtension(extension: ExtensionArg): GraphBuilder {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
266
|
+
const extensions = flattenExtensions(extension);
|
|
267
|
+
untracked(() => {
|
|
268
|
+
extensions.forEach((extension) => {
|
|
269
|
+
this._dispatcher.state[extension.id] = [];
|
|
270
|
+
this._extensions[extension.id] = extension;
|
|
271
|
+
});
|
|
272
|
+
});
|
|
258
273
|
return this;
|
|
259
274
|
}
|
|
260
275
|
|
|
@@ -262,7 +277,9 @@ export class GraphBuilder {
|
|
|
262
277
|
* Remove a node builder from the graph builder.
|
|
263
278
|
*/
|
|
264
279
|
removeExtension(id: string): GraphBuilder {
|
|
265
|
-
|
|
280
|
+
untracked(() => {
|
|
281
|
+
delete this._extensions[id];
|
|
282
|
+
});
|
|
266
283
|
return this;
|
|
267
284
|
}
|
|
268
285
|
|
|
@@ -326,7 +343,7 @@ export class GraphBuilder {
|
|
|
326
343
|
this._resolverSubscriptions.set(
|
|
327
344
|
nodeId,
|
|
328
345
|
effect(() => {
|
|
329
|
-
const extensions = Object.values(this._extensions).toSorted(
|
|
346
|
+
const extensions = Object.values(this._extensions).toSorted(byPosition);
|
|
330
347
|
for (const { id, resolver } of extensions) {
|
|
331
348
|
if (!resolver) {
|
|
332
349
|
continue;
|
|
@@ -384,7 +401,7 @@ export class GraphBuilder {
|
|
|
384
401
|
|
|
385
402
|
// TODO(wittjosiah): Consider allowing extensions to collaborate on the same node by merging their results.
|
|
386
403
|
const nodes: NodeArg<any>[] = [];
|
|
387
|
-
const extensions = Object.values(this._extensions).toSorted(
|
|
404
|
+
const extensions = Object.values(this._extensions).toSorted(byPosition);
|
|
388
405
|
for (const { id, connector, filter, type, relation = 'outbound' } of extensions) {
|
|
389
406
|
if (
|
|
390
407
|
!connector ||
|