@dxos/graph 0.8.2-staging.7ac8446 → 0.8.2

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.
@@ -0,0 +1,22 @@
1
+ import { type ReadonlySignal } from '@preact/signals-core';
2
+ /**
3
+ * Reactive selection model.
4
+ */
5
+ export declare class SelectionModel {
6
+ private readonly _singleSelect;
7
+ private readonly _selected;
8
+ private readonly _selectedIds;
9
+ constructor(_singleSelect?: boolean);
10
+ toJSON(): {
11
+ selected: string[];
12
+ };
13
+ get size(): number;
14
+ get selected(): ReadonlySignal<string[]>;
15
+ contains(id: string): boolean;
16
+ clear(): void;
17
+ add(id: string): void;
18
+ remove(id: string): void;
19
+ setSelected(ids: string[], shift?: boolean): void;
20
+ toggleSelected(ids: string[], shift?: boolean): void;
21
+ }
22
+ //# sourceMappingURL=selection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"selection.d.ts","sourceRoot":"","sources":["../../../src/selection.ts"],"names":[],"mappings":"AAIA,OAAO,EAAoB,KAAK,cAAc,EAAe,MAAM,sBAAsB,CAAC;AAI1F;;GAEG;AACH,qBAAa,cAAc;IAIb,OAAO,CAAC,QAAQ,CAAC,aAAa;IAH1C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAkD;IAC5E,OAAO,CAAC,QAAQ,CAAC,YAAY,CAA6D;gBAE7D,aAAa,GAAE,OAAe;IAE3D,MAAM,IAAI;QAAE,QAAQ,EAAE,MAAM,EAAE,CAAA;KAAE;IAMhC,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,IAAI,QAAQ,IAAI,cAAc,CAAC,MAAM,EAAE,CAAC,CAEvC;IAED,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAI7B,KAAK,IAAI,IAAI;IAIb,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IAOrB,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IAOxB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,KAAK,UAAQ,GAAG,IAAI;IAI/C,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,KAAK,UAAQ,GAAG,IAAI;CAYnD"}
@@ -1,30 +1,32 @@
1
- import { S } from '@dxos/echo-schema';
1
+ import { Schema } from 'effect';
2
2
  import { type Specialize } from '@dxos/util';
3
- export declare const BaseGraphNode: S.Struct<{
4
- id: typeof S.String;
5
- type: S.optional<typeof S.String>;
6
- data: S.optional<typeof S.Any>;
3
+ export declare const BaseGraphNode: Schema.Struct<{
4
+ id: typeof Schema.String;
5
+ type: Schema.optional<typeof Schema.String>;
6
+ data: Schema.optional<typeof Schema.Any>;
7
7
  }>;
8
8
  /** Raw base type. */
9
- export type BaseGraphNode = S.Schema.Type<typeof BaseGraphNode>;
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
- export declare const BaseGraphEdge: S.Struct<{
20
- id: typeof S.String;
21
- type: S.optional<typeof S.String>;
22
- data: S.optional<typeof S.Any>;
23
- source: typeof S.String;
24
- target: typeof S.String;
21
+ export declare const BaseGraphEdge: Schema.Struct<{
22
+ id: typeof Schema.String;
23
+ type: Schema.optional<typeof Schema.String>;
24
+ source: typeof Schema.String;
25
+ target: typeof Schema.String;
26
+ data: Schema.optional<typeof Schema.Any>;
25
27
  }>;
26
28
  /** Raw base type. */
27
- export type BaseGraphEdge = S.Schema.Type<typeof BaseGraphEdge>;
29
+ export type BaseGraphEdge = Schema.Schema.Type<typeof BaseGraphEdge>;
28
30
  /** Typed edge data. */
29
31
  export type GraphEdge<Data = any, Optional extends boolean = false> = Specialize<BaseGraphEdge, Optional extends true ? {
30
32
  data?: Data;
@@ -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
- export declare const Graph: S.Struct<{
41
- id: S.optional<typeof S.String>;
42
- nodes: S.mutable<S.Array$<S.extend<S.Struct<{
43
- id: typeof S.String;
44
- type: S.optional<typeof S.String>;
45
- data: S.optional<typeof S.Any>;
46
- }>, S.TypeLiteral<{}, readonly [{
47
- readonly key: typeof S.String;
48
- readonly value: typeof S.Any;
49
- }]>>>>;
50
- edges: S.mutable<S.Array$<S.Struct<{
51
- id: typeof S.String;
52
- type: S.optional<typeof S.String>;
53
- data: S.optional<typeof S.Any>;
54
- source: typeof S.String;
55
- target: typeof S.String;
41
+ export declare const Graph: Schema.Struct<{
42
+ id: Schema.optional<typeof Schema.String>;
43
+ nodes: Schema.mutable<Schema.Array$<Schema.Struct<{
44
+ id: typeof Schema.String;
45
+ type: Schema.optional<typeof Schema.String>;
46
+ data: Schema.optional<typeof Schema.Any>;
47
+ }>>>;
48
+ edges: Schema.mutable<Schema.Array$<Schema.Struct<{
49
+ id: typeof Schema.String;
50
+ type: Schema.optional<typeof Schema.String>;
51
+ source: typeof Schema.String;
52
+ target: typeof Schema.String;
53
+ data: Schema.optional<typeof Schema.Any>;
56
54
  }>>>;
57
55
  }>;
58
- export type Graph = S.Schema.Type<typeof Graph>;
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,CAAC,EAAE,MAAM,mBAAmB,CAAC;AACtC,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C,eAAO,MAAM,aAAa;;;;EAIxB,CAAC;AAEH,qBAAqB;AACrB,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,aAAa,CAAC,CAAC;AAEhE,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,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,aAAa,CAAC,CAAC;AAEhE,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;AAOD;;GAEG;AACH,eAAO,MAAM,KAAK;;;;;;;;;;;;;;;;;EAIhB,CAAC;AAEH,MAAM,MAAM,KAAK,GAAG,CAAC,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"}
@@ -1,6 +1,3 @@
1
- import { type ReactiveEchoObject } from '@dxos/echo-db';
2
- import { GraphModel } from './model';
3
- import { type GraphNode } from './types';
4
1
  type EdgeMeta = {
5
2
  source: string;
6
3
  target: string;
@@ -8,10 +5,5 @@ type EdgeMeta = {
8
5
  };
9
6
  export declare const createEdgeId: ({ source, target, relation }: EdgeMeta) => string;
10
7
  export declare const parseEdgeId: (id: string) => EdgeMeta;
11
- /**
12
- * Creates a new reactive graph from a set of ECHO objects.
13
- * References are mapped onto graph edges.
14
- */
15
- export declare const createGraph: (objects: ReactiveEchoObject<any>[]) => GraphModel<GraphNode<ReactiveEchoObject<any>>>;
16
8
  export {};
17
9
  //# sourceMappingURL=util.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../../../src/util.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAOxD,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAS,KAAK,SAAS,EAAE,MAAM,SAAS,CAAC;AAKhD,KAAK,QAAQ,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAEtE,eAAO,MAAM,YAAY,iCAAkC,QAAQ,WAIlE,CAAC;AAEF,eAAO,MAAM,WAAW,OAAQ,MAAM,KAAG,QAIxC,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,WAAW,YAAa,kBAAkB,CAAC,GAAG,CAAC,EAAE,KAAG,UAAU,CAAC,SAAS,CAAC,kBAAkB,CAAC,GAAG,CAAC,CAAC,CAiC7G,CAAC"}
1
+ {"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../../../src/util.ts"],"names":[],"mappings":"AASA,KAAK,QAAQ,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAEtE,eAAO,MAAM,YAAY,GAAI,8BAA8B,QAAQ,WAIlE,CAAC;AAEF,eAAO,MAAM,WAAW,GAAI,IAAI,MAAM,KAAG,QAIxC,CAAC"}
@@ -1 +1 @@
1
- {"version":"5.7.3"}
1
+ {"version":"5.8.3"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/graph",
3
- "version": "0.8.2-staging.7ac8446",
3
+ "version": "0.8.2",
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,25 +28,29 @@
31
28
  "src"
32
29
  ],
33
30
  "dependencies": {
34
- "@preact/signals-core": "^1.6.0",
31
+ "@preact/signals-core": "^1.9.0",
32
+ "effect": "3.14.21",
35
33
  "lodash.defaultsdeep": "^4.6.1",
36
- "@dxos/async": "0.8.2-staging.7ac8446",
37
- "@dxos/echo-db": "0.8.2-staging.7ac8446",
38
- "@dxos/debug": "0.8.2-staging.7ac8446",
39
- "@dxos/echo-schema": "0.8.2-staging.7ac8446",
40
- "@dxos/invariant": "0.8.2-staging.7ac8446",
41
- "@dxos/live-object": "0.8.2-staging.7ac8446",
42
- "@dxos/log": "0.8.2-staging.7ac8446",
43
- "@dxos/schema": "0.8.2-staging.7ac8446",
44
- "@dxos/util": "0.8.2-staging.7ac8446"
34
+ "@dxos/async": "0.8.2",
35
+ "@dxos/debug": "0.8.2",
36
+ "@dxos/echo-schema": "0.8.2",
37
+ "@dxos/echo-db": "0.8.2",
38
+ "@dxos/echo-signals": "0.8.2",
39
+ "@dxos/live-object": "0.8.2",
40
+ "@dxos/util": "0.8.2",
41
+ "@dxos/invariant": "0.8.2",
42
+ "@dxos/log": "0.8.2"
45
43
  },
46
44
  "devDependencies": {
47
45
  "@antv/graphlib": "^2.0.4",
48
46
  "@antv/layout": "^1.2.13",
49
47
  "@types/lodash.defaultsdeep": "^4.6.6",
50
- "@dxos/echo-db": "0.8.2-staging.7ac8446",
51
- "@dxos/random": "0.8.2-staging.7ac8446",
52
- "@dxos/echo-schema": "0.8.2-staging.7ac8446"
48
+ "@dxos/echo-schema": "0.8.2",
49
+ "@dxos/echo-db": "0.8.2",
50
+ "@dxos/random": "0.8.2"
51
+ },
52
+ "peerDependencies": {
53
+ "effect": "^3.13.3"
53
54
  },
54
55
  "publishConfig": {
55
56
  "access": "public"
package/src/index.ts CHANGED
@@ -3,5 +3,6 @@
3
3
  //
4
4
 
5
5
  export * from './model';
6
+ export * from './selection';
6
7
  export * from './types';
7
8
  export * from './util';
package/src/model.test.ts CHANGED
@@ -2,21 +2,27 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
+ import { effect } from '@preact/signals-core';
6
+ import { Schema } from 'effect';
5
7
  import { describe, test } from 'vitest';
6
8
 
7
- import { S } from '@dxos/echo-schema';
9
+ import { Trigger } from '@dxos/async';
10
+ import { registerSignalsRuntime } from '@dxos/echo-signals';
11
+ import { live } from '@dxos/live-object';
8
12
 
9
- import { GraphModel } from './model';
10
- import { BaseGraphNode, type GraphNode } from './types';
13
+ import { GraphModel, ReactiveGraphModel } from './model';
14
+ import { BaseGraphNode, type Graph, type GraphNode } from './types';
11
15
 
12
- const TestNode = S.extend(
16
+ registerSignalsRuntime();
17
+
18
+ const TestNode = Schema.extend(
13
19
  BaseGraphNode,
14
- S.Struct({
15
- value: S.String,
20
+ Schema.Struct({
21
+ value: Schema.String,
16
22
  }),
17
23
  );
18
24
 
19
- type TestNode = S.Schema.Type<typeof TestNode>;
25
+ type TestNode = Schema.Schema.Type<typeof TestNode>;
20
26
 
21
27
  type TestData = { value: string };
22
28
 
@@ -25,7 +31,7 @@ describe('Graph', () => {
25
31
  const graph = new GraphModel();
26
32
  expect(graph.nodes).to.have.length(0);
27
33
  expect(graph.edges).to.have.length(0);
28
- expect(graph.toJSON()).to.deep.eq({ nodes: [], edges: [] });
34
+ expect(graph.toJSON()).to.deep.eq({ nodes: 0, edges: 0 });
29
35
  });
30
36
 
31
37
  test('extended', ({ expect }) => {
@@ -34,9 +40,71 @@ describe('Graph', () => {
34
40
  expect(node.value.length).to.eq(4);
35
41
  });
36
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
+
37
105
  test('optional', ({ expect }) => {
38
106
  {
39
- const graph = new GraphModel<GraphNode<string>>();
107
+ const graph = new GraphModel<GraphNode.Required<string>>();
40
108
  const node = graph.addNode({ id: 'test', data: 'test' });
41
109
  expect(node.data.length).to.eq(4);
42
110
  }
@@ -49,7 +117,7 @@ describe('Graph', () => {
49
117
  });
50
118
 
51
119
  test('add and remove subgraphs', ({ expect }) => {
52
- const graph = new GraphModel<GraphNode<TestData>>();
120
+ const graph = new GraphModel<GraphNode.Required<TestData>>();
53
121
  graph.builder
54
122
  .addNode({ id: 'node1', data: { value: 'test' } })
55
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
- import { type Graph, type GraphNode, type GraphEdge, type BaseGraphNode, type BaseGraphEdge } from './types';
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,
@@ -20,10 +22,13 @@ export class ReadonlyGraphModel<
20
22
  protected readonly _graph: Graph;
21
23
 
22
24
  /**
23
- * NOTE: Pass in simple Graph or ReactiveObject.
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
+ }