TypeScript + ClickHouse · Pre-1.0
Your analytics queries.
Now with types.
Keep your ClickHouse client. Compose filters, joins, and aggregations in TypeScript, see the SQL you send, and infer the rows you receive.
A seeded database, generated types, and a working endpoint.
const query = db.selectFrom("events")
.selectExpr(eb => [
"event_type",
eb.fn.count().as("events"),
])
.where("tenant_id", "=", tenantId)
.groupBy("event_type");
const rows = await query.execute();// Inferred result
{ event_type: string; events: string }[]Counts are strings because ClickHouse returns UInt64 that way in JSON.
Use your existing connection
Quarry works with @clickhouse/client and plain TypeScript schema types.
Keep SQL visible
Inspect generated SQL and bound parameters before executing a query.
Build on ClickHouse
Use PREWHERE, FINAL, arrays, dictionaries, and LIMIT BY directly.
Learn by querying
Start with a result you need.
Daily activity
Group events into chart-ready daily counts.
LIMIT BYLatest event per user
Find one recent event for each user.
WHEREDashboard filters
Compose optional filters with typed inputs.
LIMIT / OFFSETPaginated results
Page through a consistently ordered report.
stream()Streaming exports
Process rows as they arrive from ClickHouse.
Code. SQL. Expected rows.
Every recipe uses the same seeded database. CI checks the displayed SQL and results against ClickHouse.
Already using ClickHouse?
Keep your connection and replace one query at a time. Generate types from your database as your use grows.
Add Quarry to your project →Deciding whether it fits?
Compare a real endpoint built with raw SQL, Quarry, Kysely, and hypequery. See the tradeoffs and supported clauses.
Compare approaches →Have a query that is difficult to compose or type?
Bring a real query →