Skip to content
Warlock.js v4

Query Builder API

This page documents every method on Cascade’s QueryBuilderContract. It’s a reference, not a tutorial — each method shows its signature, a one-sentence description, a minimal example, and a link to the guide that teaches the concept.

Use the in-page anchors (or Ctrl-F) when you know the name and just need the signature. For the why and when, follow the See also links into the essentials and guide pages.

:::info — How method blocks are organised

Each method shows:

  • Signature — the TypeScript signature(s) you can call
  • What it does — one sentence, mechanical
  • Example — one minimal snippet
  • See also — the guide that teaches the concept

Methods are grouped by purpose (where, select, joins, ordering, …) and alphabetised within each group.

:::


scope(scopeName: string, ...args: any[]): this

What it does: apply a local scope defined on the model.

await User.query().scope("active").get();

See also: Querying essentials

withoutGlobalScope(...scopeNames: string[]): this

What it does: disable one or more global scopes for this query.

await User.query().withoutGlobalScope("tenant").get();
withoutGlobalScopes(): this

What it does: disable all global scopes for this query.

await User.query().withoutGlobalScopes().get();

where(field: string, value: unknown): this
where(field: string, operator: WhereOperator, value: unknown): this
where(conditions: WhereObject): this
where(callback: WhereCallback<T>): this

What it does: add a where clause. Four forms — equality, operator, object, callback.

await User.where("age", ">", 18).get();
await User.where({ status: "active", role: "admin" }).get();

See also: Querying essentials

orWhere(field: string, value: unknown): this
orWhere(field: string, operator: WhereOperator, value: unknown): this
orWhere(conditions: WhereObject): this
orWhere(callback: WhereCallback<T>): this

What it does: same shapes as where, joined with OR.

await User.where("role", "admin").orWhere("role", "moderator").get();

whereIn(field, values) / whereNotIn(field, values)

Section titled “whereIn(field, values) / whereNotIn(field, values)”
whereIn(field: string, values: unknown[]): this
whereNotIn(field: string, values: unknown[]): this

What it does: match where field is (or isn’t) any value in the list.

await User.whereIn("status", ["active", "pending"]).get();
whereNull(field: string): this
whereNotNull(field: string): this

What it does: constrain the field to be NULL or NOT NULL.

await User.whereNotNull("emailVerifiedAt").get();

whereBetween(field, range) / whereNotBetween(field, range)

Section titled “whereBetween(field, range) / whereNotBetween(field, range)”
whereBetween(field: string, range: [unknown, unknown]): this
whereNotBetween(field: string, range: [unknown, unknown]): this

What it does: constrain the field to fall inside (or outside) the inclusive range.

await User.whereBetween("age", [18, 65]).get();

whereLike / whereNotLike / whereStartsWith / whereNotStartsWith / whereEndsWith / whereNotEndsWith

Section titled “whereLike / whereNotLike / whereStartsWith / whereNotStartsWith / whereEndsWith / whereNotEndsWith”
whereLike(field: string, pattern: RegExp | string): this
whereNotLike(field: string, pattern: string): this
whereStartsWith(field: string, value: string | number): this
whereNotStartsWith(field: string, value: string | number): this
whereEndsWith(field: string, value: string | number): this
whereNotEndsWith(field: string, value: string | number): this

What it does: pattern-match (or anti-match) on a string field. Case-insensitive.

await User.whereLike("name", "%john%").get();
await User.whereStartsWith("email", "admin@").get();

whereDate / whereDateEquals / whereDateBefore / whereDateAfter / whereDateBetween / whereDateNotBetween

Section titled “whereDate / whereDateEquals / whereDateBefore / whereDateAfter / whereDateBetween / whereDateNotBetween”
whereDate(field: string, value: Date | string): this
whereDateEquals(field: string, value: Date | string): this
whereDateBefore(field: string, value: Date | string): this
whereDateAfter(field: string, value: Date | string): this
whereDateBetween(field: string, range: [Date, Date]): this
whereDateNotBetween(field: string, range: [Date, Date]): this

What it does: filter on a date field — exact day, before/after, or a range. Time portion is ignored for the day-equality variants.

await Order.whereDateBetween("createdAt", [start, end]).get();

whereTime / whereDay / whereMonth / whereYear

Section titled “whereTime / whereDay / whereMonth / whereYear”
whereTime(field: string, value: string): this
whereDay(field: string, value: number): this
whereMonth(field: string, value: number): this
whereYear(field: string, value: number): this

What it does: filter on an extracted part of a date field.

await Event.whereMonth("startsAt", 12).get();

whereId(value) / whereIds(values) / whereUuid(value) / whereUlid(value)

Section titled “whereId(value) / whereIds(values) / whereUuid(value) / whereUlid(value)”
whereId(value: string | number): this
whereIds(values: Array<string | number>): this
whereUuid(value: string): this
whereUlid(value: string): this

What it does: shortcuts for filtering by primary key (single, multiple, or by id-type variant).

await User.whereIds([1, 2, 3]).get();

whereFullText / orWhereFullText / whereSearch / textSearch

Section titled “whereFullText / orWhereFullText / whereSearch / textSearch”
whereFullText(fields: string | string[], query: string): this
orWhereFullText(fields: string | string[], query: string): this
whereSearch(field: string, query: string): this
textSearch(query: string, filters?: WhereObject): this

What it does: full-text search variants. whereFullText constrains specific fields; textSearch does a driver-native full-text query with optional filters.

await Post.whereFullText(["title", "body"], "cascade orm").get();
whereNot(callback: WhereCallback<T>): this
orWhereNot(callback: WhereCallback<T>): this

What it does: negate a nested predicate block.

await User.whereNot(q => q.where("status", "banned")).get();
whereExists(callback: WhereCallback<T>): this
whereExists(field: string): this
whereNotExists(callback: WhereCallback<T>): this
whereNotExists(field: string): this

What it does: existence check — either a nested condition exists, or (MongoDB-style) the field itself is present in the document.

await User.whereExists("optionalField").get();
await User.whereExists(q => q.where("status", "active")).get();

whereColumn / orWhereColumn / whereColumns / whereBetweenColumns

Section titled “whereColumn / orWhereColumn / whereColumns / whereBetweenColumns”
whereColumn(first: string, operator: WhereOperator, second: string): this
orWhereColumn(first: string, operator: WhereOperator, second: string): this
whereColumns(comparisons: Array<[string, WhereOperator, string]>): this
whereBetweenColumns(field: string, lowerColumn: string, upperColumn: string): this

What it does: compare one column to another, instead of to a literal value.

await Product.whereColumn("stock", ">", "reserved").get();

whereSize(field, size) / whereSize(field, operator, size)

Section titled “whereSize(field, size) / whereSize(field, operator, size)”
whereSize(field: string, size: number): this
whereSize(field: string, operator: ">" | ">=" | "=" | "<" | "<=", size: number): this

What it does: filter on the size of an array/collection field.

await Post.whereSize("tags", ">", 0).get();

whereRaw(expression, bindings?) / orWhereRaw(expression, bindings?)

Section titled “whereRaw(expression, bindings?) / orWhereRaw(expression, bindings?)”
whereRaw(expression: RawExpression, bindings?: unknown[]): this
orWhereRaw(expression: RawExpression, bindings?: unknown[]): this

What it does: drop into the driver’s native query language for a where clause Cascade can’t express.

await User.whereRaw("this.age > ?", [30]).get();

whereJsonContains(path, value) / whereJsonDoesntContain(path, value)

Section titled “whereJsonContains(path, value) / whereJsonDoesntContain(path, value)”
whereJsonContains(path: string, value: unknown): this
whereJsonDoesntContain(path: string, value: unknown): this

What it does: test whether a JSON path contains a given value.

await Post.whereJsonContains("tags", "javascript").get();

See also: JSON fields guide

whereJsonContainsKey(path: string): this

What it does: test whether a JSON path exists (key is present).

await User.whereJsonContainsKey("profile.avatar").get();
whereJsonLength(path: string, operator: WhereOperator, value: number): this

What it does: filter on the length of an array or object at a JSON path.

await Post.whereJsonLength("tags", ">", 3).get();

whereJsonIsArray(path) / whereJsonIsObject(path)

Section titled “whereJsonIsArray(path) / whereJsonIsObject(path)”
whereJsonIsArray(path: string): this
whereJsonIsObject(path: string): this

What it does: assert the value at a JSON path is of the expected shape.

await User.whereJsonIsArray("profile.roles").get();

whereArrayContains / whereArrayNotContains / whereArrayHasOrEmpty / whereArrayNotHaveOrEmpty

Section titled “whereArrayContains / whereArrayNotContains / whereArrayHasOrEmpty / whereArrayNotHaveOrEmpty”
whereArrayContains(field: string, value: unknown, key?: string): this
whereArrayNotContains(field: string, value: unknown, key?: string): this
whereArrayHasOrEmpty(field: string, value: unknown, key?: string): this
whereArrayNotHaveOrEmpty(field: string, value: unknown, key?: string): this

What it does: array-flavoured containment checks. The optional key argument matches on a nested property when the array holds objects.

await Post.whereArrayContains("tags", "javascript").get();
await Order.whereArrayContains("items", "laptop", "name").get();
whereArrayLength(field: string, operator: WhereOperator, value: number): this

What it does: filter on the length of an array column.

await User.whereArrayLength("roles", ">=", 2).get();

select(fields: string[]): this
select(fields: Record<string, 0 | 1 | boolean>): this
select(...fields: Array<string | string[]>): this

What it does: choose which columns/fields the query returns. Four call shapes — array, projection object, rest-args, or mixed.

await User.query().select(["name", "email"]).get();

See also: Querying essentials

selectAs(field: string, alias: string): this

What it does: select a single field under a new alias.

query.selectAs("name", "fullName");
addSelect(fields: string[]): this

What it does: append fields to the existing projection without clearing it.

query.select(["name", "email"]).addSelect(["age"]);

selectRaw(expression, bindings?) / selectRawMany(definitions)

Section titled “selectRaw(expression, bindings?) / selectRawMany(definitions)”
selectRaw(expression: RawExpression, bindings?: unknown[]): this
selectRawMany(definitions: Array<{ alias: string; expression: RawExpression; bindings?: unknown[] }>): this

What it does: add a raw projection expression (selectRaw), or several at once (selectRawMany).

query.selectRaw({ total: { $sum: "$items.price" } });

selectSub(expression, alias) / addSelectSub(expression, alias)

Section titled “selectSub(expression, alias) / addSelectSub(expression, alias)”
selectSub(expression: RawExpression, alias: string): this
addSelectSub(expression: RawExpression, alias: string): this

What it does: inject a sub-select expression under an alias. addSelectSub does the same without clearing existing selects.

query.selectSub({ $sum: "$items.price" }, "itemsTotal");
selectAggregate(
field: string,
aggregate: "sum" | "avg" | "min" | "max" | "count" | "first" | "last",
alias: string,
): this

What it does: project a single aggregate as a named field on the result.

query.selectAggregate("items.price", "sum", "itemsTotal");

See also: Aggregates guide

selectExists(field, alias) / selectCount(field, alias)

Section titled “selectExists(field, alias) / selectCount(field, alias)”
selectExists(field: string, alias: string): this
selectCount(field: string, alias: string): this

What it does: project a boolean (selectExists) or a count (selectCount) of items at a path.

query.selectExists("deletedAt", "isDeleted");
query.selectCount("permissions", "permissionsCount");

selectCase(cases, otherwise, alias) / selectWhen(condition, then, else, alias)

Section titled “selectCase(cases, otherwise, alias) / selectWhen(condition, then, else, alias)”
selectCase(
cases: Array<{ when: RawExpression; then: RawExpression | unknown }>,
otherwise: RawExpression | unknown,
alias: string,
): this
selectWhen(
condition: RawExpression,
thenValue: RawExpression | unknown,
elseValue: RawExpression | unknown,
alias: string,
): this

What it does: project a CASE/switch expression. selectWhen is the single-condition shortcut.

query.selectWhen({ $gt: ["$age", 18] }, "Adult", "Minor", "ageLabel");

selectJson(path, alias?) / selectJsonRaw(path, expression, alias) / deselectJson(path)

Section titled “selectJson(path, alias?) / selectJsonRaw(path, expression, alias) / deselectJson(path)”
selectJson(path: string, alias?: string): this
selectJsonRaw(path: string, expression: RawExpression, alias: string): this
deselectJson(path: string): this

What it does: project a nested JSON path (selectJson), apply a raw expression to it (selectJsonRaw), or exclude it (deselectJson).

query.selectJson("profile.address.city", "city");

See also: JSON fields guide

selectConcat(fields, alias) / selectCoalesce(fields, alias)

Section titled “selectConcat(fields, alias) / selectCoalesce(fields, alias)”
selectConcat(fields: Array<string | RawExpression>, alias: string): this
selectCoalesce(fields: Array<string | RawExpression>, alias: string): this

What it does: project a concatenation, or the first non-null among several values.

query.selectConcat(["$firstName", " ", "$lastName"], "fullName");
query.selectCoalesce(["$nickname", "$name"], "displayName");
selectWindow(spec: RawExpression): this

What it does: attach a window function expression to the projection (rank, partition aggregates, running totals, …).

query.selectWindow({
partitionBy: "$category",
sortBy: { createdAt: 1 },
output: { rank: { $denseRank: {} } },
});
selectDriverProjection(callback: (projection: Record<string, unknown>) => void): this

What it does: mutate the driver’s underlying projection object directly. Last-resort escape hatch.

query.selectDriverProjection(p => { p.score = { $meta: "textScore" }; });

deselect(fields) / clearSelect() / selectAll() / selectDefault()

Section titled “deselect(fields) / clearSelect() / selectAll() / selectDefault()”
deselect(fields: string[]): this
clearSelect(): this
selectAll(): this
selectDefault(): this

What it does: exclude specific fields (deselect), or reset the projection. clearSelect, selectAll, and selectDefault all restore the “all columns” default.

query.deselect(["password", "token"]);
distinctValues(fields?: string | string[]): this

What it does: mark the query as returning distinct rows for the given fields.

query.distinctValues(["category", "status"]);

join(table, localField, foreignField) / join(options)

Section titled “join(table, localField, foreignField) / join(options)”
join(table: string, localField: string, foreignField: string): this
join(options: JoinOptions): this

What it does: INNER JOIN on SQL; $lookup on MongoDB (which is inherently left-flavoured — see the joins guide).

query.join("profiles", "id", "user_id");

See also: Joins guide

innerJoin(table, localField, foreignField) / innerJoin(options)

Section titled “innerJoin(table, localField, foreignField) / innerJoin(options)”
innerJoin(table: string, localField: string, foreignField: string): this
innerJoin(options: JoinOptions): this

What it does: explicit INNER JOIN with cross-driver semantics (MongoDB adds a $match to drop empty joins).

leftJoin(table, localField, foreignField) / leftJoin(options)

Section titled “leftJoin(table, localField, foreignField) / leftJoin(options)”
leftJoin(table: string, localField: string, foreignField: string): this
leftJoin(options: JoinOptions): this

What it does: LEFT JOIN — keep every row on the left, fill nulls on the right when there’s no match.

rightJoin(table, localField, foreignField) / rightJoin(options)

Section titled “rightJoin(table, localField, foreignField) / rightJoin(options)”
rightJoin(table: string, localField: string, foreignField: string): this
rightJoin(options: JoinOptions): this

What it does: RIGHT JOIN on SQL. Silently emulated as LEFT JOIN on MongoDB — see the joins guide before using.

fullJoin(table, localField, foreignField) / fullJoin(options)

Section titled “fullJoin(table, localField, foreignField) / fullJoin(options)”
fullJoin(table: string, localField: string, foreignField: string): this
fullJoin(options: JoinOptions): this

What it does: FULL OUTER JOIN on SQL. Silently emulated as LEFT JOIN on MongoDB — see the joins guide.

crossJoin(table: string): this

What it does: Cartesian product. Every row on the left × every row on the right.

query.crossJoin("colors");
joinRaw(expression: RawExpression, bindings?: unknown[]): this

What it does: raw join expression in driver-native syntax. Use for $graphLookup, lateral joins, anything the structured methods can’t express.

query.joinRaw("LEFT JOIN LATERAL (...) e ON true");

with(relation: string): this
with(...relations: string[]): this
with(relation: string, constraint: (query: QueryBuilderContract) => void): this
with(relations: Record<string, boolean | ((query: QueryBuilderContract) => void)>): this

What it does: eager-load related models in separate, optimised queries (avoids N+1).

await User.query().with("posts", "organization").find(1);
await User.query()
.with({ posts: q => q.where("isPublished", true) })
.find(1);

See also: Relationships essentials

joinWith(...relations: string[]): this

What it does: load relations via JOIN (single query) instead of separate queries. Best for belongsTo / hasOne.

const post = await Post.joinWith("author").first();
withCount(relation: string): this
withCount(...relations: string[]): this
withCount(relations: string[]): this
withCount(relations: Record<string, true | string | ((query: QueryBuilderContract) => void)>): this

What it does: project the count of a related model as a virtual field. Default alias ${relation}Count; override with as <alias> or the object value form.

await User.query().withCount("posts").get();
await Post.query()
.withCount({
comments: true,
"comments as approvedCount": q => q.where("approved", true),
})
.get();

has(relation) / has(relation, operator, count)

Section titled “has(relation) / has(relation, operator, count)”
has(relation: string): this
has(relation: string, operator: string, count: number): this

What it does: filter results to those that have related models (with optional count constraint).

await User.query().has("posts").get();
await User.query().has("posts", ">=", 5).get();

whereHas(relation, callback) / doesntHave(relation) / whereDoesntHave(relation, callback)

Section titled “whereHas(relation, callback) / doesntHave(relation) / whereDoesntHave(relation, callback)”
whereHas(relation: string, callback: (query: QueryBuilderContract) => void): this
doesntHave(relation: string): this
whereDoesntHave(relation: string, callback: (query: QueryBuilderContract) => void): this

What it does: existence/non-existence checks on relations, with optional callback to constrain the related query.

await User.query()
.whereHas("posts", q => q.where("isPublished", true))
.get();

orderBy(field, direction?) / orderBy(fields)

Section titled “orderBy(field, direction?) / orderBy(fields)”
orderBy(field: string, direction?: OrderDirection): this
orderBy(fields: Record<string, OrderDirection>): this

What it does: order results by one or more fields.

await User.query().orderBy("createdAt", "desc").get();
await User.query().orderBy({ status: "asc", createdAt: "desc" }).get();

See also: Querying essentials

orderByDesc(field: string): this

What it does: descending shortcut for orderBy(field, "desc").

orderByRaw(expression: RawExpression, bindings?: unknown[]): this

What it does: order by a raw driver expression.

query.orderByRaw("RANDOM()");
orderByRandom(limit: number): this

What it does: randomly order results; takes a limit to avoid scanning the whole table.

latest(column?: string): Promise<T[]>
oldest(column?: string): this

What it does: order by a timestamp column descending (latest) or ascending (oldest). Default column is createdAt. Note: latest is a terminator and returns the records directly.

const newest = await Post.query().latest();

limit(value: number): this
take(value: number): this

What it does: cap the number of returned rows. take is an alias.

skip(value: number): this
offset(value: number): this

What it does: skip the given number of rows. Aliases for each other.

cursor(after?: unknown, before?: unknown): this

What it does: apply cursor-pagination hints. Pair with cursorPaginate for the full pattern.

paginate(options: PaginationOptions): Promise<PaginationResult<T>>

What it does: terminator — page/limit pagination with total + page count.

const result = await User.query().paginate({ page: 1, limit: 10 });
// { data, pagination: { total, page, limit, pages } }

See also: Querying essentials

cursorPaginate(options: CursorPaginationOptions): Promise<CursorPaginationResult<T>>

What it does: terminator — cursor-based pagination. Better than offset for large result sets and infinite-scroll UIs.

const result = await User.query().cursorPaginate({ limit: 10, cursor: lastId });
// { data, pagination: { hasMore, nextCursor, ... } }
chunk(size: number, callback: ChunkCallback<T>): Promise<void>

What it does: terminator — iterate through results in chunks. Callback returns false to stop.

await User.query().chunk(100, async (users, index) => {
await processUsers(users);
});

groupBy(fields) / groupBy(fields, aggregates)

Section titled “groupBy(fields) / groupBy(fields, aggregates)”
groupBy(fields: GroupByInput): this
groupBy(fields: GroupByInput, aggregates: Record<string, RawExpression>): this

What it does: group results by one or more fields, with optional aggregate output. Works on both MongoDB and Postgres. The driver-agnostic $agg.count/sum/avg/min/max are cross-driver; $agg.distinct/floor/first/last are MongoDB-only and throw on Postgres (see the Aggregates guide).

import { $agg } from "@warlock.js/cascade";
query.groupBy("category", {
total: $agg.sum("amount"),
count: $agg.count(),
});

See also: Aggregates guide

groupByRaw(expression: RawExpression, bindings?: unknown[]): this

What it does: group by a raw expression (DATE(createdAt), custom JSON path, …).

having(...) / havingRaw(expression, bindings?)

Section titled “having(...) / havingRaw(expression, bindings?)”
having(field: string, value: unknown): this
having(field: string, operator: WhereOperator, value: unknown): this
having(condition: HavingInput): this
havingRaw(expression: RawExpression, bindings?: unknown[]): this

What it does: filter the grouped/aggregated results (vs .where() which filters before grouping).

query.groupBy("category", { total: $agg.sum("amount") })
.having("total", ">", 1000);

get<Output = T>(): Promise<Output[]>

What it does: execute the query and return all matching records.

const users = await User.query().where("isActive", true).get();
first<Output = T>(): Promise<Output | null>
firstOrFail<Output = T>(): Promise<Output>

What it does: return the first matching record (or null / throw if none).

const user = await User.where("email", "john@example.com").first();
last<Output = T>(field?: string): Promise<Output | null>

What it does: return the last record by the given field (default: primary key descending).

count(): Promise<number>

What it does: count matching records.

const total = await User.where("isActive", true).count();

sum(field) / avg(field) / min(field) / max(field)

Section titled “sum(field) / avg(field) / min(field) / max(field)”
sum(field: string): Promise<number>
avg(field: string): Promise<number>
min(field: string): Promise<number>
max(field: string): Promise<number>

What it does: scalar aggregates over the matched rows. Each returns a Promise<number>.

const revenue = await Order.where("status", "completed").sum("amount");
distinct<T = unknown>(field: string): Promise<T[]>
countDistinct(field: string): Promise<number>

What it does: distinct values for a field (distinct returns the values; countDistinct returns the count of distinct values).

const categories = await Product.query().distinct<string>("category");
pluck(field: string): Promise<unknown[]>

What it does: retrieve a flat array of a single field’s values across all matching rows.

const names = await User.query().pluck("name");
value<T = unknown>(field: string): Promise<T | null>

What it does: scalar — retrieve a single field’s value from the first matching row.

const email = await User.whereId(123).value<string>("email");
exists(): Promise<boolean>
notExists(): Promise<boolean>

What it does: does any row (or no row) match? Cheaper than count() > 0.

const hasActiveUsers = await User.where("isActive", true).exists();

increment(field, amount?) / decrement(field, amount?)

Section titled “increment(field, amount?) / decrement(field, amount?)”
increment(field: string, amount?: number): Promise<number>
decrement(field: string, amount?: number): Promise<number>

What it does: atomically increment/decrement a field. Returns the new value. Default amount: 1.

await User.whereId(123).increment("loginCount");

incrementMany(field, amount?) / decrementMany(field, amount?)

Section titled “incrementMany(field, amount?) / decrementMany(field, amount?)”
incrementMany(field: string, amount?: number): Promise<number>
decrementMany(field: string, amount?: number): Promise<number>

What it does: apply the increment/decrement to every matching row. Returns the number of rows modified.


parse(): DriverQuery

What it does: return the driver-native query without executing. SQL drivers populate { query, bindings }; MongoDB populates { pipeline }.

const { query, bindings } = User.where("age", ">", 18).parse();
pretty(): string

What it does: formatted string representation of the parsed query.

explain(): Promise<unknown>

What it does: ask the driver for the query’s execution plan.

const plan = await User.where("status", "active").explain();

onFetching(callback: (query: this) => void | Promise<void>): () => void

What it does: register a callback to run before query execution. Returns an unsubscribe function.

onHydrating(callback: (records: any[], context: any) => void | Promise<void>): () => void

What it does: register a callback to run after fetching raw rows but before hydrating them into model instances.

onFetched(callback: (records: any[], context: any) => void | Promise<void>): () => void

What it does: register a callback to run after hydration. Receives the hydrated model instances.

hydrate(callback: (data: any, index: number) => any): this

What it does: transform each result row during hydration.


tap(callback: (builder: this) => void): this

What it does: side-effect into the chain (logging, debugging) without breaking the fluent flow.

query.where("age", ">", 18).tap(q => console.log(q.parse())).get();
when<V>(
condition: V | boolean | (() => boolean),
callback: (builder: this, value: V) => void,
otherwise?: (builder: this) => void,
): this

What it does: conditionally apply a callback to the query. Useful for optional filters from controller input.

query.when(searchTerm, (q, term) => q.whereLike("name", term));
clone(): this

What it does: duplicate the query builder so you can branch off without mutating the original.

const base = User.query().where("isActive", true);
const admins = base.clone().where("role", "admin");
raw(builder: (native: unknown) => unknown): this

What it does: mutate the underlying native query object directly. Last-resort escape hatch.

extend<R>(extension: string, ...args: unknown[]): R

What it does: invoke a driver-specific extension registered with the query builder.


similarTo(column: string, embedding: number[], alias?: string): this

What it does: nearest-neighbour vector similarity search. Adds a similarity score to the SELECT and an ORDER BY on the same expression so the vector index can be used. Default score alias "score".

const results = await Vector.query()
.where({ organization_id: orgId, content_type: "summary" })
.similarTo("embedding", queryEmbedding)
.limit(5)
.get<VectorRow & { score: number }>();

See also: Vector search guide