# core/factories/ontology.factory.ts

> 81 lines of code and 15 definitions.

Tree: GovLab Context
Language: typescript
Layer: infrastructure
Canonical: https://banes-lab.com/anatomy/context#file-context-core-factories-ontology-factory-ts
Source text: https://banes-lab.com/source/context/core/factories/ontology.factory.ts.txt

Listed in [core/factories](https://banes-lab.com/api/source/context/core/factories.md), after [core/factories/lexicon.factory.ts](https://banes-lab.com/source/context/core/factories/lexicon.factory.ts.md) and before [core/factories/reason.factory.ts](https://banes-lab.com/source/context/core/factories/reason.factory.ts.md).

## Definitions

- `makeValidate` (lexical_declaration, line 60)
- `createOntology` (lexical_declaration, line 74, exported)
- `findDuplicateIds` (lexical_declaration, line 26)
- `danglingFor` (lexical_declaration, line 40)
- `buildIndex` (lexical_declaration, line 6)
- `seen` (lexical_declaration, line 27)
- `duplicates` (lexical_declaration, line 28)
- `id` (lexical_declaration, line 30)
- `out` (lexical_declaration, line 45)
- `identity` (lexical_declaration, line 56)
- `hasId` (lexical_declaration, line 66)
- `{ idOf, label }` (lexical_declaration, line 75, exported)
- `logger` (lexical_declaration, line 76, exported)
- `records` (lexical_declaration, line 77, exported)
- `byId` (lexical_declaration, line 78, exported)

## Contained in

- [core/factories](https://banes-lab.com/anatomy/context/folder-context-core-factories.md)

## Uses

- [configuration/strings/ontology.strings.ts](https://banes-lab.com/source/context/configuration/strings/ontology.strings.ts.md)

## Used by

- [core/stores/ontology.store.ts](https://banes-lab.com/source/context/core/stores/ontology.store.ts.md)

## Linked from

- [configuration/strings](https://banes-lab.com/anatomy/context/folder-context-configuration-strings.md)
- [core/factories](https://banes-lab.com/anatomy/context/folder-context-core-factories.md)
- [core/stores](https://banes-lab.com/anatomy/context/folder-context-core-stores.md)

## Source

```typescript
import type { Edge, Logger, Ontology, OntologyIssues, OntologyOptions } from "#types/ontology.types";
import { duplicateRecordId, emptyRecordId } from "#configuration/strings/ontology.strings";
import { NOOP_LOGGER } from "#core/reporters/ontology.reporter";
import { deepFreeze } from "#core/converters/base.converter";

const buildIndex = function buildIndex<R>(
    records: readonly R[],
    idOf: (record: R) => string,
    meta: { logger: Logger; label: string },
): Map<string, R> {
    const byId = new Map<string, R>();
    for (const record of records) {
        const id = idOf(record);
        if (id) {
            if (byId.has(id)) {
                meta.logger.warn(duplicateRecordId(meta.label, id));
            }
            byId.set(id, record);
        } else {
            meta.logger.warn(emptyRecordId(meta.label));
        }
    }
    return byId;
};

const findDuplicateIds = function findDuplicateIds<R>(records: readonly R[], idOf: (record: R) => string): string[] {
    const seen = new Set<string>();
    const duplicates: string[] = [];
    for (const record of records) {
        const id = idOf(record);
        if (seen.has(id)) {
            duplicates.push(id);
        } else {
            seen.add(id);
        }
    }
    return duplicates;
};

const danglingFor = function danglingFor<R>(
    record: R,
    from: string,
    resolver: { edgesOf: (record: R) => Edge[]; hasId: (target: string) => boolean },
): OntologyIssues["danglingEdges"] {
    const out: OntologyIssues["danglingEdges"] = [];
    for (const { relation, targets } of resolver.edgesOf(record)) {
        for (const target of targets) {
            if (!resolver.hasId(target)) {
                out.push({ from, relation, target });
            }
        }
    }
    return out;
};

const identity = function identity(target: string): string {
    return target;
};

const makeValidate = function makeValidate<R>(
    records: readonly R[],
    byId: Map<string, R>,
    idOf: (record: R) => string,
): Ontology<R>["validateOntology"] {
    return (edgesOf, resolveId = identity) => {
        const hasId = (target: string): boolean => byId.has(resolveId(target));
        return {
            danglingEdges: records.flatMap((record) => danglingFor(record, idOf(record), { edgesOf, hasId })),
            duplicateIds: findDuplicateIds(records, idOf),
        };
    };
};

export const createOntology = function createOntology<R>(options: OntologyOptions<R>): Ontology<R> {
    const { idOf, label } = options;
    const logger = options.logger ?? NOOP_LOGGER;
    const records = options.records.map((record) => deepFreeze(record));
    const byId = buildIndex(records, idOf, { label, logger });
    return {
        all: () => [...records],
        get: (id) => byId.get(id) ?? null,
        ids: () => [...byId.keys()].toSorted((a, b) => a.localeCompare(b)),
        index: () => byId,
        query: (matcher) => records.filter(matcher),
        validateOntology: makeValidate(records, byId, idOf),
    };
};
```
