ReportSchema
The document shape. Every object with fixed keys is a closed set, so an unknown key anywhere is a definition error.
The document
| Field | Type | Meaning |
|---|---|---|
data |
string |
Required. The JSONPath selecting the rows the report walks. |
params |
Record<string, JsonValue> |
Constants the definition declares, read as $.params. |
where |
`=${string}` |
Drops rows that fail it, before aggregates. |
sort |
SortKey[] |
Orders the whole set, applied left to right. |
take |
number |
Keeps this many rows after the sort. Integer >= 1. Dropped rows leave aggregates and runners too. |
aggregates |
Record<string, ReducerSpec> |
Report-level aggregates, injected bare under $. |
run |
Record<string, ReducerSpec> |
Running accumulators, read as run.<name> on detail rows. |
style |
ReportStyleDeclarations |
The report default. family and size only. |
header |
Item[] or { height, items } |
The report header band. The object form pins a box from the page top. |
empty |
Item[] |
Replaces the whole body when no rows remain. |
columns |
number |
Flows the body in this many page columns. Integer >= 2, and no nesting. |
groups |
Group[] |
Group bands, outermost first. |
detail |
Item[] or TableDetail |
The per-row band. |
footer |
Item[] |
The report footer band. |
page |
PageBands |
Furniture paginated targets draw on every page. |
Group
interface Group {
name: string
by: `=${string}`
break?: 'page'
reset?: 'page'
columns?: number
sort?: SortKey[]
take?: number
aggregates?: Record<string, ReducerSpec>
run?: Record<string, ReducerSpec>
header?: Item[]
footer?: Item[]
}name is the handle its bands and everything nested inside them read. It must
be a plain identifier, unique across the document, and can’t be @, $,
run, loop or page. A group aggregate can’t take the name key.
break: "page" starts each instance on a fresh page. reset: "page" also
restarts the page numbering.
TableDetail
interface TableDetail {
header?: TableHeaderBox
row?: TableRow
columns: [TableColumn, ...TableColumn[]]
total?: TotalBlock
}TableColumn
interface TableColumn extends Cell {
header?: string | TableHeader
width?: number
}width is a literal percentage from 0 to 100, and it’s the border-box share.
TableHeader, TableRow, TableHeaderBox
interface TableHeader { value: CellValue; style?: StyleDeclarations; span?: number }
interface TableRow { visible?: ExpressionValue<boolean>; style?: StyleDeclarations }
interface TableHeaderBox { style?: StyleDeclarations }A row’s style resolves onto that row’s cells on every target.
TotalBlock, TotalRow, TotalCell
interface TotalBlock extends TableRow { rows: [TotalRow, ...TotalRow[]] }
interface TotalRow {
cells: [TotalCell, ...TotalCell[]]
visible?: ExpressionValue<boolean>
style?: StyleDeclarations
}
type TotalCell = Cell & { span?: number }What every total row shares goes on the block. Each row must cover the columns
exactly once, counting span.
Items
Cell and TextItem
interface Cell {
value: CellValue
visible?: ExpressionValue<boolean>
style?: StyleDeclarations
}
interface TextItem extends Cell { type: 'text' }
type CellValue = string | StyledRun[]
interface StyledRun {
value: string
style?: RunStyleDeclarations
}A cell value is one template string or a non-empty array of styled runs. A
cell written as one string is one run, so a one-run array says exactly what that
string says. A run's style is literal only, never an expression, and a run has
no visible, since {{#if}} already conditions its text. A CR, LF, or CRLF in the evaluated text
is a hard line break.
ImageItem
interface ImageItem {
type: 'image'
source: `=${string}`
fit?: 'natural' | 'width'
alt?: string
visible?: ExpressionValue<boolean>
style?: ImageStyleDeclarations
}source must be an expression resolving to PNG or JPEG bytes. A nullish source
skips the item, so an optional logo needs no guard. style covers
background, align, and the box and flow spacing, and nothing else. alt is
one template string, the one cell value the run form doesn’t reach.
SplitItem
interface SplitItem {
type: 'split'
slots: [SplitSlot, SplitSlot, ...SplitSlot[]]
visible?: ExpressionValue<boolean>
style?: StyleDeclarations
}Two slots at minimum. A slot is a text or image item plus an optional width
percentage, and it takes no flow spacing. Splits don’t nest, and neither
appears in a table or total cell.
Item
type Item = TextItem | ImageItem | SplitItemPageBands
interface PageBands {
header?: Item[]
footer?: Item[]
margin?: number
}margin is the one piece of page geometry a document may declare. Declaring it
here and on the target too is a render error.
SortKey
interface SortKey {
by: `=${string}`
dir?: 'asc' | 'desc'
}dir defaults to "asc". The sort is stable. Keys compare with JavaScript
ordering, so keep a key one type where the order matters.
Supporting types
type ExpressionValue<T = unknown> = T | `=${string}`
type ReducerSpec = string
type JsonValue =
| null | boolean | number | string
| JsonValue[]
| { [key: string]: JsonValue }A leading = on any property other than a cell value makes it an expression.
params accepts JSON values and nothing else, so a Date, a Map, a NaN or
an Infinity is a definition error.