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.