core/pipelines/catalog.pipeline.ts

core/pipelines/catalog.pipeline.ts is a file in GovLab Quality. 68 lines of code and 14 definitions.

import type { CatalogKey, CatalogState, CatalogStep, CatalogWriter } from "#types/catalog.types";
import { duplicateGiver, missingNeed, stepLine, stuckSteps } from "#configuration/strings/catalog.strings";
import { importFolder } from "#core/loaders/folder.loader";
import { performance } from "node:perf_hooks";
import { registeredSteps } from "#core/registries/step.registry";

const STEP_SUFFIX = ".step.ts";

const refuseDuplicateGivers = function refuseDuplicateGivers(steps: readonly CatalogStep[]): void {
    const givers = new Map<CatalogKey, string>();
    for (const step of steps) {
        for (const key of step.gives) {
            const first = givers.get(key);
            if (first !== undefined) {
                throw new Error(duplicateGiver(key, first, step.name));
            }
            givers.set(key, step.name);
        }
    }
};

const refuseMissing = function refuseMissing(steps: readonly CatalogStep[]): void {
    const given = new Set(steps.flatMap((step) => step.gives));
    for (const step of steps) {
        const missing = step.needs.find((key) => !given.has(key));
        if (missing !== undefined) {
            throw new Error(missingNeed(step.name, missing));
        }
    }
};

const orderFrom = function orderFrom(
    pending: readonly CatalogStep[],
    available: ReadonlySet<CatalogKey>,
): CatalogStep[] {
    if (pending.length === 0) {
        return [];
    }
    const ready = pending.find((step) => step.needs.every((key) => available.has(key)));
    if (ready === undefined) {
        throw new Error(stuckSteps(pending.map((step) => step.name)));
    }
    return [
        ready,
        ...orderFrom(
            pending.filter((step) => step !== ready),
            new Set([...available, ...ready.gives]),
        ),
    ];
};

export const orderSteps = function orderSteps(steps: readonly CatalogStep[]): CatalogStep[] {
    refuseDuplicateGivers(steps);
    refuseMissing(steps);
    return orderFrom(steps, new Set());
};

export const discoverSteps = async function discoverSteps(): Promise<CatalogStep[]> {
    await importFolder("govlab.quality.steps", STEP_SUFFIX);
    return registeredSteps();
};

export const runSteps = async function runSteps(
    steps: readonly CatalogStep[],
    writer: CatalogWriter,
    write: (line: string) => void,
): Promise<CatalogState> {
    return orderSteps(steps).reduce(async (previous, step) => {
        const state = await previous;
        const started = performance.now();
        const given = await step.run(state, writer);
        write(stepLine(step.name, performance.now() - started));
        return { ...state, ...given };
    }, Promise.resolve<CatalogState>({}));
};