Use this file to discover all available pages before exploring further.
FilterQuery<T> is the primary abstraction for expressing WHERE conditions in drizzle-castor. Instead of writing raw SQL, you construct a plain JavaScript object whose keys are dot-notation paths into your entity graph and whose values are operator objects. The library’s AST compiler translates this object into dialect-aware Drizzle ORM expressions at runtime, automatically resolving joins for relational paths and JSON extraction functions for nested column paths.
type FilterQuery<T> = Partial<Conjunctions<T>> & { [K in ValidPath<T>]?: FieldOperators<ValueAt<T, K>>;};
Every key must be a ValidPath<T> — a recursively flattened union of all dot-notation strings reachable within T, including relation traversals and JSON array indices. The value for each key is a FieldOperators<V> object, where V is the TypeScript type at that path. Operators that make no sense for a given type (for example, $like on a number) are excluded from FieldOperators<V> at compile time.
Comparison operators are available on every field type. They are defined by ComparisonOps<T>.
type ComparisonOps<T> = { $eq?: T; $ne?: T;};
$eq — exact equality
Generates a SQL = value condition. Accepts the exact TypeScript type of the target path.
// Find users with exactly this nameconst result = await userRepo.searchMany({ filter: { name: { $eq: "Jane Doe" } }}, "admin");// Filter by a JSON propertyconst darkThemeUsers = await userRepo.searchMany({ filter: { "settings.theme": { $eq: "dark" } }}, "public");
$ne — not equal
Generates a SQL != value condition. Accepts the same type as $eq.
// Exclude a specific post authorconst result = await userRepo.searchMany({ filter: { "posts.title": { $ne: "Draft" } }}, "admin");
Range comparison operators are conditionally available only when the target field type extends string | number | Date. They are defined by OrderableOps<T>.
type OrderableOps<T> = NonNullable<T> extends string | number | Date ? { $gt?: T; $gte?: T; $lt?: T; $lte?: T } : {};
$gt and $gte — greater than
$gt maps to SQL > value. $gte maps to SQL >= value.
// Users older than 18const adults = await userRepo.searchMany({ filter: { age: { $gt: 18 } }}, "admin");// Records created on or after a timestampconst recent = await userRepo.searchMany({ filter: { createdAt: { $gte: Date.now() - 86_400_000 } }}, "admin");
$lt and $lte — less than
$lt maps to SQL < value. $lte maps to SQL <= value.
Null-check operators work on every field regardless of type. They are defined by NullOps.
type NullOps = { $isNull?: boolean; $notIsNull?: boolean;};
The null operator is $notIsNull, not $isNotNull. Check your query.ts for the exact identifier before migrating from other query libraries.
$isNull — check for NULL
Pass true to generate IS NULL. Pass false to generate IS NOT NULL (same effect as $notIsNull: true).
// Users who have never set a zip codeconst result = await userRepo.searchMany({ filter: { zipCode: { $isNull: true } }}, "admin");
$notIsNull — check for NOT NULL
Pass true to generate IS NOT NULL.
// Posts that have at least one comment (relational path)const result = await userRepo.searchMany({ filter: { "posts.comments.content": { $notIsNull: true } }}, "public");
Collection operators test membership. They are defined by InOps<T> and BetweenOps<T>.
$in generates SQL IN (...). $notIn generates NOT IN (...). The array is readonly and must contain values matching the field type.
// Fetch specific users by IDconst result = await userRepo.searchMany({ filter: { id: { $in: [1, 2, 3] as const } }}, "admin");
$inArray and $notInArray — array column membership
Tests whether a scalar value is contained within an array-typed column (e.g., a PostgreSQL text[] or a JSON array column). Use these instead of $in when the column itself is the array.
The column array must contain every element in the provided list. Maps to PostgreSQL @>, or a JSON equivalent on MySQL/SQLite.
// Users who have both skills listedconst result = await userRepo.searchMany({ filter: { "persona.skills": { $arrayContains: ["TypeScript", "Node.js"] as const } }}, "admin");
$arrayContained — column is contained by values
Every element in the column array must appear in the provided list (the reverse of $arrayContains).
const result = await userRepo.searchMany({ filter: { "persona.hobbies": { $arrayContained: ["reading", "coding", "gaming"] as const } }}, "admin");
$arrayOverlaps — at least one common element
The column array and the provided list must share at least one element. Maps to PostgreSQL &&.
const result = await userRepo.searchMany({ filter: { tags: { $arrayOverlaps: ["featured", "popular"] as const } }}, "public");
Logical operators combine multiple FilterQuery objects. They are defined by Conjunctions<T>.
type Conjunctions<T> = { $not?: FilterQuery<T>; $and?: FilterQuery<T>[]; $or?: FilterQuery<T>[];};
$and — all conditions must match
Generates SQL (cond1 AND cond2 AND ...). All child FilterQuery objects in the array must be satisfied. Top-level sibling keys are implicitly AND-combined, so $and is most useful when you need explicit grouping.
The conditional branches ensure that, for example, $like is only present when T extends string, and $gt/$lt are only present when T extends string | number | Date.
When RBAC is active, the allowedFilters policy is evaluated before the filter reaches the AST compiler. Any field key not listed in allowedFilters is silently removed from the FilterQuery tree — including keys nested inside $and, $or, and $not. If trimming empties an entire conjunction, the remainder of the filter still executes normally.
// Policy: public profile may only filter on name, email, settings.themeschemaMetadataBuilder.policies("users", { public: { allowedActions: ["read"], allowedFilters: ["name", "email", "settings.theme"], }});// This filter from the "public" profile — passwordHash key is silently strippedawait userRepo.searchMany({ filter: { name: { $like: "%Jane%" }, passwordHash: { $eq: "secret" } // removed before query execution }}, "public");