# core/coordinators/payload.coordinator.ts

> 134 lines of code and 28 definitions.

Tree: Bane's Lab Build Scripts
Language: typescript
Layer: application
Canonical: https://banes-lab.com/anatomy/build-scripts#file-build-scripts-core-coordinators-payload-coordinator-ts
Source text: https://banes-lab.com/source/build-scripts/core/coordinators/payload.coordinator.ts.txt

Listed in [core/coordinators](https://banes-lab.com/api/source/build-scripts/core/coordinators.md), after [core/coordinators/page.coordinator.ts](https://banes-lab.com/source/build-scripts/core/coordinators/page.coordinator.ts.md) and before [core/coordinators/server.coordinator.ts](https://banes-lab.com/source/build-scripts/core/coordinators/server.coordinator.ts.md).

## Definitions

- `answer` (lexical_declaration, line 35)
- `respondJson` (lexical_declaration, line 44)
- `respondMarkdown` (lexical_declaration, line 70)
- `respondCatalog` (lexical_declaration, line 99)
- `payloadMiddleware` (lexical_declaration, line 132, exported)
- `isRoutePath` (lexical_declaration, line 90)
- `catalogMiddleware` (lexical_declaration, line 118, exported)
- `renderMissing` (lexical_declaration, line 31)
- `isFullTextPath` (lexical_declaration, line 66)
- `fullTextOf` (lexical_declaration, line 58)
- `Next` (type_alias_declaration, line 16)
- `Handler` (type_alias_declaration, line 17)
- `JSON_TYPE` (lexical_declaration, line 19)
- `MARKDOWN_TYPE` (lexical_declaration, line 20)
- `SLASH` (lexical_declaration, line 21)
- `NOT_FOUND` (lexical_declaration, line 22)
- `OK` (lexical_declaration, line 23)
- `JSON_INDENT` (lexical_declaration, line 24)
- `HEADERS` (lexical_declaration, line 26)
- `part` (lexical_declaration, line 62)
- `discovery` (lexical_declaration, line 76)
- `full` (lexical_declaration, line 77)
- `route` (lexical_declaration, line 82)
- `body` (lexical_declaration, line 110)
- `path` (lexical_declaration, line 123, exported)
- `url` (lexical_declaration, line 134, exported)
- `address` (lexical_declaration, line 135, exported)
- `markdown` (lexical_declaration, line 140, exported)

## Contained in

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

## Uses

- [core/formatters/page.formatter.ts](https://banes-lab.com/source/build-scripts/core/formatters/page.formatter.ts.md)
- [core/formatters/site.formatter.ts](https://banes-lab.com/source/build-scripts/core/formatters/site.formatter.ts.md)
- [core/selectors/site.selector.ts](https://banes-lab.com/source/build-scripts/core/selectors/site.selector.ts.md)

## Used by

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

## Linked from

- [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/plugins](https://banes-lab.com/anatomy/build-scripts/folder-build-scripts-core-plugins.md)
- [core/selectors](https://banes-lab.com/anatomy/build-scripts/folder-build-scripts-core-selectors.md)

## Source

```typescript
import type { CatalogSource, DiscoverySource, PayloadAddress } from "#types/payload.types";
import {
    FULL_TEXT_FOLDER,
    FULL_TEXT_ROUTE,
    JSON_ROUTE,
    MARKDOWN_EXTENSION,
} from "#configuration/constants/site.constants";
import type { IncomingMessage, ServerResponse } from "node:http";
import { fullTextParts, fullTextRoutes, routeOf } from "#core/selectors/site.selector";
import { markdownRouteOf, pageIdOf, pathOf } from "#core/resolvers/payload.resolver";
import { renderFullText, renderMarkdownPage } from "#core/formatters/page.formatter";
import type { Discovery } from "#types/site.types";
import { MISSING_PAGE_ERROR } from "#configuration/strings/payload.strings";
import { renderRoutePayload } from "#core/formatters/site.formatter";

type Next = () => void;
type Handler = (request: IncomingMessage, response: ServerResponse, next: Next) => void;

const JSON_TYPE = "application/json; charset=utf-8";
const MARKDOWN_TYPE = "text/markdown; charset=utf-8";
const SLASH = "/";
const NOT_FOUND = 404;
const OK = 200;
const JSON_INDENT = 2;

const HEADERS: readonly (readonly [string, string])[] = [
    ["Access-Control-Allow-Origin", "*"],
    ["Cache-Control", "no-store"],
];

const renderMissing = function renderMissing(id: string): string {
    return `${JSON.stringify({ error: MISSING_PAGE_ERROR, id }, null, JSON_INDENT)}\n`;
};

const answer = function answer(response: ServerResponse, status: number, type: string, body: string): void {
    for (const [name, value] of HEADERS) {
        response.setHeader(name, value);
    }
    response.setHeader("Content-Type", type);
    response.statusCode = status;
    response.end(body);
};

const respondJson = async function respondJson(
    source: DiscoverySource,
    address: PayloadAddress,
    response: ServerResponse,
): Promise<void> {
    const discovery = await source();
    const route = routeOf(discovery, address.page, address.tab);
    if (route === undefined) {
        answer(response, NOT_FOUND, JSON_TYPE, renderMissing(address.page));
        return;
    }
    answer(response, OK, JSON_TYPE, renderRoutePayload(discovery, route));
};

const fullTextOf = function fullTextOf(discovery: Discovery, path: string): string | null {
    if (path === SLASH + FULL_TEXT_ROUTE) {
        return renderFullText(discovery, fullTextRoutes(discovery));
    }
    const part = fullTextParts(discovery).find((candidate) => SLASH + candidate.file === path);
    return part === undefined ? null : renderFullText(discovery, part.routes);
};

const isFullTextPath = function isFullTextPath(path: string): boolean {
    return path === SLASH + FULL_TEXT_ROUTE || path.startsWith(SLASH + FULL_TEXT_FOLDER + SLASH);
};

const respondMarkdown = async function respondMarkdown(
    source: DiscoverySource,
    path: string,
    response: ServerResponse,
    next: Next,
): Promise<void> {
    const discovery = await source();
    const full = fullTextOf(discovery, path);
    if (full !== null) {
        answer(response, OK, MARKDOWN_TYPE, full);
        return;
    }
    const route = discovery.routes.find((candidate) => candidate.path === path);
    if (route === undefined) {
        next();
        return;
    }
    answer(response, OK, MARKDOWN_TYPE, renderMarkdownPage(discovery.site, route));
};

const isRoutePath = function isRoutePath(discovery: Discovery, path: string): boolean {
    const address = pageIdOf(path);
    if (address !== null) {
        return routeOf(discovery, address.page, address.tab) !== undefined;
    }
    const markdown = markdownRouteOf(path);
    return isFullTextPath(path) || discovery.routes.some((route) => route.path === markdown);
};

const respondCatalog = async function respondCatalog(
    discoveries: DiscoverySource,
    catalog: CatalogSource,
    path: string,
    response: ServerResponse,
    next: Next,
): Promise<void> {
    if (isRoutePath(await discoveries(), path)) {
        next();
        return;
    }
    const body = (await catalog()).get(path);
    if (body === undefined) {
        next();
        return;
    }
    answer(response, OK, path.startsWith(JSON_ROUTE) ? JSON_TYPE : MARKDOWN_TYPE, body);
};

export const catalogMiddleware = function catalogMiddleware(
    discoveries: DiscoverySource,
    catalog: CatalogSource,
): Handler {
    return function serve(request, response, next): void {
        const path = pathOf(request.url ?? "");
        if (!path.startsWith(JSON_ROUTE) && !path.endsWith(MARKDOWN_EXTENSION)) {
            next();
            return;
        }
        void respondCatalog(discoveries, catalog, path, response, next);
    };
};

export const payloadMiddleware = function payloadMiddleware(source: DiscoverySource): Handler {
    return function serve(request, response, next): void {
        const url = request.url ?? "";
        const address = pageIdOf(url);
        if (address !== null) {
            void respondJson(source, address, response);
            return;
        }
        const markdown = isFullTextPath(pathOf(url)) ? pathOf(url) : markdownRouteOf(url);
        if (markdown === null) {
            next();
            return;
        }
        void respondMarkdown(source, markdown, response, next);
    };
};
```
