// Generic compile traversal: validate a pipeline graph, run each block's // `apply()` over its parsed params, group the outputs by family, and hand // the grouped result to a target. Targets live app-side (e.g. a mapping // onto an orchestration engine's inputs); future targets (direct serialSGS // constraint sets, MiniZinc) slot in behind the same interface without a // format change. import type { BlockInstance, PipelineGraph } from "./graph.ts"; import { validateGraph } from "./graph.ts"; import type { BlockDef, BlockRegistry } from "./registry.ts"; export interface CompiledBlock { def: BlockDef; instance: BlockInstance; output: unknown; } export interface CompiledFamilies { constraints: CompiledBlock[]; scorers: CompiledBlock[]; search: CompiledBlock; collectors: CompiledBlock[]; } export interface CompileTarget { fromFamilies(families: CompiledFamilies, graph: PipelineGraph): T; } export class GraphValidationError extends Error { readonly issues: readonly { instanceId?: string; code?: string; message: string; }[]; constructor(issues: GraphValidationError["issues"]) { super( `Pipeline graph failed validation:\n${issues.map((i) => `- ${i.message}`).join("\n")}`, ); this.name = "GraphValidationError"; this.issues = issues; } } export function compileGraph( registry: BlockRegistry, graph: PipelineGraph, target: CompileTarget, ): T { const validated = validateGraph(registry, graph); if (!validated.ok) throw new GraphValidationError(validated.issues); const constraints: CompiledBlock[] = []; const scorers: CompiledBlock[] = []; const collectors: CompiledBlock[] = []; let search: CompiledBlock | undefined; for (const instance of validated.graph.blocks) { // validateGraph guarantees the code resolves and already parsed the // params (defaults/transforms applied) — no second parse here. const def = registry.get(instance.code)!; const params = validated.parsedParams.get(instance.instanceId); const output: unknown = ( def.block as { apply(input: unknown): unknown } ).apply(params); const compiled: CompiledBlock = { def, instance, output }; switch (def.family) { case "constraint": constraints.push(compiled); break; case "scorer": scorers.push(compiled); break; case "search": search = compiled; break; case "collector": collectors.push(compiled); break; default: { const exhaustive: never = def.family; throw new Error(`Unhandled block family: ${String(exhaustive)}`); } } } // validateGraph enforces exactly one search block. return target.fromFamilies( { constraints, scorers, search: search!, collectors }, validated.graph, ); }