@pyreon/connector-document — API Reference
Generated from
connector-document'ssrc/manifest.ts— the same source that powersllms.txtand MCPget_api. Do not edit this page by hand; edit the manifest. For the conceptual guide, see connector-document.
The bridge between Pyreon's component layer and the @pyreon/document rendering pipeline. extractDocumentTree(vnode) walks a Pyreon JSX tree, finds components carrying a _documentType marker (the 18 @pyreon/document-primitives, or your own marked components), resolves their _documentProps and $rocketstyle CSS-in-JS styles, and produces a serializable DocNode tree that @pyreon/document renders to PDF, DOCX, XLSX, email, Markdown, and the other output formats. The hot path is fast: real rocketstyle primitives expose their accumulated .attrs() chain as __rs_attrs, and the extractor runs that chain directly — no styled-wrapper invocation, no dimension resolution.
Features
extractDocumentTree(vnode, options?) — walk a Pyreon JSX tree into a DocNode tree
_documentType marker contract — rocketstyle
.statics()metadata or a direct static on plain componentsHoisted-attrs fast path — runs a rocketstyle primitive's __rs_attrs chain directly, no component invocation
Reactive accessors resolved at extraction time — re-extract for a fresh snapshot after signal changes
resolveStyles($rocketstyle, rootSize?, resolveVar?) — CSS-in-JS theme object → document ResolvedStyles
CSS value parsers: parseCssDimension / parseBoxModel / parseFontWeight / parseLineHeight
cssVariables-mode support — resolveVar inlines var(--…) references for PDF/DOCX targets
DocNode / DocChild / NodeType / ResolvedStyles re-exported from @pyreon/document
Complete example
A full, end-to-end usage of the package:
import { extractDocumentTree, resolveStyles } from '@pyreon/connector-document'
import { render } from '@pyreon/document'
import { DocDocument, DocHeading, DocText } from '@pyreon/document-primitives'
const vnode = (
<DocDocument title="Q4 Report" author="Acme Inc.">
<DocHeading level="h1">Summary</DocHeading>
<DocText>Revenue was up 12%.</DocText>
</DocDocument>
)
// Walk the JSX tree → format-agnostic DocNode tree
const docTree = extractDocumentTree(vnode)
const pdf = await render(docTree, 'pdf') // Buffer
const docx = await render(docTree, 'docx') // Buffer
const md = await render(docTree, 'markdown') // string
// Extraction is a SNAPSHOT — reactive accessors are resolved at extraction
// time, not subscribed. Re-extract after a signal change for a fresh tree:
const freshTree = extractDocumentTree(vnode)
// Under init({ cssVariables: true }), inline var(--…) values via resolveVar:
import { resolveModeVar } from '@pyreon/rocketstyle'
import { resolveCssVarReferences, themeToCssVars } from '@pyreon/unistyle'
const { registry } = themeToCssVars(theme)
const tree = extractDocumentTree(vnode, {
resolveVar: (v) => resolveCssVarReferences(resolveModeVar(v, mode), registry),
})
// Standalone style resolution — $rocketstyle theme object → document styles:
const styles = resolveStyles({ fontSize: '1.5rem', color: '#222', padding: '12px 16px' }, 16)
// → { fontSize: 24, color: '#222', padding: [12, 16] }Exports
| Symbol | Kind | Summary |
|---|---|---|
extractDocumentTree | function | Walk a Pyreon VNode tree and extract a DocNode tree for @pyreon/document. |
resolveStyles | function | Convert a rocketstyle $rocketstyle theme object into a ResolvedStyles object compatible with @pyreon/document. |
parseCssDimension | function | Parse a CSS dimension to a number: numbers pass through, "14px" → 14, "1.5rem" / "1.5em" → 1.5 × rootSize, `"12pt" |
parseBoxModel | function | Parse a CSS padding/margin shorthand to the document tuple format: 8 → 8, "8px 16px" → [8, 16], the 3-value shor |
parseFontWeight | function | Parse a CSS font-weight: numbers pass through, the keywords "normal" / "bold" pass through AS STRINGS, numeric strin |
parseLineHeight | function | Parse a CSS line-height to a plain number: numbers pass through (a unitless ratio like 1.5 stays 1.5), dimension str |
ExtractOptions | type | Options for extractDocumentTree. |
VarResolver | type | Maps a style value to a render-target-evaluable one. |
DocumentMarker | type | Marker interface: components carrying _documentType are extractable. |
DocNode | type | The format-agnostic document node — re-exported from @pyreon/document (along with DocChild = DocNode | string, the ` |
API
extractDocumentTree function
(vnode: unknown, options?: ExtractOptions) => DocNodeWalk a Pyreon VNode tree and extract a DocNode tree for @pyreon/document. For each vnode whose component carries a _documentType marker it reads the marker → DocNode.type, resolves _documentProps → DocNode.props (pre-resolved vnode props → rocketstyle __rs_attrs fast path → full component invocation as legacy fallback), resolves $rocketstyle via resolveStyles → DocNode.styles, and recurses into children. Transparent (children flatten into the parent, no node produced): unmarked components (invoked), DOM elements (div, span), <>…</> Fragments, and a component that returns a bare VNodeChild[] array. Function values in _documentProps and reactive accessor children are resolved (called) at extraction time. ALWAYS returns a DocNode — loose children are wrapped in { type: "document" }.
Parameters
| Parameter | Type | Description |
|---|---|---|
vnode | unknown | A Pyreon VNode (JSX result), or a zero-arg component/template function — functions are called and their result extracted. |
options? | ExtractOptions | rootSize (rem→px base, default 16), includeStyles (resolve $rocketstyle, default true), resolveVar (inline var(--…) values under cssVariables theming). |
Returns DocNode — The extracted document tree. Loose children (no marked root) are wrapped in a { type: "document" } node; a non-vnode input yields an empty document node.
Example
import { extractDocumentTree } from '@pyreon/connector-document'
import { render } from '@pyreon/document'
import { DocDocument, DocHeading, DocText } from '@pyreon/document-primitives'
const vnode = (
<DocDocument title={() => reportTitle()} author="Acme Inc.">
<DocHeading level="h1">Summary</DocHeading>
<DocText>{() => summaryText()}</DocText>
</DocDocument>
)
// Snapshot extraction — accessors read LIVE values at this moment
const tree = extractDocumentTree(vnode, { rootSize: 16, includeStyles: true })
const pdf = await render(tree, 'pdf')
// After a signal change, extract again for a fresh tree:
reportTitle.set('Q4 Report (final)')
const freshTree = extractDocumentTree(vnode)Common mistakes
Testing the extraction pipeline ONLY with hand-constructed mock vnodes that pre-attach
_documentProps(the pre-resolved path) — the real rocketstyle path (__rs_attrshoisted-attrs chain) is bypassed entirely. PR #197's silent metadata drop hid for the package's whole lifetime because no test ran a realh()primitive through extraction; always pair a mock-vnode test with a real-h()testExpecting extraction to SUBSCRIBE to signals — reactive accessor children and function-valued
_documentPropsare resolved ONCE per call; callextractDocumentTreeagain after a signal change to get a fresh treeUnder
init({ cssVariables: true }), forgettingresolveVar—$rocketstylevalues arevar(--…)reference strings PDF/DOCX/email cannot evaluate; composeresolveModeVar(@pyreon/rocketstyle) withresolveCssVarReferences(@pyreon/unistyle)Expecting a
DocNode | DocChild[] | nullreturn — the internal walker produces that union, but the public function ALWAYS returns aDocNode, wrapping loose children in{ type: "document", props: {}, children }Marking a non-rocketstyle component with
_documentTypeand relying on side effects in its body — the legacy fallback path INVOKES the component to find_documentProps; keep marked components pureExpecting browser-only CSS (
transition,cursor,display, animations) to reach the document —resolveStylesextracts only the propertiesResolvedStylessupports and silently drops the restAssuming
<>…</>grouping or a component returning multiple siblings via a Fragment (or a bare array) is a no-op — it is transparent (children flatten into the parent). This was a silent DROP before the 0.45.x fix: a fragment vnode matched no branch and its whole subtree vanished from the export with no error
See also: resolveStyles · ExtractOptions · DocumentMarker · @pyreon/document · @pyreon/document-primitives
resolveStyles function
(source: Record<string, unknown>, rootSize?: number, resolveVar?: VarResolver) => ResolvedStylesConvert a rocketstyle $rocketstyle theme object into a ResolvedStyles object compatible with @pyreon/document. Extracts typography (fontSize/fontFamily/fontWeight/fontStyle/textDecoration/color/backgroundColor/textAlign/lineHeight/letterSpacing), box model (padding/margin as tuples), border (radius/width/color/style), sizing (width/height/maxWidth — numeric when parseable, raw string like "100%" otherwise), and opacity. Everything else (transitions, cursor, display) is silently dropped. Dimensions parse via parseCssDimension (rem/em × rootSize, pt × 4/3). resolveVar inlines var(--…) string values up front for cssVariables-mode apps.
Parameters
| Parameter | Type | Description |
|---|---|---|
source | Record<string, unknown> | The $rocketstyle theme object (or any flat CSS-in-JS style object). |
rootSize? | number | Base font size for rem/em→px conversion. Default 16. |
resolveVar? | VarResolver | Optional value resolver that inlines var(--…) string values to raw values before parsing (cssVariables theming mode). |
Returns ResolvedStyles — Flat document-compatible style object; unsupported properties are absent.
Example
import { resolveStyles } from '@pyreon/connector-document'
const styles = resolveStyles(
{
fontSize: '1.5rem',
fontWeight: 'bold',
color: '#222',
padding: '12px 16px',
transition: 'all 0.2s', // silently dropped — not a document property
},
16,
)
// → { fontSize: 24, fontWeight: 'bold', color: '#222', padding: [12, 16] }Common mistakes
Expecting
fontWeight: 'bold'to resolve to700—parseFontWeightpasses'normal'/'bold'through as string literals (both validResolvedStylesvalues); only numeric strings become numbersExpecting
emto track the element's own font size — at extraction time there is no cascade, soemis treated likerem(multiplied byrootSize)Passing enum values outside the supported sets —
textAlignaccepts only left/center/right/justify,borderStyleonly solid/dashed/dotted,fontStyleonly normal/italic,textDecorationonly none/underline/line-through; anything else is droppedAssuming percentage sizing parses to a number —
width: "100%"is kept as the raw string (only px/rem/em/pt/unitless parse to numbers)
See also: extractDocumentTree · VarResolver · parseCssDimension · parseBoxModel
parseCssDimension function
(value: string | number | null | undefined, rootSize?: number) => number | undefinedParse a CSS dimension to a number: numbers pass through, "14px" → 14, "1.5rem" / "1.5em" → 1.5 × rootSize, "12pt" → 16 (pt × 4/3), unitless numeric strings parse. Anything else ("auto", percentages, calc/var expressions) returns undefined.
Example
parseCssDimension(14) // 14
parseCssDimension('14px') // 14
parseCssDimension('1.5rem', 16) // 24
parseCssDimension('12pt') // 16
parseCssDimension('auto') // undefined
parseCssDimension('50%') // undefinedCommon mistakes
Feeding a
var(--…)/calc(…)string — returnsundefined; inline it first via aVarResolver
See also: parseBoxModel · resolveStyles
parseBoxModel function
(value: string | number | undefined, rootSize?: number) => number | [number, number] | [number, number, number, number] | undefinedParse a CSS padding/margin shorthand to the document tuple format: 8 → 8, "8px 16px" → [8, 16], the 3-value shorthand "8px 16px 12px" expands to the CSS-equivalent 4-tuple [8, 16, 12, 16], and 4 values map 1/* zero-content: unhandled mdast node "textDirective" */. Each segment parses via parseCssDimension.
Example
parseBoxModel(8) // 8
parseBoxModel('8px 16px') // [8, 16]
parseBoxModel('8px 16px 12px') // [8, 16, 12, 16]
parseBoxModel('0.5rem 1rem', 16) // [8, 16]Common mistakes
One unparseable segment (
"8px auto") invalidates the WHOLE shorthand — the function returnsundefined, not a partial tuple
See also: parseCssDimension · resolveStyles
parseFontWeight function
(value: string | number | undefined) => 'normal' | 'bold' | number | undefinedParse a CSS font-weight: numbers pass through, the keywords "normal" / "bold" pass through AS STRINGS, numeric strings ("600") parse to numbers. Other keywords ("lighter", "bolder") return undefined.
Example
parseFontWeight(600) // 600
parseFontWeight('600') // 600
parseFontWeight('bold') // 'bold' (string, NOT 700)
parseFontWeight('bolder') // undefinedCommon mistakes
Expecting
'bold'→700/'normal'→400— the keywords pass through unchanged asResolvedStyles-valid string literals
See also: resolveStyles
parseLineHeight function
(value: string | number | undefined, rootSize?: number) => number | undefinedParse a CSS line-height to a plain number: numbers pass through (a unitless ratio like 1.5 stays 1.5), dimension strings parse via parseCssDimension ("24px" → 24, "1.5rem" → 24 with rootSize 16), and "normal" returns undefined. Note the return is a bare number — a unitless ratio and a px value are not distinguished in the type.
Example
parseLineHeight(1.5) // 1.5 (ratio, passes through)
parseLineHeight('1.5') // 1.5
parseLineHeight('24px') // 24
parseLineHeight('1.5rem', 16) // 24
parseLineHeight('normal') // undefinedCommon mistakes
Expecting a discriminated
{ ratio }/{ px }result — the return is a plainnumber | undefined; callers must know which semantic they fed in
See also: parseCssDimension · resolveStyles
ExtractOptions type
interface ExtractOptions { rootSize?: number; includeStyles?: boolean; resolveVar?: VarResolver }Options for extractDocumentTree. rootSize (default 16) is the rem→px base for style resolution; includeStyles (default true) toggles resolving $rocketstyle into DocNode.styles; resolveVar inlines var(--…) style values to raw values during extraction — required when the app runs under init({ cssVariables: true }), since PDF/DOCX/email targets cannot evaluate CSS custom properties.
Example
import { resolveModeVar } from '@pyreon/rocketstyle'
import { resolveCssVarReferences, themeToCssVars } from '@pyreon/unistyle'
const { registry } = themeToCssVars(theme)
const tree = extractDocumentTree(vnode, {
rootSize: 16,
includeStyles: true,
resolveVar: (v) => resolveCssVarReferences(resolveModeVar(v, mode), registry),
})See also: extractDocumentTree · VarResolver
VarResolver type
type VarResolver = (value: unknown) => unknownMaps a style value to a render-target-evaluable one. Under cssVariables theming, $rocketstyle values can be var(--…) reference strings; a resolver (compose resolveModeVar from @pyreon/rocketstyle with resolveCssVarReferences from @pyreon/unistyle) inlines them to raw values at extraction time. Only own STRING values are remapped; non-strings pass through unchanged.
Example
const resolveVar: VarResolver = (v) =>
resolveCssVarReferences(resolveModeVar(v, 'light'), registry)
const styles = resolveStyles(rocketstyleTheme, 16, resolveVar)See also: ExtractOptions · resolveStyles
DocumentMarker type
interface DocumentMarker { _documentType: NodeType }Marker interface: components carrying _documentType are extractable. Rocketstyle primitives set it via .statics({ _documentType: "heading" }) (the extractor reads it from the component's .meta); plain function components set it as a direct static property. @pyreon/document-primitives ships 18 pre-marked primitives; follow the same convention for custom ones.
Example
import type { VNodeChild } from '@pyreon/core'
// Plain-function marked component (non-rocketstyle):
function Callout(props: { children?: VNodeChild }) {
return <div _documentProps={{}}>{props.children}</div>
}
Callout._documentType = 'section'Common mistakes
Forgetting the marker — an unmarked component is TRANSPARENT: the extractor invokes it and flattens its children into the parent instead of producing a node
See also: extractDocumentTree · @pyreon/document-primitives
DocNode type
interface DocNode { type: NodeType; props: Record<string, unknown>; children: DocChild[]; styles?: ResolvedStyles }The format-agnostic document node — re-exported from @pyreon/document (along with DocChild = DocNode | string, the NodeType union of 18 node kinds, and ResolvedStyles) so extracted trees stay assignment-compatible across the package boundary without a duplicate type identity.
Example
import type { DocChild, DocNode, NodeType, ResolvedStyles } from '@pyreon/connector-document'
const node: DocNode = {
type: 'heading',
props: { level: 1 },
children: ['Summary'],
}See also: extractDocumentTree · @pyreon/document
Package-level notes
Note: Extraction is a snapshot — reactive accessor children and function-valued
_documentPropsare resolved (called) at extraction time, not subscribed; re-runextractDocumentTreeafter a signal change to export the live state.
Marker contract: A component is extractable when it carries
_documentType— via rocketstyle.statics()(read from.meta) or as a direct static on a plain function. Unmarked components, DOM elements, and<>…</>fragments are transparent: their children flatten into the parent.
Test with real primitives: Mock vnodes that pre-attach
_documentPropsbypass the rocketstyle__rs_attrsfast path — the PR #197 silent-metadata-drop hid exactly there. Pair every mock-vnode test with a real-h()primitive test (see.claude/rules/test-environment-parity.md).
cssVariables mode: Under
init({ cssVariables: true }),$rocketstylevalues arevar(--…)strings that PDF/DOCX/email cannot evaluate — passExtractOptions.resolveVar(composeresolveModeVar+resolveCssVarReferences) to inline them at extraction time.