anfra docs
anfra SDK

Environment

The globals, datasets and field names available to your page

Everything in the SDK hangs off the global Anfra. It is ready before your first <script> runs, so there is no import, no setup and no await.

Datasets

Anfra.datasets maps each dataset's fqn, as anfra show lists it, to a descriptor of what the current reader can use:

Anfra.datasets.sales
// {
//   name, label,
//   data_models: [{ name, label, fields: [{ name, label, type, is_custom_measure }] }],
//   metrics: [{ name, label, type }]
// }

is_custom_measure marks a model measure. Use the descriptors to build field pickers or to check a name before you use it. To list the same datasets from a terminal, run anfra show.

User

Anfra.user describes the reader:

Anfra.user
// { id, name, email, role, timezone, permissions: { canViewGeneratedSql, canExportData } }

Under anfra serve, it is the machine's user, with every permission and the machine's time zone. Treat permissions as rendering hints only, such as hiding an export button. The backend re-checks every query as this reader.

Plugins

Anfra.plugins lists the plugins the host installed. A plugin adds its API under its own name, such as query.export or Anfra.lineage, and only where it is installed:

Anfra.plugins.has('lineage') // check before using an optional plugin
Anfra.plugins.list           // [{ name, version }, ...]

Anfra.features is deprecated. See Migrating from Anfra.features.

Field references

When you name a field to the SDK, in createFilter or mapControl, use:

  • model.field for a model field, such as users.region.
  • A bare name for a dataset-level metric, such as revenue.

Names come from Anfra.datasets, never from labels.

Inside a query's AQL the same names apply. Everywhere else the SDK uses the aliases your AQL declared: result row keys, setSort, and the fields option of query.select.

A filter's field is checked immediately, and a typo throws a ValidationError with a "did you mean" suggestion. A mapControl field is not checked until the query runs, so a typo there arrives as a QueryError.

Creating an app

const app = Anfra.createApp({ title: 'Sales', timezone: 'Asia/Singapore' }) // both optional

Omit timezone to use anfra's default. Set it to an IANA zone to evaluate relative dates and date truncation in that zone. title names the app in tools such as the Inspect panel.

A page may create several apps, but one is almost always right. Names are unique per app across queries and controls.

The app object

Anfra.createApp() returns the app object. Its members:

MemberDoes
app.createQuery(name, { dataset, aql, pageSize? })Declares a query.
app.createFilter(name, decl)Declares a filter.
app.createDateDrill(name, { default?, label? })Declares a date drill.
app.mapControl(control, query, { field, aggregation? })Applies a control to a query.
app.mapCrossFilter(fromQuery, toQuery)Applies selected rows in one query to another. See Cross-filtering.
await app.execute()Runs every query whose inputs changed. Resolves { succeeded, failed } and never rejects.
await app.refresh()Re-runs every query, whether or not its inputs changed.
app.subscribe(fn)Calls fn on any change in the app or its entities. Returns an unsubscribe function.
app.hasChangestrue when a control or the selection changed since the last execute. Use it for an "Apply" button.
app.selection / app.clearSelection()The current cross-filter selection, and a way to clear it.
app.abort()Cancels in-flight queries.
  • Semantic UI: how queries, controls and cross-filters work together.
  • Build a data app: where Anfra comes from and what the page can do.
  • Plugins: what the host installs, and how to check for a plugin.

On this page