runtime/entrypoints/pattern.entrypoint.ts

runtime/entrypoints/pattern.entrypoint.ts is a file in GovLab Patterns. 161 lines of code and 29 definitions.

import {
    COMMANDS,
    DEFAULT_RESULTS,
    DEFAULT_SYNTH_COUNT,
    FAILURE_EXIT,
    JSON_INDENT,
    PATTERN_FLAG_NAMES,
    SNAPSHOT_DIR,
} from "#configuration/constants/invocation.constants";
import { type ParsedArgv, flagValue, numberFlag, resolveArgv } from "@govlab/argv";
import { analyzeRefusal, analyzeWrote, unknownCommand, windowWrote } from "#configuration/strings/pattern.strings";
import { inferMapping, unrepresentable } from "#core/resolvers/representation.resolver";
import { mkdirSync, readFileSync } from "node:fs";
import type { AnalyzeOptions } from "#types/record.types";
import { DataLoadError } from "#core/classifiers/schema.classifier";
import { PATTERN_ARGV } from "#configuration/configs/invocation.config";
import { detectSchema } from "#core/analyzers/schema.analyzer";
import { isRecord } from "#core/predicates/record.predicate";
import { isRepresentation } from "#core/predicates/representation.predicate";
import { join } from "node:path";
import { loadRecords } from "#core/loaders/record.loader";
import process from "node:process";
import { report } from "#core/pipelines/record.pipeline";
import { schemaFromDict } from "#core/parsers/schema.parser";
import { synthesize } from "#core/factories/record.factory";
import { validateRecord } from "#core/validators/schema.validator";
import { windowedReports } from "#core/pipelines/snapshot.pipeline";
import { writeCanonicalJson } from "@govlab/canonical-write";

type Command = (path: string, argv: ParsedArgv) => Promise<void>;

const readJson = function readJson(path: string): unknown {
    return JSON.parse(readFileSync(path, "utf8"));
};

const jsonText = function jsonText(value: unknown): string {
    return `${JSON.stringify(value, null, JSON_INDENT)}\n`;
};

const writeOrEmit = async function writeOrEmit(value: unknown, output: string | undefined): Promise<void> {
    if (output === undefined) {
        process.stdout.write(jsonText(value));
        return;
    }
    await writeCanonicalJson(output, value);
};

const loadMapping = function loadMapping(path: string): Map<string, readonly string[]> {
    const parsed = readJson(path);
    const entries = isRecord(parsed) ? Object.entries(parsed) : [];
    return new Map(
        entries.map(([field, value]): [string, string[]] => [
            field,
            Array.isArray(value) ? value.filter(isRepresentation) : [],
        ]),
    );
};

const analyzeOptions = function analyzeOptions(argv: ParsedArgv, floatFields: ReadonlySet<string>): AnalyzeOptions {
    const mappingPath = flagValue(argv, PATTERN_FLAG_NAMES.mapping);
    return mappingPath === undefined ? { floatFields } : { floatFields, mapping: loadMapping(mappingPath) };
};

const assertRepresentable = function assertRepresentable(records: readonly unknown[], options: AnalyzeOptions): void {
    if (records.length === 0) {
        return;
    }
    const schema = detectSchema(records, options.floatFields);
    if ((options.mapping ?? inferMapping(schema)).size === 0) {
        throw new DataLoadError(
            analyzeRefusal(
                unrepresentable(schema)
                    .map((field) => field.name)
                    .join(", "),
            ),
        );
    }
};

const streamWindows = async function streamWindows(
    records: readonly unknown[],
    size: number,
    options: AnalyzeOptions,
): Promise<void> {
    mkdirSync(SNAPSHOT_DIR, { recursive: true });
    await Promise.all(
        [...windowedReports(records, size, options)].map(async (snapshot) => {
            const file = join(SNAPSHOT_DIR, `${snapshot.count}.generated.json`);
            await writeCanonicalJson(file, snapshot.report);
            process.stdout.write(windowWrote(String(snapshot.count), String(snapshot.report.headline.findings), file));
        }),
    );
};

const analyzeCommand: Command = async function analyzeCommand(path, argv) {
    const { records, floatFields } = loadRecords(path);
    const options = analyzeOptions(argv, floatFields);
    assertRepresentable(records, options);
    const window = flagValue(argv, PATTERN_FLAG_NAMES.window);
    if (window !== undefined) {
        await streamWindows(records, Number(window), options);
    }
    const output = flagValue(argv, PATTERN_FLAG_NAMES.output) ?? DEFAULT_RESULTS;
    const result = report(records, options);
    await writeCanonicalJson(output, result);
    process.stdout.write(analyzeWrote(String(result.headline.findings), output));
};

const COMMAND_TABLE: ReadonlyMap<string, Command> = new Map<string, Command>([
    [
        COMMANDS.inspect,
        async function inspect(path, argv) {
            const { records, floatFields } = loadRecords(path);
            await writeOrEmit(detectSchema(records, floatFields), flagValue(argv, PATTERN_FLAG_NAMES.output));
        },
    ],
    [
        COMMANDS.plan,
        async function plan(path, argv) {
            const { records, floatFields } = loadRecords(path);
            const mapping =
                analyzeOptions(argv, floatFields).mapping ?? inferMapping(detectSchema(records, floatFields));
            await writeOrEmit(
                [...mapping].map(([field, representations]) => ({ field, representations })),
                flagValue(argv, PATTERN_FLAG_NAMES.output),
            );
        },
    ],
    [COMMANDS.analyze, analyzeCommand],
    [
        COMMANDS.synthesize,
        async function synthesizeCommand(path, argv) {
            const { records, floatFields } = loadRecords(path);
            const count = numberFlag(argv, PATTERN_FLAG_NAMES.count, DEFAULT_SYNTH_COUNT);
            const seed = numberFlag(argv, PATTERN_FLAG_NAMES.seed, 0);
            const synthesized = synthesize(records, { ...analyzeOptions(argv, floatFields), count, seed });
            await writeOrEmit(synthesized, flagValue(argv, PATTERN_FLAG_NAMES.output));
        },
    ],
    [
        COMMANDS.validate,
        async function validate(path, argv) {
            const { records, floatFields } = loadRecords(path);
            const schemaPath = flagValue(argv, PATTERN_FLAG_NAMES.schema);
            const schema =
                schemaPath === undefined ? detectSchema(records, floatFields) : schemaFromDict(readJson(schemaPath));
            for (const record of records) {
                validateRecord(record, schema);
            }
            const verdict = { fields: schema.length, records: records.length, valid: true };
            await writeOrEmit(verdict, flagValue(argv, PATTERN_FLAG_NAMES.output));
        },
    ],
]);

const argv = resolveArgv(PATTERN_ARGV);
const [operation = "", source = ""] = argv.positionals;
const command = COMMAND_TABLE.get(operation);

if (command === undefined) {
    process.stderr.write(`${unknownCommand(operation)}\n`);
    process.exitCode = FAILURE_EXIT;
} else {
    try {
        await command(source, argv);
    } catch (error) {
        if (!(error instanceof DataLoadError)) {
            throw error;
        }
        process.stderr.write(`${error.message}\n`);
        process.exitCode = FAILURE_EXIT;
    }
}