UNPKG

@dudousxd/nestjs-filter-drizzle

Version:

Drizzle ORM adapter for @dudousxd/nestjs-filter.

249 lines • 11.7 kB
import { type AnyColumn, Column, SQL, type SQLWrapper, type Table } from 'drizzle-orm'; import type { DrizzleDialect } from './operator-resolver.js'; import { type DrizzleSchemaMetadata, type ResolvedRelation } from './schema-metadata.js'; import type { DrizzleDatabase } from './types.js'; /** * The slice of a drizzle select builder the query uses. Every dialect's builder * has this shape at runtime; the structural type sidesteps the union of three * dialect-specific overload sets, which TypeScript cannot call through. */ interface SelectChain extends SQLWrapper { where(condition: SQL | undefined): SelectChain; orderBy(...columns: Array<SQL | SQL.Aliased | AnyColumn>): SelectChain; groupBy(...columns: Array<SQL | AnyColumn>): SelectChain; limit(limit: number): SelectChain; offset(offset: number): SelectChain; as(alias: string): Table; toSQL(): { sql: string; params: unknown[]; }; then<R>(onfulfilled: (rows: Record<string, unknown>[]) => R): Promise<R>; } interface QueryDatabase { select(fields?: Record<string, unknown>): { from(source: unknown): SelectChain; }; selectDistinct(fields?: Record<string, unknown>): { from(source: unknown): SelectChain; }; } /** A row of `TTable`, plus whatever includes / computed aliases were attached. */ export type DrizzleRow<TTable extends Table> = TTable['$inferSelect'] & Record<string, unknown>; /** A projected field: a column, a raw expression, or an aliased expression. */ export type SelectionValue = AnyColumn | SQL | SQL.Aliased; /** * State shared by a root query and every child it spawns (relation constraints, * nested `EXISTS`): the database, the dialect, the schema metadata, and ONE * alias counter — so two subqueries over the same table never collide, however * deeply they nest. */ export declare class DrizzleQueryContext { readonly db: DrizzleDatabase; readonly dialect: DrizzleDialect; readonly metadata: DrizzleSchemaMetadata; private aliasSeq; constructor(db: DrizzleDatabase, dialect: DrizzleDialect, metadata: DrizzleSchemaMetadata); /** A fresh, SQL-safe table alias (`posts_1`, `manager_2`, …). */ nextAlias(base: string): string; /** @internal — the untyped select entry points. */ get queryDb(): QueryDatabase; } /** * The query state the core hands to a Drizzle filter as `this.$query`. * * Drizzle's select builders are immutable-ish and single-shot (`.where()` * replaces, the builder is bound to one projection at creation time), which is * the opposite of what a filter pipeline needs: many independent methods each * contributing a condition. So filters write into this accumulator instead — * conditions, ordering, a page window, a projection, includes — and the * accumulated state is materialized into ONE `db.select().from(table)` at * execution time ({@link toSelect}, {@link execute}). * * ```ts * @FilterFor('minAge') * applyMinAge(value: number) { * this.$query.where(gte(users.age, value)); * } * ``` * * Relations never become joins on the root query. A relation constraint is an * `EXISTS (…)` subquery and an include is a second, batched query — so the * root query always returns exactly one row per matching entity, and * `LIMIT`/`OFFSET`/`COUNT(*)` stay correct with any number of to-many * relations involved. */ export declare class DrizzleQuery<TTable extends Table = Table> { readonly context: DrizzleQueryContext; readonly baseTable: TTable; private readonly conditions; private readonly orderings; private limitValue; private offsetValue; private projection; private readonly extras; private distinctFlag; private readonly includePaths; /** * @param context - Shared db/dialect/metadata/alias state. * @param baseTable - The ORIGINAL table object (metadata is keyed by it). * @param aliased - When this query targets an aliased copy of the table (a * relation constraint's subquery), that alias; columns are read off it. */ constructor(context: DrizzleQueryContext, baseTable: TTable, aliased?: TTable); /** The table the query reads — the original, or its alias inside a subquery. */ readonly table: TTable; /** The drizzle database the query executes against. */ get db(): DrizzleDatabase; /** `'postgres' | 'mysql' | 'sqlite'`. */ get dialect(): DrizzleDialect; /** The table's columns keyed by property name — `this.$query.columns.email`. */ get columns(): TTable['_']['columns']; /** * ANDs one or more conditions onto the query. `undefined` entries are * ignored, so drizzle's `and()`/`or()` (which return `undefined` for an empty * list) compose without guards. */ where(...conditions: Array<SQL | undefined>): this; /** * Like {@link where}, but also accepts an equality map keyed by column * property — `andWhere({ status: 'active', role: ['a', 'b'] })` — which is * the shape the core's `related()` helper and `@TenantScoped` emit. Array * values become `IN`, `null` becomes `IS NULL`; keys that are not columns of * the table are ignored. */ andWhere(condition: SQL | Record<string, unknown> | undefined): this; /** * Constrains the query to rows that HAVE a related row — `EXISTS (SELECT 1 * FROM <relation> WHERE <correlation> AND <build(target)>)`. The callback * receives the related table (an alias — reference its columns through the * argument, not the imported table object). A dotted path follows several * relations (`'posts.comments'`). * * ```ts * this.$query.whereHas('posts', (posts) => eq(posts.status, 'published')); * ``` * * An unknown relation throws — silently ignoring a constraint would return * rows the caller asked to exclude. */ whereHas(relationPath: string, build?: (target: Table) => SQL | undefined): this; /** The negation of {@link whereHas}: rows with NO matching related row. */ whereDoesntHave(relationPath: string, build?: (target: Table) => SQL | undefined): this; /** The accumulated WHERE — every condition ANDed — or `undefined` when empty. */ getWhere(): SQL | undefined; /** * Appends ORDER BY terms. A bare column sorts ascending; use drizzle's * `asc()`/`desc()` for an explicit direction. */ orderBy(...terms: Array<SQL | SQL.Aliased | AnyColumn>): this; /** Drops every ORDER BY term accumulated so far. */ clearOrderBy(): this; limit(limit: number | undefined): this; offset(offset: number | undefined): this; /** * Replaces the projection with the given fields (keyed by output name). The * default projection is every column of the table. */ select(fields: Record<string, SelectionValue>): this; /** * Adds fields to the projection without replacing it — computed values that * should come back on each row next to the table's own columns. */ addSelect(fields: Record<string, SelectionValue>): this; /** Marks the projection `SELECT DISTINCT`. */ distinct(on?: boolean): this; /** * Relations to load onto the fetched rows (dotted for nesting: * `'posts.comments'`). Loaded by {@link execute} in separate batched * queries after the page is fetched, never joined — see the class doc. */ include(...relationPaths: string[]): this; isDistinct(): boolean; getIncludes(): readonly string[]; getOrderBy(): ReadonlyArray<SQL | SQL.Aliased | AnyColumn>; getLimit(): number | undefined; getOffset(): number | undefined; /** The projection currently in effect (explicit, or all columns), plus additive fields. */ getSelection(): Record<string, SelectionValue>; /** True when the projection was narrowed (`select`/`distinct`) rather than all columns. */ hasExplicitProjection(): boolean; /** * Materializes the accumulated state as a drizzle select builder: * `db.select(<projection>).from(table).where(…).orderBy(…).limit(…).offset(…)`. * Use it to extend the query with anything the accumulator does not model, * or to hand it to drizzle APIs that take a builder. */ toSelect(opts?: { withWindow?: boolean; withOrder?: boolean; }): SelectChain; /** The SQL + bound params the query would run. */ toSQL(): { sql: string; params: unknown[]; }; /** Runs the query and loads every {@link include}d relation onto the rows. */ execute(): Promise<DrizzleRow<TTable>[]>; /** * `COUNT(*)` over the same WHERE, ignoring ordering and the page window. For * a DISTINCT projection, counts distinct TUPLES of the projection (via a * derived table, the one form every dialect accepts for several columns). */ count(): Promise<number>; /** {@link execute} + {@link count} — the page and the total it was cut from. */ executeAndCount(): Promise<{ rows: DrizzleRow<TTable>[]; total: number; }>; /** * The projection sent to the database. Includes need the key they join on: * when a narrowed projection dropped it, it is added back so the include can * still be resolved. */ private selectionForExecution; /** * A column of this query's table by property key — the aliased column when * the query runs over an alias. */ column(key: string): Column | undefined; /** * `EXISTS` over a relation chain starting at this query's table. The last * hop's (aliased) table is handed to `build` for the inner condition. * Returns `undefined` when any segment is not a relation. */ relationExists(segments: string[], build?: (target: Table) => SQL | undefined): SQL | undefined; /** * A scalar expression for a to-one relation path's column * (`manager.name` → `(SELECT m.name FROM users m WHERE m.id = users.manager_id)`), * used for ORDER BY / DISTINCT on relation fields without joining. Returns * `undefined` when the path crosses a to-many relation (it has no single * value) or does not resolve. */ relationScalar(segments: string[]): SQL | undefined; /** * Creates the child query a relation constraint's filter writes into: it * targets a fresh alias of the related table, and {@link relationExistsFor} * later folds its WHERE into an `EXISTS` on this query. */ childFor(relation: ResolvedRelation): DrizzleQuery<Table>; /** * The predicate correlating `targetView` (an alias of `relation.target`) to * this query's table — the WHERE of a correlated subquery over the relation. */ correlate(relation: ResolvedRelation, targetView: Table): SQL | undefined; /** `EXISTS` correlating `child` (made by {@link childFor}) to this query's table. */ relationExistsFor(relation: ResolvedRelation, child: DrizzleQuery<Table>): SQL; } /** * Loads relation paths onto already-fetched rows with one batched * `SELECT … WHERE <key> IN (…)` per relation (and per nesting level), then * grafts the results back: a to-many relation becomes an array (empty when * nothing matched), a to-one relation the row or `null`. The shape matches * drizzle's relational queries (`with: { posts: true }`). * * Unknown relations and composite-key relations are skipped. */ export declare function loadRelations(context: DrizzleQueryContext, table: Table, rows: Record<string, unknown>[], paths: readonly string[]): Promise<void>; export {}; //# sourceMappingURL=drizzle-query.d.ts.map