UNPKG

jsm-utilities

Version:
460 lines (459 loc) 17.3 kB
export interface QueryLike { where: (filter: any) => QueryLike; } export interface FilterResult<Q, T> { q: Q; totalQ: T; } export interface StringFilterOptions { omit?: boolean; operator?: "and" | "or"; mode?: "exact" | "contains" | "regex" | "startsWith" | "endsWith"; caseSensitive?: boolean; useRegex?: boolean; } export interface NumberFilterOptions { omit?: boolean; allowZero?: boolean; precision?: number; operator?: "and" | "or"; } export interface DateFilterOptions { omit?: boolean; timezone?: string; includeTime?: boolean; format?: string; dateRange?: [Date, Date] | null; } export interface GeoFilter { type: 'near' | 'within' | 'intersects'; longitude?: number; latitude?: number; maxDistance?: number; minDistance?: number; geometry?: any; spherical?: boolean; } export interface NumberRange { min?: number; max?: number; exact?: number; } export interface FilterConfig { [fieldName: string]: { type: 'string' | 'number' | 'date' | 'boolean' | 'geo' | 'array' | 'custom'; options?: any; transform?: (value: any) => any; validator?: (value: any) => boolean; }; } /** * Enhanced string filter with support for multiple fields and various matching modes */ export declare const createStringFilter: <F = string, Q = any, T = any>(q: Q, totalQ: T | null, value: any, fields: F | F[], options?: StringFilterOptions) => FilterResult<Q, T | null>; /** * Enhanced boolean filter */ export declare const createBooleanFilter: <F = string, Q = any, T = any>(q: Q, totalQ: T | null, value: any, field: F, omit?: boolean) => FilterResult<Q, T | null>; /** * Enhanced date range filter */ export declare const createDateRangeFilter: <F = string, Q = any, T = any>(q: Q, totalQ: T | null, value: any, field: F, options?: DateFilterOptions) => FilterResult<Q, T | null>; /** * Enhanced number filter with range support */ export declare const createNumberFilter: <F = string, Q = any, T = any>(q: Q, totalQ: T | null, value: any, field: F | F[], options?: NumberFilterOptions) => FilterResult<Q, T | null>; /** * Array filter for matching array fields */ export declare const createArrayFilter: <F = string, Q = any, T = any>(q: Q, totalQ: T | null, value: any, field: F, options?: { omit?: boolean; matchAll?: boolean; size?: number; }) => FilterResult<Q, T | null>; /** * Geospatial filter */ export declare const createGeoFilter: <F = string, Q = any, T = any>(q: Q, totalQ: T | null, geoFilter: GeoFilter, field: F) => FilterResult<Q, T | null>; /** * Text search filter */ export declare const createTextSearchFilter: <Q = any, T = any>(q: Q, totalQ: T | null, searchText: string, options?: { language?: string; caseSensitive?: boolean; diacriticSensitive?: boolean; }) => FilterResult<Q, T | null>; /** * Configuration-driven filter function */ export declare const createConfigurableFilter: <Q = any, T = any>(q: Q, totalQ: T | null, filters: Record<string, any>, config: FilterConfig) => FilterResult<Q, T | null>; export declare const createStringFilter_deprecated: <F extends string = string, Q = any, T = any>(q: Q, totalQ: T | null, value: any, fields: F | F[], omit?: boolean, operator?: "and" | "or") => FilterResult<Q, T | null>; export declare const createBooleanFilter_deprecated: <F = string, Q = any, T = any>(q: Q, totalQ: T | null, value: any, field: F, omit?: boolean) => FilterResult<Q, T | null>; export declare const createDateRangeFilter_deprecated: <F = string, Q = any, T = any>(q: Q, totalQ: T | null, value: any, field: F, omit?: boolean) => FilterResult<Q, T | null>; export declare const createNumberFilter_deprecated: <F = string, Q = any, T = any>(q: Q, totalQ: T | null, value: any, field: F, omit?: boolean) => FilterResult<Q, T | null>; /** * Fluent filter builder for creating complex MongoDB queries with method chaining. * * This class provides a chainable interface for building complex database queries * by combining multiple filter types. It supports both main queries and total count queries * simultaneously, making it ideal for paginated results. * * @template F - Field names type constraint (extends string) * @template Q - Query type (typically a Mongoose Query object) * @template T - Total query type (typically a Mongoose Query object for counting) * * @example * ```typescript * const builder = createQueryBuilder(userQuery, totalQuery) * .string('name', 'john', { mode: 'contains', caseSensitive: false }) * .number('age', { min: 18, max: 65 }) * .boolean('isActive', true) * .dateRange('createdAt', [startDate, endDate]); * * const { q, totalQ } = builder.build(); * ``` * * @since 2.0.0 */ export declare class QueryFilterBuilder<F extends string, Q = any, T = any> { private q; private totalQ; /** * Creates a new QueryFilterBuilder instance. * * @param q - The main query object to build filters on * @param totalQ - The total count query object (can be null) */ constructor(q: Q, totalQ: T | null); /** * Adds a string filter to the query with flexible matching options. * * @param field - The field name(s) to filter on (single field or array of fields) * @param value - The string value to search for (can be any type, null, or undefined) * @param options - Configuration options for string matching * @param options.mode - Search mode: 'exact', 'contains', 'startsWith', 'endsWith' * @param options.caseSensitive - Whether to perform case-sensitive matching (default: false) * @param options.trim - Whether to trim whitespace from the value (default: true) * @param options.allowEmpty - Whether to include empty strings in results (default: false) * * @returns This QueryFilterBuilder instance for method chaining * * @example * ```typescript * builder * .string('name', 'john', { mode: 'contains', caseSensitive: false }) * .string(['firstName', 'lastName'], 'smith', { mode: 'contains' }); * ``` */ string(field: F | F[], value: any, options?: StringFilterOptions): this; /** * Adds a number filter to the query with range and equality options. * * @param field - The field name(s) to filter on (single field or array of fields) * @param value - The number value or range object to filter by * @param options - Configuration options for number filtering * @param options.allowDecimals - Whether to allow decimal values (default: true) * @param options.strictMode - Whether to enforce strict number validation (default: false) * * @returns This QueryFilterBuilder instance for method chaining * * @example * ```typescript * builder * .number('price', { min: 10, max: 100 }) * .number('age', 25) * .number(['width', 'height'], { min: 0 }, { allowDecimals: false }); * ``` */ number(field: F | F[], value: any, options?: NumberFilterOptions): this; /** * Adds a date range filter to the query for filtering by date ranges. * * @param field - The field name to filter on * @param value - The date range value (array of dates, single date, or date range object) * @param options - Configuration options for date range filtering * @param options.omit - Whether to omit null/undefined values (default: false) * @param options.timezone - Timezone to use for date calculations * @param options.includeTime - Whether to include time in date comparison (default: false) * @param options.format - Date format string for parsing * * @returns This QueryFilterBuilder instance for method chaining * * @example * ```typescript * const startDate = new Date('2023-01-01'); * const endDate = new Date('2023-12-31'); * * builder * .dateRange('createdAt', [startDate, endDate]) * .dateRange('updatedAt', { start: startDate, end: endDate }) * .dateRange('publishedAt', startDate); * ``` */ dateRange(field: F, value: any, options?: DateFilterOptions): this; /** * Add a boolean filter with chainable interface */ /** * Adds a boolean filter to the query for true/false value filtering. * * @param field - The field name to filter on * @param value - The boolean value to filter by * @param omit - Whether to omit null/undefined values (default: false) * * @returns This QueryFilterBuilder instance for method chaining * * @example * ```typescript * builder * .boolean('isActive', true) * .boolean('isPublished', false) * .boolean('isVerified', true, true); // omit null values * ``` */ boolean(field: F, value: any, omit?: boolean): this; /** * Adds an array filter to the query for filtering by array values. * * @param field - The field name to filter on * @param value - The array value or element to filter by * @param options - Configuration options for array filtering * @param options.omit - Whether to omit null/undefined values (default: false) * @param options.matchAll - Whether to match all elements (AND) or any element (OR) * @param options.size - Filter arrays by their size/length * * @returns This QueryFilterBuilder instance for method chaining * * @example * ```typescript * builder * .array('tags', ['javascript', 'typescript']) * .array('categories', 'electronics', { matchAll: false }) * .array('permissions', [], { size: 0 }); // empty arrays * ``` */ array(field: F, value: any, options?: { omit?: boolean; matchAll?: boolean; size?: number; }): this; /** * Adds a geospatial filter to the query for location-based filtering. * * @param field - The field name containing geospatial data * @param geoFilter - The geospatial filter configuration * * @returns This QueryFilterBuilder instance for method chaining * * @example * ```typescript * builder * .geo('location', { * near: { type: 'Point', coordinates: [-74.006, 40.7128] }, * maxDistance: 1000 // meters * }) * .geo('boundaries', { * within: { * type: 'Polygon', * coordinates: [[[...], [...], [...], [...]]] * } * }); * ``` */ geo(field: F, geoFilter: GeoFilter): this; /** * Adds a text search filter to the query for full-text search capabilities. * * @param searchText - The text to search for * @param options - Configuration options for text search * @param options.language - Language for text search (default: 'english') * @param options.caseSensitive - Whether search is case sensitive (default: false) * @param options.diacriticSensitive - Whether search is diacritic sensitive (default: false) * * @returns This QueryFilterBuilder instance for method chaining * * @example * ```typescript * builder * .textSearch('javascript programming') * .textSearch('café', { language: 'french', diacriticSensitive: true }) * .textSearch('TYPESCRIPT', { caseSensitive: true }); * ``` */ textSearch(searchText: string, options?: { language?: string; caseSensitive?: boolean; diacriticSensitive?: boolean; }): this; /** * Applies multiple filters using a configuration object. * * @param filters - Record of filter values keyed by field names * @param config - Configuration object defining how to apply filters * * @returns This QueryFilterBuilder instance for method chaining * * @example * ```typescript * const filters = { * name: 'john', * age: 25, * active: true * }; * const config = { * name: { type: 'string', match: 'contains' }, * age: { type: 'number', operator: 'gte' }, * active: { type: 'boolean' } * }; * * builder.configurable(filters, config); * ``` */ configurable(filters: Record<string, any>, config: FilterConfig): this; /** * Applies a custom filter function for advanced filtering scenarios. * * @param filterFn - Custom function that takes query objects and returns modified queries * * @returns This QueryFilterBuilder instance for method chaining * * @example * ```typescript * builder.custom((q, totalQ) => { * // Add complex MongoDB aggregation pipeline * q.push({ $lookup: { from: 'users', localField: 'userId', foreignField: '_id', as: 'user' } }); * return { q, totalQ }; * }); * * // Or for simple query modifications * builder.custom((q, totalQ) => { * q.sort = { createdAt: -1, priority: 1 }; * return { q, totalQ }; * }); * ``` */ custom(filterFn: (q: Q, totalQ: T | null) => FilterResult<Q, T | null>): this; /** * Builds and returns the final filter result containing both main and total queries. * * @returns FilterResult object containing the constructed query and totalQuery * * @example * ```typescript * const { q, totalQ } = builder * .string('name', 'john') * .number('age', 25, { operator: 'gte' }) * .build(); * * // Use with MongoDB/Mongoose * const results = await Model.find(q); * const total = await Model.countDocuments(totalQ || q); * ``` */ build(): FilterResult<Q, T | null>; /** * Gets the current main query object. * * @returns The current query object * * @example * ```typescript * const currentQuery = builder.string('name', 'john').getQuery(); * console.log(currentQuery); // { name: { $regex: 'john', $options: 'i' } } * ``` */ getQuery(): Q; /** * Gets the current total query object used for counting. * * @returns The current total query object, or null if not set * * @example * ```typescript * const totalQuery = builder.string('name', 'john').getTotalQuery(); * const count = await Model.countDocuments(totalQuery || {}); * ``` */ getTotalQuery(): T | null; /** * Resets the builder to a new initial state with fresh query objects. * * @param q - New initial query object * @param totalQ - New initial total query object (optional) * * @returns This QueryFilterBuilder instance for method chaining * * @example * ```typescript * // Reset with new base queries * builder.reset({}, {}) * .string('category', 'electronics') * .boolean('active', true); * * // Reset with pre-existing query * const baseQuery = { organizationId: '123' }; * builder.reset(baseQuery).string('name', 'product'); * ``` */ reset(q: Q, totalQ?: T | null): this; /** * Creates a clone of the current builder with the same state. * * @returns A new QueryFilterBuilder instance with the same query and totalQuery state * * @example * ```typescript * const baseBuilder = createQueryBuilder({}, {}) * .string('organization', 'company1'); * * // Create variations from the base * const activeUsersQuery = baseBuilder.clone() * .boolean('active', true) * .build(); * * const inactiveUsersQuery = baseBuilder.clone() * .boolean('active', false) * .build(); * ``` */ clone(): QueryFilterBuilder<F, Q, T>; } /** * Factory function to create a new QueryFilterBuilder instance with fluent interface. * * @template F - Union type of allowed field names for type safety * @template Q - Type of the main query object (e.g., MongoDB filter, Mongoose query) * @template T - Type of the total/count query object * * @param q - Initial query object to start building from * @param totalQ - Initial total query object for counting (optional) * * @returns A new QueryFilterBuilder instance ready for method chaining * * @example * ```typescript * // Basic usage with MongoDB-style queries * const builder = createQueryBuilder({}, {}) * .string('name', 'john') * .number('age', 25, { operator: 'gte' }) * .boolean('active', true); * * const { q, totalQ } = builder.build(); * * // With typed field names for better IntelliSense * type UserFields = 'name' | 'email' | 'age' | 'active'; * const typedBuilder = createQueryBuilder<UserFields>({}) * .string('name', 'john') // ✅ Type-safe * .number('invalidField', 123); // ❌ TypeScript error * * // With Mongoose Query and aggregation pipeline * interface MongoQuery { * [key: string]: any; * } * * const mongoBuilder = createQueryBuilder<string, MongoQuery>({}) * .string('title', 'typescript') * .dateRange('createdAt', [startDate, endDate]); * ``` */ export declare const createQueryBuilder: <F extends string = string, Q = any, T = any>(q: Q, totalQ?: T | null) => QueryFilterBuilder<F, Q, T>;