@dxos/app-graph 0.6.3-main.9e4e207 → 0.6.3-next.2f65b78
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 +191 -582
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node/index.cjs +185 -587
- 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 +7 -99
- package/dist/types/src/graph-builder.d.ts.map +1 -1
- package/dist/types/src/graph.d.ts +47 -96
- package/dist/types/src/graph.d.ts.map +1 -1
- package/dist/types/src/helpers.d.ts +12 -0
- package/dist/types/src/helpers.d.ts.map +1 -0
- package/dist/types/src/index.d.ts +1 -0
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/node.d.ts +40 -99
- 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 +12 -14
- package/src/graph-builder.ts +19 -332
- package/src/graph.test.ts +179 -431
- package/src/graph.ts +149 -336
- package/src/helpers.ts +27 -0
- package/src/index.ts +1 -0
- package/src/node.ts +42 -15
- package/src/stories/EchoGraph.stories.tsx +102 -84
- package/dist/types/src/graph-builder.test.d.ts +0 -2
- package/dist/types/src/graph-builder.test.d.ts.map +0 -1
- package/src/graph-builder.test.ts +0 -310
package/src/node.ts
CHANGED
|
@@ -8,21 +8,16 @@ import { type MaybePromise, type MakeOptional } from '@dxos/util';
|
|
|
8
8
|
* Represents a node in the graph.
|
|
9
9
|
*/
|
|
10
10
|
// TODO(wittjosiah): Use Effect Schema.
|
|
11
|
-
export type
|
|
11
|
+
export type NodeBase<TData = any, TProperties extends Record<string, any> = Record<string, any>> = {
|
|
12
12
|
/**
|
|
13
13
|
* Globally unique ID.
|
|
14
14
|
*/
|
|
15
15
|
id: string;
|
|
16
16
|
|
|
17
|
-
/**
|
|
18
|
-
* Typename of the data the node represents.
|
|
19
|
-
*/
|
|
20
|
-
type: string;
|
|
21
|
-
|
|
22
17
|
/**
|
|
23
18
|
* Properties of the node relevant to displaying the node.
|
|
24
19
|
*/
|
|
25
|
-
properties:
|
|
20
|
+
properties: TProperties;
|
|
26
21
|
|
|
27
22
|
/**
|
|
28
23
|
* Data the node represents.
|
|
@@ -30,14 +25,46 @@ export type Node<TData = any, TProperties extends Record<string, any> = Record<s
|
|
|
30
25
|
// TODO(burdon): Type system (e.g., minimally provide identifier string vs. TypedObject vs. Graph mixin type system)?
|
|
31
26
|
// type field would prevent convoluted sniffing of object properties. And allow direct pass-through for ECHO TypedObjects.
|
|
32
27
|
data: TData;
|
|
33
|
-
}
|
|
28
|
+
};
|
|
34
29
|
|
|
35
30
|
export type NodeFilter<T = any, U extends Record<string, any> = Record<string, any>> = (
|
|
36
31
|
node: Node<unknown, Record<string, any>>,
|
|
37
32
|
connectedNode: Node,
|
|
38
33
|
) => node is Node<T, U>;
|
|
39
34
|
|
|
40
|
-
export type
|
|
35
|
+
export type EdgeDirection = 'outbound' | 'inbound';
|
|
36
|
+
|
|
37
|
+
export type ConnectedNodes = {
|
|
38
|
+
/**
|
|
39
|
+
* Edges that this node is connected to in default order.
|
|
40
|
+
*/
|
|
41
|
+
edges(params?: { direction?: EdgeDirection }): Readonly<string[]>;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Nodes that this node is connected to in default order.
|
|
45
|
+
*/
|
|
46
|
+
nodes<T = any, U extends Record<string, any> = Record<string, any>>(params?: {
|
|
47
|
+
direction?: EdgeDirection;
|
|
48
|
+
filter?: NodeFilter<T, U>;
|
|
49
|
+
}): Node<T>[];
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Get a specific connected node by id.
|
|
53
|
+
*/
|
|
54
|
+
node(id: string): Node | undefined;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export type ConnectedActions = {
|
|
58
|
+
/**
|
|
59
|
+
* Actions or action groups that this node is connected to in default order.
|
|
60
|
+
*/
|
|
61
|
+
actions(): ActionLike[];
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export type Node<TData = any, TProperties extends Record<string, any> = Record<string, any>> = Readonly<
|
|
65
|
+
Omit<NodeBase<TData, TProperties>, 'properties'> & { properties: Readonly<TProperties> } & ConnectedNodes &
|
|
66
|
+
ConnectedActions
|
|
67
|
+
>;
|
|
41
68
|
|
|
42
69
|
export const isGraphNode = (data: unknown): data is Node =>
|
|
43
70
|
data && typeof data === 'object' && 'id' in data && 'properties' in data && data.properties
|
|
@@ -45,14 +72,14 @@ export const isGraphNode = (data: unknown): data is Node =>
|
|
|
45
72
|
: false;
|
|
46
73
|
|
|
47
74
|
export type NodeArg<TData, TProperties extends Record<string, any> = Record<string, any>> = MakeOptional<
|
|
48
|
-
|
|
75
|
+
NodeBase<TData, TProperties>,
|
|
49
76
|
'data' | 'properties'
|
|
50
77
|
> & {
|
|
51
78
|
/** Will automatically add nodes with an edge from this node to each. */
|
|
52
79
|
nodes?: NodeArg<unknown>[];
|
|
53
80
|
|
|
54
81
|
/** Will automatically add specified edges. */
|
|
55
|
-
edges?: [string,
|
|
82
|
+
edges?: [string, EdgeDirection][];
|
|
56
83
|
};
|
|
57
84
|
|
|
58
85
|
//
|
|
@@ -69,9 +96,9 @@ export type InvokeParams = {
|
|
|
69
96
|
export type ActionData = (params: InvokeParams) => MaybePromise<void>;
|
|
70
97
|
|
|
71
98
|
export type Action<TProperties extends Record<string, any> = Record<string, any>> = Readonly<
|
|
72
|
-
Omit<
|
|
99
|
+
Omit<NodeBase<ActionData, TProperties>, 'properties'> & {
|
|
73
100
|
properties: Readonly<TProperties>;
|
|
74
|
-
}
|
|
101
|
+
} & ConnectedNodes
|
|
75
102
|
>;
|
|
76
103
|
|
|
77
104
|
export const isAction = (data: unknown): data is Action =>
|
|
@@ -80,9 +107,9 @@ export const isAction = (data: unknown): data is Action =>
|
|
|
80
107
|
export const actionGroupSymbol = Symbol('ActionGroup');
|
|
81
108
|
|
|
82
109
|
export type ActionGroup = Readonly<
|
|
83
|
-
Omit<
|
|
110
|
+
Omit<NodeBase<typeof actionGroupSymbol, Record<string, any>>, 'properties'> & {
|
|
84
111
|
properties: Readonly<Record<string, any>>;
|
|
85
|
-
}
|
|
112
|
+
} & ConnectedActions
|
|
86
113
|
>;
|
|
87
114
|
|
|
88
115
|
export const isActionGroup = (data: unknown): data is ActionGroup =>
|
|
@@ -5,21 +5,15 @@
|
|
|
5
5
|
import '@dxosTheme';
|
|
6
6
|
|
|
7
7
|
import { Pause, Play, Plus, Timer } from '@phosphor-icons/react';
|
|
8
|
+
import { effect } from '@preact/signals-core';
|
|
8
9
|
import React, { useEffect, useState } from 'react';
|
|
9
10
|
|
|
10
|
-
import {
|
|
11
|
+
import { EventSubscriptions } from '@dxos/async';
|
|
12
|
+
import { create, type EchoReactiveObject } from '@dxos/echo-schema';
|
|
11
13
|
import { registerSignalRuntime } from '@dxos/echo-signals';
|
|
12
14
|
import { faker } from '@dxos/random';
|
|
13
15
|
import { Client } from '@dxos/react-client';
|
|
14
|
-
import {
|
|
15
|
-
type Space,
|
|
16
|
-
SpaceState,
|
|
17
|
-
isSpace,
|
|
18
|
-
type Echo,
|
|
19
|
-
type FilterSource,
|
|
20
|
-
type QueryOptions,
|
|
21
|
-
type Query,
|
|
22
|
-
} from '@dxos/react-client/echo';
|
|
16
|
+
import { type Space, SpaceState } from '@dxos/react-client/echo';
|
|
23
17
|
import { ClientRepeater, TestBuilder } from '@dxos/react-client/testing';
|
|
24
18
|
import { Button, DensityProvider, Input, Select } from '@dxos/react-ui';
|
|
25
19
|
import { getSize, mx } from '@dxos/react-ui-theme';
|
|
@@ -27,8 +21,8 @@ import { withTheme } from '@dxos/storybook-utils';
|
|
|
27
21
|
import { safeParseInt } from '@dxos/util';
|
|
28
22
|
|
|
29
23
|
import { Tree } from './Tree';
|
|
30
|
-
import {
|
|
31
|
-
import {
|
|
24
|
+
import { type Graph } from '../graph';
|
|
25
|
+
import { GraphBuilder } from '../graph-builder';
|
|
32
26
|
|
|
33
27
|
export default {
|
|
34
28
|
title: 'app-graph/EchoGraph',
|
|
@@ -45,66 +39,89 @@ await client.halo.createIdentity();
|
|
|
45
39
|
await client.spaces.create();
|
|
46
40
|
await client.spaces.create();
|
|
47
41
|
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
)
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
42
|
+
const spaceBuilderExtension = (graph: Graph) => {
|
|
43
|
+
const subscriptions = new EventSubscriptions();
|
|
44
|
+
const { unsubscribe } = client.spaces.subscribe((spaces) => {
|
|
45
|
+
subscriptions.clear();
|
|
46
|
+
spaces.forEach((space) => {
|
|
47
|
+
subscriptions.add(
|
|
48
|
+
effect(() => {
|
|
49
|
+
if (space.state.get() === SpaceState.READY) {
|
|
50
|
+
graph.addNodes({ id: space.key.toHex(), properties: { label: space.properties.name }, data: space });
|
|
51
|
+
graph.addEdge({ source: 'root', target: space.key.toHex() });
|
|
52
|
+
} else {
|
|
53
|
+
graph.removeNode(space.key.toHex());
|
|
54
|
+
}
|
|
55
|
+
}),
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
const query = space.db.query();
|
|
59
|
+
subscriptions.add(query.subscribe());
|
|
60
|
+
subscriptions.add(
|
|
61
|
+
effect(() => {
|
|
62
|
+
query.objects.forEach((object) => {
|
|
63
|
+
graph.addEdge({ source: space.key.toHex(), target: object.id });
|
|
64
|
+
});
|
|
65
|
+
}),
|
|
66
|
+
);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
66
69
|
|
|
67
|
-
return
|
|
70
|
+
return () => {
|
|
71
|
+
unsubscribe();
|
|
72
|
+
subscriptions.clear();
|
|
73
|
+
};
|
|
68
74
|
};
|
|
69
75
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
76
|
+
// TODO(wittjosiah): Hypergraph query isn't working.
|
|
77
|
+
// const objectBuilderExtension = (graph: Graph) => {
|
|
78
|
+
// const query = client.spaces.query({ type: 'test' });
|
|
79
|
+
// let previousObjects: Expando[] = [];
|
|
80
|
+
// return effect(() => {
|
|
81
|
+
// const removedObjects = previousObjects.filter((object) => !query.objects.includes(object));
|
|
82
|
+
// previousObjects = query.objects;
|
|
83
|
+
|
|
84
|
+
// removedObjects.forEach((object) => graph.removeNode(object.id));
|
|
85
|
+
// query.objects.forEach((object) => {
|
|
86
|
+
// console.log('add object');
|
|
87
|
+
// graph.addNodes({ id: object.id, properties: { label: object.name }, data: object });
|
|
88
|
+
// });
|
|
89
|
+
// });
|
|
90
|
+
// };
|
|
91
|
+
|
|
92
|
+
const objectBuilderExtension = (graph: Graph) => {
|
|
93
|
+
const subscriptions = new EventSubscriptions();
|
|
94
|
+
const { unsubscribe } = client.spaces.subscribe((spaces) => {
|
|
95
|
+
subscriptions.clear();
|
|
96
|
+
spaces.forEach((space) => {
|
|
97
|
+
const query = space.db.query({ type: 'test' });
|
|
98
|
+
subscriptions.add(query.subscribe());
|
|
99
|
+
let previousObjects: EchoReactiveObject<any>[] = [];
|
|
100
|
+
subscriptions.add(
|
|
101
|
+
effect(() => {
|
|
102
|
+
const removedObjects = previousObjects.filter((object) => !query.objects.includes(object));
|
|
103
|
+
previousObjects = query.objects;
|
|
81
104
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
data: object,
|
|
103
|
-
}));
|
|
104
|
-
},
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
const graph = new GraphBuilder().addExtension(spaceBuilderExtension).addExtension(objectBuilderExtension).graph;
|
|
105
|
+
removedObjects.forEach((object) => graph.removeNode(object.id));
|
|
106
|
+
query.objects.forEach((object) => {
|
|
107
|
+
console.log('add object');
|
|
108
|
+
graph.addNodes({ id: object.id, properties: { label: object.name }, data: object });
|
|
109
|
+
});
|
|
110
|
+
}),
|
|
111
|
+
);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
return () => {
|
|
116
|
+
unsubscribe();
|
|
117
|
+
subscriptions.clear();
|
|
118
|
+
};
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const graph = new GraphBuilder()
|
|
122
|
+
.addExtension('space', spaceBuilderExtension)
|
|
123
|
+
.addExtension('object', objectBuilderExtension)
|
|
124
|
+
.build();
|
|
108
125
|
|
|
109
126
|
enum Action {
|
|
110
127
|
CREATE_SPACE = 'CREATE_SPACE',
|
|
@@ -132,31 +149,32 @@ const randomAction = () => {
|
|
|
132
149
|
return actionDistribution[Math.floor(Math.random() * actionDistribution.length)];
|
|
133
150
|
};
|
|
134
151
|
|
|
135
|
-
const
|
|
152
|
+
const getSpace = (): Space | undefined => {
|
|
136
153
|
const spaces = client.spaces.get().filter((space) => space.state.get() === SpaceState.READY);
|
|
137
|
-
|
|
138
|
-
return space;
|
|
154
|
+
return spaces[Math.floor(Math.random() * spaces.length)];
|
|
139
155
|
};
|
|
140
156
|
|
|
141
|
-
const getSpaceWithObjects =
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
157
|
+
const getSpaceWithObjects = (): Space | undefined => {
|
|
158
|
+
const spaces = client.spaces
|
|
159
|
+
.get()
|
|
160
|
+
.filter((space) => space.state.get() === SpaceState.READY)
|
|
161
|
+
.filter((space) => space.db.query({ type: 'test' }).objects.length > 0);
|
|
162
|
+
|
|
145
163
|
return spaces[Math.floor(Math.random() * spaces.length)];
|
|
146
164
|
};
|
|
147
165
|
|
|
148
|
-
const runAction =
|
|
166
|
+
const runAction = (action: Action) => {
|
|
149
167
|
switch (action) {
|
|
150
168
|
case Action.CREATE_SPACE:
|
|
151
169
|
void client.spaces.create();
|
|
152
170
|
break;
|
|
153
171
|
|
|
154
172
|
case Action.CLOSE_SPACE:
|
|
155
|
-
void
|
|
173
|
+
void getSpace()?.close();
|
|
156
174
|
break;
|
|
157
175
|
|
|
158
176
|
case Action.RENAME_SPACE: {
|
|
159
|
-
const space =
|
|
177
|
+
const space = getSpace();
|
|
160
178
|
if (space) {
|
|
161
179
|
space.properties.name = faker.commerce.productName();
|
|
162
180
|
}
|
|
@@ -164,22 +182,22 @@ const runAction = async (action: Action) => {
|
|
|
164
182
|
}
|
|
165
183
|
|
|
166
184
|
case Action.ADD_OBJECT:
|
|
167
|
-
|
|
185
|
+
getSpace()?.db.add(create({ type: 'test', name: faker.commerce.productName() }));
|
|
168
186
|
break;
|
|
169
187
|
|
|
170
188
|
case Action.REMOVE_OBJECT: {
|
|
171
|
-
const space =
|
|
189
|
+
const space = getSpaceWithObjects();
|
|
172
190
|
if (space) {
|
|
173
|
-
const
|
|
191
|
+
const objects = space.db.query({ type: 'test' }).objects;
|
|
174
192
|
space.db.remove(objects[Math.floor(Math.random() * objects.length)]);
|
|
175
193
|
}
|
|
176
194
|
break;
|
|
177
195
|
}
|
|
178
196
|
|
|
179
197
|
case Action.RENAME_OBJECT: {
|
|
180
|
-
const space =
|
|
198
|
+
const space = getSpaceWithObjects();
|
|
181
199
|
if (space) {
|
|
182
|
-
const
|
|
200
|
+
const objects = space.db.query({ type: 'test' }).objects;
|
|
183
201
|
objects[Math.floor(Math.random() * objects.length)].name = faker.commerce.productName();
|
|
184
202
|
}
|
|
185
203
|
break;
|
|
@@ -243,7 +261,7 @@ const EchoGraphStory = () => {
|
|
|
243
261
|
</Select.Root>
|
|
244
262
|
</DensityProvider>
|
|
245
263
|
</div>
|
|
246
|
-
<Tree data={graph.toJSON(
|
|
264
|
+
<Tree data={graph.toJSON()} />
|
|
247
265
|
</>
|
|
248
266
|
);
|
|
249
267
|
};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"graph-builder.test.d.ts","sourceRoot":"","sources":["../../../src/graph-builder.test.ts"],"names":[],"mappings":""}
|