summaryrefslogtreecommitdiffhomepage
path: root/src/features/plugins/logic/prereqs.ts
blob: 90664130aea3b2b30575174195becc19163e8fc3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import type { PluginNode } from "./types";

/**
 * When CHECKING (loading) `targetId`, the transitive set of its dependencies
 * that are NOT currently loaded — the plugins that must be auto-checked
 * (loaded) first (§8: "Checking a frontend plugin auto-checks its required
 * backend plugins (transitively)").
 *
 * One-directional by construction: only `dependsOn` edges FROM the target are
 * followed (frontend→backend / backend→backend). The reverse direction is
 * NEVER traversed here — backend plugins never depend on frontend ones (§8),
 * so a dependency auto-check never pulls the other way.
 *
 * Returns the ids in deterministic load order (dependencies before their
 * dependents — alphabetical within a level) so the caller can issue the load
 * requests in a safe sequence. Pure input → output.
 */
export function autoCheckBackendPrereqs(
  targetId: string,
  nodes: readonly PluginNode[],
): readonly string[] {
  const byId = new Map(nodes.map((n) => [n.id, n]));

  // BFS over dependency edges (target → deps), collecting UNLOADED deps.
  const out: string[] = [];
  const seen = new Set<string>([targetId]);
  // Queue of [id, depth] — sorted per level so ties resolve alphabetically.
  let frontier: string[] = [targetId];
  while (frontier.length > 0) {
    const next: string[] = [];
    for (const id of frontier) {
      const node = byId.get(id);
      for (const dep of node?.dependsOn ?? []) {
        if (seen.has(dep)) continue;
        seen.add(dep);
        next.push(dep);
        if (!byId.get(dep)?.loaded) out.push(dep);
      }
    }
    frontier = next.sort((a, b) => a.localeCompare(b));
  }
  // Load order: dependencies before dependents (BFS level order already is),
  // alphabetical within a level.
  return out;
}