---
title: "Supplying the data — quario"
description: "Where a quario report gets its data. The host resolves the payload and the parameters, and the engine renders one already-resolved object."
url: "https://getquario.com/docs/diving-deeper/supplying-data"
---

# Supplying the data

## Intent

quario renders a payload you have already resolved. Your application fetches,
joins and reshapes, then passes one object to `render`. The engine opens no
connection, holds no driver, and knows no credential.

That division is the whole integration story, and it’s a design position rather
than an unfinished one.

## Design decisions

**quario never opens a socket.** The engine reads the object you pass and walks
it. There is no database driver, no HTTP client, and no connection string
anywhere in the render path. A report therefore runs wherever your application
already runs, and a security review has one less surface to examine. [Trust and
CSP](https://getquario.com/docs/diving-deeper/security.md) describes the rest of that boundary.

**One payload holds as many collections as you like.** `data` is a JSONPath
into the object you passed, so `$.orders[*]` and `$.customers[*]` are two
record sets in one payload. A reader who assumes one input means one table
writes one report per query and never needs to.

**Parameters travel two ways, and choosing between them is yours.**
[Scopes](https://getquario.com/docs/diving-deeper/scopes.md) defines both anchors. A reporting period
belongs in `$.input`, because it changes per render and arrives with the data.
A region or a threshold that the data doesn’t carry belongs in
`$.params`. Declared there, it makes a render that forgets
it fail before any expression runs, instead of printing a blank.

**Resolving the payload is yours, and it finishes before the call.** `render`
returns a promise because a target may work asynchronously, not because the
engine fetches anything. The one place the engine waits for your code is an
[asynchronous registered function](https://getquario.com/docs/diving-deeper/host-functions.md). That
fills a gap in a row rather than selecting the rows.

**A definition stays portable because it names no source.** It carries a
JSONPath into a shape, not a query against a system. The same file renders in
a web request, a scheduled job, a test and a developer’s laptop, against
whatever produced the shape that time.

## API walkthrough

### Handing render one object

**`render(target, data)`** takes the payload as its second argument. Everything
the report reads comes from there, and the engine copies nothing onto it.

```ts title="report.ts"
const orders = await listOrders({ from: '2026-07-01', to: '2026-09-30' })

const report = q.report(definition)
const output = await report.render(target, { orders })
```

`$.input` is that object, untouched. [Scopes](https://getquario.com/docs/diving-deeper/scopes.md)
covers what each anchor reads once it’s in.

### Selecting from more than one collection

**`data`** narrows the payload to the rows one report walks. Two definitions
over the same object select different sets, so a single fetch can serve a
statement and a customer list.

```json title="two definitions, one payload"
{ "data": "$.orders[*]" }
```

```json title="the sibling definition"
{ "data": "$.customers[*]" }
```

```ts title="report.ts"
const payload = {
  orders: await listOrders({ from: '2026-07-01', to: '2026-09-30' }),
  customers: await listCustomers(),
}

const statement = q.report(definition)
const directory = q.report(customerList)

const [a, b] = await Promise.all([
  statement.render(target, payload),
  directory.render(target, payload),
])
```

A band outside the detail still reads the whole payload through `$.input`, so a
header can count the customers a row-level report never walks.

### Declaring what a report needs

**`params`** on the document names the values a report reads as `$.params`. A
bare value is the parameter’s value, and it travels with the file. A plain
object is a declaration, closed at `type`, `default` and `label`.

```json title="a constant and a declaration"
{
  "data": "$.orders[*]",
  "params": {
    "threshold": 1000,
    "region": { "type": "string", "label": "Region" }
  }
}
```

**A declaration without a `default` makes the parameter required.** The engine refuses a render
that supplies no value for it, before any expression runs. `type` names a JSON
kind: `string`, `number`, `boolean`, `array` or `object`. A `null` default
beside a `type` makes the parameter optional, and a render may then supply
`null`.

**`render(target, data, params)`** supplies the values, and `stream` takes them
in the same place. A render may supply only a key the document names, and a
supplied value answers to the declared `type` where there is one. A bare value
has no `type`, so a render may replace it with any JSON value. Values stay JSON,
frozen for the one render.

```ts title="report.ts"
const output = await report.render(target, data, { region: 'EU' })
```

**`report.params`** lists the declarations in document order, each `{ name,
required, type?, default?, label? }`. A `label` marks a parameter a person
fills in, so a host draws its form from the labelled ones. The engine never
renders a label.

Put the reporting period in the payload and the threshold in `params`. A
definition that hard-coded the period would need one copy per period. A
threshold in `params` keeps its value in the document. A caller that changes it
says so in the render call, not in the payload.

**An object value needs wrapping.** The engine reads any plain object under
`params` as a declaration, so a constant whose value is an object goes under
`default`, as in `{ "config": { "default": { "retries": 3 } } }`. An empty
object, `{ "config": {} }`, declares a required parameter with no type.

### Combining two sources

Fetch in parallel, join in your own code, and pass the result. The engine sees
one shape and never learns there were two systems behind it.

```ts title="report.ts"
const period = { from: '2026-07-01', to: '2026-09-30' }

const [orders, customers] = await Promise.all([listOrders(period), listCustomers()])

const byId = new Map(customers.map((c) => [c.id, c]))
const rows = orders.map((o) => ({ ...o, customer: byId.get(o.customerId)?.name ?? null }))

const report = q.report(definition)
const output = await report.render(target, { orders: rows })
```

Reshape here rather than in the definition. An expression can read a nested
field, but it can’t join two collections. A report that tried would state a
relationship your database already knows.

### Fetching again when a parameter changes

A parameter changes on your side, so the round trip is yours too. Resolve the
new data, then render the same compiled report again.

```ts title="report.ts"
const report = q.report(definition)

const run = async (period: { from: string; to: string }) => report.render(target, { orders: await listOrders(period) })

const q3 = await run({ from: '2026-07-01', to: '2026-09-30' })
const q4 = await run({ from: '2026-10-01', to: '2026-12-31' })
```

Compile once and render per payload. `report()` is the expensive call and
`render()` the cheap one, so a long-running service holds the compiled report
and gives it fresh data.

### What the boundary buys

Your dependency tree holds no drivers, and the rendering path holds no
credentials. There is nothing to allow through a firewall, because nothing
dials out. A definition reads the same in production and in a test, because the
shape is the only contract between them.

```json
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://getquario.com/"},{"@type":"ListItem","position":2,"name":"Docs","item":"https://getquario.com/docs"},{"@type":"ListItem","position":3,"name":"Supplying the data","item":"https://getquario.com/docs/diving-deeper/supplying-data"}]}
```
