import { effect } from "@preact/signals-core"; import type { Signal, ReadonlySignal } from "@preact/signals-core"; import type { NodeStatus } from "../schema/enums.js"; const TERMINAL_STATUSES: Set = new Set([ "completed", "failed", "aborted", "skipped", ]); export interface NodeStatusContext { statusMap: Map>; predecessors: string[]; } export function computePreconditions( _nodeKey: string, ctx: NodeStatusContext, ): boolean { if (ctx.predecessors.length === 0) return true; return ctx.predecessors.every((pred: string) => { const predStatus = ctx.statusMap.get(pred); if (!predStatus) return false; return predStatus.value === "completed" || predStatus.value === "skipped"; }); } export function computeBlockedByFailure( _nodeKey: string, ctx: NodeStatusContext, ): boolean { return ctx.predecessors.some((pred: string) => { const predStatus = ctx.statusMap.get(pred); if (!predStatus) return false; return predStatus.value === "failed" || predStatus.value === "aborted"; }); } export function registerStartEffect( status: Signal, preconditions: ReadonlySignal, effectDisposers: (() => void)[], ): void { const disposer = effect(() => { if (preconditions.value) { const current = status.value; if (current === "idle" || current === "waiting") { status.value = "ready"; } } }); effectDisposers.push(disposer); } export interface AbortEffectOptions { abortDependents?: boolean; } export function registerAbortEffect( status: Signal, blockedByFailure: ReadonlySignal, effectDisposers: (() => void)[], options?: AbortEffectOptions, ): void { const disposer = effect(() => { if (blockedByFailure.value) { const current = status.value; if (options?.abortDependents) { if (!TERMINAL_STATUSES.has(current)) { status.value = "aborted"; } } else { if (current === "idle" || current === "waiting") { status.value = "aborted"; } } } }); effectDisposers.push(disposer); }