oconv-ir/v1→ structured-markdown profile v1, the frozen wire contract.
Module oconvIrToMd | Source packages/front/office/oconv/src/write/ir-to-md.js | Deps oconvIr, md, mdNode | Worker-safe yes
Resolve
import { ModuleRuntime } from '@awacloud/fw/core/runtime.js';
import { fw_require, modules } from '@awacloud/oconv';
const runtime = new ModuleRuntime();
runtime.registerAll(fw_require);
runtime.registerAll(modules);
const oconvIrToMd = runtime.resolve('oconvIrToMd');
In practice this module is reached through the oconv facade
(oconv.toMd(...)/oconv.fromMd for the read direction, none for write —
toMd is the ONLY documented facade member for markdown output); resolving
it directly is for tooling that wants the profile-v1 writer in isolation.
API
| Member | Signature | Returns | Throws |
|---|---|---|---|
irToMd |
(ir: object, meta: OconvMeta, opts?: object) => OconvMdResult |
{markdown, anchors, lossy, losses, assets} |
— (no input validation; a malformed ir produces malformed output rather than throwing — the caller-facing facade oconv.toMd validates upstream) |
OconvMeta (all caller-supplied provenance — this module reads no clock and
computes no hash): sourceFormat, sourceName, sourceBytes,
sourceSha256, convertedAt, converter, converterVersion, engine,
optional losses (reader losses, merged FIRST into the returned ledger).
opts is reserved — v1 defines no key on it.
OconvMdResult: markdown (front matter + rendered body), anchors
({level, anchor}[], document order), lossy (losses.length > 0),
losses (reader losses then this module's own, document order), assets
({kind: 'image', name, bytes?}[], deduplicated by name).
Examples
Convert a two-block IR document
// Build the IR with the oconvIr helpers: `node` fills each node's frozen
// defaults, so the tree passes `oconvIr.validate` (a bare
// `{ kind: 'run', text }` literal does not).
const { node, doc } = runtime.resolve('oconvIr');
const ir = doc([
node('heading', { level: 1 }, [node('run', { text: 'Title' })]),
node('paragraph', {}, [
node('run', { text: 'Hello ' }),
node('run', { text: 'world', bold: true })
])
]);
const meta = {
sourceFormat: 'docx', sourceName: 'x.docx', sourceBytes: 10,
sourceSha256: 'abc123', convertedAt: '2026-09-15T00:00:00Z',
converter: 'oconv', converterVersion: '1.0.0', engine: 'bun'
};
const { markdown, anchors, lossy } = oconvIrToMd.irToMd(ir, meta);
// markdown starts with the YAML front matter, then "# Title\n\nHello **world**\n"
// anchors deep-equals [{ level: 1, anchor: 'title' }]
// lossy === false
Executed against the live package (2026-10-03): the front matter opens
---\nprofile: v1\nir: oconv-ir/v1\n…, the body renders # Title followed
by Hello **world**, anchors is exactly [{ level: 1, anchor: 'title' }]
and lossy is false.
Notes
- Front matter key order is exact and frozen — see
profile-v1 for the full key-by-key reference.
anchors:,losses:(only present whenlossy: true) andassets:are each omitted entirely when empty, never emitted as[]. - The body is never assembled as raw text. This module builds an
@awacloud/mdAST with themdNodefactory and serialises it with the resolvedmdmodule'srenderMarkdown(ast)— escaping, table padding and fence widths are@awacloud/md's job (src/write/ir-to-md.js:8-11). - The asset manifest's
bytesfield is read from the shape the reader really produces.imageNodepopulatesassets[].bytesfrom an IRimagenode'sescapes.docx.bytes— the nested fielddocx-to-ir.jsstores for an embedded image. The array is passed through by reference (the sameUint8Array, never copied) and is never serialized into the YAML front matter. A flatescapes.bytesis not read: no shipped reader produces it, so the single nested shape is the contract. The real-corpus legpoi-with-gif.docxintests/fidelity.integration.test.jspins it end to end:toMdreturns one asset (Grafik 1) whosebytesis a 6554-byteUint8Array, withlossystillfalse. - A
run.link === ''(present but empty) recordslink/target-missingand emits the run unlinked; arun.link === undefinedis a plain run, no loss. See the loss matrix for the published Preserved/Degraded/Dropped classification ofblock/droppedandlink/target-missing. - A table cell's block content is flattened to inline text (paragraphs and
headings joined by a space); a heading flattened inside a cell produces no
#-heading and contributes no anchor. - Capture-free by contract (
fw/no-factory-capture): the anchor-slug algorithm is re-declared inside this module's factory rather than imported from./anchors.js, and a drift test pins the two copies identical. optsis accepted but read nowhere (void optsin the source) — v1 defines no writer option.
See also
ir-to-docx/ir-to-odt/ir-to-pdf— the other three IR writers.- Structured-markdown profile v1 — the full wire contract this module emits.
- Loss matrix — published fidelity classification.