quario
The engine. Compiles a definition into a tree of closures and emits an event stream that every render target consumes.
Creating an instance
quario(options?)
function quario(options?: QuarioOptions): QuarioCreates a configured instance. Host-level configuration only.
QuarioOptions
| Field | Type | Meaning |
|---|---|---|
license |
string |
License key, verified offline once at creation. The engine marks output when the key ends before this version’s release date. |
locale |
string |
Locale for format. Defaults to "en-US". |
currency |
string |
Default ISO 4217 code for format: "currency". |
timeZone |
string |
Timezone for format: "date". Defaults to "UTC". |
query |
QueryOptions |
Budgets for the JSONPath selection, checked at construction. |
schemes |
readonly string[] |
The URL schemes an href may use, lower-case and colon-terminated. Defaults to https: and mailto:. A value the engine can’t read throws a TypeError at construction. |
quario() refuses what it doesn’t understand, with a TypeError naming the
path, such as options.locale. That covers an unknown key, a value of the
wrong type, and an options that isn’t an object. It also covers a locale or
timeZone that Intl refuses, and a currency that isn’t three upper-case
letters. An options object shared
with other code therefore throws, so pass quario() only these six keys.
Quario
interface Quario {
readonly license: Promise<LicenseInfo>
report(schema: ReportSchema, functions?: FunctionRegistry): CompiledReport
plan(schema: unknown, functions?: FunctionRegistry, options?: PlanOptions): Plan
}
interface PlanOptions {
readonly targets?: readonly TargetCapabilities[]
}
interface TargetCapabilities {
readonly target: string
readonly declarations: Readonly<Record<string, string>>
}license settles with the verification result and never rejects. report
throws on the first definition problem. plan returns everything one traversal
produced. An entry in functions that isn’t a function is a problem at
functions.<name>. plan refuses an unknown key in options the same way,
and a targets that isn’t an array throws a TypeError.
options.targets takes the capabilities descriptors the published targets
export beside their factories. Every declaration the document marks required
that one of them withdraws or leaves unread becomes a problem at
required.<name>. An approximated declaration isn’t one, and passing no
descriptors checks nothing.
LicenseInfo
interface LicenseInfo {
licensed: boolean
licensee?: string
id?: string
}Compiling and rendering
CompiledReport
interface CompiledReport {
stream(
data?: unknown,
params?: Readonly<Record<string, JsonValue>>,
): Generator<ReportEvent, void, undefined>
readonly params: readonly DeclaredParam[]
readonly names: readonly string[]
readonly readPaths: ReadPaths
readonly functions: readonly FunctionSignature[]
readonly paths: readonly QueryPath[]
render<Out>(
target: Target<string, Out>,
data?: unknown,
params?: Readonly<Record<string, JsonValue>>,
): Promise<Awaited<Out>>
}params supplies values for the parameters the document names. The engine
refuses a key the document doesn’t name and a value of the wrong declared
type. It refuses a render that leaves out a required parameter before any
expression runs.
render resolves the target’s output whether the target renders synchronously
or not. A malformed target throws synchronously as a definition error.
render also settles every asynchronous registered
function the report calls, before the
target reads an event. stream returns events as you pull them, so it can
wait for nothing: it reports the call it reached and names the cell. A target
therefore never sees a promise, whichever way the host drives the report.
names is the union of free variables excluding engine anchors and group
handles. readPaths maps each compiled source’s schema path to the member
paths it reads off its anchors. functions lists the registry functions
called, in call-first-seen order. paths is the frozen dependency topology of
the data query.
type ReadPath = readonly [string, ...(string | null)[]]
type ReadPaths = Readonly<Record<string, readonly ReadPath[]>>DeclaredParam
interface DeclaredParam {
readonly name: string
readonly required: boolean
readonly type?: ParamType
readonly default?: JsonValue
readonly label?: string
}One entry per declared parameter, in declaration order. type, default and
label are present only where the document wrote one, and required is the
absence of a default. The list holds a bare value too, as an optional
parameter whose default is that value.
Target<Name, Out>
interface Target<Name extends string = string, Out = unknown> {
name: Name
compile: (stream: ReportEventStream) => (data?: unknown) => Out
}The contract every render target honours. The engine calls compile once per
render call, so it must be cheap and stateless. Per-render work belongs in
the renderer it returns.
FunctionRegistry
type FunctionRegistry = Record<string, (...args: any[]) => any>Host functions a definition may call. A registered name shadows a built-in of the same name.
Checking a definition
validate(schema, functions?)
function validate(schema: unknown, functions?: FunctionRegistry): string[]Every problem in the definition, in document order, as located strings. Needs no instance and no data.
Plan
interface Plan {
readonly report: CompiledReport | null
readonly params: readonly DeclaredParam[]
readonly problems: readonly Problem[]
readonly anchors: Readonly<Record<string, readonly string[]>>
readonly readPaths: ReadPaths
readonly uses: Readonly<Partial<Record<RequirableName, readonly string[]>>>
readonly warnings: readonly Warning[]
}report is null while the document has problems, and params is there
either way, so an editor can draw a parameter form beside the problems. anchors maps a compiled
source’s schema path to the anchors and handles it reads. readPaths maps the
same paths to the member paths each source reads, ending in null where it
reads on through a key the definition doesn’t name. uses maps each
name in REQUIRABLE the document writes to the paths that write it. It never adds a
problem and needs no option. warnings is advisory, in document order from
the same descent.
Problem
interface Problem {
readonly path: string
readonly source?: string
readonly message: string
readonly diagnostic?: QuarioDiagnostic
}Frozen. message is exactly the string validate returns for this problem.
Warning
interface Warning {
readonly path: string
readonly message: string
}An advisory says the document declares something nothing will read. Not a
Problem at lower severity, and not its shape. It carries no source and no
diagnostic, because nothing raised, and the engine decides nothing will read
a declaration only after every compile succeeds.
isDiagnostic(e)
function isDiagnostic(e: unknown): e is QuarioDiagnosticTrue for a located diagnostic raised by a delegated engine, authenticated by identity. False for quario’s own verdicts and for a registered function’s throw.
QuarioDiagnostic
interface QuarioDiagnostic extends Error {
readonly code?: QuarioErrorCode
readonly start?: number
readonly end?: number
readonly limit?: number
readonly actual?: number
readonly blocks?: readonly SjabloonBlock[]
}code is absent on option and target-definition faults. limit and actual
appear on a budget failure. Diagnostic codes sit outside the frozen authoring
surface and may change.
Writing a target
These exports exist for code consuming the event stream. An application
rendering through html(), pdf(), xlsx() or csv() needs none of them.
Custom targets covers what they’re for
and builds a complete target from them.
walk(events, handlers)
function walk(events: Iterable<ReportEvent>, handlers: WalkHandlers): Promise<void>Dispatches one render’s events to per-event handlers in stream order and once each. It pulls lazily and yields the loop between batches. Handlers are synchronous, and a promise one returns isn’t awaited.
breathe()
function breathe(): Promise<void>Yields the event loop to the host, and resolves after the host takes its turn.
splits(events)
function splits(events: Iterable<ReportEvent>): Generator<WalkEvent, void>Folds each split bracket into one split event and passes every other event
through unchanged, lazily. Call walk(splits(events), handlers) where you want
a split whole rather than a bracket you track yourself.
collapse(events)
function collapse(events: Iterable<WalkEvent>): Generator<WalkEvent, void>Drops the content of every instance whose group declares collapsed, nested
instances whole, and keeps its brackets and its own bands. Call
walk(collapse(events), handlers) from a target with nowhere to hide a row a
reader could open again. The engine computes aggregates before the stream, so a
footer still totals rows nobody sees.
text(tokens) and display(value)
function text(tokens: readonly Token[]): string
function display(value: unknown): stringtext joins a token stream to display text. display is the scalar rule it
joins with, so a target stringifies exactly as the stream does. A valid Date
renders as ISO 8601 UTC, an invalid one stays "Invalid Date", and nullish
displays empty.
typed(tokens, decl?)
function typed(
tokens: readonly Token[],
decl?: ResolvedFormat | FormatKind,
): number | boolean | Date | undefinedThe typed-cell seam. Exactly one value token holding a finite number, a boolean
or a valid Date keeps its pre-stringify value. Anything else reports
undefined. Pass the resolved format to revive a date string under the
date kind. One run of one value token is still a single-token
array. The second argument is that token’s run’s resolved format rather
than the cell’s.
styledRuns(tokens)
interface TokenRun {
style: Record<string, unknown> | null
tokens: Token[]
}
function styledRuns(tokens: Token[]): TokenRun[]Groups a cell’s tokens into its runs, in order, consecutive tokens with equal
styles forming one. style is null where the cell’s own applies. Equality
goes one level deep, which is exhaustive rather than approximate, since
format is the only object-valued declaration and is itself flat. The built-in
targets consume it, so a custom target grouping through it can’t drift from
them.
format(value, style?, options?)
function format(
value: unknown,
style?: { format?: unknown; currency?: unknown } | null,
options?: { locale?: string; currency?: string; timeZone?: string } | null,
): string | undefinedPresents a token for a cell’s resolved style. Returns nothing when the kind
doesn’t apply, so the caller uses display() instead.
currencyOf(style?, options?)
function currencyOf(
style?: { currency?: unknown } | null,
options?: { currency?: string } | null,
): string | nullWhich code a cell wears under format: "currency". Branch on this rather than
on style.currency ?? instanceCurrency, which gets an unusable code wrong.
fractionDigits(style?, options?)
function fractionDigits(
style?: { format?: unknown; currency?: unknown } | null,
options?: { currency?: string } | null,
): number | undefinedHow many fraction digits a cell presents. Returns nothing where the cell presents no count.
isReportBand(role)
function isReportBand(role: string | undefined): booleanWhether a band role names one of the report’s own bands rather than a group instance’s.
hostOptions(value, keys, at) and hostMeta(value, at)
interface QuarioMeta {
title?: string
author?: string
subject?: string
}
function hostOptions<T>(value: T, keys: readonly string[], at: string): T
function hostMeta<T extends QuarioMeta | null | undefined>(value: T, at: string): TThe checks every official target refuses its options with, exported so a target
of your own refuses them the same way. hostOptions closes a key set. It throws
a TypeError prefixed with at when the value carries a key outside keys. A
host’s typo then costs one stack trace rather than an option silently lost.
Nullish is absence, and own enumerable keys only, so an inherited property never
counts as an option the host wrote.
hostMeta validates the { title, author, subject } contract the document
targets share — each a string, and nothing else. It checks the three once here
because every document format carries the same three. What each format calls them
inside its own package stays that target’s business. Both return the value you
give them, so a factory reads its options through them.
imageError(path, said, cause?)
function imageError(path: string | undefined, said: string, cause?: unknown): ErrorMints an image failure named on the item that asked for the bytes. The result
takes cause’s class. Not a diagnostic.
pageBox(page?, at?) and paper(page?, opening?, at?)
function pageBox(
page?: PageOption,
at?: string,
): { width: number; height: number; margin: number | PageSides }
function paper(
page?: PageOption,
opening?: { size?: unknown; margin?: unknown; orientation?: unknown },
at?: string,
): {
size: PageOption['size']
margin: number | PageSides | undefined
orientation: PageOption['orientation']
width: number
height: number
sides: PageSides
}
interface PageOption {
size?: 'A4' | 'letter' | [number, number]
margin?: number | PageSides
orientation?: 'portrait' | 'landscape'
}
interface PageSides { top: number; right: number; bottom: number; left: number }The rules every target with a page follows, exported so a target of your own
applies them the same way. pageBox validates a host’s page option and
returns its box. paper resolves the option against the document’s opening
event. The document’s key wins, the host’s fills a gap, and both declaring one
key throws. It returns the declared keys, undefined where nobody declared one, plus width, height and sides
in points, turned by the orientation. Both throw a TypeError prefixed with
at, "options.page" by default.
The vocabularies as data
const STYLE_NAMES: readonly string[]
const RUN_STYLE_NAMES: readonly string[]
const REPORT_STYLE_NAMES: readonly string[]
const REQUIRABLE: readonly RequirableName[]
const ZERO_DASH: string
function isBoxName(name: string): boolean
const FORMAT_VOCABULARY: {
readonly kinds: readonly FormatKind[]
readonly forms: readonly DateForm[]
readonly negatives: readonly NegativeReading[]
readonly zeroes: readonly ZeroReading[]
readonly modifiers: Readonly<Record<FormatKind, readonly string[]>>
readonly defaultForm: DateForm
readonly defaultNegative: NegativeReading
readonly defaultZero: ZeroReading
readonly maxDigits: number
}The closed vocabularies as data, for a tool that offers them rather than checks
them. RUN_STYLE_NAMES is the inline half a styled run may wear, in the
vocabulary’s own order, and REPORT_STYLE_NAMES the report default’s two
names. REQUIRABLE is the set a document may mark required, and the exported
RequirableName is that set as a type. A marking no target could answer for is
therefore an error where an author writes it, rather than a plan problem
afterwards. A name outside the set is a definition error either way. isBoxName answers whether a style name is a padding
or a border side, the partition that defines a table row’s box. ZERO_DASH is
what a cell reads as under zero: "dash", stated once because the XLSX target
writes the same character into its sectioned number format.
FORMAT_VOCABULARY.modifiers names every modifier a kind takes rather than one
of them. A surface offering them then needs no knowledge that the three number
kinds share theirs and date doesn’t.