Something went wrong. Try again.
A functional toolkit for authoring resource-leveling algorithms — compose constraint and scoring blocks, enumerate feasible schedules
Something went wrong. Try again.
4.0 kB · 106 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107// Block registry — the single definition every PRD-08 projection derives// from (panel, canvas, compiler, assumptions sheet). A `BlockDef` wraps the// existing `Block<I, O>` contract with pure-data metadata; levelset gains no// UI dependency. There is deliberately no registry singleton: the consuming// app constructs its own instance and passes it to `validateGraph` /// `compileGraph`, keeping levelset side-effect-free.
import type { Block } from "./types.ts";
export type BlockFamily = "constraint" | "scorer" | "search" | "collector";
/** Drives toolbox disclosure in the consuming app (UI state, not authz). */export type BlockTier = "sales" | "estimator" | "engineer";
/** Projects one block parameter onto a Tier-1 panel control. */export interface PanelBinding { /** Dot-path into the block's input params (e.g. "window.start"). */ paramPath: string; label: string; control: "date" | "slider" | "number" | "select" | "toggle"; options?: Record<string, unknown>;}
export interface BlockDef<I = unknown, O = unknown> { /** Stable id used in graphs ("PeakCap"). Unique per registry. */ code: string; family: BlockFamily; tier: BlockTier; /** The existing contract: schema, apply, doc. */ block: Block<I, O>; /** Curated default params (Blockly shadow values). Validated against * `block.schema.input` at `register()` time. */ defaultShadow: I; /** e.g. "max {max} concurrent {resource} crews" */ assumptionTemplate?: string; /** Tier-1 panel projection. */ panelBindings?: PanelBinding[]; /** Params reset on template save. */ projectAnchoredPaths?: string[];}
export interface BlockRegistry { /** Throws on duplicate `code` or a `defaultShadow` that fails the * block's input schema. */ register(def: BlockDef): void; get(code: string): BlockDef | undefined; list(filter?: { family?: BlockFamily; tier?: BlockTier }): BlockDef[];}
function resolveDotPath(value: unknown, path: string): unknown { let current = value; for (const segment of path.split(".")) { if (current === null || typeof current !== "object") return undefined; current = (current as Record<string, unknown>)[segment]; } return current;}
export function createRegistry(defs?: BlockDef[]): BlockRegistry { const byCode = new Map<string, BlockDef>();
const registry: BlockRegistry = { register(def: BlockDef): void { if (byCode.has(def.code)) { throw new Error(`Duplicate block code: ${def.code}`); } const parsed = def.block.schema.input.safeParse(def.defaultShadow); if (!parsed.success) { throw new Error( `Invalid defaultShadow for block "${def.code}": ${parsed.error.message}`, ); } // Dot-paths are consumed app-side (panel generation, template save); // a typo there would otherwise fail silently at first use. Check them // against the parsed shadow so they fail here instead — defaultShadow // is curated to be complete, so every declared path must resolve. const paths = [ ...(def.panelBindings ?? []).map((binding) => binding.paramPath), ...(def.projectAnchoredPaths ?? []), ]; for (const path of paths) { if (resolveDotPath(parsed.data, path) === undefined) { throw new Error( `Block "${def.code}": path "${path}" does not resolve in defaultShadow`, ); } } byCode.set(def.code, def); }, get(code: string): BlockDef | undefined { return byCode.get(code); }, list(filter?: { family?: BlockFamily; tier?: BlockTier }): BlockDef[] { const all = [...byCode.values()]; if (!filter) return all; return all.filter( (def) => (filter.family === undefined || def.family === filter.family) && (filter.tier === undefined || def.tier === filter.tier), ); }, };
for (const def of defs ?? []) registry.register(def); return registry;}