apx-toolkit
Version:
Automatically discover APIs and generate complete integration packages: code in 12 languages, TypeScript types, test suites, SDK packages, API documentation, mock servers, performance reports, and contract tests. Saves 2-4 weeks of work in seconds.
241 lines (227 loc) • 8.3 kB
JavaScript
/**
* API Dependency Graph Generator
*
* Creates visual dependency graphs showing API relationships
*/
import * as fs from 'fs/promises';
import * as path from 'path';
/**
* Analyze API dependencies
*/
export function analyzeDependencies(apis) {
const nodes = apis.map(api => ({
id: api.url,
label: `${api.method} ${extractEndpointName(api.url)}`,
method: api.method,
url: api.url,
group: extractGroup(api.url),
}));
const edges = [];
const dependencies = [];
// Analyze dependencies
for (const api of apis) {
// Check for references to other endpoints in response data
if (api.responseExample) {
const referencedEndpoints = findReferencedEndpoints(api.responseExample, apis);
for (const ref of referencedEndpoints) {
dependencies.push({
from: api.url,
to: ref.url,
type: 'data',
description: 'Data dependency',
});
edges.push({
from: api.url,
to: ref.url,
label: 'uses',
arrows: 'to',
});
}
}
// Check for authentication dependencies
if (api.headers?.Authorization || api.bearerToken) {
const authEndpoint = findAuthEndpoint(apis);
if (authEndpoint && authEndpoint.url !== api.url) {
dependencies.push({
from: api.url,
to: authEndpoint.url,
type: 'auth',
description: 'Authentication dependency',
});
edges.push({
from: api.url,
to: authEndpoint.url,
label: 'auth',
arrows: 'to',
});
}
}
}
// Find critical paths
const criticalPaths = findCriticalPaths(nodes, edges);
return {
nodes,
edges,
criticalPaths,
};
}
function extractEndpointName(url) {
const urlObj = new URL(url);
const pathParts = urlObj.pathname.split('/').filter(p => p);
return pathParts[pathParts.length - 1] || 'root';
}
function extractGroup(url) {
const urlObj = new URL(url);
const pathParts = urlObj.pathname.split('/').filter(p => p);
return pathParts[0] || 'root';
}
function findReferencedEndpoints(data, apis) {
const referenced = [];
const dataStr = JSON.stringify(data);
for (const api of apis) {
const endpointName = extractEndpointName(api.url);
if (dataStr.includes(endpointName) || dataStr.includes(api.url)) {
referenced.push(api);
}
}
return referenced;
}
function findAuthEndpoint(apis) {
return apis.find(api => api.url.includes('/auth') ||
api.url.includes('/login') ||
api.url.includes('/token'));
}
function findCriticalPaths(nodes, edges) {
// Simple critical path analysis - find longest dependency chains
const paths = [];
// Find entry points (nodes with no incoming edges)
const entryPoints = nodes.filter(node => !edges.some(edge => edge.to === node.id));
for (const entry of entryPoints) {
const path = findLongestPath(entry.id, edges, []);
if (path.length > 1) {
paths.push(path);
}
}
return paths.sort((a, b) => b.length - a.length).slice(0, 5); // Top 5 critical paths
}
function findLongestPath(nodeId, edges, visited) {
if (visited.includes(nodeId)) {
return [nodeId]; // Cycle detected
}
visited.push(nodeId);
const outgoing = edges.filter(e => e.from === nodeId);
if (outgoing.length === 0) {
return [nodeId];
}
const paths = outgoing.map(edge => [nodeId, ...findLongestPath(edge.to, edges, [...visited])]);
return paths.reduce((longest, current) => current.length > longest.length ? current : longest, [nodeId]);
}
/**
* Generate Mermaid diagram
*/
export function generateMermaidDiagram(graph) {
return `graph TD
${graph.nodes.map(node => ` ${node.id.replace(/[^a-zA-Z0-9]/g, '_')}["${node.label}"]`).join('\n')}
${graph.edges.map(edge => ` ${edge.from.replace(/[^a-zA-Z0-9]/g, '_')} -->|${edge.label}| ${edge.to.replace(/[^a-zA-Z0-9]/g, '_')}`).join('\n')}
classDef getMethod fill:#90EE90
classDef postMethod fill:#FFB6C1
classDef putMethod fill:#87CEEB
classDef deleteMethod fill:#FFA07A
${graph.nodes.filter(n => n.method === 'GET').map(n => `class ${n.id.replace(/[^a-zA-Z0-9]/g, '_')} getMethod`).join('\n')}
${graph.nodes.filter(n => n.method === 'POST').map(n => `class ${n.id.replace(/[^a-zA-Z0-9]/g, '_')} postMethod`).join('\n')}
${graph.nodes.filter(n => n.method === 'PUT').map(n => `class ${n.id.replace(/[^a-zA-Z0-9]/g, '_')} putMethod`).join('\n')}
${graph.nodes.filter(n => n.method === 'DELETE').map(n => `class ${n.id.replace(/[^a-zA-Z0-9]/g, '_')} deleteMethod`).join('\n')}
`;
}
/**
* Generate interactive HTML graph (using vis.js)
*/
export function generateInteractiveGraph(graph) {
return `<!DOCTYPE html>
<html>
<head>
<title>API Dependency Graph - APX Toolkit</title>
<script type="text/javascript" src="https://unpkg.com/vis-network/standalone/umd/vis-network.min.js"></script>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
#graph { width: 100%; height: 800px; border: 1px solid #ccc; }
.info { margin: 20px 0; padding: 10px; background: #f0f0f0; }
</style>
</head>
<body>
<h1>API Dependency Graph</h1>
<div class="info">
<p><strong>Total APIs:</strong> ${graph.nodes.length}</p>
<p><strong>Dependencies:</strong> ${graph.edges.length}</p>
<p><strong>Critical Paths:</strong> ${graph.criticalPaths.length}</p>
</div>
<div id="graph"></div>
<script type="text/javascript">
const nodes = new vis.DataSet(${JSON.stringify(graph.nodes.map(n => ({
id: n.id,
label: n.label,
group: n.method,
title: n.url,
})))});
const edges = new vis.DataSet(${JSON.stringify(graph.edges.map(e => ({
from: e.from,
to: e.to,
label: e.label,
arrows: e.arrows,
})))});
const data = { nodes, edges };
const options = {
nodes: {
shape: 'box',
font: { size: 14 },
},
edges: {
arrows: { to: { enabled: true } },
smooth: { type: 'continuous' },
},
layout: {
hierarchical: {
direction: 'UD',
sortMethod: 'directed',
},
},
physics: {
enabled: true,
},
};
const container = document.getElementById('graph');
const network = new vis.Network(container, data, options);
</script>
</body>
</html>`;
}
/**
* Save dependency graph
*/
export async function saveDependencyGraph(graph, outputPath) {
await fs.mkdir(outputPath, { recursive: true });
// Save Mermaid diagram
await fs.writeFile(path.join(outputPath, 'dependency-graph.mmd'), generateMermaidDiagram(graph));
// Save interactive HTML
await fs.writeFile(path.join(outputPath, 'dependency-graph.html'), generateInteractiveGraph(graph));
// Save JSON data
await fs.writeFile(path.join(outputPath, 'dependency-graph.json'), JSON.stringify(graph, null, 2));
// Save critical paths report
const criticalPathsReport = `# Critical API Paths
## Analysis
Found ${graph.criticalPaths.length} critical dependency paths.
${graph.criticalPaths.map((path, index) => `
### Critical Path ${index + 1} (${path.length} endpoints)
${path.map((endpoint, i) => `${i + 1}. ${endpoint}`).join('\n')}
`).join('\n')}
## Recommendations
- Monitor critical paths for performance issues
- Consider caching for frequently accessed endpoints
- Implement circuit breakers for critical dependencies
- Add retry logic for critical path endpoints
---
Generated by APX Toolkit
`;
await fs.writeFile(path.join(outputPath, 'CRITICAL-PATHS.md'), criticalPathsReport);
}
//# sourceMappingURL=dependency-graph.js.map