@dxos/graph 0.8.2-main.fbd8ed0 → 0.8.2-staging.42af850
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 +118 -80
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node/index.cjs +119 -77
- package/dist/lib/node/index.cjs.map +4 -4
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +118 -80
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/types/src/index.d.ts +1 -1
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/model.d.ts +30 -35
- package/dist/types/src/model.d.ts.map +1 -1
- package/dist/types/src/selection.d.ts +22 -0
- package/dist/types/src/selection.d.ts.map +1 -0
- package/dist/types/src/types.d.ts +12 -13
- package/dist/types/src/types.d.ts.map +1 -1
- package/package.json +15 -18
- package/src/index.ts +1 -1
- package/src/model.test.ts +74 -5
- package/src/model.ts +70 -19
- package/src/selection.ts +70 -0
- package/src/types.ts +21 -15
- package/dist/types/src/create.d.ts +0 -9
- package/dist/types/src/create.d.ts.map +0 -1
- package/src/create.ts +0 -52
|
@@ -8,20 +8,22 @@ export declare const BaseGraphNode: Schema.Struct<{
|
|
|
8
8
|
/** Raw base type. */
|
|
9
9
|
export type BaseGraphNode = Schema.Schema.Type<typeof BaseGraphNode>;
|
|
10
10
|
/** Typed node data. */
|
|
11
|
-
|
|
11
|
+
type GraphNode<Data = any, Optional extends boolean = false> = Specialize<BaseGraphNode, Optional extends true ? {
|
|
12
12
|
data?: Data;
|
|
13
13
|
} : {
|
|
14
14
|
data: Data;
|
|
15
15
|
}>;
|
|
16
16
|
export declare namespace GraphNode {
|
|
17
|
-
type
|
|
17
|
+
type Any = GraphNode<any, true>;
|
|
18
|
+
type Optional<Data = any> = GraphNode<Data, true>;
|
|
19
|
+
type Required<Data = any> = GraphNode<Data, false>;
|
|
18
20
|
}
|
|
19
21
|
export declare const BaseGraphEdge: Schema.Struct<{
|
|
20
22
|
id: typeof Schema.String;
|
|
21
23
|
type: Schema.optional<typeof Schema.String>;
|
|
22
|
-
data: Schema.optional<typeof Schema.Any>;
|
|
23
24
|
source: typeof Schema.String;
|
|
24
25
|
target: typeof Schema.String;
|
|
26
|
+
data: Schema.optional<typeof Schema.Any>;
|
|
25
27
|
}>;
|
|
26
28
|
/** Raw base type. */
|
|
27
29
|
export type BaseGraphEdge = Schema.Schema.Type<typeof BaseGraphEdge>;
|
|
@@ -32,28 +34,25 @@ export type GraphEdge<Data = any, Optional extends boolean = false> = Specialize
|
|
|
32
34
|
data: Data;
|
|
33
35
|
}>;
|
|
34
36
|
export declare namespace GraphEdge {
|
|
35
|
-
type
|
|
37
|
+
type Any = GraphEdge<any, true>;
|
|
38
|
+
type Optional<Data = any> = GraphEdge<Data, true>;
|
|
39
|
+
type Required<Data = any> = GraphEdge<Data, false>;
|
|
36
40
|
}
|
|
37
|
-
/**
|
|
38
|
-
* Generic graph.
|
|
39
|
-
*/
|
|
40
41
|
export declare const Graph: Schema.Struct<{
|
|
41
42
|
id: Schema.optional<typeof Schema.String>;
|
|
42
|
-
nodes: Schema.mutable<Schema.Array$<Schema.
|
|
43
|
+
nodes: Schema.mutable<Schema.Array$<Schema.Struct<{
|
|
43
44
|
id: typeof Schema.String;
|
|
44
45
|
type: Schema.optional<typeof Schema.String>;
|
|
45
46
|
data: Schema.optional<typeof Schema.Any>;
|
|
46
|
-
}
|
|
47
|
-
readonly key: typeof Schema.String;
|
|
48
|
-
readonly value: typeof Schema.Any;
|
|
49
|
-
}]>>>>;
|
|
47
|
+
}>>>;
|
|
50
48
|
edges: Schema.mutable<Schema.Array$<Schema.Struct<{
|
|
51
49
|
id: typeof Schema.String;
|
|
52
50
|
type: Schema.optional<typeof Schema.String>;
|
|
53
|
-
data: Schema.optional<typeof Schema.Any>;
|
|
54
51
|
source: typeof Schema.String;
|
|
55
52
|
target: typeof Schema.String;
|
|
53
|
+
data: Schema.optional<typeof Schema.Any>;
|
|
56
54
|
}>>>;
|
|
57
55
|
}>;
|
|
58
56
|
export type Graph = Schema.Schema.Type<typeof Graph>;
|
|
57
|
+
export {};
|
|
59
58
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/types.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/types.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AAO7C,eAAO,MAAM,aAAa;;;;EAIxB,CAAC;AAEH,qBAAqB;AACrB,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,aAAa,CAAC,CAAC;AAErE,uBAAuB;AACvB,KAAK,SAAS,CAAC,IAAI,GAAG,GAAG,EAAE,QAAQ,SAAS,OAAO,GAAG,KAAK,IAAI,UAAU,CACvE,aAAa,EACb,QAAQ,SAAS,IAAI,GAAG;IAAE,IAAI,CAAC,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,IAAI,CAAA;CAAE,CACzD,CAAC;AAEF,MAAM,CAAC,OAAO,WAAW,SAAS,CAAC;IACjC,KAAY,GAAG,GAAG,SAAS,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;IACvC,KAAY,QAAQ,CAAC,IAAI,GAAG,GAAG,IAAI,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IACzD,KAAY,QAAQ,CAAC,IAAI,GAAG,GAAG,IAAI,SAAS,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;CAC3D;AAMD,eAAO,MAAM,aAAa;;;;;;EAMxB,CAAC;AAEH,qBAAqB;AACrB,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,aAAa,CAAC,CAAC;AAErE,uBAAuB;AACvB,MAAM,MAAM,SAAS,CAAC,IAAI,GAAG,GAAG,EAAE,QAAQ,SAAS,OAAO,GAAG,KAAK,IAAI,UAAU,CAC9E,aAAa,EACb,QAAQ,SAAS,IAAI,GAAG;IAAE,IAAI,CAAC,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,IAAI,CAAA;CAAE,CACzD,CAAC;AAEF,MAAM,CAAC,OAAO,WAAW,SAAS,CAAC;IACjC,KAAY,GAAG,GAAG,SAAS,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;IACvC,KAAY,QAAQ,CAAC,IAAI,GAAG,GAAG,IAAI,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IACzD,KAAY,QAAQ,CAAC,IAAI,GAAG,GAAG,IAAI,SAAS,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;CAC3D;AAMD,eAAO,MAAM,KAAK;;;;;;;;;;;;;;EAIhB,CAAC;AAEH,MAAM,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,KAAK,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/graph",
|
|
3
|
-
"version": "0.8.2-
|
|
3
|
+
"version": "0.8.2-staging.42af850",
|
|
4
4
|
"description": "Low-level graph API",
|
|
5
5
|
"homepage": "https://dxos.org",
|
|
6
6
|
"bugs": "https://github.com/dxos/dxos/issues",
|
|
@@ -18,11 +18,8 @@
|
|
|
18
18
|
"types": "dist/types/src/index.d.ts",
|
|
19
19
|
"typesVersions": {
|
|
20
20
|
"*": {
|
|
21
|
-
"meta": [
|
|
22
|
-
"dist/types/src/meta.d.ts"
|
|
23
|
-
],
|
|
24
21
|
"types": [
|
|
25
|
-
"dist/types/src/types
|
|
22
|
+
"dist/types/src/types.d.ts"
|
|
26
23
|
]
|
|
27
24
|
}
|
|
28
25
|
},
|
|
@@ -31,26 +28,26 @@
|
|
|
31
28
|
"src"
|
|
32
29
|
],
|
|
33
30
|
"dependencies": {
|
|
34
|
-
"@preact/signals-core": "^1.
|
|
31
|
+
"@preact/signals-core": "^1.9.0",
|
|
35
32
|
"effect": "3.14.21",
|
|
36
33
|
"lodash.defaultsdeep": "^4.6.1",
|
|
37
|
-
"@dxos/async": "0.8.2-
|
|
38
|
-
"@dxos/debug": "0.8.2-
|
|
39
|
-
"@dxos/echo-db": "0.8.2-
|
|
40
|
-
"@dxos/
|
|
41
|
-
"@dxos/
|
|
42
|
-
"@dxos/
|
|
43
|
-
"@dxos/live-object": "0.8.2-
|
|
44
|
-
"@dxos/
|
|
45
|
-
"@dxos/util": "0.8.2-
|
|
34
|
+
"@dxos/async": "0.8.2-staging.42af850",
|
|
35
|
+
"@dxos/debug": "0.8.2-staging.42af850",
|
|
36
|
+
"@dxos/echo-db": "0.8.2-staging.42af850",
|
|
37
|
+
"@dxos/echo-signals": "0.8.2-staging.42af850",
|
|
38
|
+
"@dxos/echo-schema": "0.8.2-staging.42af850",
|
|
39
|
+
"@dxos/invariant": "0.8.2-staging.42af850",
|
|
40
|
+
"@dxos/live-object": "0.8.2-staging.42af850",
|
|
41
|
+
"@dxos/log": "0.8.2-staging.42af850",
|
|
42
|
+
"@dxos/util": "0.8.2-staging.42af850"
|
|
46
43
|
},
|
|
47
44
|
"devDependencies": {
|
|
48
45
|
"@antv/graphlib": "^2.0.4",
|
|
49
46
|
"@antv/layout": "^1.2.13",
|
|
50
47
|
"@types/lodash.defaultsdeep": "^4.6.6",
|
|
51
|
-
"@dxos/echo-
|
|
52
|
-
"@dxos/
|
|
53
|
-
"@dxos/
|
|
48
|
+
"@dxos/echo-db": "0.8.2-staging.42af850",
|
|
49
|
+
"@dxos/echo-schema": "0.8.2-staging.42af850",
|
|
50
|
+
"@dxos/random": "0.8.2-staging.42af850"
|
|
54
51
|
},
|
|
55
52
|
"peerDependencies": {
|
|
56
53
|
"effect": "^3.13.3"
|
package/src/index.ts
CHANGED
package/src/model.test.ts
CHANGED
|
@@ -2,11 +2,18 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
+
import { effect } from '@preact/signals-core';
|
|
5
6
|
import { Schema } from 'effect';
|
|
6
7
|
import { describe, test } from 'vitest';
|
|
7
8
|
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
9
|
+
import { Trigger } from '@dxos/async';
|
|
10
|
+
import { registerSignalsRuntime } from '@dxos/echo-signals';
|
|
11
|
+
import { live } from '@dxos/live-object';
|
|
12
|
+
|
|
13
|
+
import { GraphModel, ReactiveGraphModel } from './model';
|
|
14
|
+
import { BaseGraphNode, type Graph, type GraphNode } from './types';
|
|
15
|
+
|
|
16
|
+
registerSignalsRuntime();
|
|
10
17
|
|
|
11
18
|
const TestNode = Schema.extend(
|
|
12
19
|
BaseGraphNode,
|
|
@@ -24,7 +31,7 @@ describe('Graph', () => {
|
|
|
24
31
|
const graph = new GraphModel();
|
|
25
32
|
expect(graph.nodes).to.have.length(0);
|
|
26
33
|
expect(graph.edges).to.have.length(0);
|
|
27
|
-
expect(graph.toJSON()).to.deep.eq({ nodes:
|
|
34
|
+
expect(graph.toJSON()).to.deep.eq({ nodes: 0, edges: 0 });
|
|
28
35
|
});
|
|
29
36
|
|
|
30
37
|
test('extended', ({ expect }) => {
|
|
@@ -33,9 +40,71 @@ describe('Graph', () => {
|
|
|
33
40
|
expect(node.value.length).to.eq(4);
|
|
34
41
|
});
|
|
35
42
|
|
|
43
|
+
test('reactive', async ({ expect }) => {
|
|
44
|
+
const graph = new GraphModel(live({ nodes: [], edges: [] }));
|
|
45
|
+
|
|
46
|
+
const done = new Trigger<Graph>();
|
|
47
|
+
|
|
48
|
+
// NOTE: Requires `registerSignalsRuntime` to be called.
|
|
49
|
+
const unsubscribe = effect(() => {
|
|
50
|
+
if (graph.edges.length === 2) {
|
|
51
|
+
done.wake(graph.graph);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
setTimeout(() => {
|
|
56
|
+
graph.builder.addNode({ id: 'node-1' });
|
|
57
|
+
graph.builder.addNode({ id: 'node-2' });
|
|
58
|
+
graph.builder.addNode({ id: 'node-3' });
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
setTimeout(() => {
|
|
62
|
+
graph.builder.addEdge({ source: 'node-1', target: 'node-2' });
|
|
63
|
+
graph.builder.addEdge({ source: 'node-2', target: 'node-3' });
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
{
|
|
67
|
+
const graph = await done.wait();
|
|
68
|
+
expect(graph.nodes).to.have.length(3);
|
|
69
|
+
expect(graph.edges).to.have.length(2);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
unsubscribe();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test('reactive model', async ({ expect }) => {
|
|
76
|
+
const graph = new ReactiveGraphModel();
|
|
77
|
+
|
|
78
|
+
const done = new Trigger<Graph>();
|
|
79
|
+
const unsubscribe = graph.subscribe((graph) => {
|
|
80
|
+
if (graph.edges.length === 2) {
|
|
81
|
+
done.wake(graph.graph);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
setTimeout(() => {
|
|
86
|
+
graph.builder.addNode({ id: 'node-1' });
|
|
87
|
+
graph.builder.addNode({ id: 'node-2' });
|
|
88
|
+
graph.builder.addNode({ id: 'node-3' });
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
setTimeout(() => {
|
|
92
|
+
graph.builder.addEdge({ source: 'node-1', target: 'node-2' });
|
|
93
|
+
graph.builder.addEdge({ source: 'node-2', target: 'node-3' });
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
{
|
|
97
|
+
const graph = await done.wait();
|
|
98
|
+
expect(graph.nodes).to.have.length(3);
|
|
99
|
+
expect(graph.edges).to.have.length(2);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
unsubscribe();
|
|
103
|
+
});
|
|
104
|
+
|
|
36
105
|
test('optional', ({ expect }) => {
|
|
37
106
|
{
|
|
38
|
-
const graph = new GraphModel<GraphNode<string>>();
|
|
107
|
+
const graph = new GraphModel<GraphNode.Required<string>>();
|
|
39
108
|
const node = graph.addNode({ id: 'test', data: 'test' });
|
|
40
109
|
expect(node.data.length).to.eq(4);
|
|
41
110
|
}
|
|
@@ -48,7 +117,7 @@ describe('Graph', () => {
|
|
|
48
117
|
});
|
|
49
118
|
|
|
50
119
|
test('add and remove subgraphs', ({ expect }) => {
|
|
51
|
-
const graph = new GraphModel<GraphNode<TestData>>();
|
|
120
|
+
const graph = new GraphModel<GraphNode.Required<TestData>>();
|
|
52
121
|
graph.builder
|
|
53
122
|
.addNode({ id: 'node1', data: { value: 'test' } })
|
|
54
123
|
.addNode({ id: 'node2', data: { value: 'test' } })
|
package/src/model.ts
CHANGED
|
@@ -2,16 +2,18 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
+
import { effect } from '@preact/signals-core';
|
|
6
|
+
|
|
5
7
|
import { inspectCustom } from '@dxos/debug';
|
|
6
8
|
import { failedInvariant, invariant } from '@dxos/invariant';
|
|
7
|
-
import {
|
|
8
|
-
import { type MakeOptional, isNotFalsy, removeBy
|
|
9
|
+
import { type Live, live } from '@dxos/live-object';
|
|
10
|
+
import { type MakeOptional, isNotFalsy, removeBy } from '@dxos/util';
|
|
9
11
|
|
|
10
12
|
import { type BaseGraphEdge, type BaseGraphNode, type Graph, type GraphEdge, type GraphNode } from './types';
|
|
11
13
|
import { createEdgeId } from './util';
|
|
12
14
|
|
|
13
15
|
/**
|
|
14
|
-
*
|
|
16
|
+
* Readonly Graph wrapper.
|
|
15
17
|
*/
|
|
16
18
|
export class ReadonlyGraphModel<
|
|
17
19
|
Node extends BaseGraphNode = BaseGraphNode,
|
|
@@ -23,7 +25,10 @@ export class ReadonlyGraphModel<
|
|
|
23
25
|
* NOTE: Pass in simple Graph or Live.
|
|
24
26
|
*/
|
|
25
27
|
constructor(graph?: Graph) {
|
|
26
|
-
this._graph = graph ?? {
|
|
28
|
+
this._graph = graph ?? {
|
|
29
|
+
nodes: [],
|
|
30
|
+
edges: [],
|
|
31
|
+
};
|
|
27
32
|
}
|
|
28
33
|
|
|
29
34
|
[inspectCustom]() {
|
|
@@ -33,12 +38,11 @@ export class ReadonlyGraphModel<
|
|
|
33
38
|
/**
|
|
34
39
|
* Return stable sorted JSON representation of graph.
|
|
35
40
|
*/
|
|
36
|
-
// TODO(burdon): Create separate toJson method with computed signal.
|
|
37
41
|
toJSON() {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
+
return {
|
|
43
|
+
nodes: this.nodes.length,
|
|
44
|
+
edges: this.edges.length,
|
|
45
|
+
};
|
|
42
46
|
}
|
|
43
47
|
|
|
44
48
|
get graph(): Graph {
|
|
@@ -65,7 +69,7 @@ export class ReadonlyGraphModel<
|
|
|
65
69
|
return this.findNode(id) ?? failedInvariant();
|
|
66
70
|
}
|
|
67
71
|
|
|
68
|
-
filterNodes({ type }: Partial<GraphNode> = {}): Node[] {
|
|
72
|
+
filterNodes({ type }: Partial<GraphNode.Any> = {}): Node[] {
|
|
69
73
|
return this.nodes.filter((node) => !type || type === node.type);
|
|
70
74
|
}
|
|
71
75
|
|
|
@@ -111,13 +115,13 @@ export class ReadonlyGraphModel<
|
|
|
111
115
|
}
|
|
112
116
|
|
|
113
117
|
/**
|
|
114
|
-
*
|
|
118
|
+
* Mutable Graph wrapper.
|
|
115
119
|
*/
|
|
116
120
|
export abstract class AbstractGraphModel<
|
|
117
|
-
Node extends BaseGraphNode,
|
|
118
|
-
Edge extends BaseGraphEdge,
|
|
119
|
-
Model extends AbstractGraphModel<Node, Edge, Model, Builder
|
|
120
|
-
Builder extends AbstractGraphBuilder<Node, Edge, Model>,
|
|
121
|
+
Node extends BaseGraphNode = BaseGraphNode,
|
|
122
|
+
Edge extends BaseGraphEdge = BaseGraphEdge,
|
|
123
|
+
Model extends AbstractGraphModel<Node, Edge, Model, Builder> = any,
|
|
124
|
+
Builder extends AbstractGraphBuilder<Node, Edge, Model> = AbstractGraphBuilder<Node, Edge, Model>,
|
|
121
125
|
> extends ReadonlyGraphModel<Node, Edge> {
|
|
122
126
|
/**
|
|
123
127
|
* Allows chaining.
|
|
@@ -177,8 +181,8 @@ export abstract class AbstractGraphModel<
|
|
|
177
181
|
}
|
|
178
182
|
|
|
179
183
|
removeNode(id: string): Model {
|
|
180
|
-
const nodes = removeBy<Node>(this._graph.nodes as Node[], (node) => node.id === id);
|
|
181
184
|
const edges = removeBy<Edge>(this._graph.edges as Edge[], (edge) => edge.source === id || edge.target === id);
|
|
185
|
+
const nodes = removeBy<Node>(this._graph.nodes as Node[], (node) => node.id === id);
|
|
182
186
|
return this.copy({ nodes, edges });
|
|
183
187
|
}
|
|
184
188
|
|
|
@@ -199,7 +203,7 @@ export abstract class AbstractGraphModel<
|
|
|
199
203
|
}
|
|
200
204
|
|
|
201
205
|
/**
|
|
202
|
-
* Chainable wrapper
|
|
206
|
+
* Chainable builder wrapper
|
|
203
207
|
*/
|
|
204
208
|
export abstract class AbstractGraphBuilder<
|
|
205
209
|
Node extends BaseGraphNode,
|
|
@@ -242,6 +246,9 @@ export abstract class AbstractGraphBuilder<
|
|
|
242
246
|
}
|
|
243
247
|
}
|
|
244
248
|
|
|
249
|
+
/**
|
|
250
|
+
* Basic model.
|
|
251
|
+
*/
|
|
245
252
|
export class GraphModel<
|
|
246
253
|
Node extends BaseGraphNode = BaseGraphNode,
|
|
247
254
|
Edge extends BaseGraphEdge = BaseGraphEdge,
|
|
@@ -250,16 +257,60 @@ export class GraphModel<
|
|
|
250
257
|
return new GraphBuilder<Node, Edge>(this);
|
|
251
258
|
}
|
|
252
259
|
|
|
253
|
-
override copy(graph?: Partial<Graph>) {
|
|
260
|
+
override copy(graph?: Partial<Graph>): GraphModel<Node, Edge> {
|
|
254
261
|
return new GraphModel<Node, Edge>({ nodes: graph?.nodes ?? [], edges: graph?.edges ?? [] });
|
|
255
262
|
}
|
|
256
263
|
}
|
|
257
264
|
|
|
265
|
+
export type GraphModelSubscription = (model: GraphModel, graph: Live<Graph>) => void;
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Subscription.
|
|
269
|
+
* NOTE: Requires `registerSignalsRuntime` to be called.
|
|
270
|
+
*/
|
|
271
|
+
export const subscribe = (model: GraphModel, cb: GraphModelSubscription, fire = false) => {
|
|
272
|
+
if (fire) {
|
|
273
|
+
cb(model, model.graph);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return effect(() => {
|
|
277
|
+
cb(model, model.graph); // TODO(burdon): This won't work unless model.graph is reactive.
|
|
278
|
+
});
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Basic reactive model.
|
|
283
|
+
*/
|
|
284
|
+
export class ReactiveGraphModel<
|
|
285
|
+
Node extends BaseGraphNode = BaseGraphNode,
|
|
286
|
+
Edge extends BaseGraphEdge = BaseGraphEdge,
|
|
287
|
+
> extends GraphModel<Node, Edge> {
|
|
288
|
+
constructor(graph?: Partial<Graph>) {
|
|
289
|
+
super(
|
|
290
|
+
live({
|
|
291
|
+
nodes: graph?.nodes ?? [],
|
|
292
|
+
edges: graph?.edges ?? [],
|
|
293
|
+
}),
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
override copy(graph?: Partial<Graph>): ReactiveGraphModel<Node, Edge> {
|
|
298
|
+
return new ReactiveGraphModel<Node, Edge>(graph);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
subscribe(cb: GraphModelSubscription, fire = false): () => void {
|
|
302
|
+
return subscribe(this, cb, fire);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Basic builder.
|
|
308
|
+
*/
|
|
258
309
|
export class GraphBuilder<
|
|
259
310
|
Node extends BaseGraphNode = BaseGraphNode,
|
|
260
311
|
Edge extends BaseGraphEdge = BaseGraphEdge,
|
|
261
312
|
> extends AbstractGraphBuilder<Node, Edge, GraphModel<Node, Edge>> {
|
|
262
|
-
override call(cb: (builder: this) => void) {
|
|
313
|
+
override call(cb: (builder: this) => void): this {
|
|
263
314
|
cb(this);
|
|
264
315
|
return this;
|
|
265
316
|
}
|
package/src/selection.ts
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { computed, signal, type ReadonlySignal, type Signal } from '@preact/signals-core';
|
|
6
|
+
|
|
7
|
+
import { invariant } from '@dxos/invariant';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Reactive selection model.
|
|
11
|
+
*/
|
|
12
|
+
export class SelectionModel {
|
|
13
|
+
private readonly _selected: Signal<Set<string>> = signal(new Set<string>());
|
|
14
|
+
private readonly _selectedIds = computed(() => Array.from(this._selected.value.values()));
|
|
15
|
+
|
|
16
|
+
constructor(private readonly _singleSelect: boolean = false) {}
|
|
17
|
+
|
|
18
|
+
toJSON(): { selected: string[] } {
|
|
19
|
+
return {
|
|
20
|
+
selected: Array.from(this._selected.value.values()),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
get size(): number {
|
|
25
|
+
return this._selected.value.size;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
get selected(): ReadonlySignal<string[]> {
|
|
29
|
+
return this._selectedIds;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
contains(id: string): boolean {
|
|
33
|
+
return this._selected.value.has(id);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
clear(): void {
|
|
37
|
+
this._selected.value = new Set();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
add(id: string): void {
|
|
41
|
+
invariant(id);
|
|
42
|
+
this._selected.value = new Set<string>(
|
|
43
|
+
this._singleSelect ? [id] : [...Array.from(this._selected.value.values()), id],
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
remove(id: string): void {
|
|
48
|
+
invariant(id);
|
|
49
|
+
this._selected.value = new Set<string>(Array.from(this._selected.value.values()).filter((_id) => _id !== id));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// TODO(burdon): Handle single select.
|
|
53
|
+
|
|
54
|
+
setSelected(ids: string[], shift = false): void {
|
|
55
|
+
this._selected.value = new Set([...(shift ? Array.from(this._selected.value.values()) : []), ...ids]);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
toggleSelected(ids: string[], shift = false): void {
|
|
59
|
+
const set = new Set<string>(shift ? Array.from(this._selected.value.values()) : undefined);
|
|
60
|
+
ids.forEach((id) => {
|
|
61
|
+
if (this._selected.value.has(id)) {
|
|
62
|
+
set.delete(id);
|
|
63
|
+
} else {
|
|
64
|
+
set.add(id);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
this._selected.value = set;
|
|
69
|
+
}
|
|
70
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -6,6 +6,11 @@ import { Schema } from 'effect';
|
|
|
6
6
|
|
|
7
7
|
import { type Specialize } from '@dxos/util';
|
|
8
8
|
|
|
9
|
+
//
|
|
10
|
+
// Node
|
|
11
|
+
//
|
|
12
|
+
|
|
13
|
+
// TODO(burdon): Make type extensible (i.e., not dependent on `data` property)?
|
|
9
14
|
export const BaseGraphNode = Schema.Struct({
|
|
10
15
|
id: Schema.String,
|
|
11
16
|
type: Schema.optional(Schema.String),
|
|
@@ -16,21 +21,27 @@ export const BaseGraphNode = Schema.Struct({
|
|
|
16
21
|
export type BaseGraphNode = Schema.Schema.Type<typeof BaseGraphNode>;
|
|
17
22
|
|
|
18
23
|
/** Typed node data. */
|
|
19
|
-
|
|
24
|
+
type GraphNode<Data = any, Optional extends boolean = false> = Specialize<
|
|
20
25
|
BaseGraphNode,
|
|
21
26
|
Optional extends true ? { data?: Data } : { data: Data }
|
|
22
27
|
>;
|
|
23
28
|
|
|
24
29
|
export declare namespace GraphNode {
|
|
25
|
-
export type
|
|
30
|
+
export type Any = GraphNode<any, true>;
|
|
31
|
+
export type Optional<Data = any> = GraphNode<Data, true>;
|
|
32
|
+
export type Required<Data = any> = GraphNode<Data, false>;
|
|
26
33
|
}
|
|
27
34
|
|
|
35
|
+
//
|
|
36
|
+
// Edge
|
|
37
|
+
//
|
|
38
|
+
|
|
28
39
|
export const BaseGraphEdge = Schema.Struct({
|
|
29
40
|
id: Schema.String,
|
|
30
41
|
type: Schema.optional(Schema.String),
|
|
31
|
-
data: Schema.optional(Schema.Any),
|
|
32
42
|
source: Schema.String,
|
|
33
43
|
target: Schema.String,
|
|
44
|
+
data: Schema.optional(Schema.Any),
|
|
34
45
|
});
|
|
35
46
|
|
|
36
47
|
/** Raw base type. */
|
|
@@ -43,23 +54,18 @@ export type GraphEdge<Data = any, Optional extends boolean = false> = Specialize
|
|
|
43
54
|
>;
|
|
44
55
|
|
|
45
56
|
export declare namespace GraphEdge {
|
|
46
|
-
export type
|
|
57
|
+
export type Any = GraphEdge<any, true>;
|
|
58
|
+
export type Optional<Data = any> = GraphEdge<Data, true>;
|
|
59
|
+
export type Required<Data = any> = GraphEdge<Data, false>;
|
|
47
60
|
}
|
|
48
61
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const ExtendableBaseGraphNode = Schema.extend(
|
|
53
|
-
BaseGraphNode,
|
|
54
|
-
Schema.Struct({}, { key: Schema.String, value: Schema.Any }),
|
|
55
|
-
);
|
|
62
|
+
//
|
|
63
|
+
// Graph
|
|
64
|
+
//
|
|
56
65
|
|
|
57
|
-
/**
|
|
58
|
-
* Generic graph.
|
|
59
|
-
*/
|
|
60
66
|
export const Graph = Schema.Struct({
|
|
61
67
|
id: Schema.optional(Schema.String),
|
|
62
|
-
nodes: Schema.mutable(Schema.Array(
|
|
68
|
+
nodes: Schema.mutable(Schema.Array(BaseGraphNode)),
|
|
63
69
|
edges: Schema.mutable(Schema.Array(BaseGraphEdge)),
|
|
64
70
|
});
|
|
65
71
|
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { type AnyLiveObject } from '@dxos/echo-db';
|
|
2
|
-
import { GraphModel } from './model';
|
|
3
|
-
import { type GraphNode } from './types';
|
|
4
|
-
/**
|
|
5
|
-
* Creates a new reactive graph from a set of ECHO objects.
|
|
6
|
-
* References are mapped onto graph edges.
|
|
7
|
-
*/
|
|
8
|
-
export declare const createGraph: (objects: AnyLiveObject<any>[]) => GraphModel<GraphNode<AnyLiveObject<any>>>;
|
|
9
|
-
//# sourceMappingURL=create.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../../src/create.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,eAAe,CAAC;AAMnD,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAS,KAAK,SAAS,EAAE,MAAM,SAAS,CAAC;AAGhD;;;GAGG;AACH,eAAO,MAAM,WAAW,GAAI,SAAS,aAAa,CAAC,GAAG,CAAC,EAAE,KAAG,UAAU,CAAC,SAAS,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC,CAiCnG,CAAC"}
|
package/src/create.ts
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
//
|
|
2
|
-
// Copyright 2024 DXOS.org
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
import { type AnyLiveObject } from '@dxos/echo-db';
|
|
6
|
-
import { FormatEnum, getSchema } from '@dxos/echo-schema';
|
|
7
|
-
import { live } from '@dxos/live-object';
|
|
8
|
-
import { log } from '@dxos/log';
|
|
9
|
-
import { getSchemaProperties } from '@dxos/schema';
|
|
10
|
-
|
|
11
|
-
import { GraphModel } from './model';
|
|
12
|
-
import { Graph, type GraphNode } from './types';
|
|
13
|
-
import { createEdgeId } from './util';
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Creates a new reactive graph from a set of ECHO objects.
|
|
17
|
-
* References are mapped onto graph edges.
|
|
18
|
-
*/
|
|
19
|
-
export const createGraph = (objects: AnyLiveObject<any>[]): GraphModel<GraphNode<AnyLiveObject<any>>> => {
|
|
20
|
-
const graph = new GraphModel<GraphNode<AnyLiveObject<any>>>(live(Graph, { nodes: [], edges: [] }));
|
|
21
|
-
|
|
22
|
-
// Map objects.
|
|
23
|
-
objects.forEach((object) => {
|
|
24
|
-
graph.addNode({ id: object.id, type: object.typename, data: object });
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
// Find references.
|
|
28
|
-
objects.forEach((object) => {
|
|
29
|
-
const schema = getSchema(object);
|
|
30
|
-
if (!schema) {
|
|
31
|
-
log.info('no schema for object', { id: object.id.slice(0, 8) });
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// Parse schema to follow referenced objects.
|
|
36
|
-
for (const prop of getSchemaProperties(schema.ast, object)) {
|
|
37
|
-
if (prop.format === FormatEnum.Ref) {
|
|
38
|
-
const source = object;
|
|
39
|
-
const target = object[prop.name]?.target;
|
|
40
|
-
if (target) {
|
|
41
|
-
graph.addEdge({
|
|
42
|
-
id: createEdgeId({ source: source.id, target: target.id, relation: String(prop.name) }),
|
|
43
|
-
source: source.id,
|
|
44
|
-
target: target.id,
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
return graph;
|
|
52
|
-
};
|