Skip to content

Agnostic Query

Write your query once. Share it between client and server. Reuse across Drizzle, Kysely, and raw SQL.

TanStack DB on the client, Drizzle on the server. The same user action — search, filter, paginate — produces LoadSubsetOptions on one end and needs Drizzle conditions on the other. agnostic-query bridges them with a portable QuerySchema.

TanStack DB ──fromTanDb──> QuerySchema ──toDrizzle──> Drizzle
aq builder ──.toJSON()──> QuerySchema ──toKysely──> Kysely
Kysely query ──fromKysely──> QuerySchema ──toSql──────> Raw SQL

Runtime-agnostic — plain data that work in clients, servers, and edge runtimes. Serialize to JSON, transmit over HTTP, consume on any platform.

Database-agnostic — the same QuerySchema drives Drizzle, Kysely, raw SQL (PostgreSQL), or any future adapter.

Zero dependencies, tree-shakeable — the core has no runtime dependencies; optional peer deps are only loaded when you import that adapter. Unused adapters are eliminated by your bundler.

No More Duplicate Queries

Write the query once. fromTanDbWhere converts TanStack DB expressions, toDrizzle executes them — zero duplication.

Fluent Builder

Chain .where(), .orderBy(), .limit(), .offset() with full TypeScript type safety.

WHERE System

Comparison operators, logical nesting with and/or/not, raw QueryWhere objects, and standalone newWhere() builder.

Database Adapters

Drizzle, Kysely, raw SQL (PostgreSQL), db0, TanStack DB — all from the same QuerySchema.

Runtime Validation

Optional Zod or Valibot schemas for validating QuerySchema at runtime boundaries.

Type Safety

Recursive field paths, operator-dependent value types, and full generic inference across the entire API.

Terminal window
bun add agnostic-query
import { aq } from 'agnostic-query'
interface User {
name: string
age: number
status: string
}
const schema = aq<User>()
.where('name', '=', 'Alice')
.where('age', '>=', 18)
.orderBy('name', 'asc')
.limit(20)
.toJSON()