core/converters/ontology.index.converter.ts

core/converters/ontology.index.converter.ts is a file in Bane's Lab Build Scripts. 165 lines of code and 23 definitions.

import { ALGO_FACE, ARCH_FACE, FACE_SEPARATOR, FORCE_FACE, PAG_FACE, REASON_FACE, STAGE_FACE } from "@govlab/constants";
import {
    AXIS_KIND,
    LENS_KIND,
    NODE_KIND,
    TECHNIQUE_KIND,
    TEST_SURFACE_KIND,
} from "#configuration/constants/ontology.constants";
import { type FieldSpec, type GovlabContext, ONTOLOGY_SCHEMA, PAG_KINDS, keywordIdOf, valuesAt } from "@govlab/context";
import type { RefIndex, Resolver, ReverseIndex } from "#types/ontology.types";
import { unreadKind, unresolvableTarget } from "#configuration/strings/ontology.strings";
import type { EdgeRef } from "@banes-lab/web/types/link.types.js";
import { forceNames } from "#core/converters/layer.converter";
import { refOf } from "#core/resolvers/ontology.resolver";

interface Indexed {
    readonly record: unknown;
    readonly self: EdgeRef;
}

interface Scope {
    readonly forces: ReadonlySet<string>;
    readonly resolve: Resolver;
}

type KindRecords = (context: GovlabContext, resolve: Resolver) => readonly Indexed[];

const REASON_PREFIX = REASON_FACE + FACE_SEPARATOR;

const push = function push(index: Map<string, EdgeRef[]>, key: string | null, value: EdgeRef): void {
    if (key === null) {
        return;
    }
    const held = index.get(key) ?? [];
    if (!held.some((entry) => entry.ref === value.ref && entry.label === value.label)) {
        index.set(key, [...held, value]);
    }
};

export const lookup = function lookup(
    index: ReverseIndex,
    relation: string,
    face: string,
    id: string,
): readonly EdgeRef[] {
    return index.get(relation)?.get(refOf(face, id)) ?? [];
};

const kindOf = function kindOf<T>(
    records: (context: GovlabContext) => readonly T[],
    self: (record: T, resolve: Resolver) => EdgeRef,
): KindRecords {
    return (context, resolve) => records(context).map((record) => ({ record, self: self(record, resolve) }));
};

const reasonKind = function reasonKind<T extends { readonly id: string }>(
    kind: string,
    records: (context: GovlabContext) => readonly T[],
): KindRecords {
    return kindOf(records, (record, resolve) => resolve.reasonAs(kind, record.id));
};

const pagSelf = function pagSelf(resolve: Resolver, kind: string, id: string, label: string): EdgeRef {
    return { label, ref: resolve.pag(kind + FACE_SEPARATOR + id).ref };
};

const KIND_RECORDS: ReadonlyMap<string, KindRecords> = new Map([
    [REASON_PREFIX + AXIS_KIND, reasonKind(AXIS_KIND, (context) => context.reason.axes())],
    [REASON_PREFIX + LENS_KIND, reasonKind(LENS_KIND, (context) => context.reason.lenses())],
    [REASON_PREFIX + NODE_KIND, reasonKind(NODE_KIND, (context) => context.reason.nodes())],
    [REASON_PREFIX + TECHNIQUE_KIND, reasonKind(TECHNIQUE_KIND, (context) => context.reason.techniques())],
    [REASON_PREFIX + TEST_SURFACE_KIND, reasonKind(TEST_SURFACE_KIND, (context) => context.reason.testSurfaces())],
    [
        `${REASON_PREFIX}edge`,
        kindOf(
            (context) => context.reason.edges(),
            (edge, resolve) => resolve.edgeSource(edge.from),
        ),
    ],
    [
        `${ALGO_FACE}${FACE_SEPARATOR}contract`,
        kindOf(
            (context) => context.algo.all(),
            (contract, resolve) => resolve.algo(contract.id),
        ),
    ],
    [
        `${ARCH_FACE}${FACE_SEPARATOR}principle`,
        kindOf(
            (context) => context.arch.all(),
            (principle, resolve) => resolve.archId(principle.id),
        ),
    ],
    [
        PAG_FACE + FACE_SEPARATOR + PAG_KINDS.documentType,
        kindOf(
            (context) => context.pag.documentTypes(),
            (type, resolve) => pagSelf(resolve, PAG_KINDS.documentType, type.type, type.type),
        ),
    ],
    [
        PAG_FACE + FACE_SEPARATOR + PAG_KINDS.keyword,
        kindOf(
            (context) => context.pag.keywords(),
            (keyword, resolve) => pagSelf(resolve, PAG_KINDS.keyword, keywordIdOf(keyword), keyword.keyword),
        ),
    ],
    [
        PAG_FACE + FACE_SEPARATOR + PAG_KINDS.production,
        kindOf(
            (context) => context.pag.productions(),
            (production, resolve) => pagSelf(resolve, PAG_KINDS.production, production.lhs, production.lhs),
        ),
    ],
]);

const TARGETS: ReadonlyMap<string, (value: string, scope: Scope) => EdgeRef> = new Map([
    [ALGO_FACE, (value: string, scope: Scope) => scope.resolve.algo(value)],
    [ARCH_FACE, (value: string, scope: Scope) => scope.resolve.arch(value)],
    [STAGE_FACE, (value: string, scope: Scope) => scope.resolve.stage(value)],
    [FORCE_FACE, (value: string, scope: Scope) => scope.resolve.force(value, scope.forces)],
]);

const targetOf = function targetOf(target: string | undefined, value: string, scope: Scope): EdgeRef {
    if (target === undefined) {
        return scope.resolve.target(value);
    }
    if (target.startsWith(REASON_PREFIX)) {
        return scope.resolve.reasonAs(target.slice(REASON_PREFIX.length), value);
    }
    const resolveIn = TARGETS.get(target);
    if (resolveIn === undefined) {
        throw new Error(unresolvableTarget(target));
    }
    return resolveIn(value, scope);
};

const inverseFields = function inverseFields(
    schema: Readonly<Record<string, FieldSpec>>,
): readonly (readonly [string, FieldSpec & { readonly inverse: string }])[] {
    return Object.entries(schema).flatMap(([path, spec]) => {
        const { inverse } = spec;
        return inverse === undefined ? [] : [[path, { ...spec, inverse }] as const];
    });
};

const recordsFor = function recordsFor(kind: string): KindRecords {
    const records = KIND_RECORDS.get(kind);
    if (records === undefined) {
        throw new Error(unreadKind(kind));
    }
    return records;
};

const indexRecord = function indexRecord(
    index: Map<string, Map<string, EdgeRef[]>>,
    fields: ReturnType<typeof inverseFields>,
    indexed: Indexed,
    scope: Scope,
): void {
    for (const [path, spec] of fields) {
        const held = index.get(spec.inverse) ?? new Map<string, EdgeRef[]>();
        index.set(spec.inverse, held);
        for (const value of valuesAt(indexed.record, path)) {
            push(held, targetOf(spec.target, value, scope).ref, indexed.self);
        }
    }
};

export const createReverseIndex = function createReverseIndex(context: GovlabContext, resolve: Resolver): ReverseIndex {
    const scope: Scope = { forces: forceNames(context), resolve };
    const index = new Map<string, Map<string, EdgeRef[]>>();
    for (const [kind, schema] of ONTOLOGY_SCHEMA) {
        const fields = inverseFields(schema);
        const records = fields.length === 0 ? [] : recordsFor(kind)(context, resolve);
        for (const indexed of records) {
            indexRecord(index, fields, indexed, scope);
        }
    }
    return new Map<string, RefIndex>(index);
};