# core/resolvers/link.resolver.ts

> 80 lines of code and 20 definitions.

Tree: Bane's Lab Build Scripts
Language: typescript
Layer: infrastructure
Canonical: https://banes-lab.com/anatomy/build-scripts#file-build-scripts-core-resolvers-link-resolver-ts
Source text: https://banes-lab.com/source/build-scripts/core/resolvers/link.resolver.ts.txt

Listed in [core/resolvers](https://banes-lab.com/api/source/build-scripts/core/resolvers.md), after [core/resolvers/icons.resolver.ts](https://banes-lab.com/source/build-scripts/core/resolvers/icons.resolver.ts.md) and before [core/resolvers/ontology.resolver.ts](https://banes-lab.com/source/build-scripts/core/resolvers/ontology.resolver.ts.md).

## Definitions

- `pageOf` (lexical_declaration, line 20)
- `linkOf` (lexical_declaration, line 25, exported)
- `createLinker` (lexical_declaration, line 36, exported)
- `link` (lexical_declaration, line 52, exported)
- `relink` (lexical_declaration, line 62, exported)
- `ROOT` (lexical_declaration, line 5)
- `FRAGMENT` (lexical_declaration, line 6)
- `EXTENSION_MARK` (lexical_declaration, line 7)
- `isPageLink` (lexical_declaration, line 9)
- `absoluteOf` (lexical_declaration, line 13)
- `{ markdown }` (lexical_declaration, line 21)
- `{ address, href }` (lexical_declaration, line 26, exported)
- `byRef` (lexical_declaration, line 42, exported)
- `byHref` (lexical_declaration, line 43, exported)
- `missed` (lexical_declaration, line 51, exported)
- `href` (lexical_declaration, line 64, exported)
- `identity` (lexical_declaration, line 68, exported)
- `leaf` (lexical_declaration, line 69, exported)
- `crossPage` (lexical_declaration, line 70, exported)
- `unrouted` (lexical_declaration, line 71, exported)

## Contained in

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

## Uses

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

## Linked from

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

## Source

```typescript
import type { Canonical, Identity, Link, Linker, Unresolved } from "#types/catalog.types";
import { MARKDOWN_EXTENSION } from "#configuration/constants/site.constants";
import { rewriteTargets } from "#core/converters/link.converter";

const ROOT = "/";
const FRAGMENT = "#";
const EXTENSION_MARK = ".";

const isPageLink = function isPageLink(target: string): boolean {
    return target.startsWith(ROOT) && !target.includes(FRAGMENT) && !target.includes(EXTENSION_MARK);
};

const absoluteOf = function absoluteOf(target: string, base: string): string | null {
    if (target.startsWith(FRAGMENT)) {
        return base + target;
    }
    return target.startsWith(ROOT) ? target : null;
};

const pageOf = function pageOf(identity: Identity): string | null {
    const { markdown } = identity.address;
    return markdown?.endsWith(MARKDOWN_EXTENSION) === true ? markdown.slice(0, -MARKDOWN_EXTENSION.length) : null;
};

export const linkOf = function linkOf(identity: Identity, site: string, label: string): Link {
    const { address, href } = identity;
    return {
        href: href === null ? null : site + href,
        json: site + address.json,
        label,
        markdown: address.markdown === null ? null : site + address.markdown,
        ref: identity.ref,
    };
};

export const createLinker = function createLinker(
    identities: readonly Identity[],
    site: string,
    canonical: Canonical,
    routes: ReadonlySet<string> | null = null,
): Linker {
    const byRef = new Map(identities.map((identity) => [identity.ref, identity]));
    const byHref = new Map<string, Identity>();
    for (const identity of identities) {
        for (const address of [identity.href, pageOf(identity)]) {
            if (address !== null && !byHref.has(canonical(address))) {
                byHref.set(canonical(address), identity);
            }
        }
    }
    const missed: Unresolved[] = [];
    const link = function link(label: string, ref: string | null): Link {
        const identity = ref === null ? undefined : byRef.get(ref);
        if (identity === undefined) {
            if (ref !== null) {
                missed.push({ from: null, label, target: ref });
            }
            return { href: null, json: null, label, markdown: null, ref };
        }
        return linkOf(identity, site, label);
    };
    const relink = function relink(markdown: string, base: string): string {
        return rewriteTargets(markdown, (target) => {
            const href = absoluteOf(target, base);
            if (href === null) {
                return target;
            }
            const identity = byHref.get(canonical(href));
            const leaf = identity?.address.markdown ?? identity?.address.json;
            const crossPage = target.startsWith(ROOT) && target.includes(FRAGMENT);
            const unrouted = isPageLink(target) && routes !== null && !routes.has(href);
            if (leaf === undefined && (crossPage || unrouted)) {
                missed.push({ from: base, label: target, target: href });
            }
            return leaf === undefined ? site + href : site + leaf;
        });
    };
    return {
        byHref: (href) => byHref.get(canonical(href)) ?? null,
        byRef: (ref) => byRef.get(ref) ?? null,
        link,
        relink,
        site,
        unresolved: () => missed,
    };
};
```
