packages feed

salmon-ops-0.2.0.0: ui/vendor/tramaj-js/node.js

/**
 * `Node`/`NodeAttribute` and the strict `specs/node-json.md` encode/decode,
 * hand-written rather than derived so the "a decoder MUST reject ... MUST NOT
 * infer a missing field from a default" rules are enforced exactly.
 */
import { hasField, isJsonObject, jsonObjectFrom, JsonNumberError, normalizeNumbers, } from "./json.js";
export function noAnnotations() {
    return {};
}
export function textNode(value, annotations = noAnnotations()) {
    return { t: "text", value, annotations };
}
export function elementNode(tag, attributes, value, children, annotations = noAnnotations()) {
    return { t: "element", tag, attributes, value, children, annotations };
}
export function fragmentNode(children, annotations = noAnnotations()) {
    return { t: "fragment", children, annotations };
}
function annotationsToJson(anns) {
    return jsonObjectFrom(Object.keys(anns).map((k) => [k, anns[k]]));
}
export function nodeToJson(n) {
    switch (n.t) {
        case "text":
            return { type: "text", value: n.value, annotations: annotationsToJson(n.annotations) };
        case "element":
            return {
                type: "element",
                tag: n.tag,
                attributes: n.attributes.map(nodeAttributeToJson),
                value: n.value,
                children: n.children.map(nodeToJson),
                annotations: annotationsToJson(n.annotations),
            };
        case "fragment":
            return {
                type: "fragment",
                children: n.children.map(nodeToJson),
                annotations: annotationsToJson(n.annotations),
            };
    }
}
export function nodeAttributeToJson(a) {
    return a.t === "attribute"
        ? { kind: "attribute", name: a.name, value: a.value }
        : { kind: "action", event: a.event, key: a.key, payload: a.payload };
}
export class NodeDecodeError extends Error {
    name = "NodeDecodeError";
}
function req(what, field, obj) {
    if (!hasField(obj, field)) {
        throw new NodeDecodeError(`${what}: missing required field ${JSON.stringify(field)}`);
    }
    return obj[field];
}
/**
 * A `Value` field (`specs/node-json.md` *Numbers*, *Decoding*): its numbers
 * are typed by `json.ts`'s classification and brought to canonical form, and
 * one outside the value domain is rejected rather than rounded.
 */
function reqValue(what, field, obj) {
    const v = req(what, field, obj);
    try {
        return normalizeNumbers(v);
    }
    catch (e) {
        if (!(e instanceof JsonNumberError))
            throw e;
        throw new NodeDecodeError(`${what}: field ${JSON.stringify(field)}: ${e.message}`);
    }
}
function reqString(what, field, obj) {
    const v = req(what, field, obj);
    if (typeof v !== "string") {
        throw new NodeDecodeError(`${what}: field ${JSON.stringify(field)} must be a string`);
    }
    return v;
}
function reqArray(what, field, obj) {
    const v = req(what, field, obj);
    if (!Array.isArray(v)) {
        throw new NodeDecodeError(`${what}: field ${JSON.stringify(field)} must be an array`);
    }
    return v;
}
function reqAnnotations(obj) {
    const v = req("node", "annotations", obj);
    if (!isJsonObject(v)) {
        throw new NodeDecodeError('node: field "annotations" must be an object');
    }
    return jsonObjectFrom(Object.keys(v).map((k) => [k, v[k]]));
}
export function nodeFromJson(v) {
    if (!isJsonObject(v))
        throw new NodeDecodeError("expected a JSON object for a node");
    const type = reqString("node", "type", v);
    switch (type) {
        case "text":
            return { t: "text", value: reqValue("text node", "value", v), annotations: reqAnnotations(v) };
        case "element":
            return {
                t: "element",
                tag: reqString("element node", "tag", v),
                attributes: reqArray("element node", "attributes", v).map(nodeAttributeFromJson),
                value: reqValue("element node", "value", v),
                children: reqArray("element node", "children", v).map(nodeFromJson),
                annotations: reqAnnotations(v),
            };
        case "fragment":
            return {
                t: "fragment",
                children: reqArray("fragment node", "children", v).map(nodeFromJson),
                annotations: reqAnnotations(v),
            };
        default:
            throw new NodeDecodeError(`unknown node type: ${JSON.stringify(type)}`);
    }
}
export function nodeAttributeFromJson(v) {
    if (!isJsonObject(v))
        throw new NodeDecodeError("expected a JSON object for a node attribute");
    const kind = reqString("node attribute", "kind", v);
    switch (kind) {
        case "attribute":
            return {
                t: "attribute",
                name: reqString("attribute", "name", v),
                value: reqValue("attribute", "value", v),
            };
        case "action":
            return {
                t: "action",
                event: reqString("action", "event", v),
                key: reqString("action", "key", v),
                payload: reqValue("action", "payload", v),
            };
        default:
            throw new NodeDecodeError(`unknown node attribute kind: ${JSON.stringify(kind)}`);
    }
}
/** Rewrites every action reachable in a tree, leaving everything else untouched. */
export function mapActions(n, f) {
    switch (n.t) {
        case "text":
            return n;
        case "element":
            return {
                ...n,
                attributes: n.attributes.map((a) => a.t === "action" ? f(a.event, a.key, a.payload) : a),
                children: n.children.map((c) => mapActions(c, f)),
            };
        case "fragment":
            return { ...n, children: n.children.map((c) => mapActions(c, f)) };
    }
}