pi-lens
Version:
Real-time code feedback for pi — LSP, linters, formatters, type-checking, structural analysis & booboo
69 lines (68 loc) • 2.63 kB
JavaScript
/**
* Orders providers topologically so each provider's `requires` are satisfied
* before it runs. Tie-breaks alphabetically by `id`. Detects cycles.
*
* Only deps that have a provider in the input list count toward in-degree;
* external facts (provided outside this list) are treated as always available.
*/
export function scheduleProviders(providers) {
if (providers.length <= 1)
return providers.slice();
// Map: factKey → provider that provides it
const factToProvider = new Map();
for (const p of providers) {
for (const key of p.provides) {
factToProvider.set(key, p);
}
}
// For each provider, track in-degree and which providers depend on it
const inDegree = new Map();
// dependents[id] = set of provider ids that require a fact provided by this provider
const dependents = new Map();
for (const p of providers) {
if (!inDegree.has(p.id))
inDegree.set(p.id, 0);
if (!dependents.has(p.id))
dependents.set(p.id, new Set());
}
for (const p of providers) {
const seenDeps = new Set();
for (const req of p.requires) {
const dep = factToProvider.get(req);
if (dep && dep.id !== p.id && !seenDeps.has(dep.id)) {
seenDeps.add(dep.id);
inDegree.set(p.id, (inDegree.get(p.id) ?? 0) + 1);
dependents.get(dep.id).add(p.id);
}
}
}
const idToProvider = new Map(providers.map((p) => [p.id, p]));
// Start with providers that have no unsatisfied deps, sorted by id
let wave = providers
.filter((p) => (inDegree.get(p.id) ?? 0) === 0)
.sort((a, b) => a.id.localeCompare(b.id));
const result = [];
while (wave.length > 0) {
const nextWave = [];
for (const p of wave) {
result.push(p);
for (const depId of dependents.get(p.id) ?? []) {
const newDegree = (inDegree.get(depId) ?? 0) - 1;
inDegree.set(depId, newDegree);
if (newDegree === 0) {
nextWave.push(idToProvider.get(depId));
}
}
}
nextWave.sort((a, b) => a.id.localeCompare(b.id));
wave = nextWave;
}
if (result.length < providers.length) {
const cycleParticipants = providers
.filter((p) => !result.includes(p))
.map((p) => p.id)
.sort((a, b) => a.localeCompare(b));
throw new Error(`Cycle detected among FactProviders: ${cycleParticipants.join(", ")}`);
}
return result;
}