@dxos/app-graph 0.7.5-main.9d2a38b → 0.7.5-main.e9bb01b

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.7.5-main.9d2a38b",
3
+ "version": "0.7.5-main.e9bb01b",
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",
@@ -25,14 +25,14 @@
25
25
  "dependencies": {
26
26
  "@preact/signals-core": "^1.6.0",
27
27
  "main-thread-scheduling": "^14.1.1",
28
- "@dxos/debug": "0.7.5-main.9d2a38b",
29
- "@dxos/echo-schema": "0.7.5-main.9d2a38b",
30
- "@dxos/async": "0.7.5-main.9d2a38b",
31
- "@dxos/echo-signals": "0.7.5-main.9d2a38b",
32
- "@dxos/live-object": "0.7.5-main.9d2a38b",
33
- "@dxos/invariant": "0.7.5-main.9d2a38b",
34
- "@dxos/log": "0.7.5-main.9d2a38b",
35
- "@dxos/util": "0.7.5-main.9d2a38b"
28
+ "@dxos/async": "0.7.5-main.e9bb01b",
29
+ "@dxos/debug": "0.7.5-main.e9bb01b",
30
+ "@dxos/echo-schema": "0.7.5-main.e9bb01b",
31
+ "@dxos/echo-signals": "0.7.5-main.e9bb01b",
32
+ "@dxos/invariant": "0.7.5-main.e9bb01b",
33
+ "@dxos/log": "0.7.5-main.e9bb01b",
34
+ "@dxos/util": "0.7.5-main.e9bb01b",
35
+ "@dxos/live-object": "0.7.5-main.e9bb01b"
36
36
  },
37
37
  "devDependencies": {
38
38
  "@phosphor-icons/react": "^2.1.5",
@@ -41,18 +41,18 @@
41
41
  "react": "~18.2.0",
42
42
  "react-dom": "~18.2.0",
43
43
  "vite": "5.4.7",
44
- "@dxos/random": "0.7.5-main.9d2a38b",
45
- "@dxos/react-ui-theme": "0.7.5-main.9d2a38b",
46
- "@dxos/react-ui": "0.7.5-main.9d2a38b",
47
- "@dxos/react-client": "0.7.5-main.9d2a38b",
48
- "@dxos/storybook-utils": "0.7.5-main.9d2a38b"
44
+ "@dxos/random": "0.7.5-main.e9bb01b",
45
+ "@dxos/react-client": "0.7.5-main.e9bb01b",
46
+ "@dxos/react-ui": "0.7.5-main.e9bb01b",
47
+ "@dxos/react-ui-theme": "0.7.5-main.e9bb01b",
48
+ "@dxos/storybook-utils": "0.7.5-main.e9bb01b"
49
49
  },
50
50
  "peerDependencies": {
51
51
  "@phosphor-icons/react": "^2.1.5",
52
52
  "react": "~18.2.0",
53
53
  "react-dom": "~18.2.0",
54
- "@dxos/react-ui": "0.7.5-main.9d2a38b",
55
- "@dxos/react-ui-theme": "0.7.5-main.9d2a38b"
54
+ "@dxos/react-ui": "0.7.5-main.e9bb01b",
55
+ "@dxos/react-ui-theme": "0.7.5-main.e9bb01b"
56
56
  },
57
57
  "publishConfig": {
58
58
  "access": "public"
@@ -5,6 +5,8 @@
5
5
  import { batch, signal } from '@preact/signals-core';
6
6
  import { describe, expect, test } from 'vitest';
7
7
 
8
+ import { updateCounter } from '@dxos/echo-schema/testing';
9
+
8
10
  import { ACTION_TYPE, ROOT_ID, ROOT_TYPE } from './graph';
9
11
  import { GraphBuilder, createExtension, memoize } from './graph-builder';
10
12
  import { type Node } from './node';
@@ -175,6 +177,40 @@ describe('GraphBuilder', () => {
175
177
  expect(node.properties.label).to.equal('updated');
176
178
  });
177
179
 
180
+ test('updates with new extensions', async () => {
181
+ const name = signal('default');
182
+ const builder = new GraphBuilder();
183
+ builder.addExtension(
184
+ createExtension({
185
+ id: 'connector',
186
+ connector: () => [{ id: EXAMPLE_ID, type: EXAMPLE_TYPE, data: name, properties: { label: name.value } }],
187
+ }),
188
+ );
189
+ const graph = builder.graph;
190
+ await graph.expand(graph.root);
191
+
192
+ let nodes: Node[] = [];
193
+ using updates = updateCounter(() => {
194
+ nodes = graph.nodes(graph.root);
195
+ });
196
+
197
+ expect(updates.count).to.equal(0);
198
+ expect(nodes).has.length(1);
199
+ expect(nodes[0].id).to.equal(EXAMPLE_ID);
200
+
201
+ builder.addExtension(
202
+ createExtension({
203
+ id: 'connector-2',
204
+ connector: () => [{ id: exampleId(2), type: EXAMPLE_TYPE, data: 0 }],
205
+ }),
206
+ );
207
+
208
+ expect(updates.count).to.equal(1);
209
+ expect(nodes).has.length(2);
210
+ expect(nodes[0].id).to.equal(EXAMPLE_ID);
211
+ expect(nodes[1].id).to.equal(exampleId(2));
212
+ });
213
+
178
214
  test('removes', async () => {
179
215
  const nodes = signal([
180
216
  { id: exampleId(1), type: EXAMPLE_TYPE, data: 1 },
@@ -2,13 +2,13 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import { effect, type Signal, signal } from '@preact/signals-core';
5
+ import { effect, type Signal, signal, untracked } from '@preact/signals-core';
6
6
 
7
7
  import { Trigger, type UnsubscribeCallback } from '@dxos/async';
8
8
  import { invariant } from '@dxos/invariant';
9
9
  import { create } from '@dxos/live-object';
10
10
  import { log } from '@dxos/log';
11
- import { byDisposition, type Disposition, isNode, type MaybePromise, nonNullable } from '@dxos/util';
11
+ import { byPosition, type Position, isNode, type MaybePromise, nonNullable } from '@dxos/util';
12
12
 
13
13
  import { ACTION_GROUP_TYPE, ACTION_TYPE, Graph, ROOT_ID, type GraphParams } from './graph';
14
14
  import { type ActionData, actionGroupSymbol, type Node, type NodeArg, type Relation } from './node';
@@ -63,7 +63,7 @@ export type CreateExtensionOptions<T = any> = {
63
63
  id: string;
64
64
  relation?: Relation;
65
65
  type?: string;
66
- disposition?: Disposition;
66
+ position?: Position;
67
67
  filter?: (node: Node) => node is Node<T>;
68
68
  resolver?: ResolverExtension;
69
69
  connector?: ConnectorExtension<GuardedNodeType<CreateExtensionOptions<T>['filter']>>;
@@ -75,16 +75,16 @@ export type CreateExtensionOptions<T = any> = {
75
75
  * Create a graph builder extension.
76
76
  */
77
77
  export const createExtension = <T = any>(extension: CreateExtensionOptions<T>): BuilderExtension[] => {
78
- const { id, disposition = 'static', resolver, connector, actions, actionGroups, ...rest } = extension;
78
+ const { id, position = 'static', resolver, connector, actions, actionGroups, ...rest } = extension;
79
79
  const getId = (key: string) => `${id}/${key}`;
80
80
  return [
81
- resolver ? { id: getId('resolver'), disposition, resolver } : undefined,
82
- connector ? { ...rest, id: getId('connector'), disposition, connector } : undefined,
81
+ resolver ? { id: getId('resolver'), position, resolver } : undefined,
82
+ connector ? { ...rest, id: getId('connector'), position, connector } : undefined,
83
83
  actionGroups
84
84
  ? ({
85
85
  ...rest,
86
86
  id: getId('actionGroups'),
87
- disposition,
87
+ position,
88
88
  type: ACTION_GROUP_TYPE,
89
89
  relation: 'outbound',
90
90
  connector: ({ node }) =>
@@ -95,7 +95,7 @@ export const createExtension = <T = any>(extension: CreateExtensionOptions<T>):
95
95
  ? ({
96
96
  ...rest,
97
97
  id: getId('actions'),
98
- disposition,
98
+ position,
99
99
  type: ACTION_TYPE,
100
100
  relation: 'outbound',
101
101
  connector: ({ node }) => actions({ node })?.map((arg) => ({ ...arg, type: ACTION_TYPE })),
@@ -174,7 +174,7 @@ export const toSignal = <T>(
174
174
 
175
175
  export type BuilderExtension = Readonly<{
176
176
  id: string;
177
- disposition: Disposition;
177
+ position: Position;
178
178
  resolver?: ResolverExtension;
179
179
  connector?: ConnectorExtension;
180
180
  // Only for connector.
@@ -185,6 +185,14 @@ export type BuilderExtension = Readonly<{
185
185
 
186
186
  type ExtensionArg = BuilderExtension | BuilderExtension[] | ExtensionArg[];
187
187
 
188
+ export const flattenExtensions = (extension: ExtensionArg, acc: BuilderExtension[] = []): BuilderExtension[] => {
189
+ if (Array.isArray(extension)) {
190
+ return [...acc, ...extension.flatMap((ext) => flattenExtensions(ext, acc))];
191
+ } else {
192
+ return [...acc, extension];
193
+ }
194
+ };
195
+
188
196
  /**
189
197
  * The builder provides an extensible way to compose the construction of the graph.
190
198
  */
@@ -244,17 +252,24 @@ export class GraphBuilder {
244
252
  return this._graph;
245
253
  }
246
254
 
255
+ /**
256
+ * @reactive
257
+ */
258
+ get extensions() {
259
+ return Object.values(this._extensions);
260
+ }
261
+
247
262
  /**
248
263
  * Register a node builder which will be called in order to construct the graph.
249
264
  */
250
265
  addExtension(extension: ExtensionArg): GraphBuilder {
251
- if (Array.isArray(extension)) {
252
- extension.forEach((ext) => this.addExtension(ext));
253
- return this;
254
- }
255
-
256
- this._dispatcher.state[extension.id] = [];
257
- this._extensions[extension.id] = extension;
266
+ const extensions = flattenExtensions(extension);
267
+ untracked(() => {
268
+ extensions.forEach((extension) => {
269
+ this._dispatcher.state[extension.id] = [];
270
+ this._extensions[extension.id] = extension;
271
+ });
272
+ });
258
273
  return this;
259
274
  }
260
275
 
@@ -262,7 +277,9 @@ export class GraphBuilder {
262
277
  * Remove a node builder from the graph builder.
263
278
  */
264
279
  removeExtension(id: string): GraphBuilder {
265
- delete this._extensions[id];
280
+ untracked(() => {
281
+ delete this._extensions[id];
282
+ });
266
283
  return this;
267
284
  }
268
285
 
@@ -326,7 +343,7 @@ export class GraphBuilder {
326
343
  this._resolverSubscriptions.set(
327
344
  nodeId,
328
345
  effect(() => {
329
- const extensions = Object.values(this._extensions).toSorted(byDisposition);
346
+ const extensions = Object.values(this._extensions).toSorted(byPosition);
330
347
  for (const { id, resolver } of extensions) {
331
348
  if (!resolver) {
332
349
  continue;
@@ -384,7 +401,7 @@ export class GraphBuilder {
384
401
 
385
402
  // TODO(wittjosiah): Consider allowing extensions to collaborate on the same node by merging their results.
386
403
  const nodes: NodeArg<any>[] = [];
387
- const extensions = Object.values(this._extensions).toSorted(byDisposition);
404
+ const extensions = Object.values(this._extensions).toSorted(byPosition);
388
405
  for (const { id, connector, filter, type, relation = 'outbound' } of extensions) {
389
406
  if (
390
407
  !connector ||