# core/persistence/catalog.persistence.ts

> 52 lines of code and 15 definitions.

Tree: GovLab Quality
Language: typescript
Layer: infrastructure
Canonical: https://banes-lab.com/anatomy/quality#file-quality-core-persistence-catalog-persistence-ts
Source text: https://banes-lab.com/source/quality/core/persistence/catalog.persistence.ts.txt

Listed in [core/persistence](https://banes-lab.com/api/source/quality/core/persistence.md), before [core/persistence/comment.persistence.ts](https://banes-lab.com/source/quality/core/persistence/comment.persistence.ts.md).

## Definitions

- `writeProduct` (lexical_declaration, line 33, exported)
- `writeKnobs` (lexical_declaration, line 48, exported)
- `productFile` (lexical_declaration, line 29, exported)
- `knobFile` (lexical_declaration, line 38)
- `readKnobs` (lexical_declaration, line 42, exported)
- `SORT_LOCALE` (lexical_declaration, line 10)
- `PRODUCT_SUFFIX` (lexical_declaration, line 11)
- `KNOB_FILE` (lexical_declaration, line 12)
- `catalogWriter` (lexical_declaration, line 14, exported)
- `base` (lexical_declaration, line 15, exported)
- `rules` (lexical_declaration, line 34, exported)
- `file` (lexical_declaration, line 43, exported)
- `parsed` (lexical_declaration, line 44, exported)
- `merged` (lexical_declaration, line 52, exported)
- `sorted` (lexical_declaration, line 53, exported)

## Contained in

- [core/persistence](https://banes-lab.com/anatomy/quality/folder-quality-core-persistence.md)

## Used by

- [core/coordinators/catalog.coordinator.ts](https://banes-lab.com/source/quality/core/coordinators/catalog.coordinator.ts.md)
- [core/steps/knob.step.ts](https://banes-lab.com/source/quality/core/steps/knob.step.ts.md)

## Linked from

- [core/coordinators](https://banes-lab.com/anatomy/quality/folder-quality-core-coordinators.md)
- [core/persistence](https://banes-lab.com/anatomy/quality/folder-quality-core-persistence.md)
- [core/steps](https://banes-lab.com/anatomy/quality/folder-quality-core-steps.md)

## Source

```typescript
import type { CatalogProduct, CatalogWriter, KnobEntry } from "#types/catalog.types";
import { existsSync, readFileSync } from "node:fs";
import { writeCanonicalJson, writeCanonicalText, writeGeneratedMarkdown } from "@govlab/canonical-write";
import { absolutePath } from "@ssot/paths";
import { byRuleId } from "#core/comparators/rule.comparator";
import { govlabPrettierConfig } from "#core/adapters/tool.prettier.adapter";
import { isRecord } from "#core/selectors/record.selector";
import path from "node:path";

const SORT_LOCALE = "en";
const PRODUCT_SUFFIX = ".generated.json";
const KNOB_FILE = "knobs.generated.json";

export const catalogWriter = async function catalogWriter(): Promise<CatalogWriter> {
    const base = await govlabPrettierConfig(process.cwd());
    return {
        json: async (file, data): Promise<void> => {
            await writeCanonicalJson(file, data, base);
        },
        markdown: async (file, text): Promise<void> => {
            await writeGeneratedMarkdown(file, text, base);
        },
        text: async (file, text): Promise<void> => {
            await writeCanonicalText(file, text, base);
        },
    };
};

export const productFile = function productFile(source: string): string {
    return path.join(absolutePath("govlab.quality.catalog.generated"), `${source}${PRODUCT_SUFFIX}`);
};

export const writeProduct = async function writeProduct(writer: CatalogWriter, product: CatalogProduct): Promise<void> {
    const rules = product.rules.toSorted(byRuleId);
    await writer.json(productFile(product.source), { rules, summary: product.summary });
};

const knobFile = function knobFile(): string {
    return absolutePath("govlab.quality.generated", KNOB_FILE);
};

export const readKnobs = function readKnobs(): Record<string, unknown> {
    const file = knobFile();
    const parsed: unknown = existsSync(file) ? JSON.parse(readFileSync(file, "utf8")) : {};
    return isRecord(parsed) ? parsed : {};
};

export const writeKnobs = async function writeKnobs(
    writer: CatalogWriter,
    refreshed: ReadonlyMap<string, KnobEntry[]>,
): Promise<void> {
    const merged = { ...readKnobs(), ...Object.fromEntries(refreshed) };
    const sorted = Object.fromEntries(
        Object.keys(merged)
            .toSorted((a, b) => a.localeCompare(b, SORT_LOCALE))
            .map((name) => [name, merged[name]]),
    );
    await writer.json(knobFile(), sorted);
};
```
