v0.12.1 — LayoutInfo type/runtime drift fix + accessor helpers
Patch release. No runtime behavior changed — the engine emits the exact same JSON it always did. The declared TypeScript types for renderDocumentWithLayout()'s output had drifted from that JSON in eight places, first flagged internally, then again by an external consumer's dogfood test. This release fixes them at the root and adds enforcement so it doesn't happen a third time.
Fixed
@formepdf/core — declared types now match runtime output.
ElementNodeTypeis now a closed literal union of the 30 nodeType values the engine actually emits (wasstring). Code likeif (element.nodeType === 'Heading')— silently wrong before, since reality is discrete'H1'–'H6'— is now a TypeScript compile error. Same treatment forElementKind(10 values) and 11 style enums (ElementFlexDirection,ElementJustifyContent,ElementAlignItems,ElementAlignContent,ElementFlexWrap,ElementFontStyle,ElementTextAlign,ElementTextDecoration,ElementTextTransform,ElementOverflow,ElementPosition). All exported for narrowing.ElementStyleInfoexpanded from 19 to 34 fields. Previously missing:alignContent,breakBefore,breakable,columnGap,rowGap,flexGrow,flexShrink,letterSpacing,minOrphanLines,minWidowLines,overflow,position,top/right/bottom/left,width/height,textDecoration,textTransform.textContentonElementInfois now typed asstring | null | undefined(wasstring?). Only populated onTextLineleaves — every non-TextLinenode emitsnullat runtime. The old declaration made consumers reach for the wrong node.- Every layout-time transform now documented explicitly on the
ElementInfoJSDoc:<Table>unwraps into siblingTableRownodes,<OrderedList>becomesList+ListItem+Lbl,<Fixed>splits intoFixedHeader/FixedFooter, headings are discreteH1–H6,<Text>block content is split intoTextLineleaves, inline elements don't get their own nodes,<PageBreak>produces no node.
Added
@formepdf/core/layout — new subpath export with stable accessor helpers. Additive; the raw ElementInfo tree is unchanged.
import {
getNodeText, getTextLines,
getHeadingLevel, getTableRows, getFixedRegions,
getListItems, getListItemMarker,
walkElements, findElements, findFirstElement,
isNodeType,
} from '@formepdf/core/layout';The helpers encapsulate each documented layout-time transform in a narrow, deliberately-maintained surface. Consumers get to say getNodeText(paragraph) instead of hand-rolling "walk TextLine children, gather textContent, join lines." When the transforms change in a future release, the helpers absorb the change — consumers ride through transparently.
See the Layout API docs for the full reference and the "prefer helpers unless you need raw" guidance.
⚠️ Arguable-break notes — type-tightening exposes latent bugs
This is technically a patch because the underlying bug was in our declarations, not consumer code. But if your TypeScript code compiled against @formepdf/core@0.12.0 and now fails against 0.12.1, one of these is almost certainly why:
style.flexDirection === 'row'— silently wrong before (runtime always emitted'Row', Rust-side PascalCase); now a compile error. Standard types-tighten territory. Fix: use the PascalCase values, now exported asElementFlexDirection. Same story for the other 10 style enums.element.textContentchanging fromstring | undefinedtostring | null | undefinedwill flag code that assumed non-null. Fix: use the newgetNodeText()helper (it handles this correctly by walkingTextLinechildren), or explicitly handlenull(which is what the runtime always emitted anyway).
Both cases were bugs before the release; TypeScript is now catching them for you. The CHANGELOG in @formepdf/core has more detail.
Enforcement
Three-directional invariant now enforced in @formepdf/core's own CI:
- Every emitted
nodeType/kind/ style enum → member of its declared union (runtime test) - Every declared
ElementNodeType→ appears in the rich fixture (coverage tripwire — catches "component shipped without structural coverage") - Every union member → present in the test file's key record (compile-time check — catches "union grew without updating the test")
This closes the drift risk at all three angles. Runtime drift trips one test; declaration-side gaps trip a compile error before the file even runs.
Full changelog
@formepdf/core— the substantive work
All other @formepdf/* npm packages (shared, react, svelte, preact, renderer, cli, hono, next, resend, mcp, sdk, tailwind, templates) got version-alignment bumps to 0.12.1. No functional changes.