jsm-utilities
Version:
A utilities library.
460 lines (459 loc) • 17.3 kB
TypeScript
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>;