# core/converters/graph.site.converter.ts

> 121 lines of code and 28 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-site-converter-ts
Source text: https://banes-lab.com/source/build-scripts/core/converters/graph.site.converter.ts.txt

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

## Definitions

- `captionsOf` (lexical_declaration, line 38, exported)
- `siteNodes` (lexical_declaration, line 81, exported)
- `partGraph` (lexical_declaration, line 99, exported)
- `PART_PREFIX` (lexical_declaration, line 18)
- `PART_MARK` (lexical_declaration, line 19)
- `NUMBER_MARK` (lexical_declaration, line 20)
- `KIND_KEY` (lexical_declaration, line 21)
- `CAPTION_KEY` (lexical_declaration, line 22)
- `listOf` (lexical_declaration, line 24)
- `held` (lexical_declaration, line 25)
- `captionOf` (lexical_declaration, line 29)
- `kind` (lexical_declaration, line 33)
- `tag` (lexical_declaration, line 34)
- `blocks` (lexical_declaration, line 39, exported)
- `caption` (lexical_declaration, line 44, exported)
- `refOf` (lexical_declaration, line 49)
- `routeGraph` (lexical_declaration, line 53, exported)
- `pages` (lexical_declaration, line 59, exported)
- `tabs` (lexical_declaration, line 71, exported)
- `page` (lexical_declaration, line 72, exported)
- `sections` (lexical_declaration, line 77, exported)
- `nodes` (lexical_declaration, line 105, exported)
- `edges` (lexical_declaration, line 106, exported)
- `section` (lexical_declaration, line 108, exported)
- `number` (lexical_declaration, line 109, exported)
- `citation` (lexical_declaration, line 110, exported)
- `ordinal` (lexical_declaration, line 112, exported)
- `ref` (lexical_declaration, line 113, exported)

## Contained in

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

## Uses

- [core/converters/graph.code.converter.ts](https://banes-lab.com/source/build-scripts/core/converters/graph.code.converter.ts.md)

## Linked from

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

## Source

```typescript
import {
    CHAPTER_PREFIX,
    PAGE_KIND,
    PART_KIND,
    ROUTE_PREFIX,
    SECTION_KIND,
    SITE_LAYER,
    TAB_KIND,
} from "#configuration/constants/graph.constants";
import type { Graph, SiteCodes } from "#types/graph.types";
import type { GraphEdge, GraphNode } from "@banes-lab/web/types/graph.types.js";
import type { DiscoveredRoute } from "#types/site.types";
import type { SectionPlan } from "#types/section.types";
import { TAG_FIELD_BY_KIND } from "@banes-lab/content/configuration/constants/panel.constants.ts";
import { citationOf } from "#core/converters/graph.code.converter";
import { isRecord } from "#core/selectors/base.selector";

const PART_PREFIX = "part:";
const PART_MARK = "/";
const NUMBER_MARK = ".";
const KIND_KEY = "kind";
const CAPTION_KEY = "caption";

const listOf = function listOf(value: unknown, key: string): readonly unknown[] {
    const held = isRecord(value) ? value[key] : null;
    return Array.isArray(held) ? held : [];
};

const captionOf = function captionOf(block: unknown): string | null {
    if (!isRecord(block)) {
        return null;
    }
    const kind = typeof block[KIND_KEY] === "string" ? block[KIND_KEY] : "";
    const tag = block[TAG_FIELD_BY_KIND.get(kind) ?? CAPTION_KEY];
    return typeof tag === "string" && tag.length > 0 ? tag : null;
};

export const captionsOf = function captionsOf(section: unknown): readonly string[] {
    const blocks = [
        ...listOf(section, "blocks"),
        ...listOf(section, "subsections").flatMap((subsection) => listOf(subsection, "blocks")),
    ];
    return blocks.flatMap((block) => {
        const caption = captionOf(block);
        return caption === null ? [] : [caption];
    });
};

const refOf = function refOf(path: string): string {
    return ROUTE_PREFIX + path;
};

export const routeGraph = function routeGraph(
    routes: readonly DiscoveredRoute[],
    plans: readonly SectionPlan[],
    codes: SiteCodes,
    contains: string,
): Graph {
    const pages = new Map(routes.filter((route) => route.tab === null).map((route) => [route.page, route.path]));
    const nodes: GraphNode[] = routes.map((route) => ({
        address: null,
        citation: null,
        fields: {},
        href: route.path,
        kind: route.tab === null ? PAGE_KIND : TAB_KIND,
        layer: SITE_LAYER,
        number: (route.tab === null ? codes.pages.get(route.page) : codes.tabs.get(route.path)) ?? null,
        ref: refOf(route.path),
        title: route.label,
    }));
    const tabs = routes.flatMap((route) => {
        const page = pages.get(route.page);
        return route.tab === null || page === undefined
            ? []
            : [{ from: refOf(page), relation: contains, to: refOf(route.path) }];
    });
    const sections = plans.map((plan) => ({ from: refOf(plan.tab.path), relation: contains, to: plan.identity.ref }));
    return { edges: [...tabs, ...sections], nodes };
};

export const siteNodes = function siteNodes(
    plans: readonly SectionPlan[],
    numbers: ReadonlyMap<string, string>,
    codes: SiteCodes,
): readonly GraphNode[] {
    return plans.map((plan) => ({
        address: plan.identity.address,
        citation: citationOf(plan, numbers, codes),
        fields: {},
        href: plan.identity.href,
        kind: SECTION_KIND,
        layer: SITE_LAYER,
        number: numbers.get(plan.identity.ref) ?? null,
        ref: plan.identity.ref,
        title: plan.identity.title,
    }));
};

export const partGraph = function partGraph(
    plans: readonly SectionPlan[],
    numbers: ReadonlyMap<string, string>,
    codes: SiteCodes,
    contains: string,
): Graph {
    const nodes: GraphNode[] = [];
    const edges: GraphEdge[] = [];
    for (const plan of plans) {
        const section = plan.identity.ref;
        const number = numbers.get(section) ?? null;
        const citation = citationOf(plan, numbers, codes);
        captionsOf(plan.section).forEach((caption, index) => {
            const ordinal = String(index + 1);
            const ref = PART_PREFIX + section.slice(CHAPTER_PREFIX.length) + PART_MARK + ordinal;
            nodes.push({
                address: null,
                citation: citation === null ? null : citation + NUMBER_MARK + ordinal,
                fields: {},
                href: plan.identity.href,
                kind: PART_KIND,
                layer: SITE_LAYER,
                number: number === null ? null : number + NUMBER_MARK + ordinal,
                ref,
                title: caption,
            });
            edges.push({ from: section, relation: contains, to: ref });
        });
    }
    return { edges, nodes };
};
```
