# core/converters/catalog.converter.ts

> 129 lines of code and 32 definitions.

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

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

## Definitions

- `siteLeaf` (lexical_declaration, line 116, exported)
- `siteBuildOf` (lexical_declaration, line 99, exported)
- `groupNameOf` (lexical_declaration, line 32)
- `SITE_REF` (lexical_declaration, line 21)
- `IDS_REF` (lexical_declaration, line 22)
- `IDS_SHARD_PREFIX` (lexical_declaration, line 23)
- `PART_REF_MARK` (lexical_declaration, line 24)
- `REF_SEPARATOR` (lexical_declaration, line 25)
- `ID_COLUMNS` (lexical_declaration, line 26)
- `groupOf` (lexical_declaration, line 28)
- `collection` (lexical_declaration, line 33)
- `name` (lexical_declaration, line 34)
- `rowOf` (lexical_declaration, line 41)
- `IdsShard` (interface_declaration, line 54)
- `groupShards` (lexical_declaration, line 60)
- `parts` (lexical_declaration, line 61)
- `groupTitle` (lexical_declaration, line 62)
- `single` (lexical_declaration, line 64)
- `part` (lexical_declaration, line 65)
- `ref` (lexical_declaration, line 66)
- `title` (lexical_declaration, line 67)
- `address` (lexical_declaration, line 68)
- `leaf` (lexical_declaration, line 69)
- `idsLeaves` (lexical_declaration, line 78, exported)
- `grouped` (lexical_declaration, line 79, exported)
- `shards` (lexical_declaration, line 82, exported)
- `listed` (lexical_declaration, line 83, exported)
- `kinds` (lexical_declaration, line 84, exported)
- `head` (lexical_declaration, line 104, exported)
- `stamp }` (lexical_declaration, line 117, exported)
- `identity` (lexical_declaration, line 118, exported)
- `data` (lexical_declaration, line 126, 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)
- [core/converters/route.converter.ts](https://banes-lab.com/source/build-scripts/core/converters/route.converter.ts.md)
- [core/formatters/index.formatter.ts](https://banes-lab.com/source/build-scripts/core/formatters/index.formatter.ts.md)
- [core/resolvers/catalog.resolver.ts](https://banes-lab.com/source/build-scripts/core/resolvers/catalog.resolver.ts.md)

## Used by

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

## Source

```typescript
import { ADDRESS_GROUP_TITLES, COLLECTION_TITLES, addressGroupTitle } from "@banes-lab/web/strings/catalog.strings";
import {
    CANONICAL_FORM,
    IDS_TITLE,
    NO_MANIFEST_HEAD,
    indexPartTitle,
    untitledGroup,
} from "#configuration/strings/catalog.strings";
import { CATALOG_VERSION, INDEX_KIND, INDEX_PART_BUDGET } from "#configuration/constants/catalog.constants";
import type { Entry, Identity, Leaf, SiteBuild, SiteParts } from "#types/catalog.types";
import { idsIndex, idsShard, indexPart, localAddress, siteIndex } from "#core/resolvers/catalog.resolver";
import type { Discovery } from "#types/site.types";
import type { RouteLedger } from "#types/route.types";
import { grammarRows } from "#core/converters/catalog.grammar.converter";
import { groupedBy } from "#core/converters/base.converter";
import { latestStamp } from "#core/converters/route.converter";
import { packedParts } from "#core/stores/catalog.store";
import { renderSiteIndex } from "#core/formatters/index.formatter";
import { trainingConsent } from "#configuration/strings/site.strings";

const SITE_REF = "api:";
const IDS_REF = "api:ids";
const IDS_SHARD_PREFIX = "api:ids/";
const PART_REF_MARK = "/";
const REF_SEPARATOR = ":";
const ID_COLUMNS = ["ref", "kind", "title", "summary", "json", "markdown", "bytes", "fingerprint"] as const;

const groupOf = function groupOf(entry: Entry): string {
    return entry.ref.slice(0, entry.ref.indexOf(REF_SEPARATOR));
};

const groupNameOf = function groupNameOf(group: string): string {
    const collection = COLLECTION_TITLES.get(group);
    const name = collection === undefined ? ADDRESS_GROUP_TITLES.get(group) : collection.toLowerCase();
    if (name === undefined) {
        throw new Error(untitledGroup(group));
    }
    return name;
};

const rowOf = function rowOf(entry: Entry): readonly unknown[] {
    return [
        entry.ref,
        entry.kind,
        entry.title,
        entry.summary,
        entry.json,
        entry.markdown,
        entry.bytes,
        entry.fingerprint,
    ];
};

interface IdsShard {
    readonly count: number;
    readonly group: string;
    readonly leaf: Leaf;
}

const groupShards = function groupShards(group: string, members: readonly Entry[]): readonly IdsShard[] {
    const parts = packedParts(members, INDEX_PART_BUDGET);
    const groupTitle = addressGroupTitle(groupNameOf(group));
    return parts.map((rows, index): IdsShard => {
        const single = parts.length === 1;
        const part = index + 1;
        const ref = IDS_SHARD_PREFIX + group + (single ? "" : PART_REF_MARK + String(part));
        const title = single ? groupTitle : indexPartTitle(groupTitle, part, parts.length);
        const address = single ? idsShard(group) : indexPart(idsShard(group), String(part));
        const leaf: Leaf = {
            data: { columns: ID_COLUMNS, count: rows.length, group, ref, rows: rows.map(rowOf), title },
            identity: { address, href: null, kind: INDEX_KIND, ref, summary: null, title },
            markdown: null,
        };
        return { count: rows.length, group, leaf };
    });
};

export const idsLeaves = function idsLeaves(entries: readonly Entry[], site: string): readonly Leaf[] {
    const grouped = [...groupedBy(entries, groupOf).entries()].flatMap(([group, members]) =>
        groupShards(group, members),
    );
    const shards = grouped.map((shard) => shard.leaf);
    const listed = grouped.map(({ count, group, leaf }) => ({ count, group, json: site + leaf.identity.address.json }));
    const kinds = [...new Set([...entries, ...shards.map((shard) => shard.identity)].map((entry) => entry.kind))];
    const head: Leaf = {
        data: {
            count: entries.length,
            kinds: kinds.toSorted((left, right) => left.localeCompare(right)),
            ref: IDS_REF,
            shards: listed,
            title: IDS_TITLE,
        },
        identity: { address: idsIndex(), href: null, kind: INDEX_KIND, ref: IDS_REF, summary: null, title: IDS_TITLE },
        markdown: null,
    };
    return [...shards, head];
};

export const siteBuildOf = function siteBuildOf(
    entries: readonly Entry[],
    ledger: RouteLedger,
    site: string,
): SiteBuild {
    const head = entries.find((entry) => entry.ref === IDS_REF);
    if (head === undefined) {
        throw new Error(NO_MANIFEST_HEAD);
    }
    return {
        build: head.fingerprint,
        published: entries.map((entry) => localAddress(site, entry.json)),
        updated: latestStamp(ledger),
        version: CATALOG_VERSION,
    };
};

export const siteLeaf = function siteLeaf(discovery: Discovery, parts: SiteParts, build: SiteBuild): Leaf {
    const { published, ...stamp } = build;
    const identity: Identity = {
        address: siteIndex(),
        href: "/",
        kind: INDEX_KIND,
        ref: SITE_REF,
        summary: discovery.summary,
        title: discovery.name,
    };
    const data = {
        ...parts,
        ...stamp,
        canonical: CANONICAL_FORM,
        consent: trainingConsent(discovery.consent, discovery.author, discovery.site),
        grammar: grammarRows(published),
        ref: SITE_REF,
        site: discovery.site,
        summary: discovery.summary,
        title: discovery.name,
    };
    return { data, identity, markdown: renderSiteIndex(data, discovery.site) };
};
```
