@dxos/app-graph 0.8.4-main.84f28bd → 0.8.4-main.ae835ea

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/app-graph",
3
- "version": "0.8.4-main.84f28bd",
3
+ "version": "0.8.4-main.ae835ea",
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",
@@ -10,6 +10,7 @@
10
10
  "type": "module",
11
11
  "exports": {
12
12
  ".": {
13
+ "source": "./src/index.ts",
13
14
  "types": "./dist/types/src/index.d.ts",
14
15
  "browser": "./dist/lib/browser/index.mjs",
15
16
  "node": "./dist/lib/node-esm/index.mjs"
@@ -24,46 +25,43 @@
24
25
  "src"
25
26
  ],
26
27
  "dependencies": {
27
- "@preact/signals-core": "^1.9.0",
28
+ "@preact/signals-core": "^1.12.1",
28
29
  "main-thread-scheduling": "^14.1.1",
29
- "@dxos/async": "0.8.4-main.84f28bd",
30
- "@dxos/debug": "0.8.4-main.84f28bd",
31
- "@dxos/echo": "0.8.4-main.84f28bd",
32
- "@dxos/echo-signals": "0.8.4-main.84f28bd",
33
- "@dxos/echo-schema": "0.8.4-main.84f28bd",
34
- "@dxos/invariant": "0.8.4-main.84f28bd",
35
- "@dxos/live-object": "0.8.4-main.84f28bd",
36
- "@dxos/log": "0.8.4-main.84f28bd",
37
- "@dxos/util": "0.8.4-main.84f28bd"
30
+ "@dxos/async": "0.8.4-main.ae835ea",
31
+ "@dxos/debug": "0.8.4-main.ae835ea",
32
+ "@dxos/invariant": "0.8.4-main.ae835ea",
33
+ "@dxos/echo-signals": "0.8.4-main.ae835ea",
34
+ "@dxos/live-object": "0.8.4-main.ae835ea",
35
+ "@dxos/log": "0.8.4-main.ae835ea",
36
+ "@dxos/util": "0.8.4-main.ae835ea",
37
+ "@dxos/echo": "0.8.4-main.ae835ea"
38
38
  },
39
39
  "devDependencies": {
40
- "@effect-rx/rx-react": "0.38.0",
41
- "@effect/platform": "0.88.0",
42
- "@phosphor-icons/react": "^2.1.5",
43
- "@types/react": "~18.2.0",
44
- "@types/react-dom": "~18.2.0",
45
- "effect": "3.16.13",
46
- "react": "~18.2.0",
47
- "react-dom": "~18.2.0",
48
- "vite": "5.4.7",
49
- "@dxos/echo-db": "0.8.4-main.84f28bd",
50
- "@dxos/random": "0.8.4-main.84f28bd",
51
- "@dxos/react-client": "0.8.4-main.84f28bd",
52
- "@dxos/react-ui": "0.8.4-main.84f28bd",
53
- "@dxos/react-ui-list": "0.8.4-main.84f28bd",
54
- "@dxos/react-ui-tabs": "0.8.4-main.84f28bd",
55
- "@dxos/storybook-utils": "0.8.4-main.84f28bd",
56
- "@dxos/react-ui-theme": "0.8.4-main.84f28bd"
40
+ "@effect-rx/rx-react": "0.42.4",
41
+ "@effect/platform": "0.92.1",
42
+ "@types/react": "~19.2.2",
43
+ "@types/react-dom": "~19.2.2",
44
+ "effect": "3.18.3",
45
+ "react": "~19.2.0",
46
+ "react-dom": "~19.2.0",
47
+ "vite": "7.1.9",
48
+ "@dxos/echo-db": "0.8.4-main.ae835ea",
49
+ "@dxos/random": "0.8.4-main.ae835ea",
50
+ "@dxos/react-client": "0.8.4-main.ae835ea",
51
+ "@dxos/react-ui": "0.8.4-main.ae835ea",
52
+ "@dxos/react-ui-list": "0.8.4-main.ae835ea",
53
+ "@dxos/react-ui-theme": "0.8.4-main.ae835ea",
54
+ "@dxos/react-ui-tabs": "0.8.4-main.ae835ea",
55
+ "@dxos/storybook-utils": "0.8.4-main.ae835ea"
57
56
  },
58
57
  "peerDependencies": {
59
58
  "@effect-rx/rx-react": "^0.34.1",
60
59
  "@effect/platform": "^0.80.12",
61
- "@phosphor-icons/react": "^2.1.5",
62
60
  "effect": "3.14.21",
63
- "react": "~18.2.0",
64
- "react-dom": "~18.2.0",
65
- "@dxos/react-ui": "0.8.4-main.84f28bd",
66
- "@dxos/react-ui-theme": "0.8.4-main.84f28bd"
61
+ "react": "^19.0.0",
62
+ "react-dom": "^19.0.0",
63
+ "@dxos/react-ui": "0.8.4-main.ae835ea",
64
+ "@dxos/react-ui-theme": "0.8.4-main.ae835ea"
67
65
  },
68
66
  "publishConfig": {
69
67
  "access": "public"
@@ -3,13 +3,14 @@
3
3
  //
4
4
 
5
5
  import { Registry, Rx } from '@effect-rx/rx-react';
6
- import { Option, pipe } from 'effect';
6
+ import * as Function from 'effect/Function';
7
+ import * as Option from 'effect/Option';
7
8
  import { describe, expect, onTestFinished, test } from 'vitest';
8
9
 
9
- import { sleep, Trigger } from '@dxos/async';
10
+ import { Trigger, sleep } from '@dxos/async';
10
11
 
11
12
  import { ROOT_ID } from './graph';
12
- import { createExtension, GraphBuilder } from './graph-builder';
13
+ import { GraphBuilder, createExtension } from './graph-builder';
13
14
  import { type Node } from './node';
14
15
 
15
16
  const exampleId = (id: number) => `dx:test:${id}`;
@@ -17,6 +18,63 @@ const EXAMPLE_ID = exampleId(1);
17
18
  const EXAMPLE_TYPE = 'dxos.org/type/example';
18
19
 
19
20
  describe('GraphBuilder', () => {
21
+ describe('resolver', () => {
22
+ test('works', async () => {
23
+ const registry = Registry.make();
24
+ const builder = new GraphBuilder({ registry });
25
+ const graph = builder.graph;
26
+
27
+ {
28
+ const node = graph.getNode(EXAMPLE_ID).pipe(Option.getOrNull);
29
+ expect(node).to.be.null;
30
+ }
31
+
32
+ builder.addExtension(
33
+ createExtension({
34
+ id: 'resolver',
35
+ resolver: () => {
36
+ console.log('resolver');
37
+ return Rx.make({ id: EXAMPLE_ID, type: EXAMPLE_TYPE, data: 1 });
38
+ },
39
+ }),
40
+ );
41
+ await graph.initialize(EXAMPLE_ID);
42
+
43
+ {
44
+ const node = graph.getNode(EXAMPLE_ID).pipe(Option.getOrNull);
45
+ expect(node?.id).to.equal(EXAMPLE_ID);
46
+ expect(node?.type).to.equal(EXAMPLE_TYPE);
47
+ expect(node?.data).to.equal(1);
48
+ }
49
+ });
50
+
51
+ test('updates', async () => {
52
+ const registry = Registry.make();
53
+ const builder = new GraphBuilder({ registry });
54
+ const name = Rx.make('default');
55
+ builder.addExtension(
56
+ createExtension({
57
+ id: 'resolver',
58
+ resolver: () => Rx.make((get) => ({ id: EXAMPLE_ID, type: EXAMPLE_TYPE, data: get(name) })),
59
+ }),
60
+ );
61
+ const graph = builder.graph;
62
+ await graph.initialize(EXAMPLE_ID);
63
+
64
+ {
65
+ const node = graph.getNode(EXAMPLE_ID).pipe(Option.getOrNull);
66
+ expect(node?.data).to.equal('default');
67
+ }
68
+
69
+ registry.set(name, 'updated');
70
+
71
+ {
72
+ const node = graph.getNode(EXAMPLE_ID).pipe(Option.getOrNull);
73
+ expect(node?.data).to.equal('updated');
74
+ }
75
+ });
76
+ });
77
+
20
78
  describe('connector', () => {
21
79
  test('works', () => {
22
80
  const registry = Registry.make();
@@ -181,7 +239,7 @@ describe('GraphBuilder', () => {
181
239
  id: 'root',
182
240
  connector: (node) =>
183
241
  Rx.make((get) =>
184
- pipe(
242
+ Function.pipe(
185
243
  get(node),
186
244
  Option.flatMap((node) => (node.id === 'root' ? Option.some(get(name)) : Option.none())),
187
245
  Option.filter((name) => name !== 'removed'),
@@ -273,7 +331,7 @@ describe('GraphBuilder', () => {
273
331
  id: 'root',
274
332
  connector: (node) =>
275
333
  Rx.make((get) =>
276
- pipe(
334
+ Function.pipe(
277
335
  get(node),
278
336
  Option.flatMap((node) => (node.id === 'root' ? Option.some(get(name)) : Option.none())),
279
337
  Option.filter((name) => name !== 'removed'),
@@ -286,7 +344,7 @@ describe('GraphBuilder', () => {
286
344
  id: 'connector1',
287
345
  connector: (node) =>
288
346
  Rx.make((get) =>
289
- pipe(
347
+ Function.pipe(
290
348
  get(node),
291
349
  Option.flatMap((node) => (node.id === EXAMPLE_ID ? Option.some(get(sub)) : Option.none())),
292
350
  Option.map((sub) => [{ id: exampleId(2), type: EXAMPLE_TYPE, data: sub }]),
@@ -298,7 +356,7 @@ describe('GraphBuilder', () => {
298
356
  id: 'connector2',
299
357
  connector: (node) =>
300
358
  Rx.make((get) =>
301
- pipe(
359
+ Function.pipe(
302
360
  get(node),
303
361
  Option.flatMap((node) => (node.id === EXAMPLE_ID ? Option.some(node.data) : Option.none())),
304
362
  Option.map((data) => [{ id: exampleId(3), type: EXAMPLE_TYPE, data }]),
@@ -382,7 +440,7 @@ describe('GraphBuilder', () => {
382
440
  id: 'connector',
383
441
  connector: (node) => {
384
442
  return Rx.make((get) =>
385
- pipe(
443
+ Function.pipe(
386
444
  get(node),
387
445
  Option.map((node) => (node.data ? node.data + 1 : 1)),
388
446
  Option.filter((data) => data <= 5),
@@ -418,7 +476,7 @@ describe('GraphBuilder', () => {
418
476
  id: 'connector',
419
477
  connector: (node) =>
420
478
  Rx.make((get) =>
421
- pipe(
479
+ Function.pipe(
422
480
  get(node),
423
481
  Option.map((node) => (node.data ? node.data + 1 : 1)),
424
482
  Option.filter((data) => data <= 5),
@@ -4,14 +4,22 @@
4
4
 
5
5
  import { Registry, Rx } from '@effect-rx/rx-react';
6
6
  import { effect } from '@preact/signals-core';
7
- import { Array, type Option, pipe, Record } from 'effect';
7
+ import * as Array from 'effect/Array';
8
+ import * as Function from 'effect/Function';
9
+ import * as Option from 'effect/Option';
10
+ import * as Record from 'effect/Record';
8
11
 
9
- import { type MulticastObservable, type CleanupFn } from '@dxos/async';
12
+ import { type CleanupFn, type MulticastObservable, type Trigger } from '@dxos/async';
10
13
  import { log } from '@dxos/log';
11
- import { byPosition, getDebugName, isNode, isNonNullable, type MaybePromise, type Position } from '@dxos/util';
14
+ import { type MaybePromise, type Position, byPosition, getDebugName, isNode, isNonNullable } from '@dxos/util';
12
15
 
13
- import { ACTION_GROUP_TYPE, ACTION_TYPE, Graph, ROOT_ID, type GraphParams, type ExpandableGraph } from './graph';
14
- import { actionGroupSymbol, type ActionData, type Node, type NodeArg, type Relation } from './node';
16
+ import { ACTION_GROUP_TYPE, ACTION_TYPE, type ExpandableGraph, Graph, type GraphParams, ROOT_ID } from './graph';
17
+ import { type ActionData, type Node, type NodeArg, type Relation, actionGroupSymbol } from './node';
18
+
19
+ /**
20
+ * Graph builder extension for adding nodes to the graph based on a node id.
21
+ */
22
+ export type ResolverExtension = (id: string) => Rx.Rx<NodeArg<any> | null>;
15
23
 
16
24
  /**
17
25
  * Graph builder extension for adding nodes to the graph based on a connection to an existing node.
@@ -49,8 +57,7 @@ export type CreateExtensionOptions = {
49
57
  id: string;
50
58
  relation?: Relation;
51
59
  position?: Position;
52
- // TODO(wittjosiah): On initialize to restore state from cache.
53
- // resolver?: ResolverExtension;
60
+ resolver?: ResolverExtension;
54
61
  connector?: ConnectorExtension;
55
62
  actions?: ActionsExtension;
56
63
  actionGroups?: ActionGroupsExtension;
@@ -64,12 +71,16 @@ export const createExtension = (extension: CreateExtensionOptions): BuilderExten
64
71
  id,
65
72
  position = 'static',
66
73
  relation = 'outbound',
74
+ resolver: _resolver,
67
75
  connector: _connector,
68
76
  actions: _actions,
69
77
  actionGroups: _actionGroups,
70
78
  } = extension;
71
79
  const getId = (key: string) => `${id}/${key}`;
72
80
 
81
+ const resolver =
82
+ _resolver && Rx.family((id: string) => _resolver(id).pipe(Rx.withLabel(`graph-builder:_resolver:${id}`)));
83
+
73
84
  const connector =
74
85
  _connector &&
75
86
  Rx.family((node: Rx.Rx<Option.Option<Node>>) =>
@@ -87,7 +98,7 @@ export const createExtension = (extension: CreateExtensionOptions): BuilderExten
87
98
  Rx.family((node: Rx.Rx<Option.Option<Node>>) => _actions(node).pipe(Rx.withLabel(`graph-builder:_actions:${id}`)));
88
99
 
89
100
  return [
90
- // resolver ? { id: getId('resolver'), position, resolver } : undefined,
101
+ resolver ? { id: getId('resolver'), position, resolver } : undefined,
91
102
  connector
92
103
  ? ({
93
104
  id: getId('connector'),
@@ -157,7 +168,7 @@ export type BuilderExtension = Readonly<{
157
168
  id: string;
158
169
  position: Position;
159
170
  relation?: Relation; // Only for connector.
160
- // resolver?: ResolverExtension;
171
+ resolver?: ResolverExtension;
161
172
  connector?: (node: Rx.Rx<Option.Option<Node>>) => Rx.Rx<NodeArg<any>[]>;
162
173
  }>;
163
174
 
@@ -179,12 +190,12 @@ export const flattenExtensions = (extension: BuilderExtensions, acc: BuilderExte
179
190
  // Should track LRU nodes that are not in the set/radius and remove them beyond a certain threshold.
180
191
  export class GraphBuilder {
181
192
  // TODO(wittjosiah): Use Context.
182
- private readonly _connectorSubscriptions = new Map<string, CleanupFn>();
193
+ private readonly _subscriptions = new Map<string, CleanupFn>();
183
194
  private readonly _extensions = Rx.make(Record.empty<string, BuilderExtension>()).pipe(
184
195
  Rx.keepAlive,
185
196
  Rx.withLabel('graph-builder:extensions'),
186
197
  );
187
-
198
+ private readonly _initialized: Record<string, Trigger> = {};
188
199
  private readonly _registry: Registry.Registry;
189
200
  private readonly _graph: Graph;
190
201
 
@@ -194,7 +205,7 @@ export class GraphBuilder {
194
205
  ...params,
195
206
  registry: this._registry,
196
207
  onExpand: (id, relation) => this._onExpand(id, relation),
197
- // onInitialize: (id) => this._onInitialize(id),
208
+ onInitialize: (id) => this._onInitialize(id),
198
209
  onRemoveNode: (id) => this._onRemoveNode(id),
199
210
  });
200
211
  }
@@ -275,16 +286,31 @@ export class GraphBuilder {
275
286
  }
276
287
 
277
288
  destroy(): void {
278
- this._connectorSubscriptions.forEach((unsubscribe) => unsubscribe());
279
- this._connectorSubscriptions.clear();
289
+ this._subscriptions.forEach((unsubscribe) => unsubscribe());
290
+ this._subscriptions.clear();
280
291
  }
281
292
 
293
+ private readonly _resolvers = Rx.family<string, Rx.Rx<Option.Option<NodeArg<any>>>>((id) => {
294
+ return Rx.make((get) => {
295
+ return Function.pipe(
296
+ get(this._extensions),
297
+ Record.values,
298
+ Array.sortBy(byPosition),
299
+ Array.map(({ resolver }) => resolver),
300
+ Array.filter(isNonNullable),
301
+ Array.map((resolver) => get(resolver(id))),
302
+ Array.filter(isNonNullable),
303
+ Array.head,
304
+ );
305
+ });
306
+ });
307
+
282
308
  private readonly _connectors = Rx.family<string, Rx.Rx<NodeArg<any>[]>>((key) => {
283
309
  return Rx.make((get) => {
284
310
  const [id, relation] = key.split('+');
285
311
  const node = this._graph.node(id);
286
312
 
287
- return pipe(
313
+ return Function.pipe(
288
314
  get(this._extensions),
289
315
  Record.values,
290
316
  // TODO(wittjosiah): Sort on write rather than read.
@@ -341,17 +367,38 @@ export class GraphBuilder {
341
367
  { immediate: true },
342
368
  );
343
369
 
344
- this._connectorSubscriptions.set(id, cancel);
370
+ this._subscriptions.set(id, cancel);
345
371
  }
346
372
 
347
- // TODO(wittjosiah): On initialize to restore state from cache.
348
- // private async _onInitialize(id: string) {
349
- // log('onInitialize', { id });
350
- // }
373
+ // TODO(wittjosiah): If the same node is added by a connector, the resolver should probably cancel itself?
374
+ private async _onInitialize(id: string) {
375
+ log('onInitialize', { id });
376
+ const resolver = this._resolvers(id);
377
+
378
+ const cancel = this._registry.subscribe(
379
+ resolver,
380
+ (node) => {
381
+ const trigger = this._initialized[id];
382
+ Option.match(node, {
383
+ onSome: (node) => {
384
+ this._graph.addNodes([node]);
385
+ trigger?.wake();
386
+ },
387
+ onNone: () => {
388
+ trigger?.wake();
389
+ this._graph.removeNodes([id]);
390
+ },
391
+ });
392
+ },
393
+ { immediate: true },
394
+ );
395
+
396
+ this._subscriptions.set(id, cancel);
397
+ }
351
398
 
352
399
  private _onRemoveNode(id: string): void {
353
- this._connectorSubscriptions.get(id)?.();
354
- this._connectorSubscriptions.delete(id);
400
+ this._subscriptions.get(id)?.();
401
+ this._subscriptions.delete(id);
355
402
  }
356
403
  }
357
404
 
package/src/graph.test.ts CHANGED
@@ -3,10 +3,10 @@
3
3
  //
4
4
 
5
5
  import { Registry, Rx } from '@effect-rx/rx-react';
6
- import { Option } from 'effect';
6
+ import * as Option from 'effect/Option';
7
7
  import { assert, describe, expect, onTestFinished, test } from 'vitest';
8
8
 
9
- import { getGraph, Graph, ROOT_ID, ROOT_TYPE } from './graph';
9
+ import { Graph, ROOT_ID, ROOT_TYPE, getGraph } from './graph';
10
10
  import { type Node } from './node';
11
11
 
12
12
  const exampleId = (id: number) => `dx:test:${id}`;
package/src/graph.ts CHANGED
@@ -3,15 +3,17 @@
3
3
  //
4
4
 
5
5
  import { Registry, Rx } from '@effect-rx/rx-react';
6
- import { Option, pipe, Record } from 'effect';
6
+ import * as Function from 'effect/Function';
7
+ import * as Option from 'effect/Option';
8
+ import * as Record from 'effect/Record';
7
9
 
8
10
  import { Event, Trigger } from '@dxos/async';
9
11
  import { todo } from '@dxos/debug';
10
12
  import { invariant } from '@dxos/invariant';
11
13
  import { log } from '@dxos/log';
12
- import { isNonNullable, type MakeOptional } from '@dxos/util';
14
+ import { type MakeOptional, isNonNullable } from '@dxos/util';
13
15
 
14
- import { type NodeArg, type Node, type Relation, type Action, type ActionGroup } from './node';
16
+ import { type Action, type ActionGroup, type Node, type NodeArg, type Relation } from './node';
15
17
 
16
18
  const graphSymbol = Symbol('graph');
17
19
  type DeepWriteable<T> = { -readonly [K in keyof T]: T[K] extends object ? DeepWriteable<T[K]> : T[K] };
@@ -59,8 +61,7 @@ export type GraphParams = {
59
61
  nodes?: MakeOptional<Node, 'data' | 'cacheable'>[];
60
62
  edges?: Record<string, Edges>;
61
63
  onExpand?: Graph['_onExpand'];
62
- // TODO(wittjosiah): On initialize to restore state from cache.
63
- // onInitialize?: Graph['_onInitialize'];
64
+ onInitialize?: Graph['_onInitialize'];
64
65
  onRemoveNode?: Graph['_onRemoveNode'];
65
66
  };
66
67
 
@@ -166,7 +167,7 @@ export interface ExpandableGraph extends ReadableGraph {
166
167
  *
167
168
  * Fires the `onInitialize` callback to provide initial data for a node.
168
169
  */
169
- // initialize(id: string): Promise<void>;
170
+ initialize(id: string): Promise<void>;
170
171
 
171
172
  /**
172
173
  * Expand a node in the graph.
@@ -230,7 +231,7 @@ export class Graph implements WritableGraph {
230
231
  readonly onNodeChanged = new Event<{ id: string; node: Option.Option<Node> }>();
231
232
 
232
233
  private readonly _onExpand?: (id: string, relation: Relation) => void;
233
- // private readonly _onInitialize?: (id: string) => Promise<void>;
234
+ private readonly _onInitialize?: (id: string) => Promise<void>;
234
235
  private readonly _onRemoveNode?: (id: string) => void;
235
236
 
236
237
  private readonly _registry: Registry.Registry;
@@ -286,7 +287,7 @@ export class Graph implements WritableGraph {
286
287
  const toJSON = (node: Node, seen: string[] = []): any => {
287
288
  const nodes = get(this.connections(node.id));
288
289
  const obj: Record<string, any> = {
289
- id: node.id.length > 32 ? `${node.id.slice(0, 32)}...` : node.id,
290
+ id: node.id,
290
291
  type: node.type,
291
292
  };
292
293
  if (node.properties.label) {
@@ -309,8 +310,9 @@ export class Graph implements WritableGraph {
309
310
  }).pipe(Rx.withLabel(`graph:json:${id}`));
310
311
  });
311
312
 
312
- constructor({ registry, nodes, edges, onExpand, onRemoveNode }: GraphParams = {}) {
313
+ constructor({ registry, nodes, edges, onInitialize, onExpand, onRemoveNode }: GraphParams = {}) {
313
314
  this._registry = registry ?? Registry.make();
315
+ this._onInitialize = onInitialize;
314
316
  this._onExpand = onExpand;
315
317
  this._onRemoveNode = onRemoveNode;
316
318
 
@@ -379,15 +381,14 @@ export class Graph implements WritableGraph {
379
381
  return this._registry.get(this.edges(id));
380
382
  }
381
383
 
382
- // TODO(wittjosiah): On initialize to restore state from cache.
383
- // async initialize(id: string) {
384
- // const initialized = Record.get(this._initialized, id).pipe(Option.getOrElse(() => false));
385
- // log('initialize', { id, initialized });
386
- // if (!initialized) {
387
- // await this._onInitialize?.(id);
388
- // Record.set(this._initialized, id, true);
389
- // }
390
- // }
384
+ async initialize(id: string) {
385
+ const initialized = Record.get(this._initialized, id).pipe(Option.getOrElse(() => false));
386
+ log('initialize', { id, initialized });
387
+ if (!initialized) {
388
+ await this._onInitialize?.(id);
389
+ Record.set(this._initialized, id, true);
390
+ }
391
+ }
391
392
 
392
393
  expand(id: string, relation: Relation = 'outbound'): void {
393
394
  const key = `${id}$${relation}`;
@@ -554,7 +555,7 @@ export class Graph implements WritableGraph {
554
555
  }
555
556
 
556
557
  getPath({ source = 'root', target }: { source?: string; target: string }): Option.Option<string[]> {
557
- return pipe(
558
+ return Function.pipe(
558
559
  this.getNode(source),
559
560
  Option.flatMap((node) => {
560
561
  let found: Option.Option<string[]> = Option.none();
package/src/node.ts CHANGED
@@ -2,7 +2,9 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import { type MaybePromise, type MakeOptional } from '@dxos/util';
5
+ import { type MakeOptional, type MaybePromise } from '@dxos/util';
6
+
7
+ import { ACTION_GROUP_TYPE, ACTION_TYPE } from './graph';
6
8
 
7
9
  /**
8
10
  * Represents a node in the graph.
@@ -84,7 +86,7 @@ export type Action<TProperties extends Record<string, any> = Record<string, any>
84
86
  >;
85
87
 
86
88
  export const isAction = (data: unknown): data is Action =>
87
- isGraphNode(data) ? typeof data.data === 'function' : false;
89
+ isGraphNode(data) ? typeof data.data === 'function' && data.type === ACTION_TYPE : false;
88
90
 
89
91
  export const actionGroupSymbol = Symbol('ActionGroup');
90
92
 
@@ -95,7 +97,7 @@ export type ActionGroup<TProperties extends Record<string, any> = Record<string,
95
97
  >;
96
98
 
97
99
  export const isActionGroup = (data: unknown): data is ActionGroup =>
98
- isGraphNode(data) ? data.data === actionGroupSymbol : false;
100
+ isGraphNode(data) ? data.data === actionGroupSymbol && data.type === ACTION_GROUP_TYPE : false;
99
101
 
100
102
  export type ActionLike = Action | ActionGroup;
101
103
 
@@ -7,14 +7,14 @@ import { signal } from '@preact/signals-core';
7
7
  import { afterEach, beforeEach, describe, expect, onTestFinished, test } from 'vitest';
8
8
 
9
9
  import { Trigger } from '@dxos/async';
10
+ import { Obj, Type } from '@dxos/echo';
11
+ import { Ref } from '@dxos/echo/internal';
10
12
  import { Filter } from '@dxos/echo-db';
11
13
  import { EchoTestBuilder } from '@dxos/echo-db/testing';
12
- import { Expando, Ref } from '@dxos/echo-schema';
13
14
  import { registerSignalsRuntime } from '@dxos/echo-signals';
14
- import { live } from '@dxos/live-object';
15
15
 
16
16
  import { ROOT_ID } from './graph';
17
- import { createExtension, GraphBuilder, rxFromSignal } from './graph-builder';
17
+ import { GraphBuilder, createExtension, rxFromSignal } from './graph-builder';
18
18
  import { rxFromQuery } from './testing';
19
19
 
20
20
  registerSignalsRuntime();
@@ -174,15 +174,15 @@ describe('signals integration', () => {
174
174
  const registry = Registry.make();
175
175
  await using peer = await dbBuilder.createPeer();
176
176
  await using db = await peer.createDatabase();
177
- db.add(live(Expando, { name: 'a' }));
178
- db.add(live(Expando, { name: 'b' }));
177
+ db.add(Obj.make(Type.Expando, { name: 'a' }));
178
+ db.add(Obj.make(Type.Expando, { name: 'b' }));
179
179
 
180
180
  const builder = new GraphBuilder({ registry });
181
181
  builder.addExtension(
182
182
  createExtension({
183
183
  id: 'expando',
184
184
  connector: () => {
185
- const query = db.query(Filter.type(Expando));
185
+ const query = db.query(Filter.type(Type.Expando));
186
186
 
187
187
  return Rx.make((get) => {
188
188
  const objects = get(rxFromQuery(query));
@@ -205,7 +205,7 @@ describe('signals integration', () => {
205
205
  graph.expand(ROOT_ID);
206
206
  expect(count).to.eq(2);
207
207
 
208
- const object = db.add(live(Expando, { name: 'c' }));
208
+ const object = db.add(Obj.make(Type.Expando, { name: 'c' }));
209
209
  await db.flush();
210
210
  expect(count).to.eq(3);
211
211