Query Filters
A filter is a named $where fragment attached to an entity and applied to every query on it unless bypassed. Soft-delete is a built-in one; define your own for visibility flags, tenants and the like.
Defining a filter
Section titled “Defining a filter”Use the @Filter decorator, the @Entity({ filters }) option, or defineFilter from the imperative API:
import { Entity, Id, Field, Filter } from 'uql-orm';
@Filter('active', { where: { status: 'active' }, default: false })@Entity()export class Task { @Id({ type: Number }) id?: number; @Field({ type: String }) status?: string | null;}defineFilter(Task, 'active', { where: { status: 'active' }, default: false });where is a $where fragment, or a function of the context that returns one. default (true unless set) says whether the filter applies without being asked for; set it false for an opt-in filter like active. When a where function returns undefined, onMissing decides: 'skip' (the default) leaves the filter out, and 'throw' fails the query.
A filter that is not a security filter yields to your query: if your $where names the same field, your condition wins.
Bypassing filters
Section titled “Bypassing filters”Every read, update and delete takes filters in its options:
pool.findMany(Task, {}, { filters: false }); // disable all filterspool.findMany(Task, {}, { filters: { softDelete: false } }); // disable onepool.findMany(Task, {}, { filters: { active: true } }); // enable an opt-in filterParameterized filters & context
Section titled “Parameterized filters & context”A where function receives the current context, such as the tenant. Set it for a block of code with withContext, and read it anywhere with getContext():
@Filter('tenant', { // undefined makes the query throw UqlSecurityError instead of running unscoped where: (ctx) => ctx?.tenantId != null ? { companyId: ctx.tenantId } : undefined, security: true,})@Entity()export class Invoice { @Id({ type: Number }) id?: number; @Field({ type: Number }) companyId?: number | null;}
await withContext({ tenantId: 42 }, () => pool.findMany(Invoice, {}));// ... WHERE (companyId = 42)The context follows await, Promise.all and transactions, but not event callbacks such as emitters, timers and queues; see event-driven pipelines. To type its keys, declare interface UqlContext { tenantId: number } inside declare module 'uql-orm'.
Security filters (row-level security)
Section titled “Security filters (row-level security)”A filter with security: true enforces isolation:
- It always applies, ignoring
filters: falseand a bypass by name. - It is AND-merged, so a
$whereon the same field cannot widen it. - It fails closed: when its
wherereturnsundefined, as with no context, the query throwsUqlSecurityError.
Over HTTP the server reads only query keys, so a client cannot send filters or context.
Multi-tenancy covers setting the context per request, the {} escape hatch for trusted cross-tenant jobs, and how security filters guard writes.