@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.
- package/dist/lib/browser/{chunk-W47H2NND.mjs → chunk-3T75MQOS.mjs} +42 -166
- package/dist/lib/browser/chunk-3T75MQOS.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +1 -1
- package/dist/lib/browser/index.mjs.map +2 -2
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +1 -1
- package/dist/lib/node-esm/{chunk-LYZWNJ7J.mjs → chunk-UEXRLXMS.mjs} +42 -166
- package/dist/lib/node-esm/chunk-UEXRLXMS.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +1 -1
- package/dist/lib/node-esm/index.mjs.map +2 -2
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/testing/index.mjs +1 -1
- package/dist/types/src/atoms.d.ts.map +1 -1
- package/dist/types/src/graph-builder.d.ts +1 -1
- package/dist/types/src/graph-builder.d.ts.map +1 -1
- package/dist/types/src/graph.d.ts.map +1 -1
- package/dist/types/src/node-matcher.d.ts +2 -2
- package/dist/types/src/node-matcher.d.ts.map +1 -1
- package/dist/types/src/node.d.ts +2 -2
- package/dist/types/src/node.d.ts.map +1 -1
- package/dist/types/src/stories/EchoGraph.stories.d.ts.map +1 -1
- package/dist/types/src/testing/setup-graph-builder.d.ts.map +1 -1
- package/dist/types/src/util.d.ts +1 -0
- package/dist/types/src/util.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +19 -26
- package/src/graph-builder.test.ts +126 -0
- package/src/graph-builder.ts +18 -0
- package/src/node-matcher.ts +2 -2
- package/src/stories/EchoGraph.stories.tsx +2 -2
- package/src/util.ts +9 -3
- package/dist/lib/browser/chunk-W47H2NND.mjs.map +0 -7
- 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.
|
|
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.
|
|
44
|
-
"@dxos/debug": "0.8.4-main.
|
|
45
|
-
"@dxos/echo-atom": "0.8.4-main.
|
|
46
|
-
"@dxos/
|
|
47
|
-
"@dxos/
|
|
48
|
-
"@dxos/
|
|
49
|
-
"@dxos/
|
|
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": "^
|
|
60
|
-
"@dxos/random": "0.8.4-main.
|
|
61
|
-
"@dxos/echo-db": "0.8.4-main.
|
|
62
|
-
"@dxos/react-client": "0.8.4-main.
|
|
63
|
-
"@dxos/react-ui
|
|
64
|
-
"@dxos/
|
|
65
|
-
"@dxos/
|
|
66
|
-
"@dxos/
|
|
67
|
-
"@dxos/ui-
|
|
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.
|
|
76
|
-
"@dxos/ui-theme": "0.8.4-main.
|
|
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 });
|
package/src/graph-builder.ts
CHANGED
|
@@ -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 } => {
|
package/src/node-matcher.ts
CHANGED
|
@@ -201,10 +201,10 @@ export const whenAll: {
|
|
|
201
201
|
*
|
|
202
202
|
* @example
|
|
203
203
|
* ```ts
|
|
204
|
-
* // Match nodes that are either Sequences or
|
|
204
|
+
* // Match nodes that are either Sequences or Routines
|
|
205
205
|
* const whenInvocable = NodeMatcher.whenAny(
|
|
206
206
|
* NodeMatcher.whenEchoTypeMatches(Sequence),
|
|
207
|
-
* NodeMatcher.whenEchoTypeMatches(
|
|
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.
|
|
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.
|
|
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)
|
|
41
|
-
|
|
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
|
};
|