// construct-render-4.ts import { $RENDER } from "../../../core/constants.js"; import { make_string } from "../../../core/stringify.js"; import { serialize_hson } from "../serializers/serialize-hson.js"; import { serialize_json } from "../serializers/serialize-json.js"; import type { TransformFrameRender, TransformHsonSerialize, TransformJsonValue, TransformOutputRenderFormat, TransformSerialize, } from "../transform.types.js"; import { ROOT_TAG } from "../../../core/constants.js"; import { detach_hson_root_value } from "../utils/node-utils/detach-hson-root-value.js"; import { admit_projected_value } from "../../../core/projected-value-admission.js"; import { materialize_projected_value } from "../../../core/projected-value-materialization.js"; import { sha256_text } from "../sha256.js"; /** * Hson pipeline, stage 4: finalize the selected output. * * This is the terminal stage of the transformer chain. The incoming context * already contains: * - the chosen render format, * - the materialized representation on the frame, * - and any formatting options applied in stage 3. * * This stage exposes the final data operations: * - `serialize()` → string output in the chosen format * - JSON `value()` → the in-memory JsonValue projection * * Hson and HTML use serialization-only finalizers. Canonical graph access is * handled uniformly by the source constructor's `toNode()` terminal. * * LiveTree creation is not part of this final render stage. * * @param context - Render context containing the frame and chosen format. * @returns Stage-4 terminal render API. */ function serialize_render(context: TransformFrameRender): string { const { frame, output } = context; switch (output) { case $RENDER.JSON: if (frame.json === undefined) throw new Error("serialize(): frame is missing JSON data"); // Text emission must retain the canonical graph's property sequence. // The public `.value()` projection is an ordinary JavaScript value and // cannot carry source order for integer-index property names. return serialize_json(frame.node); case $RENDER.HTML: if (frame.html == null) throw new Error("serialize(): frame is missing HTML data"); return typeof frame.html === "string" ? frame.html : make_string(frame.html); } } /** Hson output is serialization-only; graph access belongs to source `.toNode()`. */ export function construct_hson_render_4( context: TransformFrameRender<(typeof $RENDER)["Hson"]>, ): TransformHsonSerialize { const serialize = () => { const origin = context.frame.meta?.origin; const parserOwnsRoot = origin === "json" || origin === "html"; const node = parserOwnsRoot && context.frame.node.$_tag === ROOT_TAG ? detach_hson_root_value(context.frame.node) : context.frame.node; return serialize_hson(node, { noBreak: context.frame.options?.noBreak ?? false, noQuid: context.frame.options?.noQuid ?? false, }); }; return { serialize, sha256: () => sha256_text(serialize()), }; } export function construct_html_render_4( context: TransformFrameRender<(typeof $RENDER)["HTML"]>, ): TransformSerialize { const serialize = () => serialize_render(context); return { serialize, sha256: () => sha256_text(serialize()), }; } export function construct_json_render_4( context: TransformFrameRender<(typeof $RENDER)["JSON"]>, ): TransformJsonValue { const serialize = () => serialize_render(context); return { serialize, sha256: () => sha256_text(serialize()), value: () => { if (context.frame.json === undefined) { throw new Error("value(): frame is missing JSON data"); } return materialize_projected_value(admit_projected_value(context.frame.json)); }, }; }