@dxos/app-graph 0.8.4-main.9be5663bfe → 0.8.4-main.abd8ff62ef

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.
Files changed (33) hide show
  1. package/dist/lib/browser/{chunk-W47H2NND.mjs → chunk-3T75MQOS.mjs} +42 -166
  2. package/dist/lib/browser/chunk-3T75MQOS.mjs.map +7 -0
  3. package/dist/lib/browser/index.mjs +1 -1
  4. package/dist/lib/browser/index.mjs.map +2 -2
  5. package/dist/lib/browser/meta.json +1 -1
  6. package/dist/lib/browser/testing/index.mjs +1 -1
  7. package/dist/lib/node-esm/{chunk-LYZWNJ7J.mjs → chunk-UEXRLXMS.mjs} +42 -166
  8. package/dist/lib/node-esm/chunk-UEXRLXMS.mjs.map +7 -0
  9. package/dist/lib/node-esm/index.mjs +1 -1
  10. package/dist/lib/node-esm/index.mjs.map +2 -2
  11. package/dist/lib/node-esm/meta.json +1 -1
  12. package/dist/lib/node-esm/testing/index.mjs +1 -1
  13. package/dist/types/src/atoms.d.ts.map +1 -1
  14. package/dist/types/src/graph-builder.d.ts +1 -1
  15. package/dist/types/src/graph-builder.d.ts.map +1 -1
  16. package/dist/types/src/graph.d.ts.map +1 -1
  17. package/dist/types/src/node-matcher.d.ts +2 -2
  18. package/dist/types/src/node-matcher.d.ts.map +1 -1
  19. package/dist/types/src/node.d.ts +2 -2
  20. package/dist/types/src/node.d.ts.map +1 -1
  21. package/dist/types/src/stories/EchoGraph.stories.d.ts.map +1 -1
  22. package/dist/types/src/testing/setup-graph-builder.d.ts.map +1 -1
  23. package/dist/types/src/util.d.ts +1 -0
  24. package/dist/types/src/util.d.ts.map +1 -1
  25. package/dist/types/tsconfig.tsbuildinfo +1 -1
  26. package/package.json +19 -26
  27. package/src/graph-builder.test.ts +126 -0
  28. package/src/graph-builder.ts +18 -0
  29. package/src/node-matcher.ts +2 -2
  30. package/src/stories/EchoGraph.stories.tsx +2 -2
  31. package/src/util.ts +9 -3
  32. package/dist/lib/browser/chunk-W47H2NND.mjs.map +0 -7
  33. package/dist/lib/node-esm/chunk-LYZWNJ7J.mjs.map +0 -7
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/app-graph",
3
- "version": "0.8.4-main.9be5663bfe",
3
+ "version": "0.8.4-main.abd8ff62ef",
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",
@@ -27,26 +27,19 @@
27
27
  }
28
28
  },
29
29
  "types": "dist/types/src/index.d.ts",
30
- "typesVersions": {
31
- "*": {
32
- "testing": [
33
- "./dist/types/src/testing/index.d.ts"
34
- ]
35
- }
36
- },
37
30
  "files": [
38
31
  "dist",
39
32
  "src"
40
33
  ],
41
34
  "dependencies": {
42
35
  "main-thread-scheduling": "^14.1.1",
43
- "@dxos/async": "0.8.4-main.9be5663bfe",
44
- "@dxos/debug": "0.8.4-main.9be5663bfe",
45
- "@dxos/echo-atom": "0.8.4-main.9be5663bfe",
46
- "@dxos/invariant": "0.8.4-main.9be5663bfe",
47
- "@dxos/echo": "0.8.4-main.9be5663bfe",
48
- "@dxos/util": "0.8.4-main.9be5663bfe",
49
- "@dxos/log": "0.8.4-main.9be5663bfe"
36
+ "@dxos/async": "0.8.4-main.abd8ff62ef",
37
+ "@dxos/debug": "0.8.4-main.abd8ff62ef",
38
+ "@dxos/echo-atom": "0.8.4-main.abd8ff62ef",
39
+ "@dxos/echo": "0.8.4-main.abd8ff62ef",
40
+ "@dxos/log": "0.8.4-main.abd8ff62ef",
41
+ "@dxos/invariant": "0.8.4-main.abd8ff62ef",
42
+ "@dxos/util": "0.8.4-main.abd8ff62ef"
50
43
  },
51
44
  "devDependencies": {
52
45
  "@effect-atom/atom-react": "^0.5.0",
@@ -56,15 +49,15 @@
56
49
  "effect": "3.20.0",
57
50
  "react": "~19.2.3",
58
51
  "react-dom": "~19.2.3",
59
- "vite": "^7.1.11",
60
- "@dxos/random": "0.8.4-main.9be5663bfe",
61
- "@dxos/echo-db": "0.8.4-main.9be5663bfe",
62
- "@dxos/react-client": "0.8.4-main.9be5663bfe",
63
- "@dxos/react-ui-list": "0.8.4-main.9be5663bfe",
64
- "@dxos/storybook-utils": "0.8.4-main.9be5663bfe",
65
- "@dxos/react-ui-tabs": "0.8.4-main.9be5663bfe",
66
- "@dxos/react-ui": "0.8.4-main.9be5663bfe",
67
- "@dxos/ui-theme": "0.8.4-main.9be5663bfe"
52
+ "vite": "^8.0.10",
53
+ "@dxos/random": "0.8.4-main.abd8ff62ef",
54
+ "@dxos/echo-db": "0.8.4-main.abd8ff62ef",
55
+ "@dxos/react-client": "0.8.4-main.abd8ff62ef",
56
+ "@dxos/react-ui": "0.8.4-main.abd8ff62ef",
57
+ "@dxos/react-ui-list": "0.8.4-main.abd8ff62ef",
58
+ "@dxos/ui-theme": "0.8.4-main.abd8ff62ef",
59
+ "@dxos/storybook-utils": "0.8.4-main.abd8ff62ef",
60
+ "@dxos/react-ui-tabs": "0.8.4-main.abd8ff62ef"
68
61
  },
69
62
  "peerDependencies": {
70
63
  "@effect-atom/atom-react": "^0.5.0",
@@ -72,8 +65,8 @@
72
65
  "effect": "3.20.0",
73
66
  "react": "~19.2.3",
74
67
  "react-dom": "~19.2.3",
75
- "@dxos/react-ui": "0.8.4-main.9be5663bfe",
76
- "@dxos/ui-theme": "0.8.4-main.9be5663bfe"
68
+ "@dxos/react-ui": "0.8.4-main.abd8ff62ef",
69
+ "@dxos/ui-theme": "0.8.4-main.abd8ff62ef"
77
70
  },
78
71
  "publishConfig": {
79
72
  "access": "public"
@@ -485,6 +485,132 @@ describe('GraphBuilder', () => {
485
485
  expect(exists).to.be.true;
486
486
  });
487
487
 
488
+ describe('inline nodes', () => {
489
+ const parent = (child?: Node.NodeArg<any>): Node.NodeArg<any> => ({
490
+ id: 'parent-node',
491
+ type: EXAMPLE_TYPE,
492
+ data: null,
493
+ nodes: child ? [child] : [],
494
+ });
495
+
496
+ const inlineChild = (overrides?: Partial<Node.NodeArg<any>>): Node.NodeArg<any> => ({
497
+ id: 'inline-child',
498
+ type: EXAMPLE_TYPE,
499
+ data: null,
500
+ ...overrides,
501
+ });
502
+
503
+ const makeGraph = () => {
504
+ const registry = Registry.make();
505
+ const builder = GraphBuilder.make({ registry });
506
+ const nodesAtom = Atom.make<Node.NodeArg<any>[]>([]);
507
+ GraphBuilder.addExtension(
508
+ builder,
509
+ GraphBuilder.createExtensionRaw({
510
+ id: 'inline-connector',
511
+ connector: () => Atom.make((get) => get(nodesAtom)),
512
+ }),
513
+ );
514
+ const graph = builder.graph;
515
+ Graph.expand(graph, Node.RootId, 'child');
516
+ return { registry, builder, graph, nodesAtom };
517
+ };
518
+
519
+ const getInlineChild = (graph: Graph.ExpandableGraph) =>
520
+ Graph.getNode(graph, 'root/parent-node/inline-child').pipe(Option.getOrNull);
521
+
522
+ test('are removed when connector re-runs with data change', async () => {
523
+ const { registry, builder, graph, nodesAtom } = makeGraph();
524
+ registry.set(nodesAtom, [parent(inlineChild())]);
525
+ await GraphBuilder.flush(builder);
526
+
527
+ expect(getInlineChild(graph)).to.not.be.null;
528
+
529
+ // Remove inline child while also changing parent data to trigger the update.
530
+ registry.set(nodesAtom, [{ ...parent(), data: 'v2' }]);
531
+ await GraphBuilder.flush(builder);
532
+
533
+ expect(getInlineChild(graph)).to.be.null;
534
+ });
535
+
536
+ test('are removed when only inline children change', async () => {
537
+ const { registry, builder, graph, nodesAtom } = makeGraph();
538
+ registry.set(nodesAtom, [parent(inlineChild())]);
539
+ await GraphBuilder.flush(builder);
540
+
541
+ expect(getInlineChild(graph)).to.not.be.null;
542
+
543
+ // Remove inline child without touching parent — tests change detection covers inline children.
544
+ registry.set(nodesAtom, [parent()]);
545
+ await GraphBuilder.flush(builder);
546
+
547
+ expect(getInlineChild(graph)).to.be.null;
548
+ });
549
+
550
+ test('are added when connector re-runs', async () => {
551
+ const { registry, builder, graph, nodesAtom } = makeGraph();
552
+ registry.set(nodesAtom, [parent()]);
553
+ await GraphBuilder.flush(builder);
554
+
555
+ expect(getInlineChild(graph)).to.be.null;
556
+
557
+ registry.set(nodesAtom, [parent(inlineChild())]);
558
+ await GraphBuilder.flush(builder);
559
+
560
+ expect(getInlineChild(graph)).to.not.be.null;
561
+ });
562
+
563
+ test('reactively update data', async ({ expect }) => {
564
+ const { registry, builder, graph, nodesAtom } = makeGraph();
565
+ registry.set(nodesAtom, [parent(inlineChild({ data: 'v1' }))]);
566
+ await GraphBuilder.flush(builder);
567
+
568
+ expect(getInlineChild(graph)?.data).to.equal('v1');
569
+
570
+ // Change only the inline child's data — parent is unchanged.
571
+ registry.set(nodesAtom, [parent(inlineChild({ data: 'v2' }))]);
572
+ await GraphBuilder.flush(builder);
573
+
574
+ expect(getInlineChild(graph)?.data).to.equal('v2');
575
+ });
576
+
577
+ test('reactively update properties', async ({ expect }) => {
578
+ const { registry, builder, graph, nodesAtom } = makeGraph();
579
+ registry.set(nodesAtom, [parent(inlineChild({ properties: { label: 'before' } }))]);
580
+ await GraphBuilder.flush(builder);
581
+
582
+ expect(getInlineChild(graph)?.properties.label).to.equal('before');
583
+
584
+ registry.set(nodesAtom, [parent(inlineChild({ properties: { label: 'after' } }))]);
585
+ await GraphBuilder.flush(builder);
586
+
587
+ expect(getInlineChild(graph)?.properties.label).to.equal('after');
588
+ });
589
+
590
+ test('deeply nested inline nodes reactively update', async ({ expect }) => {
591
+ const { registry, builder, graph, nodesAtom } = makeGraph();
592
+
593
+ const withGrandchild = (data: string) =>
594
+ parent({
595
+ id: 'child',
596
+ type: EXAMPLE_TYPE,
597
+ data: null,
598
+ nodes: [{ id: 'grandchild', type: EXAMPLE_TYPE, data }],
599
+ });
600
+
601
+ registry.set(nodesAtom, [withGrandchild('v1')]);
602
+ await GraphBuilder.flush(builder);
603
+
604
+ expect(Graph.getNode(graph, 'root/parent-node/child/grandchild').pipe(Option.getOrNull)?.data).to.equal('v1');
605
+
606
+ // Change only the grandchild's data — all ancestors unchanged.
607
+ registry.set(nodesAtom, [withGrandchild('v2')]);
608
+ await GraphBuilder.flush(builder);
609
+
610
+ expect(Graph.getNode(graph, 'root/parent-node/child/grandchild').pipe(Option.getOrNull)?.data).to.equal('v2');
611
+ });
612
+ });
613
+
488
614
  test('sort edges', async () => {
489
615
  const registry = Registry.make();
490
616
  const builder = GraphBuilder.make({ registry });
@@ -125,6 +125,8 @@ class GraphBuilderImpl implements GraphBuilder {
125
125
  >();
126
126
  /** Last-flushed node IDs per connector key, used for edge removal on update. */
127
127
  readonly _connectorPrevious = new Map<string, string[]>();
128
+ /** All inline-descendant IDs per connector key, used to remove stale inline nodes on update. */
129
+ readonly _connectorPreviousInlineIds = new Map<string, string[]>();
128
130
  /** Last-flushed node args per connector key, used for change detection. */
129
131
  readonly _connectorPreviousArgs = new Map<string, Node.NodeArg<any>[]>();
130
132
  /** Whether a dirty-flush task is already scheduled. */
@@ -178,6 +180,12 @@ class GraphBuilderImpl implements GraphBuilder {
178
180
  this._connectorPrevious.set(key, ids);
179
181
  this._connectorPreviousArgs.set(key, nodes);
180
182
 
183
+ const currentInlineIds = collectAllInlineIds(nodes);
184
+ const previousInlineIds = this._connectorPreviousInlineIds.get(key) ?? [];
185
+ const staleInlineIds = previousInlineIds.filter((pid) => !currentInlineIds.includes(pid));
186
+ this._connectorPreviousInlineIds.set(key, currentInlineIds);
187
+
188
+ Graph.removeNodes(this._graph, staleInlineIds, true);
181
189
  Graph.removeEdges(
182
190
  this._graph,
183
191
  removed.map((target) => ({ source: id, target, relation })),
@@ -841,6 +849,16 @@ const qualifyNodeArgs =
841
849
  };
842
850
  });
843
851
 
852
+ /**
853
+ * Recursively collect all inline-descendant IDs (the `nodes` arrays at every level)
854
+ * from a list of top-level NodeArgs. Top-level IDs are excluded because they are
855
+ * already tracked via `_connectorPrevious`.
856
+ */
857
+ const collectAllInlineIds = (nodes: Node.NodeArg<any>[]): string[] =>
858
+ nodes.flatMap((node) =>
859
+ node.nodes ? [...node.nodes.map((child) => child.id), ...collectAllInlineIds(node.nodes)] : [],
860
+ );
861
+
844
862
  const connectorKey = (id: string, relation: Node.RelationInput): string => primaryKey(id, Graph.relationKey(relation));
845
863
 
846
864
  const relationFromConnectorKey = (key: string): { id: string; relation: Node.Relation } => {
@@ -201,10 +201,10 @@ export const whenAll: {
201
201
  *
202
202
  * @example
203
203
  * ```ts
204
- * // Match nodes that are either Sequences or Prompts
204
+ * // Match nodes that are either Sequences or Routines
205
205
  * const whenInvocable = NodeMatcher.whenAny(
206
206
  * NodeMatcher.whenEchoTypeMatches(Sequence),
207
- * NodeMatcher.whenEchoTypeMatches(Prompt.Prompt),
207
+ * NodeMatcher.whenEchoTypeMatches(Routine.Routine),
208
208
  * );
209
209
  * ```
210
210
  */
@@ -145,7 +145,7 @@ const runAction = async (client: Client, action: Action) => {
145
145
  case Action.RENAME_SPACE: {
146
146
  const space = getRandomSpace(client);
147
147
  if (space) {
148
- Obj.change(space.properties, (obj) => {
148
+ Obj.update(space.properties, (obj) => {
149
149
  obj.name = random.commerce.productName();
150
150
  });
151
151
  }
@@ -175,7 +175,7 @@ const runAction = async (client: Client, action: Action) => {
175
175
  if (space) {
176
176
  const objects = await space.db.query(Filter.type(TestSchema.Expando, { type: 'test' })).run();
177
177
  const object = objects[Math.floor(Math.random() * objects.length)];
178
- Obj.change(object, (object) => {
178
+ Obj.update(object, (object) => {
179
179
  object.name = random.commerce.productName();
180
180
  });
181
181
  }
package/src/util.ts CHANGED
@@ -37,8 +37,12 @@ export const normalizeRelation = (relation?: Node.RelationInput): Node.Relation
37
37
  * Shallow-compare two values: same reference, or same own-keys with === values.
38
38
  */
39
39
  export const shallowEqual = (a: unknown, b: unknown): boolean => {
40
- if (a === b) return true;
41
- if (a == null || b == null || typeof a !== 'object' || typeof b !== 'object') return false;
40
+ if (a === b) {
41
+ return true;
42
+ }
43
+ if (a == null || b == null || typeof a !== 'object' || typeof b !== 'object') {
44
+ return false;
45
+ }
42
46
  const keysA = Object.keys(a as Record<string, unknown>);
43
47
  const keysB = Object.keys(b as Record<string, unknown>);
44
48
  if (keysA.length !== keysB.length) {
@@ -49,6 +53,7 @@ export const shallowEqual = (a: unknown, b: unknown): boolean => {
49
53
 
50
54
  /**
51
55
  * Returns true if two NodeArg arrays are semantically identical (same id, type, data, properties per index).
56
+ * Inline child nodes (the `nodes` field) are compared recursively.
52
57
  */
53
58
  export const nodeArgsUnchanged = (prev: Node.NodeArg<any>[], next: Node.NodeArg<any>[]): boolean => {
54
59
  if (prev.length !== next.length) {
@@ -61,7 +66,8 @@ export const nodeArgsUnchanged = (prev: Node.NodeArg<any>[], next: Node.NodeArg<
61
66
  prevNode.id === nextNode.id &&
62
67
  prevNode.type === nextNode.type &&
63
68
  shallowEqual(prevNode.data, nextNode.data) &&
64
- shallowEqual(prevNode.properties, nextNode.properties)
69
+ shallowEqual(prevNode.properties, nextNode.properties) &&
70
+ nodeArgsUnchanged(prevNode.nodes ?? [], nextNode.nodes ?? [])
65
71
  );
66
72
  });
67
73
  };