Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/fajarnugraha37/drizzle-castor/llms.txt

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 definition

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.

Path types

A filter key can represent three distinct things:
  • Direct column"name", "age", "createdAt"
  • Relational path"posts.title", "posts.comments.content" (resolved via configured oneToMany / oneToOne metadata)
  • JSON sub-path"settings.theme", "persona.skills.0" (resolved via dialect-specific JSON extraction)
// Direct column
{ name: { $like: "%John%" } }

// Relational path (auto-JOIN)
{ "posts.title": { $like: "%Drizzle%" } }

// JSON column path
{ "settings.theme": { $eq: "dark" } }

// JSON array index
{ "persona.skills.0": { $eq: "TypeScript" } }

Operator reference

Comparison operators are available on every field type. They are defined by ComparisonOps<T>.
type ComparisonOps<T> = {
  $eq?: T;
  $ne?: T;
};
Generates a SQL = value condition. Accepts the exact TypeScript type of the target path.
// Find users with exactly this name
const result = await userRepo.searchMany({
  filter: { name: { $eq: "Jane Doe" } }
}, "admin");

// Filter by a JSON property
const darkThemeUsers = await userRepo.searchMany({
  filter: { "settings.theme": { $eq: "dark" } }
}, "public");
Generates a SQL != value condition. Accepts the same type as $eq.
// Exclude a specific post author
const result = await userRepo.searchMany({
  filter: { "posts.title": { $ne: "Draft" } }
}, "admin");

Complete operator type

For reference, the full FieldOperators<T> intersection applied to a given path is:
type FieldOperators<T> = ComparisonOps<T> &
  OrderableOps<T> &
  NullOps &
  InOps<LeafType<T>> &
  BetweenOps<T> &
  ([NonNullable<T>] extends [string] ? StringOps : {}) &
  ArrayContainmentOps<T>;
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.

RBAC and field trimming

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.theme
schemaMetadataBuilder.policies("users", {
  public: {
    allowedActions: ["read"],
    allowedFilters: ["name", "email", "settings.theme"],
  }
});

// This filter from the "public" profile — passwordHash key is silently stripped
await userRepo.searchMany({
  filter: {
    name: { $like: "%Jane%" },
    passwordHash: { $eq: "secret" } // removed before query execution
  }
}, "public");

Build docs developers (and LLMs) love