@t1mmen/srtd
Version:
Supabase Repeatable Template Definitions (srtd): 🪄 Live-reloading SQL templates for Supabase DX. Make your database changes reviewable and migrations maintainable! 🚀
109 lines • 3.41 kB
JavaScript
/**
* Dependency Graph Builder
*
* Builds a dependency graph from SQL templates using @depends-on comments
* and provides topological sorting and cycle detection.
*/
import * as path from 'node:path';
import { extractDependsOn } from './dependencyParser.js';
/**
* Build a dependency graph from templates
*
* Uses @depends-on comments to determine dependencies.
* Dependencies are matched by filename (basename) to full paths.
*
* Returns a Map where:
* - Keys are template paths
* - Values are arrays of template paths that the key depends on
*/
export function buildDependencyGraph(templates) {
// Build filename -> full path mapping for resolution
const filenameToPath = new Map();
for (const template of templates) {
const filename = path.basename(template.path);
filenameToPath.set(filename.toLowerCase(), template.path);
}
// Build dependency graph
const graph = new Map();
for (const template of templates) {
const declaredDeps = extractDependsOn(template.content);
const resolvedDeps = [];
for (const dep of declaredDeps) {
const depPath = filenameToPath.get(dep.toLowerCase());
// Only add if dependency exists in template set and isn't self
if (depPath && depPath !== template.path && !resolvedDeps.includes(depPath)) {
resolvedDeps.push(depPath);
}
}
graph.set(template.path, resolvedDeps);
}
return graph;
}
/**
* Topologically sort templates based on dependencies
*
* Returns templates in an order where dependencies come before dependents.
* Uses depth-first search for stable ordering.
*
* Note: If cycles exist, returns a best-effort ordering.
* Use detectCycles() first to warn users.
*/
export function topologicalSort(graph) {
const visited = new Set();
const result = [];
function visit(node) {
if (visited.has(node))
return;
visited.add(node);
// Visit all dependencies first
const deps = graph.get(node) || [];
for (const dep of deps) {
visit(dep);
}
// Then add this node
result.push(node);
}
// Visit all nodes
for (const node of graph.keys()) {
visit(node);
}
return result;
}
/**
* Detect cycles in the dependency graph
*
* Returns an array of cycles found. Each cycle is an array of template paths
* representing the cycle (e.g., ['/a.sql', '/b.sql'] means a -> b -> a).
*/
export function detectCycles(graph) {
const cycles = [];
const visited = new Set();
const inStack = new Set();
function dfs(node, path) {
if (inStack.has(node)) {
// Found a cycle - extract it from the path
const cycleStart = path.indexOf(node);
if (cycleStart !== -1) {
cycles.push(path.slice(cycleStart));
}
return;
}
if (visited.has(node))
return;
visited.add(node);
inStack.add(node);
path.push(node);
const deps = graph.get(node) || [];
for (const dep of deps) {
dfs(dep, [...path]);
}
inStack.delete(node);
}
for (const node of graph.keys()) {
if (!visited.has(node)) {
dfs(node, []);
}
}
return cycles;
}
//# sourceMappingURL=dependencyGraph.js.map