/** * Represents a dependency edge in the service dependency graph. */ export interface ServiceDependency { from: string; to: string; } /** * Builds and analyzes dependency graph of focused services. * * Tracks which services depend on which other services and * detects circular dependencies. */ export class DependencyGraphBuilder { private readonly dependencies: ServiceDependency[] = []; /** * Adds a dependency edge to the graph. * * @param from - Source service name * @param to - Target service name */ addDependency(from: string, to: string): void { // Avoid duplicate edges if (!this.dependencies.some((d) => d.from === from && d.to === to)) { this.dependencies.push({ from, to }); } } /** * Detects circular dependencies in the graph. * * Uses depth-first search to find cycles. * * @returns Array of circular dependency paths (e.g., ['A → B → C → A']) */ detectCircularDependencies(): string[] { const cycles: string[] = []; const visited = new Set(); const recursionStack = new Set(); // Get all unique service names const services = new Set(); for (const dep of this.dependencies) { services.add(dep.from); services.add(dep.to); } // DFS from each service for (const service of services) { if (!visited.has(service)) { this.dfs(service, visited, recursionStack, [], cycles); } } return cycles; } /** * Depth-first search to detect cycles. * * @param service - Current service being visited * @param visited - Set of all visited services * @param recursionStack - Set of services in current DFS path * @param path - Current path being explored * @param cycles - Array to collect detected cycles */ private dfs( service: string, visited: Set, recursionStack: Set, path: string[], cycles: string[], ): void { visited.add(service); recursionStack.add(service); path.push(service); // Get all services that this service depends on const dependents = this.dependencies .filter((d) => d.from === service) .map((d) => d.to); for (const dependent of dependents) { if (!visited.has(dependent)) { // Continue DFS this.dfs(dependent, visited, recursionStack, path, cycles); } else if (recursionStack.has(dependent)) { // Cycle detected! const cycleStartIndex = path.indexOf(dependent); const cyclePath = [...path.slice(cycleStartIndex), dependent]; cycles.push(cyclePath.join(' → ')); } } recursionStack.delete(service); path.pop(); } /** * Gets all dependencies for a given service. * * @param serviceName - Service name * @returns Array of service names that this service depends on */ getDependencies(serviceName: string): string[] { return this.dependencies .filter((d) => d.from === serviceName) .map((d) => d.to); } /** * Gets the full dependency graph. * * @returns Array of all dependency edges */ getGraph(): ServiceDependency[] { return [...this.dependencies]; } }