The type-safe analytics backend for ClickHouse
Build ClickHouse queries once, run them inline, over HTTP, in React, or from agents.
The problem
Querying ClickHouse from TypeScript with the official client means writing raw SQL strings, casting results to any, and maintaining hand-rolled types that drift from your real schema:
// Raw @clickhouse/client — no types, no safety, breaks silently const result = await client.query({ query: `SELECT region, sum(total) as revenue FROM orders WHERE created_at >= '2026-01-01' GROUP BY region ORDER BY revenue DESC`, format: 'JSONEachRow', }); const rows = await result.json(); // typed as any[] // ^^^^ schema drift, typos, and runtime errors are on you
The solution
hypequery generates TypeScript types directly from your live ClickHouse schema, then gives you a fluent query builder where every table name, column, filter, and result is fully typed:
import { createQueryBuilder } from '@hypequery/clickhouse'; import type { IntrospectedSchema } from './analytics/schema.js'; const db = createQueryBuilder<IntrospectedSchema>({ /* connection */ }); const revenueByRegion = await db .table('orders') // ✅ autocompletes your real tables .select(['region']) // ✅ only valid columns for this table .where('created_at', 'gte', '2026-01-01') // ✅ type-checked operator + value .sum('total', 'revenue') // ✅ typed aggregation .groupBy('region') .orderBy('revenue', 'DESC') .execute(); // revenueByRegion is fully typed — no casting, no surprises
If this saves you from hand-writing ClickHouse types, a ⭐ helps other TypeScript devs find it.
- Build on top of your real ClickHouse schema instead of hand-maintained query types
- Reuse the same query definition across scripts, APIs, React apps, and agents
- Start local with the query builder, then add HTTP routes only when you need them
- Keep inputs, outputs, and SQL behavior explicit enough to test and reason about
Packages
@hypequery/clickhouse: typed ClickHouse query builder@hypequery/serve: code-first runtime for query contracts, HTTP routes, docs, and adapters@hypequery/react: thin TanStack Query hooks for hypequery APIs@hypequery/cli: scaffolding, schema generation, and local dev tooling
Quick Start
npm install -D @hypequery/cli npx hypequery init
That gives you the main path:
- Generate schema types from ClickHouse
- Write typed queries locally
- Expose the queries over HTTP when you need a shared contract
Add Contracts And HTTP When Needed
import { initServe } from '@hypequery/serve'; import { z } from 'zod'; import { db } from './analytics/client.js'; const { query, serve } = initServe({ context: () => ({ db }), basePath: '/api/analytics', }); const activeUsers = query({ description: 'List active users by region', input: z.object({ region: z.string() }), query: ({ ctx, input }) => ctx.db .table('users') .where('status', 'eq', 'active') .where('region', 'eq', input.region) .execute(), }); export const api = serve({ queries: { activeUsers }, }); api.route('/activeUsers', api.queries.activeUsers);
The same query can then be:
- executed directly with
api.execute(...) - exposed as an HTTP route
- consumed from React with
@hypequery/react - described for tools and agents
If you do not need serve, a standalone query can execute itself:
const activeUsers = query({ input: z.object({ region: z.string() }), query: ({ input }) => db .table('users') .where('status', 'eq', 'active') .where('region', 'eq', input.region) .execute(), }); await activeUsers.execute({ input: { region: 'EMEA' }, });
The same served execution API also works for semantic metrics:
import { initServe } from '@hypequery/serve'; import { createQueryBuilder } from '@hypequery/clickhouse'; import { dataset, dimension, measure } from '@hypequery/datasets'; const Orders = dataset('orders', { source: 'orders', dimensions: { region: dimension.string(), }, measures: { revenue: measure.sum('total'), }, }); const revenue = Orders.metric('revenue', { measure: 'revenue' }); const queryBuilder = createQueryBuilder({ url, username, password, database }); const { serve } = initServe({ context: () => ({ db: queryBuilder }), // ✅ Pass queryBuilder via context once }); export const api = serve({ metrics: { revenue }, // ✅ Auto-extracts queryBuilder from context datasets: { orders: Orders }, }); await api.execute('revenue', { input: { dimensions: ['region'] }, }); await api.execute('dataset:orders', { input: { dimensions: ['region'], measures: ['revenue'] }, });
CLI
# Scaffold analytics files and env vars npx hypequery init # Run the local dev server with docs npx hypequery dev # Regenerate schema types npx hypequery generate
Learn More
License
Apache-2.0. See LICENSE.




























