@dxos/app-graph 0.4.6 → 0.4.7-main.06facb4
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 +289 -229
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node/index.cjs +282 -239
- package/dist/lib/node/index.cjs.map +4 -4
- package/dist/lib/node/meta.json +1 -1
- package/dist/types/src/graph-builder.d.ts +8 -16
- package/dist/types/src/graph-builder.d.ts.map +1 -1
- package/dist/types/src/graph.d.ts +71 -14
- package/dist/types/src/graph.d.ts.map +1 -1
- package/dist/types/src/helpers.d.ts +14 -0
- package/dist/types/src/helpers.d.ts.map +1 -0
- package/dist/types/src/index.d.ts +1 -1
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/node.d.ts +57 -47
- package/dist/types/src/node.d.ts.map +1 -1
- package/dist/types/src/stories/EchoGraph.stories.d.ts.map +1 -1
- package/package.json +10 -29
- package/src/graph-builder.ts +18 -206
- package/src/graph.test.ts +242 -146
- package/src/graph.ts +241 -48
- package/src/helpers.ts +27 -0
- package/src/index.ts +1 -1
- package/src/node.ts +78 -66
- package/src/stories/EchoGraph.stories.tsx +59 -27
- package/dist/lib/browser/testing.mjs +0 -98
- package/dist/lib/browser/testing.mjs.map +0 -7
- package/dist/lib/node/testing.cjs +0 -122
- package/dist/lib/node/testing.cjs.map +0 -7
- package/dist/types/src/action.d.ts +0 -68
- package/dist/types/src/action.d.ts.map +0 -1
- package/dist/types/src/testing.d.ts +0 -52
- package/dist/types/src/testing.d.ts.map +0 -1
- package/src/action.ts +0 -92
- package/src/testing.ts +0 -152
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/app-graph",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.7-main.06facb4",
|
|
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",
|
|
@@ -12,22 +12,9 @@
|
|
|
12
12
|
"import": "./dist/lib/browser/index.mjs",
|
|
13
13
|
"require": "./dist/lib/node/index.cjs",
|
|
14
14
|
"node": "./dist/lib/node/index.cjs"
|
|
15
|
-
},
|
|
16
|
-
"./testing": {
|
|
17
|
-
"browser": "./dist/lib/browser/testing.mjs",
|
|
18
|
-
"import": "./dist/lib/browser/testing.mjs",
|
|
19
|
-
"require": "./dist/lib/node/testing.cjs",
|
|
20
|
-
"node": "./dist/lib/node/testing.cjs"
|
|
21
15
|
}
|
|
22
16
|
},
|
|
23
17
|
"types": "dist/types/src/index.d.ts",
|
|
24
|
-
"typesVersions": {
|
|
25
|
-
"*": {
|
|
26
|
-
"testing": [
|
|
27
|
-
"dist/types/src/testing.d.ts"
|
|
28
|
-
]
|
|
29
|
-
}
|
|
30
|
-
},
|
|
31
18
|
"files": [
|
|
32
19
|
"dist",
|
|
33
20
|
"src"
|
|
@@ -35,31 +22,25 @@
|
|
|
35
22
|
"dependencies": {
|
|
36
23
|
"@preact/signals-core": "^1.5.1",
|
|
37
24
|
"deepsignal": "^1.5.0",
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"@dxos/
|
|
41
|
-
"@dxos/
|
|
42
|
-
"@dxos/keyboard": "0.4.6",
|
|
43
|
-
"@dxos/util": "0.4.6",
|
|
44
|
-
"@dxos/async": "0.4.6"
|
|
25
|
+
"@dxos/async": "0.4.7-main.06facb4",
|
|
26
|
+
"@dxos/invariant": "0.4.7-main.06facb4",
|
|
27
|
+
"@dxos/util": "0.4.7-main.06facb4",
|
|
28
|
+
"@dxos/debug": "0.4.7-main.06facb4"
|
|
45
29
|
},
|
|
46
30
|
"devDependencies": {
|
|
47
31
|
"@phosphor-icons/react": "^2.0.5",
|
|
48
|
-
"@types/lodash.get": "^4.4.7",
|
|
49
|
-
"@types/lodash.set": "^4.3.7",
|
|
50
32
|
"@types/react": "^18.0.21",
|
|
51
33
|
"@types/react-dom": "^18.0.6",
|
|
52
34
|
"react": "^18.2.0",
|
|
53
35
|
"react-dom": "^18.2.0",
|
|
54
36
|
"vite": "^5.1.3",
|
|
55
|
-
"@dxos/random": "0.4.
|
|
56
|
-
"@dxos/react-client": "0.4.
|
|
57
|
-
"@dxos/react-ui": "0.4.
|
|
58
|
-
"@dxos/
|
|
59
|
-
"@dxos/
|
|
37
|
+
"@dxos/random": "0.4.7-main.06facb4",
|
|
38
|
+
"@dxos/react-client": "0.4.7-main.06facb4",
|
|
39
|
+
"@dxos/react-ui": "0.4.7-main.06facb4",
|
|
40
|
+
"@dxos/react-ui-theme": "0.4.7-main.06facb4",
|
|
41
|
+
"@dxos/storybook-utils": "0.4.7-main.06facb4"
|
|
60
42
|
},
|
|
61
43
|
"peerDependencies": {
|
|
62
|
-
"@phosphor-icons/react": "^2.0.5",
|
|
63
44
|
"react": "^18.2.0",
|
|
64
45
|
"react-dom": "^18.2.0"
|
|
65
46
|
},
|
package/src/graph-builder.ts
CHANGED
|
@@ -2,238 +2,50 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
import { type RevertDeepSignal, deepSignal } from 'deepsignal/react';
|
|
5
|
+
import { EventSubscriptions, type UnsubscribeCallback } from '@dxos/async';
|
|
7
6
|
|
|
8
|
-
import { EventSubscriptions } from '@dxos/async';
|
|
9
|
-
import { Keyboard } from '@dxos/keyboard';
|
|
10
|
-
import { getHostPlatform } from '@dxos/util';
|
|
11
|
-
|
|
12
|
-
import type { ActionArg, Action } from './action';
|
|
13
7
|
import { Graph } from './graph';
|
|
14
|
-
import type { NodeArg, Node, NodeBuilder } from './node';
|
|
15
8
|
|
|
16
|
-
export
|
|
9
|
+
export type BuilderExtension = (graph: Graph) => UnsubscribeCallback | void;
|
|
17
10
|
|
|
18
11
|
/**
|
|
19
|
-
* The builder
|
|
12
|
+
* The builder provides an extensible way to compose the construction of the graph.
|
|
20
13
|
*/
|
|
21
14
|
export class GraphBuilder {
|
|
22
|
-
private readonly
|
|
23
|
-
private readonly _unsubscribe = new
|
|
15
|
+
private readonly _extensions = new Map<string, BuilderExtension>();
|
|
16
|
+
private readonly _unsubscribe = new EventSubscriptions();
|
|
24
17
|
|
|
25
18
|
/**
|
|
26
19
|
* Register a node builder which will be called in order to construct the graph.
|
|
27
20
|
*/
|
|
28
|
-
|
|
29
|
-
this.
|
|
21
|
+
addExtension(id: string, extension: BuilderExtension): GraphBuilder {
|
|
22
|
+
this._extensions.set(id, extension);
|
|
30
23
|
return this;
|
|
31
24
|
}
|
|
32
25
|
|
|
33
26
|
/**
|
|
34
27
|
* Remove a node builder from the graph builder.
|
|
35
28
|
*/
|
|
36
|
-
|
|
37
|
-
this.
|
|
29
|
+
removeExtension(id: string): GraphBuilder {
|
|
30
|
+
this._extensions.delete(id);
|
|
38
31
|
return this;
|
|
39
32
|
}
|
|
40
33
|
|
|
41
34
|
/**
|
|
42
|
-
* Construct the graph, starting by calling all registered
|
|
43
|
-
* Node builders will be filtered out as they are used such that they are only used once on any given path.
|
|
35
|
+
* Construct the graph, starting by calling all registered extensions.
|
|
44
36
|
* @param previousGraph If provided, the graph will be updated in place.
|
|
45
|
-
* @param startingPath If provided, the graph will be updated starting at the given path.
|
|
46
|
-
*/
|
|
47
|
-
build(previousGraph?: Graph, startingPath: string[] = []): Graph {
|
|
48
|
-
const graph: Graph = previousGraph ?? new Graph(this._createNode(() => graph, { id: 'root', label: 'Root' }));
|
|
49
|
-
return this._build(graph, graph.root, startingPath);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Called recursively.
|
|
54
37
|
*/
|
|
55
|
-
|
|
56
|
-
//
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
// TODO(burdon): Document.
|
|
60
|
-
const subscriptions = this._unsubscribe.get(node.id) ?? new EventSubscriptions();
|
|
61
|
-
subscriptions.clear();
|
|
38
|
+
build(previousGraph?: Graph): Graph {
|
|
39
|
+
// Clear previous extension subscriptions.
|
|
40
|
+
this._unsubscribe.clear();
|
|
62
41
|
|
|
63
|
-
|
|
64
|
-
.filter(([id]) => ignoreBuilders.findIndex((ignore) => ignore === id) === -1)
|
|
65
|
-
.forEach(([_, builder]) => {
|
|
66
|
-
const unsubscribe = builder(node);
|
|
67
|
-
unsubscribe && subscriptions.add(unsubscribe);
|
|
68
|
-
});
|
|
42
|
+
const graph: Graph = previousGraph ?? new Graph();
|
|
69
43
|
|
|
70
|
-
this.
|
|
44
|
+
Array.from(this._extensions.values()).forEach((builder) => {
|
|
45
|
+
const unsubscribe = builder(graph);
|
|
46
|
+
unsubscribe && this._unsubscribe.add(unsubscribe);
|
|
47
|
+
});
|
|
71
48
|
|
|
72
49
|
return graph;
|
|
73
50
|
}
|
|
74
|
-
|
|
75
|
-
private _createNode<TData = null, TProperties extends Record<string, any> = Record<string, any>>(
|
|
76
|
-
getGraph: () => Graph,
|
|
77
|
-
partial: NodeArg<TData, TProperties>,
|
|
78
|
-
path: string[] = [],
|
|
79
|
-
ignoreBuilders: string[] = [],
|
|
80
|
-
): Node<TData, TProperties> {
|
|
81
|
-
// TODO(burdon): Document implications and rationale of deepSignal.
|
|
82
|
-
const node: Node<TData, TProperties> = deepSignal({
|
|
83
|
-
parent: null,
|
|
84
|
-
data: null as TData, // TODO(burdon): Allow null property?
|
|
85
|
-
properties: {} as TProperties,
|
|
86
|
-
childrenMap: {},
|
|
87
|
-
actionsMap: {},
|
|
88
|
-
// TODO(burdon): Document.
|
|
89
|
-
...partial,
|
|
90
|
-
|
|
91
|
-
get children() {
|
|
92
|
-
return Object.values(node.childrenMap);
|
|
93
|
-
},
|
|
94
|
-
get actions() {
|
|
95
|
-
return Object.values(node.actionsMap);
|
|
96
|
-
},
|
|
97
|
-
|
|
98
|
-
//
|
|
99
|
-
// Properties
|
|
100
|
-
//
|
|
101
|
-
|
|
102
|
-
addProperty: (key, value) => {
|
|
103
|
-
untracked(() => {
|
|
104
|
-
(node.properties as Record<string, any>)[key] = value;
|
|
105
|
-
});
|
|
106
|
-
},
|
|
107
|
-
removeProperty: (key) => {
|
|
108
|
-
untracked(() => {
|
|
109
|
-
delete (node.properties as Record<string, any>)[key];
|
|
110
|
-
});
|
|
111
|
-
},
|
|
112
|
-
|
|
113
|
-
//
|
|
114
|
-
// Nodes
|
|
115
|
-
//
|
|
116
|
-
|
|
117
|
-
addNode: (builder, ...partials) => {
|
|
118
|
-
return untracked(() => {
|
|
119
|
-
return partials.map((partial) => {
|
|
120
|
-
const builders = [...ignoreBuilders, builder];
|
|
121
|
-
const childPath = [...path, 'childrenMap', partial.id];
|
|
122
|
-
const child = this._createNode(getGraph, { ...partial, parent: node }, childPath, builders);
|
|
123
|
-
node.childrenMap[child.id] = child;
|
|
124
|
-
// TODO(burdon): Defer triggering recursive updates until task has completed.
|
|
125
|
-
this._build(getGraph(), child, childPath, builders);
|
|
126
|
-
return child;
|
|
127
|
-
});
|
|
128
|
-
});
|
|
129
|
-
},
|
|
130
|
-
removeNode: (id) => {
|
|
131
|
-
return untracked(() => {
|
|
132
|
-
const child = node.childrenMap[id];
|
|
133
|
-
delete node.childrenMap[id];
|
|
134
|
-
return child;
|
|
135
|
-
});
|
|
136
|
-
},
|
|
137
|
-
|
|
138
|
-
//
|
|
139
|
-
// Actions
|
|
140
|
-
//
|
|
141
|
-
|
|
142
|
-
addAction: (...partials) => {
|
|
143
|
-
return untracked(() => {
|
|
144
|
-
return partials.map((partial) => {
|
|
145
|
-
const action = this._createAction(partial);
|
|
146
|
-
let shortcut: string | undefined;
|
|
147
|
-
if (typeof action.keyBinding === 'object') {
|
|
148
|
-
const availablePlatforms = Object.keys(action.keyBinding);
|
|
149
|
-
const platform = getHostPlatform();
|
|
150
|
-
shortcut = availablePlatforms.includes(platform)
|
|
151
|
-
? action.keyBinding[platform]
|
|
152
|
-
: platform === 'ios'
|
|
153
|
-
? action.keyBinding.macos // Fallback to macos if ios-specific bindings not provided.
|
|
154
|
-
: platform === 'linux' || platform === 'unknown'
|
|
155
|
-
? action.keyBinding.windows // Fallback to windows if platform-specific bindings not provided.
|
|
156
|
-
: undefined;
|
|
157
|
-
} else {
|
|
158
|
-
shortcut = action.keyBinding;
|
|
159
|
-
}
|
|
160
|
-
if (shortcut) {
|
|
161
|
-
Keyboard.singleton.getContext(path.join('/')).bind({
|
|
162
|
-
shortcut,
|
|
163
|
-
handler: () => {
|
|
164
|
-
action.invoke({ caller: KEY_BINDING });
|
|
165
|
-
},
|
|
166
|
-
data: action.label,
|
|
167
|
-
});
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
node.actionsMap[action.id] = action;
|
|
171
|
-
return action;
|
|
172
|
-
});
|
|
173
|
-
});
|
|
174
|
-
},
|
|
175
|
-
removeAction: (id) => {
|
|
176
|
-
return untracked(() => {
|
|
177
|
-
const action = node.actionsMap[id];
|
|
178
|
-
if (action.keyBinding) {
|
|
179
|
-
// keyboardjs.unbind(action.keyBinding);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
delete node.actionsMap[id];
|
|
183
|
-
return action;
|
|
184
|
-
});
|
|
185
|
-
},
|
|
186
|
-
}) as RevertDeepSignal<Node<TData, TProperties>>;
|
|
187
|
-
|
|
188
|
-
// Only actions added at this stage are available to subsequent builders.
|
|
189
|
-
// `addNode` immediately passes the new node to other builders.
|
|
190
|
-
// As such, actions added later with `addAction` are not available to those builders.
|
|
191
|
-
// Having actions available to subsequent builders is useful for building groups.
|
|
192
|
-
partial.actions && partial.actions.forEach((action) => node.addAction(action));
|
|
193
|
-
|
|
194
|
-
return node;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
private _createAction<TProperties extends Record<string, any> = Record<string, any>>(
|
|
198
|
-
partial: ActionArg<TProperties>,
|
|
199
|
-
): Action<TProperties> {
|
|
200
|
-
const action: Action<TProperties> = deepSignal({
|
|
201
|
-
properties: {} as TProperties,
|
|
202
|
-
...partial,
|
|
203
|
-
actionsMap: {},
|
|
204
|
-
get actions() {
|
|
205
|
-
return Object.values(action.actionsMap);
|
|
206
|
-
},
|
|
207
|
-
addAction: (...partials) => {
|
|
208
|
-
return untracked(() => {
|
|
209
|
-
return partials.map((partial) => {
|
|
210
|
-
const subAction = this._createAction(partial);
|
|
211
|
-
action.actionsMap[subAction.id] = subAction;
|
|
212
|
-
return subAction;
|
|
213
|
-
});
|
|
214
|
-
});
|
|
215
|
-
},
|
|
216
|
-
removeAction: (id) => {
|
|
217
|
-
return untracked(() => {
|
|
218
|
-
const subAction = action.actionsMap[id];
|
|
219
|
-
delete action.actionsMap[id];
|
|
220
|
-
return subAction;
|
|
221
|
-
});
|
|
222
|
-
},
|
|
223
|
-
addProperty: (key, value) => {
|
|
224
|
-
return untracked(() => {
|
|
225
|
-
(action.properties as Record<string, any>)[key] = value;
|
|
226
|
-
});
|
|
227
|
-
},
|
|
228
|
-
removeProperty: (key) => {
|
|
229
|
-
return untracked(() => {
|
|
230
|
-
delete (action.properties as Record<string, any>)[key];
|
|
231
|
-
});
|
|
232
|
-
},
|
|
233
|
-
}) as RevertDeepSignal<Action<TProperties>>;
|
|
234
|
-
|
|
235
|
-
partial.actions && partial.actions.forEach((subAction) => action.addAction(subAction));
|
|
236
|
-
|
|
237
|
-
return action;
|
|
238
|
-
}
|
|
239
51
|
}
|