nestjs-paginate
Version:
Pagination and filtering helper method for TypeORM repositories or query builders using Nest.js framework.
163 lines (162 loc) • 7.97 kB
TypeScript
import { EntityMetadata, FindOperator, SelectQueryBuilder } from 'typeorm';
import { WherePredicateOperator } from 'typeorm/query-builder/WhereClause';
import { PaginateQuery } from './decorator';
import { JoinMethod } from './helper';
import { EmbeddedMetadata } from 'typeorm/metadata/EmbeddedMetadata';
import { RelationMetadata } from 'typeorm/metadata/RelationMetadata';
export declare enum FilterOperator {
EQ = "$eq",
GT = "$gt",
GTE = "$gte",
IN = "$in",
NULL = "$null",
LT = "$lt",
LTE = "$lte",
BTW = "$btw",
ILIKE = "$ilike",
SW = "$sw",
CONTAINS = "$contains"
}
export declare function isOperator(value: unknown): value is FilterOperator;
export declare enum FilterSuffix {
NOT = "$not"
}
export declare function isSuffix(value: unknown): value is FilterSuffix;
export declare enum FilterQuantifier {
ALL = "$all",
ANY = "$any",
NONE = "$none"
}
export declare function isQuantifier(value: unknown): value is FilterQuantifier;
export declare enum FilterComparator {
AND = "$and",
OR = "$or"
}
export declare function isComparator(value: unknown): value is FilterComparator;
/**
* Returns true when the raw filter string explicitly carries the `$and` comparator token.
*
* This is distinct from the default AND comparator that every token carries implicitly —
* we only want to enter AND-mode when the user deliberately wrote `$and:` in the filter value.
* Using `parseFilterToken` (rather than a naive substring split) ensures that `$and` embedded
* inside a user value (e.g. `$eq:$and`) is not misidentified as the comparator.
*
* Must be called after `parseFilterToken` is defined (hoisting applies to function declarations).
*/
export declare function hasExplicitAndComparator(raw: string): boolean;
export declare const OperatorSymbolToFunction: Map<FilterOperator | FilterSuffix, (...args: any[]) => FindOperator<string>>;
type Filter = {
quantifier: FilterQuantifier;
comparator: FilterComparator;
findOperator: FindOperator<string>;
};
type ColumnFilters = {
[columnName: string]: Filter[];
};
type ColumnJoinMethods = {
[columnName: string]: JoinMethod;
};
/**
* Matches TypeORM named parameters (`:name` and `:...name` spread form) while skipping
* PostgreSQL cast syntax (`::type`).
*
* TypeORM parameter names may contain letters, digits, underscores, and dots (the latter
* for embedded-property paths, e.g. `size.height0`). The pattern captures the full name
* including any embedded-path dots.
*
* Capture groups:
* 1 — optional `...` spread prefix (present for IN parameters)
* 2 — parameter name (may contain dots for embedded paths)
*
* Examples:
* `:name` → matches, spread=undefined, name='name'
* `:...vals` → matches, spread='...', name='vals'
* `:size.height0` → matches, spread=undefined, name='size.height0'
* `col::text` → no match (lookbehind rejects `::`)
* `:param::int` → matches `:param`, skips `::int`
*/
/** @internal Exported for testing only. */
export declare const TYPEORM_PARAM_REGEX: RegExp;
export interface FilterToken {
quantifier: FilterQuantifier;
comparator: FilterComparator;
suffix?: FilterSuffix;
operator: FilterOperator;
value: string;
}
export declare function fixQueryParam(alias: string, column: string, filter: Filter, condition: WherePredicateOperator, parameters: {
[key: string]: string;
}): {
[key: string]: string;
};
export declare function generatePredicateCondition(qb: SelectQueryBuilder<unknown>, column: string, filter: Filter, alias: string, isVirtualProperty?: boolean): WherePredicateOperator;
export declare function addWhereCondition<T>(qb: SelectQueryBuilder<T>, column: string, filter: ColumnFilters): void;
export declare function parseFilterToken(raw?: string): FilterToken | null;
export declare function parseFilter<T>(query: PaginateQuery, filterableColumns?: {
[column: string]: (FilterOperator | FilterSuffix | FilterQuantifier | FilterComparator)[] | true;
}, qb?: SelectQueryBuilder<T>, throwOnInvalidFilter?: boolean): ColumnFilters;
/**
* Retrieves the relation path for a given column name within the provided metadata.
*
* This method analyzes the column name's segments to identify corresponding relations or embedded entities
* within the hierarchy described by the given metadata and returns a structured path.
*
* @param {string} columnName - The dot-delimited name of the column whose relation path is to be determined.
* @param {EntityMetadata | EmbeddedMetadata} metadata - The metadata of the entity or embedded component
* which holds the relations or embedded items.
* @return {[string, RelationMetadata | EmbeddedMetadata][]} The ordered array describing the path,
* where each element contains a field name and its corresponding relation or embedded metadata.
* Throws an error if no matching relation or embedded metadata is found.
*/
export declare function getRelationPath(columnName: string, metadata: EntityMetadata | EmbeddedMetadata): [string, RelationMetadata | EmbeddedMetadata][];
export interface AddFilterOptions {
/**
* Maximum number of `$and` values allowed per sub-column in a single to-many filter.
* Each value produces a separate correlated EXISTS subquery, so large values have a
* linear performance cost. Defaults to 20.
*/
maxAndValues?: number;
/**
* When false, skips the validation that rejects `$and` on non-to-many columns.
* Set to false when calling `addFilter` recursively for EXISTS sub-queries, where the
* entity metadata is the leaf entity and the to-many check would incorrectly throw.
* @internal
*/
validateAndComparator?: boolean;
}
export declare function addFilter<T>(qb: SelectQueryBuilder<T>, query: PaginateQuery, filterableColumns?: {
[column: string]: (FilterOperator | FilterSuffix | FilterQuantifier | FilterComparator)[] | true;
}, opts?: AddFilterOptions, throwOnInvalidFilter?: boolean): ColumnJoinMethods;
export declare function addDirectFilters<T>(qb: SelectQueryBuilder<T>, filter: ColumnFilters): void;
/**
* Adds correlated EXISTS subqueries to `qb` for all to-many relationship filters in `filter`.
*
* **AND-mode (`$and` comparator)**
*
* When a sub-column filter uses the `$and` comparator (e.g. `filter[toys.name]=$and:Ball`),
* each distinct `$and` value produces a separate correlated EXISTS subquery, ANDed on the
* outer query. This is the only correct way to express "entity has ALL of these related values"
* — a single EXISTS with AND conditions on the same column is always false on a single row.
*
* **Performance note**: each `$and` value adds one correlated EXISTS with the full join chain
* for that relation path. For a relation path of depth D and N `$and` values, this produces
* N × D joins. The `maxAndValues` option (default 20) caps N to limit query complexity.
*
* **Restrictions**:
* - `$and` may only be used on to-many relationship columns.
* - `$and` values may not be mixed with non-`$and` values on the same sub-column.
* - `$and` may not be combined with `$none` or `$all` quantifiers.
* - `$and` may only be applied to a single sub-column per relation path at a time.
*/
export declare function addToManySubFilters<T>(qb: SelectQueryBuilder<T>, filter: ColumnFilters, query: PaginateQuery, filterableColumns?: {
[column: string]: (FilterOperator | FilterSuffix | FilterQuantifier | FilterComparator)[] | true;
}, { maxAndValues, validateAndComparator }?: AddFilterOptions): void;
export declare function createSubFilter(query: PaginateQuery, filterableColumns: {
[column: string]: (FilterOperator | FilterSuffix | FilterQuantifier | FilterComparator)[] | true;
}, column: string): {
subQuery: PaginateQuery;
subFilterableColumns: {
[column: string]: true | (FilterOperator | FilterSuffix | FilterQuantifier | FilterComparator)[];
};
};
export {};