# core/adapters/document.adapter.ts

> 87 lines of code and 19 definitions.

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

Listed in [core/adapters](https://banes-lab.com/api/source/build-scripts/core/adapters.md), after [core/adapters/diagram.adapter.ts](https://banes-lab.com/source/build-scripts/core/adapters/diagram.adapter.ts.md) and before [core/adapters/security.adapter.ts](https://banes-lab.com/source/build-scripts/core/adapters/security.adapter.ts.md).

## Definitions

- `rewriteEach` (lexical_declaration, line 45, exported)
- `release` (lexical_declaration, line 19)
- `readDocument` (lexical_declaration, line 26, exported)
- `mapSettled` (lexical_declaration, line 68, exported)
- `rewriteDocument` (lexical_declaration, line 35, exported)
- `clearSelectorCache` (lexical_declaration, line 15)
- `settleTasks` (lexical_declaration, line 62)
- `installDocument` (lexical_declaration, line 80, exported)
- `SLASH` (lexical_declaration, line 4)
- `ROOT_SELECTOR` (lexical_declaration, line 5)
- `IdleObserver` (class_declaration, line 7)
- `observe` (method_definition, line 8)
- `unobserve` (method_definition, line 10)
- `disconnect` (method_definition, line 12)
- `{ document }` (lexical_declaration, line 20)
- `dom` (lexical_declaration, line 51, exported)
- `done` (lexical_declaration, line 73, exported)
- `{ window }` (lexical_declaration, line 81, exported)
- `globals` (lexical_declaration, line 82, exported)

## Contained in

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

## Uses

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

## Used by

- [core/converters/build.converter.ts](https://banes-lab.com/source/build-scripts/core/converters/build.converter.ts.md)
- [core/converters/site.converter.ts](https://banes-lab.com/source/build-scripts/core/converters/site.converter.ts.md)
- [core/coordinators/link.coordinator.ts](https://banes-lab.com/source/build-scripts/core/coordinators/link.coordinator.ts.md)
- [core/formatters/page.formatter.ts](https://banes-lab.com/source/build-scripts/core/formatters/page.formatter.ts.md)
- [core/persistence/page.persistence.ts](https://banes-lab.com/source/build-scripts/core/persistence/page.persistence.ts.md)
- [core/validators/element.validator.ts](https://banes-lab.com/source/build-scripts/core/validators/element.validator.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/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/persistence](https://banes-lab.com/anatomy/build-scripts/folder-build-scripts-core-persistence.md)
- [core/plugins](https://banes-lab.com/anatomy/build-scripts/folder-build-scripts-core-plugins.md)
- [core/validators](https://banes-lab.com/anatomy/build-scripts/folder-build-scripts-core-validators.md)

## Source

```typescript
import { JSDOM } from "jsdom";
import { setImmediate } from "node:timers";

const SLASH = "/";
const ROOT_SELECTOR = "html";

class IdleObserver {
    public observe(): void {}

    public unobserve(): void {}

    public disconnect(): void {}
}

const clearSelectorCache = function clearSelectorCache(document: Document): void {
    void document.querySelectorAll(ROOT_SELECTOR);
};

const release = function release(dom: JSDOM): void {
    const { document } = dom.window;
    document.documentElement.replaceChildren();
    clearSelectorCache(document);
    dom.window.close();
};

export const readDocument = function readDocument<T>(markup: string, read: (document: Document) => T): T {
    const dom = new JSDOM(markup);
    try {
        return read(dom.window.document);
    } finally {
        release(dom);
    }
};

export const rewriteDocument = function rewriteDocument(markup: string, edit: (document: Document) => void): string {
    const dom = new JSDOM(markup);
    try {
        edit(dom.window.document);
        return dom.serialize();
    } finally {
        release(dom);
    }
};

export const rewriteEach = function rewriteEach<T>(
    markup: string,
    items: readonly T[],
    edit: (document: Document, item: T) => void,
    write: (item: T, html: string) => void,
): void {
    const dom = new JSDOM(markup);
    try {
        for (const item of items) {
            edit(dom.window.document, item);
            write(item, dom.serialize());
        }
    } finally {
        release(dom);
    }
};

const settleTasks = async function settleTasks(): Promise<void> {
    await new Promise<void>((resolve) => {
        setImmediate(resolve);
    });
};

export const mapSettled = async function mapSettled<Item, Result>(
    items: readonly Item[],
    work: (item: Item) => Promise<Result> | Result,
): Promise<Result[]> {
    return items.reduce<Promise<Result[]>>(async (previous, item) => {
        const done = await previous;
        done.push(await work(item));
        await settleTasks();
        return done;
    }, Promise.resolve([]));
};

export const installDocument = function installDocument(site: string): (path: string) => void {
    const { window } = new JSDOM("", { url: site + SLASH });
    const globals: readonly (readonly [string, unknown])[] = [
        ["DOMParser", window.DOMParser],
        ["Element", window.Element],
        ["HTMLElement", window.HTMLElement],
        ["IntersectionObserver", IdleObserver],
        ["Node", window.Node],
        ["Text", window.Text],
        ["document", window.document],
        ["requestAnimationFrame", (callback: () => void): number => Number(setTimeout(callback, 0))],
        ["window", window],
    ];
    for (const [name, value] of globals) {
        Reflect.set(globalThis, name, value);
    }
    return (path) => {
        window.history.replaceState({}, "", path);
    };
};
```
