> hypequery

Execution

Validate, generate SQL, and execute dataset and metric queries.

createDatasetClient is the execution surface for datasets and metrics. In a ClickHouse app, pass the same query builder instance you use for hand-written queries.

import { createDatasetClient } from '@hypequery/datasets';
import { createQueryBuilder } from '@hypequery/clickhouse';

const db = createQueryBuilder({
  url: process.env.CLICKHOUSE_URL!,
  username: process.env.CLICKHOUSE_USER!,
  password: process.env.CLICKHOUSE_PASSWORD!,
  database: process.env.CLICKHOUSE_DATABASE!,
});

const analytics = createDatasetClient({ queryBuilder: db });

Use db.table(...) for local typed queries and analytics.execute(...) for semantic dataset or metric queries. Both paths share the same ClickHouse connection.

Execution methods

The dataset client exposes three main methods:

MethodUse it for
analytics.validate(target, query, context?)Check a query without generating or executing SQL
analytics.toSQL(target, query, context?)Inspect generated SQL without executing it
analytics.execute(target, query, context?)Validate, generate SQL, execute, and return rows

The target can be a metric, a grained metric, or a dataset.

Validate

Use validate when you want errors as data instead of exceptions.

const validation = analytics.validate(revenue, {
  dimensions: ['country'],
  filters: [eq('status', 'completed')],
});

if (!validation.valid) {
  console.error(validation.errors);
}

Validation checks dimensions, measures, filters, order fields, limits, time grain requirements, tenant context, and derived metric plans.

Generate SQL

Use toSQL to inspect the generated query.

const sql = analytics.toSQL(revenue, {
  dimensions: ['country'],
  filters: [eq('status', 'completed')],
  orderBy: [{ field: 'revenue', direction: 'desc' }],
  limit: 10,
});

toSQL validates first. Invalid queries throw with the same validation errors that execute would throw.

Execute a metric

Metric execution returns one named KPI, optionally grouped by dimensions or time.

const revenue = Orders.metric('revenue', { measure: 'revenue' });

const result = await analytics.execute(revenue, {
  dimensions: ['country'],
  filters: [eq('status', 'completed')],
  orderBy: [{ field: 'revenue', direction: 'desc' }],
  limit: 10,
});

Execute a dataset

Dataset execution returns an ad hoc selection of dimensions and measures from one dataset.

const result = await analytics.execute(Orders, {
  dimensions: ['country', 'status'],
  measures: ['revenue', 'orderCount'],
  filters: [eq('status', 'completed')],
  limit: 10,
});

Execution context

Pass execution context as the third argument. This is where runtime tenant scope and per-call builder overrides live.

await analytics.execute(revenue, {
  dimensions: ['country'],
}, {
  runtime: {
    tenant: 'tenant_123',
  },
});

When a dataset has tenantKey, tenant context is required unless you explicitly use a trusted cross-tenant scope. Datasets auto-inject the tenant filter from tenantKey.

Result metadata

execute returns rows plus metadata.

const result = await analytics.execute(revenue, {
  dimensions: ['country'],
  limit: 25,
});

result.data;
result.meta?.sql;
result.meta?.timingMs;
result.meta?.rowCount;
result.meta?.pagination;

Pagination metadata is present when a query sets limit. Hypequery over-fetches one extra row to compute hasMore without issuing a separate count query.

{
  limit: 25,
  offset: 0,
  hasMore: true,
}

On this page