Query DSL
Type-safe filters and sorts using KSP-generated property paths.
Building queries
Queries use the find method with a builder lambda. The builder receives a
typed expression r representing your entity, with KSP-generated property accessors.
val results = repo.find { r ->
filter(r.age.gte(18))
filter(r.address.city.eq("Berlin"))
sort(r.name.asc)
limit(20)
} Comparison operators
| Operator | MongoDB | Example |
|---|---|---|
eq | $eq | r.name.eq("Alice") |
ne | $ne | r.status.ne("deleted") |
gt | $gt | r.age.gt(18) |
gte | $gte | r.age.gte(18) |
lt | $lt | r.score.lt(50) |
lte | $lte | r.score.lte(100) |
isIn | $in | r.status.isIn(listOf("active", "pending")) |
nin | $nin | r.status.nin(listOf("deleted")) |
String operators
// Regex matching
filter(r.email.regex(".*@example\.com"))
filter(r.name.regex(Regex("^A", RegexOption.IGNORE_CASE))) Logical operators
import io.peekandpoke.monko.lang.dsl.and
import io.peekandpoke.monko.lang.dsl.or
import io.peekandpoke.monko.lang.dsl.not
// AND (implicit — multiple filter() calls are AND-ed)
filter(r.age.gte(18))
filter(r.status.eq("active"))
// Explicit AND / OR
filter(
or(
r.status.eq("active"),
r.status.eq("pending"),
)
)
filter(
and(
r.age.gte(18),
or(
r.address.city.eq("Berlin"),
r.address.city.eq("Munich"),
),
)
) Array operators
// Element match
filter(r.tags.elemMatch(Filters.eq("kotlin")))
// Array size
filter(r.tags.size(3))
// All elements in array
filter(r.tags.all(listOf("kotlin", "mongodb")))
// Existence check
filter(r.middleName.exists(true))
filter(r.deletedAt.exists(false)) Sorting
// Single field
sort(r.name.asc)
sort(r.createdAt.desc)
// Multiple fields
sort(orderBy(r.name.asc, r.age.desc)) Pagination
// Limit results
limit(20)
// Skip + limit
skip(40)
limit(20)
// The cursor provides fullCount for pagination UI
val cursor = repo.find { r ->
filter(r.status.eq("active"))
sort(r.createdAt.desc)
skip((page - 1) * pageSize)
limit(pageSize)
}
// cursor.fullCount -> total matching documents Nested property access
KSP generates accessors for nested types. Access deeply nested fields with dot notation:
// r.address is generated because Person has an Address field
filter(r.address.city.eq("Berlin"))
filter(r.address.zip.regex("^10"))
sort(r.address.country.asc) Every property path in a query is validated at compile time.
If
Address no longer has a city field, the query won't compile.