@stesura/docgen
Assembles markdown sources and a Stesura template into one document, ready to render to PDF or
Word, with front matter and {{name}} variable substitution. Framework-free, so it runs in a
server route or a CLI. The template is a doc JSON whose content_slot (inserted with
insertContentSlot from @stesura/core/commands) marks where the
sources go, and whose field nodes are the variables.
A typical render: read the first source’s front matter, resolve the variables against the template’s declarations, then assemble.
import { assemble, readFrontMatter, resolveVariableValues } from '@stesura/docgen'
const { data } = readFrontMatter(sources[0].markdown)
const resolved = resolveVariableValues(definitions, { ...data, ...requestVariables })
if (!resolved.ok) throw new Error(resolved.error.error)
const { doc, warnings } = assemble({ schema, template, sources, assets, variables: resolved.values })Assembly
assemble
Template + markdown sources + variables → one document.
- Front matter is stripped from every source (the caller merges the first source’s into the variables before resolving them).
- Sources are parsed with the template’s styles, joined by page breaks, and
put where the
content_slot(or a{{content}}paragraph) is, or at the end of the last section. - Relative image paths resolve against the source’s folder and are replaced
by the matching
assetsentry. Remote images are not fetched. - Footnote bodies are created in the doc’s footnotes container; refs are
namespaced per source so two files’
[^1]stay distinct. - Links between the files (
other.md#heading,#heading) become in-document links; unresolvable relative links lose their link. - Common raw HTML (details, img, br, kbd) is rewritten to markdown first;
code blocks get line numbers unless
codeLineNumbersis false. headingOffsetshifts a file’s heading levels (and built-in heading styles). Chapter numbering itself comes from the template’s heading styles.fieldnodes and typed{{name}}are filled everywhere, markdown included. A text watermark left empty after substitution is removed, so{{watermark}}toggles it.
Throws AssembleError only when the result is not a valid document.
| Name | Type | Default |
|---|---|---|
__0 | AssembleOptions |
AssembleOptions
What assemble takes.
AssembleResult
What assemble returns.
| Name | Type | Default |
|---|---|---|
doc | JSONContentValid against | |
warnings | DocgenWarning[] |
AssembleError
A template or doc that cannot be assembled at all (as opposed to a warning).
| Name | Type | Default |
|---|---|---|
name | string | |
message | string | |
stack | string | |
cause | unknown |
DocgenSource
One markdown file of the request. path is repo-relative, /-separated.
| Name | Type | Default |
|---|---|---|
path | string | |
markdown | string | |
headingOffset | numberAdded to every heading level of this file ( |
DocgenAssets
export type DocgenAssets = Record<string, string>;Files the sources refer to, keyed by repo-relative path; values are data: URIs.
DocgenWarning
A non-fatal degradation, safe to show in a CI log. Markdown parse warnings
keep their code from @stesura/markdown; source names the file.
| Name | Type | Default |
|---|---|---|
code | string | |
message | string | |
source | string |
Front matter
readFrontMatter
Splits YAML front matter off a markdown file. Never throws: invalid YAML and nested values are reported and skipped, and the block is still removed so it never renders as a rule and a heading.
| Name | Type | Default |
|---|---|---|
markdown | string | |
source | string |
FrontMatter
A markdown file split by readFrontMatter.
| Name | Type | Default |
|---|---|---|
data | Record<string, string | number | boolean>Scalar entries only: they are what can fill | |
body | stringThe markdown without its front matter block. | |
warnings | DocgenWarning[] |
Variables
A template declares its variables as VariableDefinitions. Request values are checked against
them by resolveVariableValues; substituteVariables fills a doc with the result, which
assemble already does for you.
resolveVariableValues
Turns request variables into the text each placeholder renders as.
Declared variables are type-checked and fall back to their default; a
missing required one is an error unless lenient (editor previews).
Undeclared request keys pass through as text, so hand-written templates
without declarations still work.
| Name | Type | Default |
|---|---|---|
definitions | VariableDefinition[] | |
input | unknown | |
__2 | { lenient?: boolean; builtins?: VariableValues; } |
substituteVariables
Fills a template with variable values, everywhere: body, headers, footers, notes and text watermarks.
- A
fieldnode becomes text (keeping its marks): the value, else its appliedvalueattr, else its fallback, else{{name}}with a warning. - Typed
{{name}}text is replaced too, for templates written before fields and for markdown sources. Reserved and unknown names stay literal; unknown ones are reported. A placeholder split across two text runs is not seen. Code blocks and code spans are left alone:{{x}}there is a sample.
Text left empty is dropped (ProseMirror has no empty text nodes). Returns a new doc; the input is not mutated.
| Name | Type | Default |
|---|---|---|
doc | JSONContent | |
values | VariableValues |
defaultBuiltins
Values every template gets unless the request, front matter or a declared
default sets them. date is the render day, ISO, so {{date}} works out of
the box.
| Name | Type | Default |
|---|---|---|
now | Date |
validateVariableDefinitions
Checks definitions before they are saved. Returns the first problem, or null.
| Name | Type | Default |
|---|---|---|
definitions | VariableDefinition[] |
parseVariableDefinitions
Reads the templates.variables column. Malformed entries are dropped, never thrown on.
| Name | Type | Default |
|---|---|---|
value | unknown |
toVariableType
value as a VariableType, or null when it isn’t one.
| Name | Type | Default |
|---|---|---|
value | unknown |
VARIABLE_TYPES
export const VARIABLE_TYPES = ["string", "number", "boolean", "date"] as const;The types a template variable can declare.
VARIABLE_NAME_PATTERN
What a declared variable name must match: a letter or _, then up to 63 of [A-Za-z0-9_.-].
RESERVED_VARIABLES
export const RESERVED_VARIABLES: readonly string[] = ["content", "page"];Placeholders the substitution leaves alone: content is the assembler’s slot, page the page-number atom’s.
VariableDefinition
A variable a template declares.
| Name | Type | Default |
|---|---|---|
name | string | |
type | "string" | "number" | "boolean" | "date" | |
required | boolean | |
default | stringUsed when the request omits the variable. Stored as the rendered text. | |
description | string |
VariableType
export type VariableType = (typeof VARIABLE_TYPES)[number];One of VARIABLE_TYPES.
VariableValues
export type VariableValues = Record<string, string>;Variable name → the text its placeholder renders as.
VariableError
Why resolveVariableValues refused a request’s variables.
| Name | Type | Default |
|---|---|---|
error | "invalid_variables" | "missing_variable" | "invalid_variable" |
API clients
For documenting a template’s render API: the shape of its variables object as JSON Schema or
as a TypeScript type.
variablesJsonSchema
JSON Schema (draft 2020-12) of a template’s variables request object.
Required means “must be sent”: a variable with a default is never required.
Unknown keys are allowed, as the API passes them through as text.
| Name | Type | Default |
|---|---|---|
definitions | VariableDefinition[] | |
title | string |
variablesTypeScript
A TypeScript type for a template’s variables object, for generated client code.
| Name | Type | Default |
|---|---|---|
definitions | VariableDefinition[] | |
typeName | string |