@dudousxd/nestjs-filter-drizzle
Version:
Drizzle ORM adapter for @dudousxd/nestjs-filter.
160 lines • 8.53 kB
TypeScript
import { type AggregatePath, type ColumnFilter, type ComputedSource, type EntityFieldInfo, type EntityRelationInfo, type FieldExtent, type FieldExtentField, type FilterAdapter, type FilterEntity, type GroupByCountField, type SortItem, type VectorSearchOptions } from '@dudousxd/nestjs-filter';
import { Table } from 'drizzle-orm';
import { DrizzleQuery } from './drizzle-query.js';
import { type DrizzleDialect } from './operator-resolver.js';
import { DrizzleSchemaMetadata } from './schema-metadata.js';
import type { DrizzleDatabase } from './types.js';
export interface DrizzleAdapterOptions {
/**
* The schema module — the same object passed to `drizzle(client, { schema })`
* (tables AND `relations()` declarations). Defaults to the schema the `db`
* instance was created with. Relation features (includes, dot-notation
* filters, `whereHas`, `@Relations`, to-many aggregates, `describe()`
* relations) need it; column filtering, sort, search and pagination do not.
*/
schema?: Record<string, unknown>;
/** Overrides dialect detection (normally read off the `db` instance). */
dialect?: DrizzleDialect;
}
/** Reads the dialect off a drizzle database instance. */
export declare function detectDialect(db: DrizzleDatabase): DrizzleDialect;
/**
* {@link FilterAdapter} for Drizzle ORM.
*
* The "entity" is a Drizzle table object (`pgTable`/`mysqlTable`/`sqliteTable`),
* and the query builder handed to filters is a {@link DrizzleQuery} — an
* accumulator of conditions/ordering/projection that becomes one
* `db.select().from(table)` at execution time. See the package README for the
* design and the differences from the MikroORM / TypeORM adapters.
*/
export declare class DrizzleAdapter implements FilterAdapter {
readonly db: DrizzleDatabase;
private readonly logger;
readonly dialect: DrizzleDialect;
readonly metadata: DrizzleSchemaMetadata;
constructor(db: DrizzleDatabase, options?: DrizzleAdapterOptions);
private newContext;
/**
* A fresh {@link DrizzleQuery} over `table` — the same object `@ApplyFilter`
* injects into controllers. Useful in services:
*
* ```ts
* const q = adapter.query(users);
* await runner.apply(UserFilter, input, q);
* const rows = await q.execute();
* ```
*/
query<TTable extends Table>(table: TTable): DrizzleQuery<TTable>;
createQueryBuilder<E>(entity: FilterEntity<E>): unknown;
applyRelationConstraint(qb: unknown, relationName: string, callback: (relationQb: unknown) => Promise<void>): Promise<void>;
getEntityFields(entity: FilterEntity): EntityFieldInfo[] | null;
getEntityRelations(entity: FilterEntity): EntityRelationInfo[] | null;
getRelatedFields(entity: FilterEntity, relationName: string): EntityFieldInfo[] | null;
resolveFieldPath(entity: FilterEntity, path: string): 'field' | 'relation' | 'json' | null;
applyIncludes(qb: unknown, includes: string[]): void;
populate(rows: unknown[], relations: string[], entity: FilterEntity): Promise<void>;
/**
* Resolves a (possibly dotted) field path of `q` to a condition built over
* the column it lands on. A root column is used directly; a relation path
* becomes nested `EXISTS` subqueries with `build` applied to the last hop's
* column; a path ending on a relation compares its key. Unknown/unsafe paths
* resolve to `undefined` — never to a condition-less `EXISTS`, which would
* quietly widen the filter to "has any related row".
*/
private pathCondition;
/**
* A condition on a bare relation (`where: [{ field: 'manager', … }]`): a
* to-one relation that owns its foreign key compares that key directly;
* any other relation compares the related row's key inside an `EXISTS`.
*/
private relationKeyCondition;
applyColumnFilters(qb: unknown, filters: ColumnFilter[]): void;
applyAutoField(qb: unknown, field: string, value: unknown): void;
applyAutoRelationField(qb: unknown, relationName: string, field: string, value: unknown): void;
applySearch(qb: unknown, term: string, columns: string[]): void;
applyVectorSearch(qb: unknown, term: string, vectorColumn: string, opts?: VectorSearchOptions): void;
/** A projectable value for a field: a root column, or a to-one relation column's scalar subquery. */
private fieldValue;
applyDistinct(qb: unknown, fields: string[]): void;
applySelect(qb: unknown, fields: string[], entity: FilterEntity): void;
/**
* What to ORDER BY for a field. Under a DISTINCT projection that already
* selected a non-column expression for this field, order by its output
* alias: a second copy of a correlated subquery is a different expression,
* which Postgres rejects under DISTINCT.
*/
private sortTarget;
applySort(qb: unknown, sorts: SortItem[]): void;
applyOffsetPagination(qb: unknown, page: number, size: number): void;
getPrimaryKey(entity: FilterEntity): string | null;
applyKeysetPagination(qb: unknown, keyset: SortItem[], values: unknown[]): void;
applyKeysetOrderAndLimit(qb: unknown, keyset: SortItem[], limit: number): void;
getResultAndCount<T = unknown>(qb: unknown): Promise<{
rows: T[];
total: number;
}>;
getDistinctResultAndCount(qb: unknown): Promise<{
rows: Record<string, unknown>[];
total: number;
}>;
getResult(qb: unknown): Promise<unknown[]>;
/**
* Resolves a developer-declared computed source to SQL. A string is emitted
* verbatim (auto-parenthesized when it is a bare `SELECT …` / `EXISTS (…)`);
* a function receives `{ alias: <root table name>, em: db }` and may return a
* SQL string, a drizzle `sql` fragment, or a drizzle select builder (used as
* a scalar subquery). Because the adapter never joins, an unqualified column
* name in a string source always means the root table's column.
*/
private computedExpression;
applyComputedField(qb: unknown, source: ComputedSource, value: unknown): void;
applyComputedSort(qb: unknown, source: ComputedSource, direction: 'asc' | 'desc'): void;
applyComputedSelect(qb: unknown, alias: string, source: ComputedSource): void;
applyComputedDistinct(qb: unknown, alias: string, source: ComputedSource): void;
private addDistinctMember;
/**
* Compiles `posts.$count` / `posts.$sum.views` / … into a correlated scalar
* subquery over the to-many relation — never a JOIN + GROUP BY, which would
* multiply the root rows. The relation and child column come from schema
* metadata; nothing client-supplied is emitted as SQL text.
*
* @returns the expression, and — for `min`/`max` — the child column, whose
* encoder binds comparison values (dates compare as the column stores them).
*/
private aggregateSubquery;
private originalColumn;
applyAggregateSort(qb: unknown, aggregate: AggregatePath, direction: 'asc' | 'desc'): void;
applyAggregateField(qb: unknown, aggregate: AggregatePath, filter: ColumnFilter): void;
applyAggregateDistinct(qb: unknown, aggregate: AggregatePath): void;
/**
* The expression for a measurable/groupable field, plus the column whose
* decoder should map its values back (so a date comes back as a `Date`, a
* SQLite boolean as `true`/`false`).
*/
private measurable;
private asText;
/**
* `SELECT <expr> AS value, COUNT(*) AS count … GROUP BY 1`, or the bucketed
* `FLOOR(<expr> / ?) * ?` variant. The bucket width is a bound parameter;
* grouping by ordinal position keeps Postgres from rejecting the query
* because the SELECT and GROUP BY copies of the expression carry different
* placeholders (`$1` vs `$3`).
*/
groupByCount(qb: unknown, field: GroupByCountField, _entity: FilterEntity, opts?: {
bucket?: number;
limit?: number;
offset?: number;
search?: string;
}): Promise<Array<{
value: unknown;
count: number;
}>>;
/**
* `MIN`/`MAX` of every requested field in ONE select over the filtered rows —
* ordering and the page window are not part of the question and are simply
* not emitted. Values are decoded through the field's column, so dates stay
* dates.
*/
fieldExtent(qb: unknown, fields: FieldExtentField[]): Promise<Record<string, FieldExtent>>;
}
//# sourceMappingURL=drizzle.adapter.d.ts.map