Approach
What Quarry is, what it is not, and the design principles that shape the public API.
Goals
Quarry is a ClickHouse-native TypeScript query builder. It is a query builder first — not an ORM, and not a generic multi-dialect SQL abstraction.
Inspiration
Quarry is not a port of Kysely or Drizzle, but both projects are useful reference points.
- Kysely influenced the overall query-builder architecture, the immutable chaining style, and a lot of the type-level thinking around source scope and selected output types.
- Drizzle is a useful reference point for ergonomics, schema-first thinking, and keeping the API approachable.
The goal is to learn from those projects without forcing ClickHouse into abstractions that were designed for other databases.
Why not a Kysely dialect or Drizzle adapter?
Because ClickHouse differs in ways that are not just syntax deep:
FINALbelongs to table sourcesPREWHEREis a distinct clauseSETTINGSis a query-level ClickHouse featureLEFT JOINruntime behavior differs from the null-default assumptions many SQL tools make- typed ClickHouse placeholders such as
{p0:UInt64}matter directly in the SQL text - ClickHouse result shapes and runtime value types do not always line up with the defaults expected by more generic SQL builders
Those differences make it possible to borrow ideas from Kysely and Drizzle without wanting to force Quarry into a dialect or adapter layer on top of them.
Quarry is not trying to be:
- a generic SQL abstraction across many databases
- an ORM
The current focus is a query builder that compiles to ClickHouse SQL cleanly and stays honest about runtime behavior.
Design principles
ClickHouse first
The API should model ClickHouse concepts directly instead of pretending every database behaves like Postgres.
That is why the current surface already includes things like:
final()prewhere(...)settings(...)
Runtime-honest typing
The builder should type execute() based on what @clickhouse/client actually
returns.
Examples from the current integration tests:
| ClickHouse type | TypeScript type at runtime |
|---|---|
UInt64 | string |
count() | string |
DateTime64(3) | string |
Decimal(18, 2) | number |
The goal is to avoid pretending values are safer or more transformed than they really are. See Runtime semantics for the details that matter when reading query results.
Builder and compiler stay separate
Internally, the library is still organized around:
builder -> AST -> compiler -> SQL + paramsThe AST is an implementation detail. The public API should feel like a query builder, not like AST construction.
Small explicit API over magical inference
When ClickHouse semantics are ambiguous, Quarry prefers explicit APIs over guessing.
Examples:
whereNull(...)/whereNotNull(...)instead of rawnullequalityparam(value, "Type")when the placeholder type should be explicit- first-class table sources like
db.table("event_logs").as("e").final()for advanced cases
Schema story today
Today, the query builder uses a plain TypeScript interface for the database shape.
interface DB {
event_logs: {
user_id: number;
event_type: string;
created_at: string;
};
}That means the schema is type-only for now.
A richer schema/runtime metadata system may come later, but it should feed into the query builder rather than replace this query-first mode.