domain/converters/tension.converter.ts

domain/converters/tension.converter.ts is a file in Bane's Lab Site. 149 lines of code and 16 definitions.

import {
    CONTRACT_LABEL,
    FIELD_SEPARATOR,
    FLOW_ARROW,
    LAYER_LABEL,
    LIST_SEPARATOR,
    NONE_LABEL,
    RULE_LABEL,
} from "#configuration/strings/ontology.strings";
import {
    DERIVED_LABEL,
    EXPLICIT_LABEL,
    INCOMING_LABEL,
    MECHANISM_LABEL,
    MEMBERSHIP_INTRO,
    MEMBERSHIP_TITLE,
    MEMBER_CATEGORIES_LABEL,
    OUTGOING_LABEL,
    RESOLUTION_INTRO,
    RESOLUTION_TITLE,
    SCOPE_A_LABEL,
    SCOPE_B_LABEL,
    TOPOLOGY_CAPTION,
    TOPOLOGY_INTRO,
    TOPOLOGY_SECTION_TITLE,
    VERSUS_LABEL,
} from "#configuration/strings/tension.strings";
import { EDGE_LABEL_OPEN, EDGE_SOLID } from "#configuration/constants/diagram.constants";
import {
    LAYER_ANCHOR,
    MEMBERSHIP_SECTION_ID,
    RESOLUTION_SECTION_ID,
    TENSION_ANCHOR,
    TOPOLOGY_SECTION_ID,
} from "#core/ids/ontology.ids";
import type { LayerEdgeView, LayerNodeView, LayerView, TensionView } from "#types/ontology.types";
import type { Section, Subsection } from "#types/document.types";
import { chips, fields, glossary, labeled, linkList, linkOf, linked, row } from "#domain/converters/ontology.converter";
import { diagram, edgeLine, node } from "#domain/converters/ontology.graph.converter";
import { LAYER_SECTION_ICON } from "#configuration/icons/ontology.icons";
import { SPACE } from "#configuration/constants/document.constants";

const HEADER = "flowchart TB";
const LAYER_ANCHOR_REF = "layer:";

const topologyDiagram = function topologyDiagram(layers: LayerView): string {
    const nodes = layers.nodes.map((layer) => node(LAYER_ANCHOR_REF + layer.id, layer.label));
    const edges = layers.topology.map((edge) =>
        edgeLine(
            edge.from.ref ?? edge.from.label,
            EDGE_LABEL_OPEN + edge.kind.label + EDGE_SOLID,
            edge.to.ref ?? edge.to.label,
        ),
    );
    return diagram(HEADER, [...nodes, ...edges]);
};

const edgeText = function edgeText(edges: readonly LayerEdgeView[], pick: (edge: LayerEdgeView) => string): string {
    return edges.length === 0
        ? NONE_LABEL
        : edges.map((edge) => linkOf(edge.kind) + FLOW_ARROW + pick(edge)).join(LIST_SEPARATOR);
};

const layerSubsection = function layerSubsection(layer: LayerNodeView): Subsection {
    return {
        blocks: [
            chips([linked(CONTRACT_LABEL, layer.contract)]),
            glossary([
                row(MEMBER_CATEGORIES_LABEL, linkList(layer.members)),
                row(
                    OUTGOING_LABEL,
                    edgeText(layer.outgoing, (edge) => linkOf(edge.to)),
                ),
                row(
                    INCOMING_LABEL,
                    edgeText(layer.incoming, (edge) => linkOf(edge.from)),
                ),
            ]),
        ],
        id: LAYER_ANCHOR + layer.id,
        title: layer.label,
    };
};

const topologySection = function topologySection(layers: LayerView): Section {
    return {
        blocks: [{ caption: TOPOLOGY_CAPTION, kind: "mermaid", text: topologyDiagram(layers) }],
        icon: LAYER_SECTION_ICON,
        id: TOPOLOGY_SECTION_ID,
        intro: TOPOLOGY_INTRO,
        subsections: layers.nodes.map(layerSubsection),
        title: TOPOLOGY_SECTION_TITLE,
    };
};

const membershipSection = function membershipSection(layers: LayerView): Section {
    return {
        icon: LAYER_SECTION_ICON,
        id: MEMBERSHIP_SECTION_ID,
        intro: MEMBERSHIP_INTRO,
        subsections: [
            {
                blocks: [
                    glossary(
                        layers.membership.map((entry) =>
                            row(
                                entry.category.label,
                                fields([
                                    linkOf(entry.category),
                                    entry.layer === null ? NONE_LABEL : linkOf(entry.layer),
                                ]),
                            ),
                        ),
                    ),
                ],
                title: MEMBERSHIP_TITLE,
            },
        ],
        title: MEMBERSHIP_TITLE,
    };
};

const tensionTitle = function tensionTitle(tension: TensionView): string {
    return tension.a.label + SPACE + VERSUS_LABEL + SPACE + tension.b.label;
};

const scopeText = function scopeText(side: TensionView["a"], scope: TensionView["scopeA"]): string {
    const layer = labeled(LAYER_LABEL, linkOf(scope)) ?? "";
    return linkOf(side) + FIELD_SEPARATOR + layer;
};

const tensionSubsection = function tensionSubsection(tension: TensionView): Subsection {
    return {
        blocks: [
            chips([linked(MECHANISM_LABEL, tension.mechanism), tension.explicit ? EXPLICIT_LABEL : DERIVED_LABEL]),
            glossary([
                row(SCOPE_A_LABEL, scopeText(tension.a, tension.scopeA)),
                row(SCOPE_B_LABEL, scopeText(tension.b, tension.scopeB)),
                row(RULE_LABEL, tension.rule),
            ]),
        ],
        id: TENSION_ANCHOR + tension.id,
        title: tensionTitle(tension),
    };
};

const tensionSection = function tensionSection(layers: LayerView): Section {
    const ordered = [...layers.resolutions].sort((left, right) => Number(right.explicit) - Number(left.explicit));
    return {
        icon: LAYER_SECTION_ICON,
        id: RESOLUTION_SECTION_ID,
        intro: RESOLUTION_INTRO,
        subsections: ordered.map(tensionSubsection),
        title: RESOLUTION_TITLE,
    };
};

export const tensionSections = function tensionSections(layers: LayerView): readonly Section[] {
    return [topologySection(layers), membershipSection(layers), tensionSection(layers)];
};