# core/converters/site.converter.ts

> 84 lines of code and 17 definitions.

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

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

## Definitions

- `tabsIn` (lexical_declaration, line 22)
- `hasString` (lexical_declaration, line 14)
- `idsIn` (lexical_declaration, line 34)
- `sectionIdsOf` (lexical_declaration, line 45)
- `isTab` (lexical_declaration, line 18)
- `secondaryTabs` (lexical_declaration, line 26)
- `hasSections` (lexical_declaration, line 30)
- `discover` (lexical_declaration, line 70, exported)
- `tabRoutes` (lexical_declaration, line 53)
- `tabsFromDiscovery` (lexical_declaration, line 66, exported)
- `NamedTab` (interface_declaration, line 9)
- `path` (lexical_declaration, line 55)
- `pages` (lexical_declaration, line 71, exported)
- `definition` (lexical_declaration, line 72, exported)
- `head` (lexical_declaration, line 73, exported)
- `tabs` (lexical_declaration, line 84, exported)
- `routes` (lexical_declaration, line 85, exported)

## Contained in

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

## Uses

- [core/adapters/document.adapter.ts](https://banes-lab.com/source/build-scripts/core/adapters/document.adapter.ts.md)

## Used by

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

## Linked from

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

## Source

```typescript
import type { DiscoveredPage, DiscoveredRoute, Discovery } from "#types/site.types";
import type { PageDefinition, SiteSource } from "#types/page.types";
import type { TabsFor } from "#types/learning.types";
import { definitionOf } from "#core/selectors/page.selector";
import { mapSettled } from "#core/adapters/document.adapter";
import { tabsIn as tabRecordsIn } from "@banes-lab/content/core/selectors/payload.selector.ts";
import { textualize } from "#core/converters/payload.converter";

interface NamedTab {
    readonly id: string;
    readonly label: string;
}

const hasString = function hasString(value: object, key: string): boolean {
    return key in value && typeof Reflect.get(value, key) === "string";
};

const isTab = function isTab(value: unknown): value is NamedTab {
    return typeof value === "object" && value !== null && hasString(value, "id") && hasString(value, "label");
};

const tabsIn = function tabsIn(content: unknown): readonly NamedTab[] {
    return tabRecordsIn(content).flatMap((tab) => (isTab(tab) ? [tab] : []));
};

const secondaryTabs = function secondaryTabs(content: unknown): readonly NamedTab[] {
    return tabsIn(content).slice(1);
};

const hasSections = function hasSections(value: unknown): value is { readonly sections: readonly unknown[] } {
    return typeof value === "object" && value !== null && "sections" in value && Array.isArray(value.sections);
};

const idsIn = function idsIn(holder: unknown): readonly string[] {
    if (!hasSections(holder)) {
        return [];
    }
    return holder.sections.flatMap((section) =>
        typeof section === "object" && section !== null && hasString(section, "id")
            ? [String(Reflect.get(section, "id"))]
            : [],
    );
};

const sectionIdsOf = function sectionIdsOf(content: unknown, tab: string | null): readonly string[] {
    if (tabRecordsIn(content).length === 0) {
        return idsIn(content);
    }
    const tabs = tabsIn(content);
    return idsIn(tab === null ? tabs[0] : tabs.find((entry) => entry.id === tab));
};

const tabRoutes = async function tabRoutes(loaded: SiteSource, definition: PageDefinition): Promise<DiscoveredRoute[]> {
    return mapSettled(secondaryTabs(definition.content), (tab) => {
        const path = loaded.links.tabLink(definition.id, tab.id);
        return {
            ...loaded.renderer.headOf(definition.id, definition, path),
            label: tab.label,
            markdown: loaded.exportText(path, definition.id, sectionIdsOf(definition.content, tab.id)),
            page: definition.id,
            tab: tab.id,
        };
    });
};

export const tabsFromDiscovery = function tabsFromDiscovery(discovery: Discovery): TabsFor {
    return (page) => tabRecordsIn(discovery.pages.find((entry) => entry.id === page)?.content ?? null);
};

export const discover = async function discover(loaded: SiteSource): Promise<Discovery> {
    const pages = await mapSettled(loaded.ids, (page): DiscoveredPage => {
        const definition = definitionOf(loaded, page);
        const head = loaded.renderer.headOf(page, definition);
        return {
            ...head,
            content: textualize(definition.content, loaded.inline),
            id: page,
            label: definition.title,
            markdown: loaded.exportText(head.path, page, sectionIdsOf(definition.content, null)),
            page,
            tab: null,
        };
    });
    const tabs = await mapSettled(pages, async (page) => tabRoutes(loaded, definitionOf(loaded, page.id)));
    const routes: DiscoveredRoute[] = pages.flatMap((page, index) => [page, ...(tabs[index] ?? [])]);
    return {
        author: loaded.author,
        consent: loaded.consent,
        name: loaded.name,
        pages,
        routes,
        site: loaded.links.SITE_URL,
        summary: loaded.summary,
    };
};
```
