core/factories/ontology.factory.ts
core/factories/ontology.factory.ts is a file in GovLab Context. 81 lines of code and 15 definitions.
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),
};
};