# core/converters/graph.converter.ts

> 127 lines of code and 29 definitions.

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

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

## Definitions

- `indexKey` (lexical_declaration, line 60)
- `add` (lexical_declaration, line 68, exported)
- `edgeKey` (lexical_declaration, line 103)
- `isDuplicate` (lexical_declaration, line 107)
- `danglingEdges` (lexical_declaration, line 133, exported)
- `edgeIndexOf` (lexical_declaration, line 64, exported)
- `mergeGraphs` (lexical_declaration, line 111, exported)
- `inboundRelations` (lexical_declaration, line 8, exported)
- `reverses` (lexical_declaration, line 13, exported)
- `inbound` (lexical_declaration, line 14, exported)
- `reverse` (lexical_declaration, line 16, exported)
- `outboundRelations` (lexical_declaration, line 37, exported)
- `outbound` (lexical_declaration, line 41, exported)
- `byRelation` (lexical_declaration, line 43, exported)
- `targets` (lexical_declaration, line 44, exported)
- `titles` (lexical_declaration, line 65, exported)
- `outgoing` (lexical_declaration, line 66, exported)
- `incoming` (lexical_declaration, line 67, exported)
- `groundingSources` (lexical_declaration, line 81, exported)
- `sharingHref` (lexical_declaration, line 86, exported)
- `href` (lexical_declaration, line 93, exported)
- `aliases` (lexical_declaration, line 94, exported)
- `held` (lexical_declaration, line 95, exported)
- `sources` (lexical_declaration, line 96, exported)
- `nodes` (lexical_declaration, line 112, exported)
- `edges` (lexical_declaration, line 113, exported)
- `duplicates` (lexical_declaration, line 114, exported)
- `first` (lexical_declaration, line 117, exported)
- `refs` (lexical_declaration, line 134, exported)

## Contained in

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

## Uses

- [configuration/strings/catalog.strings.ts](https://banes-lab.com/source/build-scripts/configuration/strings/catalog.strings.ts.md)

## Used by

- [core/coordinators/graph.coordinator.ts](https://banes-lab.com/source/build-scripts/core/coordinators/graph.coordinator.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)
- [core/coordinators](https://banes-lab.com/anatomy/build-scripts/folder-build-scripts-core-coordinators.md)

## Source

```typescript
import { CONTAINS_RELATION, EVIDENCE_RELATION_ID } from "@banes-lab/web/constants/graph.constants";
import type { EdgeIndex, Graph, Merged } from "#types/graph.types";
import type { GraphEdge, GraphNode, RelationPair } from "@banes-lab/web/types/graph.types.js";
import type { EdgeRef } from "@banes-lab/web/types/link.types.js";
import type { ReferenceRelation } from "@banes-lab/web/types/reference.types.js";
import { noReverse } from "#configuration/strings/catalog.strings";

export const inboundRelations = function inboundRelations(
    graph: Graph,
    pairs: readonly RelationPair[],
): ReadonlyMap<string, readonly ReferenceRelation[]> {
    const titles = new Map(graph.nodes.map((held) => [held.ref, held.title]));
    const reverses = new Map(pairs.map((pair) => [pair.forward, pair.reverse]));
    const inbound = new Map<string, Map<string, Map<string, string>>>();
    for (const edge of graph.edges) {
        const reverse = reverses.get(edge.relation);
        if (reverse === undefined) {
            throw new Error(noReverse(edge.relation, edge.from));
        }
        const byRelation = inbound.get(edge.to) ?? new Map<string, Map<string, string>>();
        const sources = byRelation.get(reverse) ?? new Map<string, string>();
        sources.set(edge.from, titles.get(edge.from) ?? edge.from);
        byRelation.set(reverse, sources);
        inbound.set(edge.to, byRelation);
    }
    return new Map(
        [...inbound.entries()].map(([ref, byRelation]) => [
            ref,
            [...byRelation.entries()].map(([relation, sources]) => ({
                edges: [...sources.entries()].map(([source, label]) => ({ label, ref: source })),
                relation,
            })),
        ]),
    );
};

export const outboundRelations = function outboundRelations(
    graph: Graph,
): ReadonlyMap<string, readonly ReferenceRelation[]> {
    const titles = new Map(graph.nodes.map((held) => [held.ref, held.title]));
    const outbound = new Map<string, Map<string, Map<string, string>>>();
    for (const edge of graph.edges) {
        const byRelation = outbound.get(edge.from) ?? new Map<string, Map<string, string>>();
        const targets = byRelation.get(edge.relation) ?? new Map<string, string>();
        targets.set(edge.to, titles.get(edge.to) ?? edge.to);
        byRelation.set(edge.relation, targets);
        outbound.set(edge.from, byRelation);
    }
    return new Map(
        [...outbound.entries()].map(([ref, byRelation]) => [
            ref,
            [...byRelation.entries()].map(([relation, targets]) => ({
                edges: [...targets.entries()].map(([target, label]) => ({ label, ref: target })),
                relation,
            })),
        ]),
    );
};

const indexKey = function indexKey(ref: string, relation: string): string {
    return `${ref}\n${relation}`;
};

export const edgeIndexOf = function edgeIndexOf(graph: Graph): EdgeIndex {
    const titles = new Map(graph.nodes.map((held) => [held.ref, held.title]));
    const outgoing = new Map<string, EdgeRef[]>();
    const incoming = new Map<string, EdgeRef[]>();
    const add = function add(into: Map<string, EdgeRef[]>, key: string, ref: string): void {
        into.set(key, [...(into.get(key) ?? []), { label: titles.get(ref) ?? ref, ref }]);
    };
    for (const edge of graph.edges) {
        add(outgoing, indexKey(edge.from, edge.relation), edge.to);
        add(incoming, indexKey(edge.to, edge.relation), edge.from);
    }
    return {
        incoming: (ref, relation) => incoming.get(indexKey(ref, relation)) ?? [],
        outgoing: (ref, relation) => outgoing.get(indexKey(ref, relation)) ?? [],
    };
};

export const groundingSources = function groundingSources(
    graph: Graph,
    edges: EdgeIndex,
    hrefOf: (ref: string) => string | null,
): (ref: string) => readonly EdgeRef[] {
    const sharingHref = new Map<string, string[]>();
    for (const node of graph.nodes) {
        if (node.href !== null) {
            sharingHref.set(node.href, [...(sharingHref.get(node.href) ?? []), node.ref]);
        }
    }
    return (ref) => {
        const href = hrefOf(ref);
        const aliases = href === null ? [] : (sharingHref.get(href) ?? []).filter((alias) => alias !== ref);
        const held = [ref, ...aliases, ...edges.outgoing(ref, CONTAINS_RELATION).map((edge) => edge.ref ?? "")];
        const sources = new Map(
            held.flatMap((part) => edges.incoming(part, EVIDENCE_RELATION_ID)).map((edge) => [edge.ref, edge.label]),
        );
        return [...sources].map(([source, label]) => ({ label, ref: source }));
    };
};

const edgeKey = function edgeKey(edge: GraphEdge): string {
    return `${edge.from}\n${edge.relation}\n${edge.to}`;
};

const isDuplicate = function isDuplicate(kept: GraphNode, held: GraphNode): boolean {
    return kept.kind !== held.kind || kept.layer !== held.layer;
};

export const mergeGraphs = function mergeGraphs(parts: readonly Graph[]): Merged {
    const nodes = new Map<string, GraphNode>();
    const edges = new Map<string, GraphEdge>();
    const duplicates: (readonly [GraphNode, GraphNode])[] = [];
    for (const part of parts) {
        for (const held of part.nodes) {
            const first = nodes.get(held.ref);
            if (first === undefined) {
                nodes.set(held.ref, held);
                continue;
            }
            if (isDuplicate(first, held)) {
                duplicates.push([first, held]);
            }
        }
        for (const edge of part.edges) {
            edges.set(edgeKey(edge), edge);
        }
    }
    return { duplicates, edges: [...edges.values()], nodes: [...nodes.values()] };
};

export const danglingEdges = function danglingEdges(graph: Graph): readonly GraphEdge[] {
    const refs = new Set(graph.nodes.map((held) => held.ref));
    return graph.edges.filter((edge) => !refs.has(edge.from) || !refs.has(edge.to));
};
```
