# core/converters/ontology.index.converter.ts

> 165 lines of code and 23 definitions.

Tree: Bane's Lab Build Scripts
Language: typescript
Layer: processing
Canonical: https://banes-lab.com/anatomy/build-scripts#file-build-scripts-core-converters-ontology-index-converter-ts
Source text: https://banes-lab.com/source/build-scripts/core/converters/ontology.index.converter.ts.txt

Listed in [core/converters](https://banes-lab.com/api/source/build-scripts/core/converters.md), after [core/converters/ontology.converter.ts](https://banes-lab.com/source/build-scripts/core/converters/ontology.converter.ts.md) and before [core/converters/ontology.reference.converter.ts](https://banes-lab.com/source/build-scripts/core/converters/ontology.reference.converter.ts.md).

## Definitions

- `lookup` (lexical_declaration, line 40, exported)
- `kindOf` (lexical_declaration, line 49)
- `targetOf` (lexical_declaration, line 124)
- `indexRecord` (lexical_declaration, line 155)
- `push` (lexical_declaration, line 30)
- `resolveIn` (lexical_declaration, line 131)
- `records` (lexical_declaration, line 175, exported)
- `reasonKind` (lexical_declaration, line 56)
- `recordsFor` (lexical_declaration, line 147)
- `createReverseIndex` (lexical_declaration, line 170, exported)
- `Indexed` (interface_declaration, line 16)
- `Scope` (interface_declaration, line 21)
- `KindRecords` (type_alias_declaration, line 26)
- `REASON_PREFIX` (lexical_declaration, line 28)
- `pagSelf` (lexical_declaration, line 63)
- `KIND_RECORDS` (lexical_declaration, line 67)
- `TARGETS` (lexical_declaration, line 117)
- `inverseFields` (lexical_declaration, line 138)
- `{ inverse }` (lexical_declaration, line 142)
- `held` (lexical_declaration, line 162)
- `scope` (lexical_declaration, line 171, exported)
- `index` (lexical_declaration, line 172, exported)
- `fields` (lexical_declaration, line 174, exported)

## Contained in

- [core/converters](https://banes-lab.com/anatomy/build-scripts/folder-build-scripts-core-converters.md)

## Uses

- [configuration/strings/ontology.strings.ts](https://banes-lab.com/source/build-scripts/configuration/strings/ontology.strings.ts.md)
- [core/converters/folder.converter.ts](https://banes-lab.com/source/build-scripts/core/converters/folder.converter.ts.md)

## Used by

- [core/converters/lexicon.converter.ts](https://banes-lab.com/source/build-scripts/core/converters/lexicon.converter.ts.md)
- [core/converters/ontology.converter.ts](https://banes-lab.com/source/build-scripts/core/converters/ontology.converter.ts.md)
- [core/converters/reason.catalog.converter.ts](https://banes-lab.com/source/build-scripts/core/converters/reason.catalog.converter.ts.md)
- [core/converters/reason.converter.ts](https://banes-lab.com/source/build-scripts/core/converters/reason.converter.ts.md)

## Linked from

- [configuration/strings](https://banes-lab.com/anatomy/build-scripts/folder-build-scripts-configuration-strings.md)
- [core/converters](https://banes-lab.com/anatomy/build-scripts/folder-build-scripts-core-converters.md)

## Source

```typescript
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);
};
```
