pyreon

@pyreon/connector-document — API Reference

Generated from connector-document's src/manifest.ts — the same source that powers llms.txt and MCP get_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 components

  • Hoisted-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

SymbolKindSummary
extractDocumentTreefunctionWalk a Pyreon VNode tree and extract a DocNode tree for @pyreon/document.
resolveStylesfunctionConvert a rocketstyle $rocketstyle theme object into a ResolvedStyles object compatible with @pyreon/document.
parseCssDimensionfunctionParse a CSS dimension to a number: numbers pass through, "14px" → 14, "1.5rem" / "1.5em" → 1.5 × rootSize, `"12pt"
parseBoxModelfunctionParse a CSS padding/margin shorthand to the document tuple format: 88, "8px 16px"[8, 16], the 3-value shor
parseFontWeightfunctionParse a CSS font-weight: numbers pass through, the keywords "normal" / "bold" pass through AS STRINGS, numeric strin
parseLineHeightfunctionParse a CSS line-height to a plain number: numbers pass through (a unitless ratio like 1.5 stays 1.5), dimension str
ExtractOptionstypeOptions for extractDocumentTree.
VarResolvertypeMaps a style value to a render-target-evaluable one.
DocumentMarkertypeMarker interface: components carrying _documentType are extractable.
DocNodetypeThe format-agnostic document node — re-exported from @pyreon/document (along with DocChild = DocNode | string, the `

API

extractDocumentTree function

(vnode: unknown, options?: ExtractOptions) => DocNode

Walk 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 _documentPropsDocNode.props (pre-resolved vnode props → rocketstyle __rs_attrs fast path → full component invocation as legacy fallback), resolves $rocketstyle via resolveStylesDocNode.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

ParameterTypeDescription
vnodeunknownA Pyreon VNode (JSX result), or a zero-arg component/template function — functions are called and their result extracted.
options?ExtractOptionsrootSize (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_attrs hoisted-attrs chain) is bypassed entirely. PR #197's silent metadata drop hid for the package's whole lifetime because no test ran a real h() primitive through extraction; always pair a mock-vnode test with a real-h() test

  • Expecting extraction to SUBSCRIBE to signals — reactive accessor children and function-valued _documentProps are resolved ONCE per call; call extractDocumentTree again after a signal change to get a fresh tree

  • Under init({ cssVariables: true }), forgetting resolveVar$rocketstyle values are var(--…) reference strings PDF/DOCX/email cannot evaluate; compose resolveModeVar (@pyreon/rocketstyle) with resolveCssVarReferences (@pyreon/unistyle)

  • Expecting a DocNode | DocChild[] | null return — the internal walker produces that union, but the public function ALWAYS returns a DocNode, wrapping loose children in { type: "document", props: {}, children }

  • Marking a non-rocketstyle component with _documentType and relying on side effects in its body — the legacy fallback path INVOKES the component to find _documentProps; keep marked components pure

  • Expecting browser-only CSS (transition, cursor, display, animations) to reach the document — resolveStyles extracts only the properties ResolvedStyles supports and silently drops the rest

  • Assuming <>…</> 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) => ResolvedStyles

Convert 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

ParameterTypeDescription
sourceRecord<string, unknown>The $rocketstyle theme object (or any flat CSS-in-JS style object).
rootSize?numberBase font size for rem/em→px conversion. Default 16.
resolveVar?VarResolverOptional 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 to 700parseFontWeight passes 'normal' / 'bold' through as string literals (both valid ResolvedStyles values); only numeric strings become numbers

  • Expecting em to track the element's own font size — at extraction time there is no cascade, so em is treated like rem (multiplied by rootSize)

  • Passing enum values outside the supported sets — textAlign accepts only left/center/right/justify, borderStyle only solid/dashed/dotted, fontStyle only normal/italic, textDecoration only none/underline/line-through; anything else is dropped

  • Assuming 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 | undefined

Parse 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%')       // undefined

Common mistakes

  • Feeding a var(--…) / calc(…) string — returns undefined; inline it first via a VarResolver

See also: parseBoxModel · resolveStyles


parseBoxModel function

(value: string | number | undefined, rootSize?: number) => number | [number, number] | [number, number, number, number] | undefined

Parse a CSS padding/margin shorthand to the document tuple format: 88, "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 returns undefined, not a partial tuple

See also: parseCssDimension · resolveStyles


parseFontWeight function

(value: string | number | undefined) => 'normal' | 'bold' | number | undefined

Parse 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') // undefined

Common mistakes

  • Expecting 'bold'700 / 'normal'400 — the keywords pass through unchanged as ResolvedStyles-valid string literals

See also: resolveStyles


parseLineHeight function

(value: string | number | undefined, rootSize?: number) => number | undefined

Parse 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')     // undefined

Common mistakes

  • Expecting a discriminated { ratio } / { px } result — the return is a plain number | 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) => unknown

Maps 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 _documentProps are resolved (called) at extraction time, not subscribed; re-run extractDocumentTree after 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 _documentProps bypass the rocketstyle __rs_attrs fast 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 }), $rocketstyle values are var(--…) strings that PDF/DOCX/email cannot evaluate — pass ExtractOptions.resolveVar (compose resolveModeVar + resolveCssVarReferences) to inline them at extraction time.

UI ↔ Document Bridge — API Reference