Skip to content

Builder API

The aq builder provides a fluent API for constructing QuerySchema objects.

Both interface and type work as the shape parameter — use whichever you prefer.

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)
.offset(0)
.toJSON()
OperatorDescription
=Exact match
>Greater than
>=Greater than or equal
<Less than
<=Less than or equal
likeSQL LIKE
ilikeCase-insensitive LIKE
inValue in array (outputs values field)
is nullNull check (2-argument form: .where('field', 'is null'))
@>A contains B, eg [1, 2, 3] @> [2, 3]
<@B contains A, eg [2, 3] <@ [1, 2, 3]
&&Overlap, eg [1, 2] && [2, 3]

For complex logic (and, or, not), pass a callback to .where():

const schema = aq<User>()
.where(({ or, where, not }) =>
or([
where('role', '=', 'admin'),
where('role', '=', 'moderator'),
not(where('status', '=', 'banned')),
]),
)
.toJSON()
// → {
// where: {
// op: 'or',
// conditions: [
// { field: ['role'], op: '=', value: 'admin' },
// { field: ['role'], op: '=', value: 'moderator' },
// { op: 'not', condition: { field: ['status'], op: '=', value: 'banned' } },
// ],
// },
// }

Pass a pre-built QueryWhere directly to .where():

const roleWhere: QuerySchema<User>['where'] = {
field: ['role'],
op: '=',
value: 'admin',
}
const schema = aq<User>()
.where('name', '=', 'Alice')
.where(roleWhere)
.toJSON()

This also works inside callbacks:

aq<User>()
.where(({ or, where }) =>
or([where('name', '=', 'Alice'), where(roleWhere)]),
)
.toJSON()

JSONB paths and array indices work with tuple syntax:

aq<User>()
.where(['address', 'city', 'name'], '=', 'Berlin')
.where(['tags', 0, 'name'], 'like', '%tech%')
.orderBy(['address', 'city', 'name'], 'desc')

Multiple .orderBy() calls append entries:

aq<User>()
.orderBy('name', 'asc')
.orderBy('age', 'desc')
.toJSON()
// → {
// orderBy: [
// { field: ['name'], direction: 'asc' },
// { field: ['age'], direction: 'desc' },
// ],
// }

Field paths are fully type-checked against your shape:

interface User {
name: string
age: number
tags: { id: number; name: string }[]
address: { city: { name: string } }
}
aq<User>().where(['tags', 0, 'name'], '=', 'tech') // ✓
aq<User>().where(['tags', 0, 'name'], '=', 42) // ✗ string ≠ number
aq<User>().where(['address', 'city', 'name'], '=', 'Berlin') // ✓
aq<User>().where(['address', 'city', 'zip'], '=', '12345') // ✗ no 'zip' on city

You can also construct QuerySchema as a plain object:

import type { QuerySchema } from 'agnostic-query'
const schema: QuerySchema<User> = {
limit: 20,
offset: 0,
orderBy: [{ field: ['name'], direction: 'asc' }],
where: {
op: 'and',
conditions: [
{ field: ['age'], op: '>=', value: 18 },
{ field: ['status'], op: 'in', values: ['active', 'pending'] },
],
},
}