ServeRequest whose headers are plain objects, not Fetch Headers. Use request.headers.authorization or request.headers\['x-api-key'].
```typescript
import { api } from '../analytics/queries';
export async function syncDailyRevenue() {
const result = await api.run('dailyRevenue', {
input: { start: '2024-01-01', end: '2024-01-31' },
context: { jobId: crypto.randomUUID() },
});
await warehouse.insert('daily_metrics', result);
}
```
```typescript
import { api } from '../../analytics/queries';
export async function GET() {
const result = await api.run('activeUsers');
return Response.json(result);
}
```
```typescript
import { api } from '../analytics/queries';
import { describe, it, expect } from 'vitest';
describe('activeUsers metric', () => {
it('returns the most recent rows', async () => {
const result = await api.run('activeUsers', {
input: { limit: 10 },
});
expect(result).toHaveLength(10);
});
});
```
```typescript
import { api } from '../analytics/queries';
// Metric endpoint, keyed by metric name.
const revenueByCountry = await api.run('revenue', {
input: { dimensions: ['country'], limit: 10 },
});
// Dataset endpoint, keyed as `dataset:`.
const ordersRollup = await api.run('dataset:orders', {
input: { dimensions: ['country'], measures: ['revenue', 'orderCount'] },
});
```
```typescript
// src/analytics/queries.ts
import { initServe } from '@hypequery/serve';
import { z } from 'zod';
import { db } from './client';
const { query, serve } = initServe({
context: () => ({ db }),
});
const revenue = query({
description: 'Get total revenue',
output: z.object({
total: z.number(),
count: z.number(),
}),
query: async ({ ctx }) => {
const rows = await ctx.db
.table('orders')
.sum('amount', 'total')
.count('order_id', 'count')
.execute();
return rows[0];
},
});
export const api = serve({
queries: { revenue },
});
// Optional: expose the query at a custom path/method.
// Without this, the query is already reachable at POST /queries/revenue.
api.route('/revenue', api.queries.revenue, { method: 'POST' });
```
```bash
npx hypequery dev src/analytics/queries.ts
# Server running at http://localhost:4000
```
```bash
curl -X POST http://localhost:4000/revenue
# {"total": 125000, "count": 450}
```
```typescript
// app/api/hypequery/[...hq]/route.ts
import { api } from '@/analytics/queries';
import { createFetchHandler } from '@hypequery/serve';
export const runtime = 'nodejs';
const handler = createFetchHandler(api.handler);
export const GET = handler;
export const POST = handler;
export const OPTIONS = handler;
```
```typescript
// server.ts
import express from 'express';
import { createNodeHandler } from '@hypequery/serve';
import { api } from './analytics/queries';
const app = express();
// Mount hypequery at /api/analytics
app.use('/api/analytics', createNodeHandler(api.handler));
app.listen(3000);
```
```typescript
// src/analytics/server.ts
import { api } from './queries';
await api.start({ port: 4000 });
```
```bash
npx hypequery dev src/analytics/queries.ts --port 4000
```
```typescript
// Cloudflare Worker
import { api } from './analytics/queries';
import { createFetchHandler } from '@hypequery/serve';
const handler = createFetchHandler(api.handler);
export default {
fetch(request: Request) {
return handler(request);
},
};
// Vercel Edge Function
import { api } from './analytics/queries';
import { createFetchHandler } from '@hypequery/serve';
const fetchHandler = createFetchHandler(api.handler);
export default function handler(request: Request) {
return fetchHandler(request);
}
```
```bash
curl http://localhost:4000/openapi.json
```
```typescript
const api = serve({
queries: { revenue },
openapi: {
title: 'My Analytics API',
version: '2.0.0',
servers: [
{ url: 'https://api.example.com', description: 'Production' },
],
},
});
```
```typescript
// src/analytics/queries.ts
import { initServe } from '@hypequery/serve';
import { db } from './client';
import { Orders, revenue } from './datasets/orders';
const { serve } = initServe({
context: () => ({ db }),
});
export const api = serve({
// Required whenever metrics or datasets are registered.
queryBuilder: db,
metrics: { revenue },
datasets: { orders: Orders },
});
```
```typescript
export const api = serve({
queryBuilder: db,
metrics: { revenue },
datasets: { orders: Orders },
semanticPaths: {
metrics: '/api/metrics',
datasets: '/api/data',
},
});
// POST /api/metrics/revenue
// POST /api/data/orders/query
```
```bash
curl -X POST http://localhost:4000/datasets/orders/query \
-H 'content-type: application/json' \
-d '{ "dimensions": ["country"], "measures": ["revenue"], "limit": 50, "offset": 50 }'
```
```bash
curl -X POST http://localhost:4000/datasets/orders/query \
-H 'content-type: application/json' \
-H 'x-include-meta: true' \
-d '{ "dimensions": ["country"], "measures": ["revenue"], "limit": 10 }'
```
```typescript
const api = serve({
queries: { revenue },
docs: {
title: 'My API Docs',
subtitle: 'Analytics runtime',
darkMode: true,
},
});
```
```typescript
import { buildDocsHtml } from '@hypequery/serve';
app.get('/docs', (req, res) => {
res.send(buildDocsHtml('/openapi.json', {
title: 'My API Docs',
subtitle: 'Analytics runtime',
darkMode: true,
}));
});
```
```typescript
import { api } from './analytics/queries';
// Register with custom path
api.route('/analytics/revenue', api.queries.revenue, { method: 'POST' });
// Register as GET
api.route('/health', api.queries.healthCheck, { method: 'GET' });
// Use query metadata from definition
api.route('/weekly', api.queries.weeklyRevenue);
```
```typescript
const busiestRoutes = query({
input: z.object({
minTrips: z.number().int().default(500),
limit: z.number().int().max(100).default(10),
verbose: z.boolean().optional(),
}),
query: async ({ input }) => { /* ... */ },
});
```
```bash
curl '/api/analytics/queries/busiestRoutes?minTrips=500&limit=8&verbose=true'
# input === { minTrips: 500, limit: 8, verbose: true }
```
Connect ClickHouse, generate types, and run the first query
Check the shipped builder, aggregate, dataset, React, and MCP surface
Write typed ClickHouse filters, joins, aggregations, and native clauses
Browse complete framework and dashboard examples
{JSON.stringify(stats, null, 2)};
}
```
```typescript
const api = serve({
queries: { activeUsers },
hooks: {
onRequestStart: async (event) => {
console.log('start', event.queryKey);
},
onRequestEnd: async (event) => {
console.log('end', event.queryKey, event.durationMs);
},
onAuthFailure: async (event) => {
console.warn('auth failed', event.queryKey, event.reason);
},
onAuthorizationFailure: async (event) => {
console.warn('forbidden', event.queryKey, event.reason, event.required);
},
onError: async (event) => {
console.error('error', event.queryKey, event.error);
},
},
});
```
```typescript
const api = serve({
queries: { activeUsers },
queryLogging: 'json',
slowQueryThreshold: 2_000,
});
```
```typescript
import { logger } from '@hypequery/clickhouse';
logger.configure({
enabled: true,
level: 'debug',
onQueryLog: (log) => {
console.log(log.query, log.duration, log.status);
},
});
```
```typescript
import { initServe } from '@hypequery/serve';
import { z } from 'zod';
import { db } from './client';
const { query, serve } = initServe({
context: () => ({ db }),
basePath: '/api/analytics',
});
```
```typescript
const activeUsers = query({
description: 'Most recent active users',
summary: 'List active users',
tags: ['users'],
requiredRoles: ['admin', 'editor'],
input: z.object({
limit: z.number().min(1).max(500).default(50),
}),
output: z.array(z.object({
id: z.string(),
email: z.string(),
created_at: z.string(),
})),
query: ({ ctx, input }) =>
ctx.db
.table('users')
.select(['id', 'email', 'created_at'])
.where('status', 'eq', 'active')
.orderBy('created_at', 'DESC')
.limit(input.limit)
.execute(),
});
```
```typescript
const rows = await activeUsers.execute({
input: { limit: 25 },
});
```
```typescript
export const api = serve({
queries: { activeUsers },
});
api.route('/active-users', api.queries.activeUsers, { method: 'POST' });
```
```typescript
import { initServe } from '@hypequery/serve';
import { z } from 'zod';
const { query, serve } = initServe({
context: () => ({ db }),
});
const revenue = query({
input: z.object({
startDate: z.string().datetime(),
endDate: z.string().datetime(),
currency: z.enum(['USD', 'EUR', 'GBP']).default('USD'),
includeRefunds: z.boolean().optional(),
}),
query: async ({ ctx, input }) => {
// input is fully typed based on the input schema
const { startDate, endDate, currency } = input;
return ctx.db
.table('transactions')
.where('date', 'gte', startDate)
.where('date', 'lte', endDate)
.where('currency', 'eq', currency)
.sum('amount', 'total')
.execute();
},
});
export const api = serve({
queries: { revenue },
});
```
```typescript
const revenue = query({
input: z.object({
startDate: z.string(),
endDate: z.string(),
}),
output: z.object({
total: z.number(),
currency: z.string(),
breakdown: z.array(z.object({
date: z.string(),
amount: z.number(),
transactionCount: z.number(),
})),
metadata: z.object({
generatedAt: z.string(),
queryDurationMs: z.number(),
}),
}),
query: async ({ input }) => {
// Return value is type-checked against the output schema
return {
total: 42000,
currency: 'USD',
breakdown: [
{ date: '2025-01-01', amount: 10000, transactionCount: 50 },
{ date: '2025-01-02', amount: 12000, transactionCount: 60 },
],
metadata: {
generatedAt: new Date().toISOString(),
queryDurationMs: 123,
},
};
},
});
```
```typescript
const dateRangeSchema = z.object({
startDate: z.string().datetime(),
endDate: z.string().datetime(),
}).refine(
(data) => new Date(data.startDate) < new Date(data.endDate),
{ message: 'startDate must be before endDate' }
);
queries: {
metrics: query({
input: dateRangeSchema,
query: async ({ input }) => { /* ... */ },
}),
}
```
```typescript
const reportSchema = z.discriminatedUnion('type', [
z.object({
type: z.literal('revenue'),
currency: z.enum(['USD', 'EUR']),
includeRefunds: z.boolean(),
}),
z.object({
type: z.literal('users'),
includeInactive: z.boolean(),
segment: z.enum(['free', 'paid', 'enterprise']),
}),
z.object({
type: z.literal('performance'),
metric: z.enum(['latency', 'throughput', 'errors']),
percentile: z.number().min(50).max(99),
}),
]);
queries: {
generateReport: query({
input: reportSchema,
query: async ({ input }) => {
switch (input.type) {
case 'revenue':
return generateRevenueReport(input.currency, input.includeRefunds);
case 'users':
return generateUserReport(input.includeInactive, input.segment);
case 'performance':
return generatePerformanceReport(input.metric, input.percentile);
}
},
}),
}
```
```typescript
// schemas/common.ts
import { z } from 'zod';
export const dateRangeSchema = z.object({
startDate: z.string().datetime(),
endDate: z.string().datetime(),
});
export const currencySchema = z.enum(['USD', 'EUR', 'GBP', 'JPY']);
export const paginationInputSchema = z.object({
page: z.number().int().positive().default(1),
pageSize: z.number().int().min(1).max(100).default(20),
});
export const paginationOutputSchema = (dataSchema: T) =>
z.object({
data: z.array(dataSchema),
pagination: z.object({
page: z.number(),
pageSize: z.number(),
totalPages: z.number(),
totalCount: z.number(),
}),
});
// api/index.ts
import { dateRangeSchema, currencySchema, paginationOutputSchema } from './schemas/common';
const userSchema = z.object({
id: z.string(),
email: z.string(),
name: z.string(),
});
const revenue = query({
input: dateRangeSchema.extend({
currency: currencySchema,
}),
query: async ({ input }) => { /* ... */ },
});
const users = query({
input: paginationInputSchema,
output: paginationOutputSchema(userSchema),
query: async ({ input }) => { /* ... */ },
});
const api = serve({
queries: { revenue, users },
});
```
```typescript
const userSchema = z.object({
id: z.string(),
email: z.string().email(),
name: z.string(),
age: z.number().int().positive().optional(),
});
type User = z.infer;
// type User = {
// id: string;
// email: string;
// name: string;
// age?: number | undefined;
// }
const createUser = query({
input: userSchema,
output: userSchema.extend({
createdAt: z.string().datetime(),
}),
query: async ({ input }) => {
// input: User (fully typed!)
const user = await db.table('users').insert(input).returning('*');
return {
...user,
createdAt: new Date().toISOString(),
};
},
});
```
```typescript
const revenue = query({
input: z.object({
startDate: z.string().datetime().describe('Start of date range (ISO 8601)'),
endDate: z.string().datetime().describe('End of date range (ISO 8601)'),
currency: z.enum(['USD', 'EUR']).default('USD').describe('Currency code'),
}),
output: z.object({
total: z.number().describe('Total revenue in specified currency'),
transactionCount: z.number().int().describe('Number of transactions'),
}),
summary: 'Get revenue for date range',
description: 'Returns total revenue and transaction count',
query: async ({ input }) => { /* ... */ },
});
// Auto-generates OpenAPI spec with detailed parameter descriptions
```
```typescript
import { createQueryBuilder, MemoryCacheProvider } from '@hypequery/clickhouse';
const db = createQueryBuilder({
url: process.env.CLICKHOUSE_URL!,
cache: {
mode: 'stale-while-revalidate',
ttlMs: 2_000,
staleTtlMs: 30_000,
staleIfError: true,
provider: new MemoryCacheProvider({ maxEntries: 1_000 }),
},
});
```
```typescript
const rows = await db
.table('orders')
.sum('total', 'revenue')
.groupBy(['customer_id'])
.orderBy('revenue', 'DESC')
.limit(10)
.cache({ tags: ['orders'], ttlMs: 5_000 })
.execute();
```
```typescript
const rows = await db
.table('orders')
.sum('total', 'revenue')
.groupBy(['customer_id'])
.execute({
cache: {
mode: 'network-first',
ttlMs: 1_000,
tags: ['orders', 'dashboards'],
},
});
```
```typescript
await db.cache.invalidateKey('hq:v1:analytics:orders:abc123');
await db.cache.invalidateTags(['orders', 'dashboards']);
await db.cache.clear();
await db.cache.warm([
() => db.table('orders').select(['id']).cache({ tags: ['orders'] }).execute(),
() => db.table('users').select(['id']).cache({ tags: ['users'] }).execute(),
]);
const stats = db.cache.getStats();
console.log(stats.hitRate, stats.hits, stats.misses, stats.staleHits, stats.revalidations);
```
```typescript
import type { CacheEntry, CacheProvider } from '@hypequery/clickhouse';
import { Redis } from 'ioredis';
class RedisCacheProvider implements CacheProvider {
constructor(private readonly client = new Redis(process.env.REDIS_URL!)) {}
async get(key: string) {
const raw = await this.client.get(key);
return raw ? (JSON.parse(raw) as CacheEntry) : null;
}
async set(key: string, entry: CacheEntry) {
await this.client.set(key, JSON.stringify(entry), 'PX', entry.cacheTimeMs ?? entry.ttlMs);
}
async delete(key: string) {
await this.client.del(key);
}
async deleteByTag(namespace: string, tag: string) {
const tagKey = `hq:tag:${namespace}:${tag}`;
const keys = await this.client.smembers(tagKey);
if (keys.length) await this.client.del(...keys);
await this.client.del(tagKey);
}
}
```
columns() to omit them.
DatabaseAdapter.insert method.
The built-in ClickHouse adapter supports it; custom adapters that don't will throw a clear error.
```typescript
import { createQueryBuilder, JoinRelationships } from '@hypequery/clickhouse';
import type { Schema } from './generated-schema';
const relationships = new JoinRelationships();
relationships.define('orderCustomer', {
from: 'orders',
to: 'users',
leftColumn: 'user_id',
rightColumn: 'id',
type: 'LEFT',
});
createQueryBuilder.setJoinRelationships(relationships);
export const db = createQueryBuilder({
url: process.env.CLICKHOUSE_URL!,
});
```
```typescript
const rows = await db
.table('orders')
.withRelation('orderCustomer')
.select([
'orders.id',
'orders.total',
'users.name',
'users.email',
])
.execute();
```
```typescript
const relationships = new JoinRelationships();
relationships.defineChain('orderCustomerRegion', [
{
from: 'orders',
to: 'users',
leftColumn: 'user_id',
rightColumn: 'id',
type: 'INNER',
},
{
from: 'users',
to: 'regions',
leftColumn: 'region_id',
rightColumn: 'id',
type: 'LEFT',
},
]);
```
```typescript
const rows = await db
.table('orders')
.withRelation('orderCustomerRegion')
.select([
'orders.id',
'users.name',
'regions.region_name',
])
.execute();
```
```typescript
const rows = await db
.table('orders')
.withRelation('orderCustomer', { type: 'INNER' })
.select([
'orders.id',
'users.name',
])
.execute();
```
```typescript
import type { JoinPath } from '@hypequery/clickhouse';
const orderCustomerPath = {
from: 'orders',
to: 'users',
leftColumn: 'user_id',
rightColumn: 'id',
alias: 'customer',
} as const satisfies JoinPath;
const rows = await db
.table('orders')
.withRelation(orderCustomerPath)
.select([
'orders.id',
'customer.name',
])
.execute();
```
```text
Join relationships have not been initialized. Call QueryBuilder.setJoinRelationships first.
```
```text
Join relationship 'orderCustomer' not found
```
```ts
const results = await db.table('orders')
.innerJoin('users', 'user_id', 'users.id')
.select([
'orders.id',
'orders.total',
'users.name AS customer_name',
'users.email AS customer_email',
])
.execute();
```
```ts
import { createHooks } from '@hypequery/react';
import type { InferApiType } from '@hypequery/serve';
import type { api } from '@/analytics/queries';
type Api = InferApiType;
export const { useQuery, useMutation } = createHooks({
baseUrl: '/api',
config: {
weeklyRevenue: { method: 'GET' }, // Read-only queries
tripStats: { method: 'GET' },
rebuildMetrics: { method: 'POST' }, // Write operations
updateMetric: { method: 'PUT' },
},
});
```
```bash
npx hypequery generate:manifest analytics/api.ts --output analytics/hypequery-manifest.json
```
```ts
// lib/analytics.ts
import { createHooks } from '@hypequery/react';
import type { InferApiType } from '@hypequery/serve';
import type { api } from '@/analytics/api';
import manifest from '@/analytics/hypequery-manifest.json';
type Api = InferApiType;
export const { useQuery, useMutation } = createHooks({
baseUrl: '/api/hypequery',
manifest, // ✅ Method metadata, no server code in the bundle
});
```
```ts
// app/api/hypequery-config/route.ts
import { extractClientConfig } from '@hypequery/serve';
import { api } from '@/analytics/queries';
export function GET() {
return Response.json(extractClientConfig(api));
}
```
```ts
// lib/analytics.ts
import { createHooks } from '@hypequery/react';
import type { InferApiType } from '@hypequery/serve';
import type { api } from '@/analytics/queries';
type Api = InferApiType;
let hooksPromise: Promise>> | null = null;
export function getHypequeryHooks() {
if (!hooksPromise) {
hooksPromise = fetch('/api/hypequery-config')
.then((res) => res.json())
.then((config) =>
createHooks({ baseUrl: '/api/hypequery', config })
);
}
return hooksPromise;
}
```
```tsx
// app/providers.tsx
import { getHypequeryHooks } from '@/lib/analytics';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { useEffect, useState } from 'react';
const queryClient = new QueryClient();
export function AppProviders({ children }: { children: React.ReactNode }) {
const [hooks, setHooks] = useState(null);
useEffect(() => {
getHypequeryHooks().then(setHooks);
}, []);
if (!hooks) return Loading...;
return (
{children}
);
}
```
```tsx
import { useQueryClient } from '@tanstack/react-query';
function MetricsPanel() {
const queryClient = useQueryClient();
const { data } = useQuery('metrics', {});
const handleRefresh = () => {
// Invalidate specific queries
queryClient.invalidateQueries({ queryKey: ['metrics'] });
// Or clear entire cache
queryClient.clear();
};
return (
{data?.total}
);
}
```
```tsx
import { useMutation, useQuery } from '@/lib/analytics';
import { useQueryClient } from '@tanstack/react-query';
function MetricsManager() {
const queryClient = useQueryClient();
const { data: metrics } = useQuery('metrics', {});
const rebuild = useMutation('rebuildMetrics', {
onSuccess: () => {
// Invalidate related queries
queryClient.invalidateQueries({ queryKey: ['metrics'] });
queryClient.invalidateQueries({ queryKey: ['weeklyRevenue'] });
},
});
return (
);
}
```
```tsx
import { useQueryClient } from '@tanstack/react-query';
function Dashboard() {
const queryClient = useQueryClient();
useEffect(() => {
// Prefetch on mount
queryClient.prefetchQuery({
queryKey: ['weeklyRevenue', { startDate: '2025-01-01' }],
queryFn: () => fetch('/api/weeklyRevenue?startDate=2025-01-01')
.then(res => res.json()),
});
}, []);
// ...
}
```
```tsx
// Invalidate one query (must match name + input).
queryClient.invalidateQueries({ queryKey: ['hypequery', 'weeklyRevenue'] });
// Invalidate every hypequery cache entry by prefix.
queryClient.invalidateQueries({ queryKey: ['hypequery'] });
```
```tsx
export const { useQuery, useMutation } = createHooks({
baseUrl: '/api',
headers: async () => ({
Authorization: `Bearer ${await getAuthToken()}`,
}),
onUnauthorized: async () => {
// Runs on a 401; refresh the token, then the request retries once.
await refreshSession();
},
});
```
```tsx
export const { useQuery, useMutation } = createHooks({
baseUrl: '/api',
fetchFn: async (url, options) => {
const token = getAuthToken();
return fetch(url, {
...options,
headers: {
...options?.headers,
Authorization: `Bearer ${token}`,
},
});
},
});
```
```tsx
import { Suspense } from 'react';
import { useSuspenseQuery } from '@tanstack/react-query';
function MetricsChart() {
// Throws a promise while loading, so the parent shows its fallback.
const { data } = useSuspenseQuery({
queryKey: ['hypequery', 'weeklyRevenue', { startDate: '2025-01-01' }],
queryFn: () =>
fetch('/api/weeklyRevenue?startDate=2025-01-01').then((res) => res.json()),
});
return Total: ${data.total};
}
function Dashboard() {
return (
Loading metrics...}>
);
}
```
```tsx
import { ErrorBoundary } from 'react-error-boundary';
function Dashboard() {
return (
Failed to load metrics}
onError={(error) => console.error('Metrics error:', error)}
>
);
}
```
```tsx
const { data, refetch, isFetching } = useQuery(
'weeklyRevenue',
{ startDate: '2025-01-01' },
{
// Caching
staleTime: 5 * 60 * 1000, // 5 minutes
gcTime: 10 * 60 * 1000, // 10 minutes (was `cacheTime` before TanStack v5)
// Refetching
refetchOnMount: true,
refetchOnWindowFocus: false,
refetchInterval: 30000, // Poll every 30s
// Conditional fetching
enabled: isAuthenticated,
// Retries
retry: 3,
retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000),
}
);
```
```tsx
function LiveMetrics() {
const { data, dataUpdatedAt } = useQuery(
'liveMetrics',
{},
{
staleTime: 0, // Always consider stale
refetchInterval: 5000, // Refetch every 5 seconds
refetchIntervalInBackground: true, // Continue even when tab is hidden
}
);
return (
Active Users: {data?.activeUsers}
Updated: {new Date(dataUpdatedAt).toLocaleTimeString()}
);
}
```
```tsx
function Dashboard() {
const revenue = useQuery('weeklyRevenue', { startDate: '2025-01-01' });
const users = useQuery('activeUsers', { limit: 100 });
const metrics = useQuery('systemMetrics', {});
// All three queries run in parallel
if (revenue.isLoading || users.isLoading || metrics.isLoading) {
return Loading...;
}
return (
);
}
```
```tsx
import { useInfiniteDataset } from '@/lib/analytics';
function InfiniteOrders() {
const {
data,
fetchNextPage,
hasNextPage,
isFetchingNextPage,
} = useInfiniteDataset('orders', {
dimensions: ['country', 'status'],
measures: ['revenue'],
limit: 20, // page size
});
return (
{data?.pages.map((page, i) => (
{/* Each page is the endpoint response: `{ data, meta }`. */}
{page.data.map((row, j) => (
{row.country}: {row.revenue}
))}
))}
{hasNextPage && (
)}
);
}
```
```bash
npm install @hypequery/react @tanstack/react-query
```
```ts
// lib/analytics.ts
import { createHooks } from '@hypequery/react';
import type { InferApiType } from '@hypequery/serve';
import type { api } from '@/analytics/api';
// Automatic type inference - no manual type definition needed!
type Api = InferApiType;
export const { useQuery, useMutation } = createHooks({
baseUrl: '/api', // where your hypequery routes live
});
```
```ts
// lib/analytics.ts
import { createHooks } from '@hypequery/react';
type Api = {
weeklyRevenue: {
input: { startDate: string };
output: { total: number };
};
// ... other queries
};
export const { useQuery, useMutation } = createHooks({
baseUrl: '/api',
});
```
```bash
npx hypequery generate:manifest analytics/api.ts --output analytics/hypequery-manifest.json
```
```ts
// lib/analytics.ts
import { createAnalyticsHooks } from '@hypequery/react';
import type { InferApiType } from '@hypequery/serve';
import type { api } from '@/analytics/api';
import manifest from '@/analytics/hypequery-manifest.json';
// `import type` is erased at build time, so the server module — and the
// ClickHouse client it constructs — never reaches the browser bundle.
type Api = InferApiType;
export const {
useQuery,
useMutation,
useMetric,
useDataset,
useInfiniteMetric,
useInfiniteDataset,
} = createAnalyticsHooks({
baseUrl: '/api',
manifest,
});
```
```text
./node_modules/@hypequery/clickhouse/dist/cli/generate-types.js:2:1
Error: Module not found: Can't resolve 'fs/promises'
```
config entry), `useMetric`/`useDataset` throw a clear error rather than calling the wrong URL.
```tsx
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
const queryClient = new QueryClient();
export function AppProviders({ children }: { children: React.ReactNode }) {
return (
{children}
);
}
```
```tsx
// app.tsx or _app.tsx
import { AppProviders } from './providers';
function App() {
return (
);
}
```
```tsx
import { useQuery } from '@/lib/analytics';
function RevenueChart() {
const { data, error, isLoading } = useQuery('weeklyRevenue', {
startDate: '2025-01-01',
});
if (isLoading) return Loading...;
if (error) return Error: {error.message};
return Total: ${data.total};
}
```
```tsx
// ✅ Type-safe
const { data } = useQuery('weeklyRevenue', {
startDate: '2025-01-01',
});
// ❌ TypeScript error - invalid query name
const { data } = useQuery('invalidQuery', { ... });
// ❌ TypeScript error - invalid input
const { data } = useQuery('weeklyRevenue', {
invalidField: 'value',
});
```
```tsx
const { data } = useQuery('weeklyRevenue',
{ startDate: '2025-01-01' },
{
staleTime: 5 * 60 * 1000, // 5 minutes
refetchOnWindowFocus: false,
enabled: isAuthenticated, // Conditional fetching
}
);
```
```tsx
import { useMutation } from '@/lib/analytics';
function RebuildButton() {
const rebuild = useMutation('rebuildMetrics');
return (
);
}
```
```tsx
const rebuild = useMutation('rebuildMetrics', {
onSuccess: (data) => {
console.log('Rebuild complete:', data);
// Invalidate related queries
queryClient.invalidateQueries({ queryKey: ['weeklyRevenue'] });
},
onError: (error) => {
console.error('Rebuild failed:', error);
},
});
```
```tsx
const updateMetric = useMutation('updateMetric', {
onMutate: async (newData) => {
// Cancel outgoing refetches
await queryClient.cancelQueries({ queryKey: ['metrics'] });
// Snapshot current value
const previous = queryClient.getQueryData(['metrics']);
// Optimistically update
queryClient.setQueryData(['metrics'], (old) => ({
...old,
...newData,
}));
return { previous };
},
onError: (err, variables, context) => {
// Rollback on error
queryClient.setQueryData(['metrics'], context.previous);
},
onSettled: () => {
// Refetch after success or error
queryClient.invalidateQueries({ queryKey: ['metrics'] });
},
});
```
```tsx
import { useMetric } from '@/lib/analytics';
function RevenueByCountry() {
const { data, isLoading } = useMetric('revenue', {
dimensions: ['country'],
filters: [{ field: 'status', operator: 'eq', value: 'completed' }],
orderBy: [{ field: 'revenue', direction: 'desc' }],
limit: 10,
});
if (isLoading) return Loading...;
// Semantic endpoints return `{ data, meta }`.
return (
```tsx
import { useDataset } from '@/lib/analytics';
function OrdersRollup() {
const { data, isLoading } = useDataset('orders', {
dimensions: ['country', 'status'],
measures: ['revenue', 'orderCount'],
filters: [{ field: 'status', operator: 'eq', value: 'completed' }],
limit: 25,
});
if (isLoading) return Loading...;
return (
| {row.country} | {row.status} | {row.revenue} | {row.orderCount} |
```tsx
const { data, error } = useQuery('weeklyRevenue', { startDate: '2025-01-01' });
if (error) {
// Validation errors from hypequery
if (error.status === 400) {
return Invalid input: {error.message};
}
// Network errors
if (error.name === 'TypeError') {
return Network error. Please check your connection.;
}
// Generic error
return Something went wrong: {error.message};
}
```
```tsx
function UserRevenue({ userId }: { userId: string }) {
const { data: user } = useQuery('getUser', { userId });
const { data: revenue } = useQuery(
'userRevenue',
{ userId },
{ enabled: !!user } // Only run after user is loaded
);
return {revenue?.total};
}
```
```tsx
const { data } = useQuery(
'liveMetrics',
{},
{ refetchInterval: 5000 } // Poll every 5 seconds
);
```
```tsx
function MetricsPanel() {
const { data, refetch } = useQuery('metrics', {});
return (
{data?.total}
);
}
```
```typescript
const base = db.table('events').select(['id', 'user_id']);
const recent = base.orderBy('id', 'DESC').limit(10);
const active = base.where('is_active', 'eq', 1);
```