# core/registries/manifest.registry.ts

> 100 lines of code and 30 definitions.

Tree: GovLab Docs
Language: typescript
Layer: infrastructure
Canonical: https://banes-lab.com/anatomy/docs#file-docs-core-registries-manifest-registry-ts
Source text: https://banes-lab.com/source/docs/core/registries/manifest.registry.ts.txt

Listed in [core/registries](https://banes-lab.com/api/source/docs/core/registries.md).

## Definitions

- `directDeps` (lexical_declaration, line 23)
- `matchesQuery` (lexical_declaration, line 53)
- `transitiveRequires` (lexical_declaration, line 57)
- `hasDomain` (lexical_declaration, line 34)
- `validateAll` (method_definition, line 88, exported)
- `constructor` (method_definition, line 77, exported)
- `create` (method_definition, line 83, exported)
- `requires` (method_definition, line 94, exported)
- `query` (method_definition, line 98, exported)
- `CriterionCheck` (type_alias_declaration, line 12)
- `VISIBILITY_KEY` (lexical_declaration, line 14)
- `PRIVATE_FLAG` (lexical_declaration, line 15)
- `HIDDEN_FLAG` (lexical_declaration, line 16)
- `PUBLIC_SCOPE` (lexical_declaration, line 17)
- `shortName` (lexical_declaration, line 19)
- `deps` (lexical_declaration, line 24)
- `visibilityFlag` (lexical_declaration, line 30)
- `CRITERIA_CHECKS` (lexical_declaration, line 38)
- `found` (lexical_declaration, line 61)
- `stack` (lexical_declaration, line 62)
- `dep` (lexical_declaration, line 63)
- `ManifestRegistry` (class_declaration, line 72, exported)
- `directMap` (public_field_definition, line 73, exported)
- `modules` (public_field_definition, line 74, exported)
- `plugins` (public_field_definition, line 75, exported)
- `[corePlugins, userPlugins]` (lexical_declaration, line 84, exported)
- `catalog` (method_definition, line 102, exported)
- `entryFor` (method_definition, line 109, exported)
- `entry` (lexical_declaration, line 110, exported)
- `context` (lexical_declaration, line 111, exported)

## Contained in

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

## Uses

- [configuration/strings/manifest.strings.ts](https://banes-lab.com/source/docs/configuration/strings/manifest.strings.ts.md)
- [core/loaders/manifest.loader.ts](https://banes-lab.com/source/docs/core/loaders/manifest.loader.ts.md)
- [core/predicates/record.predicate.ts](https://banes-lab.com/source/docs/core/predicates/record.predicate.ts.md)
- [core/selectors/record.selector.ts](https://banes-lab.com/source/docs/core/selectors/record.selector.ts.md)
- [core/validators/diagram.validator.ts](https://banes-lab.com/source/docs/core/validators/diagram.validator.ts.md)
- [core/validators/manifest.validator.ts](https://banes-lab.com/source/docs/core/validators/manifest.validator.ts.md)

## Linked from

- [configuration/strings](https://banes-lab.com/anatomy/docs/folder-docs-configuration-strings.md)
- [core/loaders](https://banes-lab.com/anatomy/docs/folder-docs-core-loaders.md)
- [core/predicates](https://banes-lab.com/anatomy/docs/folder-docs-core-predicates.md)
- [core/registries](https://banes-lab.com/anatomy/docs/folder-docs-core-registries.md)
- [core/selectors](https://banes-lab.com/anatomy/docs/folder-docs-core-selectors.md)
- [core/validators](https://banes-lab.com/anatomy/docs/folder-docs-core-validators.md)

## Source

```typescript
import type { CatalogEntry, ManifestModule, ManifestPlugin, PluginContext, QueryCriteria } from "#types/manifest.types";
import { arrayField, recordField, stringField } from "#core/selectors/record.selector";
import { loadPlugins, loadUserManifestPlugins } from "#core/loaders/section.loader";
import type { Manifest } from "#types/readme.types";
import { PACKAGE_SCOPE } from "#configuration/constants/manifest.constants";
import { ROOT } from "@ssot/paths";
import { discoverModules } from "#core/loaders/manifest.loader";
import { isPlainRecord } from "#core/predicates/record.predicate";
import { moduleError } from "#configuration/strings/manifest.strings";
import { validateManifest } from "#core/validators/manifest.validator";

type CriterionCheck = (manifest: Manifest, module: ManifestModule, criteria: QueryCriteria) => boolean;

const VISIBILITY_KEY = "visibility";
const PRIVATE_FLAG = "private";
const HIDDEN_FLAG = "hidden";
const PUBLIC_SCOPE = "public";

const shortName = function shortName(scoped: string): string {
    return scoped.startsWith(PACKAGE_SCOPE) ? scoped.slice(PACKAGE_SCOPE.length) : scoped;
};

const directDeps = function directDeps(pkg: ManifestModule["pkg"]): string[] {
    const deps = pkg["dependencies"];
    return Object.keys(isPlainRecord(deps) ? deps : {})
        .filter((dep) => dep.startsWith(PACKAGE_SCOPE))
        .map(shortName);
};

const visibilityFlag = function visibilityFlag(manifest: Manifest, flag: string): boolean {
    return recordField(manifest, VISIBILITY_KEY)?.[flag] === true;
};

const hasDomain = function hasDomain(manifest: Manifest, domain: string): boolean {
    return arrayField(manifest, "domains").some((entry) => isPlainRecord(entry) && entry["meta"] === domain);
};

const CRITERIA_CHECKS: readonly CriterionCheck[] = [
    (_manifest, module, criteria) => criteria.group === undefined || module.group === criteria.group,
    (manifest, _module, criteria) => criteria.maturity === undefined || manifest["maturity"] === criteria.maturity,
    (manifest, module, criteria) =>
        criteria.ecosystem === undefined ||
        (stringField(manifest, "ecosystem") ?? module.ecosystemHint) === criteria.ecosystem,
    (manifest, _module, criteria) =>
        criteria.capability === undefined || arrayField(manifest, "capabilities").includes(criteria.capability),
    (manifest, _module, criteria) => criteria.domain === undefined || hasDomain(manifest, criteria.domain),
    (manifest, _module, criteria) => criteria.visibility !== PRIVATE_FLAG || visibilityFlag(manifest, PRIVATE_FLAG),
    (manifest, _module, criteria) =>
        criteria.visibility !== PUBLIC_SCOPE ||
        (!visibilityFlag(manifest, PRIVATE_FLAG) && !visibilityFlag(manifest, HIDDEN_FLAG)),
];

const matchesQuery = function matchesQuery(module: ManifestModule, criteria: QueryCriteria): boolean {
    return CRITERIA_CHECKS.every((check) => check(module.manifest, module, criteria));
};

const transitiveRequires = function transitiveRequires(
    slug: string,
    directMap: ReadonlyMap<string, string[]>,
): string[] {
    const found = new Set<string>();
    const stack = [...(directMap.get(slug) ?? [])];
    for (let dep = stack.pop(); dep !== undefined; dep = stack.pop()) {
        if (!found.has(dep) && dep !== slug) {
            found.add(dep);
            stack.push(...(directMap.get(dep) ?? []));
        }
    }
    return [...found].toSorted((left, right) => left.localeCompare(right));
};

export class ManifestRegistry {
    public readonly directMap: ReadonlyMap<string, string[]>;
    public readonly modules: readonly ManifestModule[];
    public readonly plugins: readonly ManifestPlugin[];

    public constructor(modules: readonly ManifestModule[], plugins: readonly ManifestPlugin[]) {
        this.modules = modules;
        this.plugins = plugins;
        this.directMap = new Map(modules.map((module) => [module.slug, directDeps(module.pkg)]));
    }

    public static async create(): Promise<ManifestRegistry> {
        const [corePlugins, userPlugins] = await Promise.all([loadPlugins(), loadUserManifestPlugins(ROOT)]);
        return new ManifestRegistry(discoverModules(), [...corePlugins, ...userPlugins]);
    }

    public validateAll(): string[] {
        return this.modules.flatMap((module) =>
            validateManifest(module.manifest, this.plugins).map((error) => moduleError(module.label, error)),
        );
    }

    public requires(slug: string): string[] {
        return transitiveRequires(slug, this.directMap);
    }

    public query(criteria: QueryCriteria = {}): ManifestModule[] {
        return this.modules.filter((module) => matchesQuery(module, criteria));
    }

    public catalog(): CatalogEntry[] {
        return this.modules
            .filter((module) => !this.plugins.some((plugin) => plugin.filter?.(module.manifest, module) === true))
            .map((module) => this.entryFor(module))
            .toSorted((left, right) => left.value.localeCompare(right.value));
    }

    private entryFor(module: ManifestModule): CatalogEntry {
        const entry: CatalogEntry = { category: module.group, value: module.slug };
        const context: PluginContext = { module, requires: (slug: string) => this.requires(slug) };
        for (const plugin of this.plugins) {
            plugin.contribute?.(module.manifest, entry, context);
        }
        return entry;
    }
}
```
