UNPKG

@dudousxd/nestjs-filter-drizzle

Version:

Drizzle ORM adapter for @dudousxd/nestjs-filter.

160 lines • 8.53 kB
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