@nebutra/graph-model 0.2.2 → 0.2.3
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 +14 -4
- package/.turbo/turbo-build.log +0 -17
- package/.turbo/turbo-test.log +0 -14
- package/.turbo/turbo-typecheck.log +0 -4
- package/src/__tests__/graph-model.test.ts +0 -78
- package/src/index.ts +0 -69
- package/tsconfig.json +0 -10
- package/tsup.config.ts +0 -10
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nebutra/graph-model",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "Neutral structural DAG contract: generic GraphNode/GraphEdge/Graph types + pure cycle/inbound algorithms. Depended on by reel and the ui node-graph editor; depends on neither.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"license": "MIT",
|
|
@@ -10,10 +10,14 @@
|
|
|
10
10
|
"category": "platform",
|
|
11
11
|
"summary": "Generic acyclic graph types + pure traversal algorithms"
|
|
12
12
|
},
|
|
13
|
-
"main": "./
|
|
14
|
-
"types": "./
|
|
13
|
+
"main": "./dist/index.js",
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
15
|
"exports": {
|
|
16
|
-
".":
|
|
16
|
+
".": {
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"import": "./dist/index.js",
|
|
19
|
+
"default": "./dist/index.js"
|
|
20
|
+
}
|
|
17
21
|
},
|
|
18
22
|
"dependencies": {},
|
|
19
23
|
"devDependencies": {
|
|
@@ -34,6 +38,12 @@
|
|
|
34
38
|
"publishConfig": {
|
|
35
39
|
"access": "public"
|
|
36
40
|
},
|
|
41
|
+
"files": [
|
|
42
|
+
"dist",
|
|
43
|
+
"README.md",
|
|
44
|
+
"LICENSE",
|
|
45
|
+
"CHANGELOG.md"
|
|
46
|
+
],
|
|
37
47
|
"scripts": {
|
|
38
48
|
"build": "tsup",
|
|
39
49
|
"test": "vitest run",
|
package/.turbo/turbo-build.log
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
> @nebutra/graph-model@0.2.2 build /Users/tseka_luk/Documents/Nebutra-SaaS-Lab/Nebutra-Sailor/packages/platform/graph-model
|
|
3
|
-
> tsup
|
|
4
|
-
|
|
5
|
-
CLI Building entry: src/index.ts
|
|
6
|
-
CLI Using tsconfig: tsconfig.json
|
|
7
|
-
CLI tsup v8.5.1
|
|
8
|
-
CLI Using tsup config: /Users/tseka_luk/Documents/Nebutra-SaaS-Lab/Nebutra-Sailor/packages/platform/graph-model/tsup.config.ts
|
|
9
|
-
CLI Target: es2022
|
|
10
|
-
CLI Cleaning output folder
|
|
11
|
-
ESM Build start
|
|
12
|
-
ESM dist/index.js 911.00 B
|
|
13
|
-
ESM dist/index.js.map 3.09 KB
|
|
14
|
-
ESM ⚡️ Build success in 20ms
|
|
15
|
-
DTS Build start
|
|
16
|
-
DTS ⚡️ Build success in 2781ms
|
|
17
|
-
DTS dist/index.d.ts 1.65 KB
|
package/.turbo/turbo-test.log
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
> @nebutra/graph-model@0.2.1 test /Users/tseka_luk/Documents/Nebutra-SaaS-Lab/Nebutra-Sailor/packages/platform/graph-model
|
|
3
|
-
> vitest run
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
RUN v4.1.4 /Users/tseka_luk/Documents/Nebutra-SaaS-Lab/Nebutra-Sailor/packages/platform/graph-model
|
|
7
|
-
|
|
8
|
-
✓ src/__tests__/graph-model.test.ts (10 tests) 6ms
|
|
9
|
-
|
|
10
|
-
Test Files 1 passed (1)
|
|
11
|
-
Tests 10 passed (10)
|
|
12
|
-
Start at 09:38:27
|
|
13
|
-
Duration 205ms (transform 31ms, setup 0ms, import 42ms, tests 6ms, environment 0ms)
|
|
14
|
-
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
import { hasCycleFrom, inboundEdges, wouldCreateCycle } from "../index";
|
|
3
|
-
|
|
4
|
-
const edges = [
|
|
5
|
-
{ from: "a", to: "b" },
|
|
6
|
-
{ from: "b", to: "c" },
|
|
7
|
-
{ from: "a", to: "c" },
|
|
8
|
-
];
|
|
9
|
-
|
|
10
|
-
describe("inboundEdges", () => {
|
|
11
|
-
it("returns only edges whose target is the node", () => {
|
|
12
|
-
expect(inboundEdges(edges, "c")).toEqual([
|
|
13
|
-
{ from: "b", to: "c" },
|
|
14
|
-
{ from: "a", to: "c" },
|
|
15
|
-
]);
|
|
16
|
-
});
|
|
17
|
-
it("returns empty for a source-only node", () => {
|
|
18
|
-
expect(inboundEdges(edges, "a")).toEqual([]);
|
|
19
|
-
});
|
|
20
|
-
it("preserves the concrete edge subtype", () => {
|
|
21
|
-
const typed = [{ from: "a", to: "b", inputType: "ctx" }];
|
|
22
|
-
expect(inboundEdges(typed, "b")[0]?.inputType).toBe("ctx");
|
|
23
|
-
});
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
describe("hasCycleFrom", () => {
|
|
27
|
-
it("is false for an acyclic graph", () => {
|
|
28
|
-
expect(hasCycleFrom(edges, "a")).toBe(false);
|
|
29
|
-
});
|
|
30
|
-
it("detects a direct cycle", () => {
|
|
31
|
-
expect(
|
|
32
|
-
hasCycleFrom(
|
|
33
|
-
[
|
|
34
|
-
{ from: "a", to: "b" },
|
|
35
|
-
{ from: "b", to: "a" },
|
|
36
|
-
],
|
|
37
|
-
"a",
|
|
38
|
-
),
|
|
39
|
-
).toBe(true);
|
|
40
|
-
});
|
|
41
|
-
it("detects an indirect cycle", () => {
|
|
42
|
-
expect(
|
|
43
|
-
hasCycleFrom(
|
|
44
|
-
[
|
|
45
|
-
{ from: "a", to: "b" },
|
|
46
|
-
{ from: "b", to: "c" },
|
|
47
|
-
{ from: "c", to: "a" },
|
|
48
|
-
],
|
|
49
|
-
"a",
|
|
50
|
-
),
|
|
51
|
-
).toBe(true);
|
|
52
|
-
});
|
|
53
|
-
it("is false starting from a node not on the cycle", () => {
|
|
54
|
-
expect(
|
|
55
|
-
hasCycleFrom(
|
|
56
|
-
[
|
|
57
|
-
{ from: "b", to: "c" },
|
|
58
|
-
{ from: "c", to: "b" },
|
|
59
|
-
],
|
|
60
|
-
"a",
|
|
61
|
-
),
|
|
62
|
-
).toBe(false);
|
|
63
|
-
});
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
describe("wouldCreateCycle", () => {
|
|
67
|
-
it("rejects an edge that closes a loop", () => {
|
|
68
|
-
expect(wouldCreateCycle(edges, "c", "a")).toBe(true);
|
|
69
|
-
});
|
|
70
|
-
it("accepts an edge that keeps the graph acyclic", () => {
|
|
71
|
-
expect(wouldCreateCycle(edges, "c", "d")).toBe(false);
|
|
72
|
-
});
|
|
73
|
-
it("does not mutate the input edges array", () => {
|
|
74
|
-
const input = [{ from: "a", to: "b" }];
|
|
75
|
-
wouldCreateCycle(input, "b", "a");
|
|
76
|
-
expect(input).toEqual([{ from: "a", to: "b" }]);
|
|
77
|
-
});
|
|
78
|
-
});
|
package/src/index.ts
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @nebutra/graph-model — neutral structural DAG contract.
|
|
3
|
-
*
|
|
4
|
-
* The minimal shape every node-graph feature shares: positioned nodes, plain
|
|
5
|
-
* directed edges, and the two pure traversals (`inboundEdges`, `hasCycleFrom`)
|
|
6
|
-
* plus the `wouldCreateCycle` guard used by interactive editors. Domain
|
|
7
|
-
* packages (`@nebutra/reel`) specialize these types; the generic
|
|
8
|
-
* `@nebutra/ui` node-graph editor depends on this contract — neither depends
|
|
9
|
-
* on the other.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
/** A positioned graph node. Domain nodes extend this. */
|
|
13
|
-
export interface GraphNode {
|
|
14
|
-
readonly id: string;
|
|
15
|
-
readonly x: number;
|
|
16
|
-
readonly y: number;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/** A directed edge. Domain edges extend this (e.g. add an input-port tag). */
|
|
20
|
-
export interface GraphEdge {
|
|
21
|
-
readonly from: string;
|
|
22
|
-
readonly to: string;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/** A structural graph; `N`/`E` carry the domain specialization. */
|
|
26
|
-
export interface Graph<N extends GraphNode = GraphNode, E extends GraphEdge = GraphEdge> {
|
|
27
|
-
readonly nodes: readonly N[];
|
|
28
|
-
readonly edges: readonly E[];
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/** Edges whose target is `nodeId`. Preserves the concrete edge subtype. */
|
|
32
|
-
export function inboundEdges<E extends GraphEdge>(
|
|
33
|
-
edges: readonly E[],
|
|
34
|
-
nodeId: string,
|
|
35
|
-
): readonly E[] {
|
|
36
|
-
return edges.filter((e) => e.to === nodeId);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/** Detect a cycle reachable from `nodeId` (depth-first, edge-following). */
|
|
40
|
-
export function hasCycleFrom(edges: readonly GraphEdge[], nodeId: string): boolean {
|
|
41
|
-
const adjacency = new Map<string, string[]>();
|
|
42
|
-
for (const e of edges) {
|
|
43
|
-
const list = adjacency.get(e.from) ?? [];
|
|
44
|
-
list.push(e.to);
|
|
45
|
-
adjacency.set(e.from, list);
|
|
46
|
-
}
|
|
47
|
-
const visiting = new Set<string>();
|
|
48
|
-
const done = new Set<string>();
|
|
49
|
-
const walk = (id: string): boolean => {
|
|
50
|
-
if (visiting.has(id)) return true;
|
|
51
|
-
if (done.has(id)) return false;
|
|
52
|
-
visiting.add(id);
|
|
53
|
-
for (const next of adjacency.get(id) ?? []) {
|
|
54
|
-
if (walk(next)) return true;
|
|
55
|
-
}
|
|
56
|
-
visiting.delete(id);
|
|
57
|
-
done.add(id);
|
|
58
|
-
return false;
|
|
59
|
-
};
|
|
60
|
-
return walk(nodeId);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Would adding `from → to` make the graph cyclic? Pure: never mutates the
|
|
65
|
-
* passed `edges`. The canonical guard for interactive edge creation.
|
|
66
|
-
*/
|
|
67
|
-
export function wouldCreateCycle(edges: readonly GraphEdge[], from: string, to: string): boolean {
|
|
68
|
-
return hasCycleFrom([...edges, { from, to }], from);
|
|
69
|
-
}
|
package/tsconfig.json
DELETED