@dxos/graph 0.8.2-main.fbd8ed0 → 0.8.2-staging.4d6ad0f

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.
@@ -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
- export type GraphNode<Data = any, Optional extends boolean = false> = Specialize<BaseGraphNode, Optional extends true ? {
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 Optional<T = any> = GraphNode<T, true>;
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 Optional<T = any> = GraphEdge<T, true>;
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.extend<Schema.Struct<{
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
- }>, Schema.TypeLiteral<{}, readonly [{
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;AAE7C,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,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,QAAQ,CAAC,CAAC,GAAG,GAAG,IAAI,SAAS,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;CACpD;AAED,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,QAAQ,CAAC,CAAC,GAAG,GAAG,IAAI,SAAS,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;CACpD;AAUD;;GAEG;AACH,eAAO,MAAM,KAAK;;;;;;;;;;;;;;;;;EAIhB,CAAC;AAEH,MAAM,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,KAAK,CAAC,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-main.fbd8ed0",
3
+ "version": "0.8.2-staging.4d6ad0f",
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/index.d.ts"
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.6.0",
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-main.fbd8ed0",
38
- "@dxos/debug": "0.8.2-main.fbd8ed0",
39
- "@dxos/echo-db": "0.8.2-main.fbd8ed0",
40
- "@dxos/invariant": "0.8.2-main.fbd8ed0",
41
- "@dxos/log": "0.8.2-main.fbd8ed0",
42
- "@dxos/echo-schema": "0.8.2-main.fbd8ed0",
43
- "@dxos/live-object": "0.8.2-main.fbd8ed0",
44
- "@dxos/schema": "0.8.2-main.fbd8ed0",
45
- "@dxos/util": "0.8.2-main.fbd8ed0"
34
+ "@dxos/async": "0.8.2-staging.4d6ad0f",
35
+ "@dxos/debug": "0.8.2-staging.4d6ad0f",
36
+ "@dxos/echo-db": "0.8.2-staging.4d6ad0f",
37
+ "@dxos/echo-schema": "0.8.2-staging.4d6ad0f",
38
+ "@dxos/invariant": "0.8.2-staging.4d6ad0f",
39
+ "@dxos/live-object": "0.8.2-staging.4d6ad0f",
40
+ "@dxos/log": "0.8.2-staging.4d6ad0f",
41
+ "@dxos/util": "0.8.2-staging.4d6ad0f",
42
+ "@dxos/echo-signals": "0.8.2-staging.4d6ad0f"
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-schema": "0.8.2-main.fbd8ed0",
52
- "@dxos/random": "0.8.2-main.fbd8ed0",
53
- "@dxos/echo-db": "0.8.2-main.fbd8ed0"
48
+ "@dxos/echo-db": "0.8.2-staging.4d6ad0f",
49
+ "@dxos/echo-schema": "0.8.2-staging.4d6ad0f",
50
+ "@dxos/random": "0.8.2-staging.4d6ad0f"
54
51
  },
55
52
  "peerDependencies": {
56
53
  "effect": "^3.13.3"
package/src/index.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- export * from './create';
6
5
  export * from './model';
6
+ export * from './selection';
7
7
  export * from './types';
8
8
  export * from './util';
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 { GraphModel } from './model';
9
- import { BaseGraphNode, type GraphNode } from './types';
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: [], edges: [] });
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 { getSnapshot } from '@dxos/live-object';
8
- import { type MakeOptional, isNotFalsy, removeBy, stripUndefined } from '@dxos/util';
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
- * Wrapper class contains reactive nodes and edges.
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 ?? { nodes: [], edges: [] };
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
- const { id, nodes, edges } = getSnapshot(this._graph);
39
- nodes.sort(({ id: a }, { id: b }) => a.localeCompare(b));
40
- edges.sort(({ id: a }, { id: b }) => a.localeCompare(b));
41
- return stripUndefined({ id, nodes, edges });
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
- * Typed wrapper.
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
  }
@@ -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
- export type GraphNode<Data = any, Optional extends boolean = false> = Specialize<
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 Optional<T = any> = GraphNode<T, true>;
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 Optional<T = any> = GraphEdge<T, true>;
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
- * Allows any additional properties on graph nodes.
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(ExtendableBaseGraphNode)),
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
- };