JavaScript Embedded AnalyticsServer-Side Data: Implementation

Studio calls your engine's execute() method with one or more AgExecuteRequest objects. Each request carries an AgStudioQuery that your engine translates into your backend's native query language. Return one AgExecuteResult per request, in the same order.

Query Anatomy Copy Link

Every field reference in an AgStudioQuery is an AgStudioQueryField object carrying key, fieldId, sourceId, and optional properties like aggregation.

Core Fields Copy Link

FieldPurposeShape
axesGroup-by dimensions for aggregation[{ dimensions: [{ field: { key, fieldId, sourceId } }] }]
measuresAggregated columns[{ field: { key, fieldId, sourceId, aggregation: 'sum' } }]
projectionRaw columns when not aggregating (mutually exclusive with axes/measures)[{ field: { key, fieldId, sourceId } }]
filterWHERE clause tree (recursive groups with combinator, leaves with operator){ combinator: 'and', conditions: [{ field, operator: 'equals', value }] }
sortORDER BY specification[{ field: { key, fieldId, sourceId }, direction: 'desc' }]
limitLIMIT and OFFSET{ count: 100, offset: 20 }
computedFieldsComputed columns with expression ASTs and evaluation phaseSee Computed Fields

Additional Fields Copy Link

These fields appear when the dashboard uses features that require them. If your engine does not support a given field, the corresponding UI feature will not work correctly.

FieldPurpose
havingPost-aggregation filter (SQL HAVING)
windowWindow functions (rank, denseRank, rowNumber)
joinsMulti-source joins (see Sources and Joins)
scopeDimension-member slice for slicer/page-filter semantics
distinctRow deduplication for projection-mode queries
fromDerived-table composition (SQL FROM (SELECT ...))

Properties available on the AgStudioQuery interface.

AgAxisDefinition[]
Dimension axes for OLAP aggregation. Only axes[0] is consumed in this engine revision — additional entries are reserved for future pivot/hierarchical modes. Empty axes: [] combined with measures yields a single grand-total group. Mutually exclusive with projection.
measuresCopy Link
AgMeasureDefinition[]
Measures aggregated at each cell. Required when axes is present.
projectionCopy Link
AgDimensionDefinition[]
Output columns for detail/raw-row queries. Mutually exclusive with axes/measures.
AgJoinClause[]
Ordered join chain — the query's source topology. Omit for a single-source query. Engines must consume clauses in order and must not perform schema lookup to reconstruct the topology.
filterCopy Link
AgStudioFilterDefinition
Row-level predicate applied BEFORE any aggregation (SQL WHERE semantics). Leaves reference source fields on the joined plan. Affects both the result set and the denominator used by measures with totalsScope: 'filtered'.
havingCopy Link
AgStudioFilterDefinition
Post-aggregation predicate applied to cells, AFTER measures are computed (SQL HAVING semantics). Leaves must reference measure aliases (via AgExprRef) or dimension fields; row-level source fields are rejected. Cells the predicate rejects are dropped; the measure denominators are NOT recomputed.
AgScopeDefinition
Dimension-member slice (MDX-inspired). Restricts the visible cells to the chosen members but — unlike filter — does NOT remove the excluded rows from the denominator used by measures with totalsScope: 'unrestricted'. Use for slicer/page-filter semantics where "% of total" should still divide by the full dataset.
AgStudioSortDefinition[]
AgStudioSortDefinition[]
windowCopy Link
AgStudioWindowDefinition[]
AgStudioWindowDefinition[]
AgStudioLimitDefinition
AgStudioLimitDefinition
distinctCopy Link
boolean
Projection-mode row deduplication (SQL SELECT DISTINCT). In aggregation mode, use distinct-within-measure aggregations (countd etc.) instead.
computedFieldsCopy Link
AgStudioComputedFieldDefinition[]
Evaluation schedule for computed expression fields. Topologically sorted — engines evaluate in array order. Each entry carries the expression in AgStudioExpression AST form and an explicit evaluation phase. Synthetic entries decompose cross-source expressions into single-source intermediates. Absent when the query has no expression fields.
AgStudioQuery
Derived-table composition (SQL FROM (SELECT ...)). Engines execute the inner query, materialise its result, and run the outer query over those rows. Each materialised level costs memory and latency, so engines should consider imposing a depth limit and rejecting deeper nesting with an error rather than silently truncating.

Computed Fields Copy Link

computedFields is a topologically sorted evaluation schedule. Each entry carries an expression AST and an explicit evaluation phase:

  • phase: 'pre-agg': evaluated on source rows before grouping (SQL column expression)
  • phase: 'post-agg': evaluated on grouped output after measures are computed
  • synthetic: true: internally generated intermediates; exclude from user-facing result columns

Engines evaluate entries in array order. Each entry's dependencies are satisfied by prior entries or source scans.

Expression nodes are either operations ({ operator, inputs, options? }) or leaves: field references ({ field: AgStudioQueryField }), literals ({ literal: Primitive }), or alias references ({ ref: string }).

Field Identifiers Copy Link

Every field reference in a query is an AgStudioQueryField:

PropertyWhere it comes fromWhat to use it for
fieldIdSource-qualified column ID from getDataSources(): "sales.revenue". For computed fields (sourceId: ''), a bare identifier like "pct_of_total".Map to a backend column. Strip the source prefix for source fields; use as-is for computed fields.
sourceIdThe id of the source the field belongs to: "sales".Pick which backend table or endpoint the query targets.
keyAn opaque string Studio builds per-field per-query.The column name in your result rows. Your result rows[i][field.key] must round-trip cleanly. Treat key as opaque; do not parse it.

Measure fields also carry aggregation (e.g. 'sum', 'avg', 'count'). Access it via measure.field.aggregation.

Properties available on the AgStudioQueryField interface.

string
Composite identity key and output column alias. Treat as opaque — built by buildFieldKey.
sourceIdCopy Link
string
Owning source ID; '' for computed/inline fields.
fieldIdCopy Link
AgStudioFieldIdentifier
The underlying field identifier within the source.
aggregationCopy Link
AgAggregationFunction
Aggregation function for measure fields; omitted for raw dimensions.
determinantCopy Link
string
Additional identity component for fields that would otherwise collide on (sourceId, fieldId, aggregation) — e.g. window outputs with different orderings.
sourceAliasCopy Link
string
Alias for self-joins — disambiguates multiple instances of the same underlying source in one query.
expressionCopy Link
AgStudioExpression
Inline expression AST — present for computed / inline fields (where sourceId === '') and also the mechanism for granularity transforms such as dateTrunc and numeric bucketing.
isMeasureCopy Link
boolean
Inline-field intent disambiguator. For inline fields, true marks a measure (lives under AgStudioQuery.measures), false marks a dimension (lives under axes[].dimensions). Validator rejects mismatched slot/intent pairs.
dataTypeCopy Link
AgDataType
Optional data-type hint. Resolver-produced fields set this from the schema; hand-constructed queries may omit it, in which case the hydration step fills it in. HydratedField widens this to required.

Filter Structure Copy Link

The filter and having fields share the same recursive tree structure:

AgStudioFilterGroup: a boolean combinator ('and', 'or', or 'not') wrapping child nodes:

{
    combinator: 'and',
    conditions: [/* AgStudioFilterCondition | AgStudioFilterGroup */],
}

AgStudioFilterCondition: a leaf predicate on a single field:

{
    field: { key, fieldId, sourceId },
    operator: 'equals',
    value: 'EMEA',
}

Walk the tree recursively: groups become parenthesised boolean expressions; conditions become backend predicates. See the reference examples for complete filter translation.

Properties available on the AgStudioFilterCondition interface.

AgStudioQueryField
AgStudioQueryField
operatorCopy Link
| 'equals' | 'notEqual' | 'greaterThan' | 'greaterThanOrEqual' | 'lessThan' | 'lessThanOrEqual' | 'isNull' | 'isNotNull' | 'between' | 'isIn' | 'isTrue' | 'isFalse'
'equals' | 'notEqual' | 'greaterThan' | 'greaterThanOrEqual' | 'lessThan' | 'lessThanOrEqual' | 'isNull' | 'isNotNull' | 'between' | 'isIn' | 'isTrue' | 'isFalse'
Primitive | Primitive[] | [Primitive, Primitive]
undefined for isNull / isNotNull / isTrue / isFalse; a 2-element array for between (check operator to distinguish from isIn); an array for isIn; single primitive otherwise.
optionsCopy Link
{ crossFilter?: boolean; scopeDerived?: boolean }
Routing metadata carried to the engine and explain output.

Properties available on the AgStudioFilterGroup interface.

combinatorCopy Link
'and' | 'or' | 'not'
'and' | 'or' | 'not'
conditionsCopy Link
AgStudioFilterDefinition[]
AgStudioFilterDefinition[]

Result Format Copy Link

Return an AgExecuteResult per request, discriminated on dataShape. Return 'rows' (default) or 'columns' depending on options.shape.

The dataShape discriminator on each result must match the data you return. If you set dataShape: 'columns' but populate rows (or vice versa), widgets will render empty.

Both shapes carry an AgResultMetadata object. rowCount is the number of rows in the returned result. When your engine applies a limit, set totalRowCount to the pre-limit row count.

Rows Format (default) Copy Link

return {
    dataShape: 'rows',
    rows: [
        { 'sales.region': 'EMEA', 'sales.revenue': 123456 },
        { 'sales.region': 'APAC', 'sales.revenue': 234567 },
    ],
    metadata: { rowCount: 2, totalRowCount: 5432 },
};

Columns Format Copy Link

const columns = new Map<string, ReadonlyArray<Primitive | null>>();
columns.set('sales.region', ['EMEA', 'APAC']);
columns.set('sales.revenue', [123456, 234567]);

return {
    dataShape: 'columns',
    columns,
    metadata: { rowCount: 2 },
};

Properties available on the AgExecuteRequest<TShape extends AgResultShape = AgResultShape> interface.

AgStudioQuery
The query to execute against the data source.
optionsCopy Link
AgRequestOptions<TShape>
Per-request options controlling result shape, cancellation, and validation.
AgEngineCallInfo
Advisory metadata for logging, tracing, and batch coalescing.

Properties available on the AgRowsResult interface.

dataShapeCopy Link
'rows'
Discriminator — always 'rows' for this shape.
Record<string, unknown>[]
Result rows keyed by output field alias.
metadataCopy Link
AgResultMetadata
Row count and optional pre-limit total.

Properties available on the AgColumnsResult interface.

dataShapeCopy Link
'columns'
Discriminator — always 'columns' for this shape.
columnsCopy Link
Map<string, AgPublicColumnData>
Column arrays keyed by output field alias.
metadataCopy Link
AgResultMetadata
Row count and optional pre-limit total.

Sources and Joins Copy Link

When a widget pulls fields from multiple related sources (declared via Relationships), Studio produces a single AgStudioQuery with joins populated. Your execute() receives one query that spans multiple sources. Each field's sourceId and fieldId identify which backend table it belongs to, so your translator can resolve every reference.

If your backend does not support joins, throw a descriptive error when a query contains joins.

Batching and Cancellation Copy Link

Studio calls execute(...requests) with every request in the current render cycle. Each request carries an info object:

  • batchId: All requests in the same execute() call share a batchId. Use it to coalesce backend round-trips (e.g. one combined request per batch). When absent, treat each request independently.
  • queryId: Distinguishes independent queries from the same widget (e.g. 'rows' vs 'grandTotal'). Studio uses widgetId + queryId as the cancellation key.

Each request carries an optional options.signal: AbortSignal. Propagate it into your backend call to cancel superseded queries:

const res = await fetch(url, { signal: request.options?.signal });

Errors Copy Link

Throw from execute() when the backend fails. Any failed request in a batch will cause the whole batch to fail. This is to avoid partial data flowing into Studio.

async execute(...requests: AgExecuteRequest<AgResultShape>[]): Promise<AgExecuteResult[]> {
    try {
        return await this.runQueries(requests);
    } catch (error) {
        throw { message: 'Backend query failed', cause: error };
    }
}

Reference Examples Copy Link

These are reference implementations for learning purposes. They are not production-ready integrations and will not cover every query feature. Use them as starting points for your own engine.

ClickHouse over HTTP Copy Link

Translates AgStudioQuery to ClickHouse SQL, posts it over HTTP, and returns the response. The dashboard queries ClickHouse's uk_price_paid dataset (~28M rows) without downloading any of it.

Contains HM Land Registry data © Crown copyright and database right 2021. This data is licensed under the Open Government Licence v3.0. (Source, Fields)

REST API: World Bank Countries Copy Link

Queries the World Bank Open Data API. Supported predicates are sent as URL parameters; remaining filters, aggregation, and sorting are applied locally.

Contains data from The World Bank: Countries API, licensed under Creative Commons Attribution 4.0 (CC BY 4.0). (Terms of Use)