@classytic/mongokit 3.2.0 → 3.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/README.md +470 -193
  2. package/dist/actions/index.d.mts +9 -0
  3. package/dist/actions/index.mjs +15 -0
  4. package/dist/aggregate-BAi4Do-X.mjs +767 -0
  5. package/dist/aggregate-CCHI7F51.d.mts +269 -0
  6. package/dist/ai/index.d.mts +125 -0
  7. package/dist/ai/index.mjs +203 -0
  8. package/dist/cache-keys-C8Z9B5sw.mjs +204 -0
  9. package/dist/chunk-DQk6qfdC.mjs +18 -0
  10. package/dist/create-BuO6xt0v.mjs +55 -0
  11. package/dist/custom-id.plugin-B_zIs6gE.mjs +1818 -0
  12. package/dist/custom-id.plugin-BzZI4gnE.d.mts +893 -0
  13. package/dist/index.d.mts +1012 -0
  14. package/dist/index.mjs +1906 -0
  15. package/dist/limits-DsNeCx4D.mjs +299 -0
  16. package/dist/logger-D8ily-PP.mjs +51 -0
  17. package/dist/mongooseToJsonSchema-COdDEkIJ.mjs +317 -0
  18. package/dist/{mongooseToJsonSchema-CaRF_bCN.d.ts → mongooseToJsonSchema-Wbvjfwkn.d.mts} +16 -89
  19. package/dist/pagination/PaginationEngine.d.mts +93 -0
  20. package/dist/pagination/PaginationEngine.mjs +196 -0
  21. package/dist/plugins/index.d.mts +3 -0
  22. package/dist/plugins/index.mjs +3 -0
  23. package/dist/types-D-gploPr.d.mts +1241 -0
  24. package/dist/utils/{index.d.ts → index.d.mts} +14 -21
  25. package/dist/utils/index.mjs +5 -0
  26. package/package.json +21 -21
  27. package/dist/actions/index.d.ts +0 -3
  28. package/dist/actions/index.js +0 -5
  29. package/dist/ai/index.d.ts +0 -175
  30. package/dist/ai/index.js +0 -206
  31. package/dist/chunks/chunk-2ZN65ZOP.js +0 -93
  32. package/dist/chunks/chunk-44KXLGPO.js +0 -388
  33. package/dist/chunks/chunk-DEVXDBRL.js +0 -1226
  34. package/dist/chunks/chunk-I7CWNAJB.js +0 -46
  35. package/dist/chunks/chunk-JWUAVZ3L.js +0 -8
  36. package/dist/chunks/chunk-UE2IEXZJ.js +0 -306
  37. package/dist/chunks/chunk-URLJFIR7.js +0 -22
  38. package/dist/chunks/chunk-VWKIKZYF.js +0 -737
  39. package/dist/chunks/chunk-WSFCRVEQ.js +0 -7
  40. package/dist/index-BDn5fSTE.d.ts +0 -516
  41. package/dist/index.d.ts +0 -1422
  42. package/dist/index.js +0 -1893
  43. package/dist/pagination/PaginationEngine.d.ts +0 -117
  44. package/dist/pagination/PaginationEngine.js +0 -3
  45. package/dist/plugins/index.d.ts +0 -922
  46. package/dist/plugins/index.js +0 -6
  47. package/dist/types-Jni1KgkP.d.ts +0 -780
  48. package/dist/utils/index.js +0 -5
package/dist/index.d.ts DELETED
@@ -1,1422 +0,0 @@
1
- import { h as PaginationResult, A as AnyDocument, i as PluginType, P as PaginationConfig, R as RepositoryOptions, j as ObjectId, S as SelectSpec, e as PopulateSpec, f as SortSpec$2, a as OffsetPaginationResult, b as KeysetPaginationResult, U as UpdateOptions, d as AggregatePaginationResult, W as WithTransactionOptions, k as RepositoryContext, H as HttpError } from './types-Jni1KgkP.js';
2
- export { c as AggregatePaginationOptions, ad as AllPluginMethods, l as AnyModel, a7 as CacheAdapter, a9 as CacheOperationOptions, a8 as CacheOptions, aa as CacheStats, ac as CascadeOptions, ab as CascadeRelation, C as CreateInput, v as CreateOptions, Z as CrudSchemas, _ as DecodedCursor, D as DeepPartial, w as DeleteResult, L as EventHandlers, M as EventPayload, G as EventPhase, Q as FieldPreset, T as FieldRules, a5 as GroupResult, n as HookMode, I as InferDocument, o as InferRawDoc, Y as JsonSchema, r as KeysOfType, K as KeysetPaginationOptions, a1 as Logger, a6 as MinMaxResult, N as NonNullableFields, O as OffsetPaginationOptions, u as OperationOptions, p as PartialBy, g as Plugin, B as PluginFunction, J as RepositoryEvent, E as RepositoryInstance, F as RepositoryOperation, q as RequiredBy, X as SchemaBuilderOptions, a3 as SoftDeleteFilterMode, a2 as SoftDeleteOptions, a4 as SoftDeleteRepository, m as SortDirection, s as Strict, t as UpdateInput, x as UpdateManyResult, y as UpdateWithValidationResult, z as UserContext, a0 as ValidationChainOptions, V as ValidationResult, $ as ValidatorDefinition, ae as WithPlugins } from './types-Jni1KgkP.js';
3
- import * as mongoose from 'mongoose';
4
- import { PipelineStage, Model, Expression, ClientSession, PopulateOptions } from 'mongoose';
5
- import { PaginationEngine } from './pagination/PaginationEngine.js';
6
- import { L as LookupOptions, a as LookupBuilder } from './index-BDn5fSTE.js';
7
- export { i as actions } from './index-BDn5fSTE.js';
8
- export { AggregateHelpersMethods, BatchOperationsMethods, CacheMethods, MongoOperationsMethods, MultiTenantOptions, ObservabilityOptions, OperationMetric, SoftDeleteMethods, SubdocumentMethods, aggregateHelpersPlugin, auditLogPlugin, autoInject, batchOperationsPlugin, blockIf, cachePlugin, cascadePlugin, fieldFilterPlugin, immutableField, methodRegistryPlugin, mongoOperationsPlugin, multiTenantPlugin, observabilityPlugin, requireField, softDeletePlugin, subdocumentPlugin, timestampPlugin, uniqueField, validationChainPlugin } from './plugins/index.js';
9
- export { d as buildCrudSchemasFromModel, b as buildCrudSchemasFromMongooseSchema, k as configureLogger, j as createError, c as createFieldPreset, l as createMemoryCache, f as filterResponseData, g as getFieldsForUser, e as getImmutableFields, a as getMongooseProjection, h as getSystemManagedFields, i as isFieldUpdateAllowed, v as validateUpdateBody } from './mongooseToJsonSchema-CaRF_bCN.js';
10
-
11
- /**
12
- * Framework-Agnostic Controller Interfaces
13
- *
14
- * These interfaces define contracts for building controllers that work with any framework.
15
- * Implement these in your chosen framework (Express, Fastify, Next.js, etc.)
16
- *
17
- * @see examples/api/baseController.ts for Express implementation
18
- * @see examples/fastify for Fastify implementation
19
- * @see examples/nextjs for Next.js implementation
20
- */
21
-
22
- /**
23
- * Framework-agnostic request context
24
- *
25
- * Extract this from your framework's request object:
26
- * - Express: { query: req.query, body: req.body, params: req.params, user: req.user }
27
- * - Fastify: { query: request.query, body: request.body, params: request.params, user: request.user }
28
- * - Next.js: { query: searchParams, body: await request.json(), params: params, user: await getUser() }
29
- *
30
- * @example
31
- * ```typescript
32
- * // Express
33
- * const context: IRequestContext = {
34
- * query: req.query,
35
- * body: req.body,
36
- * params: req.params,
37
- * user: req.user,
38
- * context: { organizationId: req.headers['x-org-id'] }
39
- * };
40
- *
41
- * // Fastify
42
- * const context: IRequestContext = {
43
- * query: request.query,
44
- * body: request.body,
45
- * params: request.params,
46
- * user: request.user,
47
- * };
48
- *
49
- * // Next.js App Router
50
- * const context: IRequestContext = {
51
- * query: Object.fromEntries(request.nextUrl.searchParams),
52
- * body: await request.json(),
53
- * params: params,
54
- * user: await auth(),
55
- * };
56
- * ```
57
- */
58
- interface IRequestContext {
59
- /** Query parameters (from URL query string) */
60
- query: Record<string, unknown>;
61
- /** Request body (parsed JSON) */
62
- body: Record<string, unknown>;
63
- /** Route parameters (dynamic segments in URL) */
64
- params: Record<string, string>;
65
- /**
66
- * Authenticated user (from your auth middleware)
67
- * Shape depends on your auth system
68
- */
69
- user?: {
70
- id: string;
71
- role?: string;
72
- [key: string]: unknown;
73
- };
74
- /**
75
- * Custom context (tenant ID, organization, locale, etc.)
76
- * Use this for multi-tenant apps, feature flags, etc.
77
- */
78
- context?: Record<string, unknown>;
79
- }
80
- /**
81
- * Framework-agnostic controller response
82
- *
83
- * Your controller methods should return this shape.
84
- * Adapt it to your framework's response format in the handler layer.
85
- *
86
- * @example
87
- * ```typescript
88
- * // In controller method
89
- * async list(context: IRequestContext): Promise<IControllerResponse> {
90
- * const result = await this.repository.getAll(context.query);
91
- * return {
92
- * success: true,
93
- * data: result,
94
- * status: 200,
95
- * };
96
- * }
97
- *
98
- * // In Express handler
99
- * const response = await controller.list(context);
100
- * res.status(response.status).json(response);
101
- *
102
- * // In Fastify handler
103
- * const response = await controller.list(context);
104
- * return reply.code(response.status).send(response);
105
- *
106
- * // In Next.js handler
107
- * const response = await controller.list(context);
108
- * return NextResponse.json(response.data, { status: response.status });
109
- * ```
110
- */
111
- interface IControllerResponse<T = unknown> {
112
- /** Whether the operation succeeded */
113
- success: boolean;
114
- /** Response data (undefined on error) */
115
- data?: T;
116
- /** Error message (only if success = false) */
117
- error?: string;
118
- /** Additional error details (validation errors, stack trace, etc.) */
119
- details?: unknown;
120
- /** HTTP status code */
121
- status: number;
122
- /** Optional metadata (pagination info, warnings, etc.) */
123
- meta?: Record<string, unknown>;
124
- }
125
- /**
126
- * Framework-agnostic CRUD controller interface
127
- *
128
- * Implement this interface in your controller classes.
129
- * Each method receives a framework-agnostic context and returns a framework-agnostic response.
130
- *
131
- * @template TDoc - The Mongoose document type
132
- *
133
- * @example
134
- * ```typescript
135
- * import { IController, IRequestContext, IControllerResponse } from '@mongokit/core';
136
- *
137
- * class UserController implements IController<IUser> {
138
- * async list(context: IRequestContext): Promise<IControllerResponse> {
139
- * // Implementation
140
- * }
141
- *
142
- * async get(context: IRequestContext): Promise<IControllerResponse> {
143
- * // Implementation
144
- * }
145
- *
146
- * // ... other methods
147
- * }
148
- * ```
149
- */
150
- interface IController<TDoc> {
151
- /**
152
- * List resources with filtering, pagination, sorting, lookups
153
- *
154
- * @param context - Framework-agnostic request context
155
- * @returns Promise resolving to controller response with paginated data
156
- *
157
- * @example
158
- * ```typescript
159
- * // URL: GET /users?status=active&sort=-createdAt&limit=20
160
- * const response = await controller.list({
161
- * query: { status: 'active', sort: '-createdAt', limit: '20' },
162
- * body: {},
163
- * params: {},
164
- * });
165
- *
166
- * // Response:
167
- * {
168
- * success: true,
169
- * data: {
170
- * method: 'offset',
171
- * docs: [...],
172
- * total: 100,
173
- * page: 1,
174
- * pages: 5
175
- * },
176
- * status: 200
177
- * }
178
- * ```
179
- */
180
- list(context: IRequestContext): Promise<IControllerResponse<PaginationResult<TDoc>>>;
181
- /**
182
- * Get single resource by ID
183
- *
184
- * @param context - Framework-agnostic request context (id in params)
185
- * @returns Promise resolving to controller response with single document
186
- *
187
- * @example
188
- * ```typescript
189
- * // URL: GET /users/507f1f77bcf86cd799439011
190
- * const response = await controller.get({
191
- * query: {},
192
- * body: {},
193
- * params: { id: '507f1f77bcf86cd799439011' },
194
- * });
195
- *
196
- * // Success response:
197
- * {
198
- * success: true,
199
- * data: { _id: '...', name: 'John', ... },
200
- * status: 200
201
- * }
202
- *
203
- * // Not found response:
204
- * {
205
- * success: false,
206
- * error: 'Resource not found',
207
- * status: 404
208
- * }
209
- * ```
210
- */
211
- get(context: IRequestContext): Promise<IControllerResponse<TDoc>>;
212
- /**
213
- * Create new resource
214
- *
215
- * @param context - Framework-agnostic request context (data in body)
216
- * @returns Promise resolving to controller response with created document
217
- *
218
- * @example
219
- * ```typescript
220
- * // URL: POST /users
221
- * // Body: { name: 'John', email: 'john@example.com' }
222
- * const response = await controller.create({
223
- * query: {},
224
- * body: { name: 'John', email: 'john@example.com' },
225
- * params: {},
226
- * });
227
- *
228
- * // Response:
229
- * {
230
- * success: true,
231
- * data: { _id: '...', name: 'John', email: '...', createdAt: '...' },
232
- * status: 201
233
- * }
234
- * ```
235
- */
236
- create(context: IRequestContext): Promise<IControllerResponse<TDoc>>;
237
- /**
238
- * Update existing resource
239
- *
240
- * @param context - Framework-agnostic request context (id in params, updates in body)
241
- * @returns Promise resolving to controller response with updated document
242
- *
243
- * @example
244
- * ```typescript
245
- * // URL: PATCH /users/507f1f77bcf86cd799439011
246
- * // Body: { name: 'Jane' }
247
- * const response = await controller.update({
248
- * query: {},
249
- * body: { name: 'Jane' },
250
- * params: { id: '507f1f77bcf86cd799439011' },
251
- * });
252
- *
253
- * // Response:
254
- * {
255
- * success: true,
256
- * data: { _id: '...', name: 'Jane', ... },
257
- * status: 200
258
- * }
259
- * ```
260
- */
261
- update(context: IRequestContext): Promise<IControllerResponse<TDoc>>;
262
- /**
263
- * Delete resource
264
- *
265
- * @param context - Framework-agnostic request context (id in params)
266
- * @returns Promise resolving to controller response with deletion result
267
- *
268
- * @example
269
- * ```typescript
270
- * // URL: DELETE /users/507f1f77bcf86cd799439011
271
- * const response = await controller.delete({
272
- * query: {},
273
- * body: {},
274
- * params: { id: '507f1f77bcf86cd799439011' },
275
- * });
276
- *
277
- * // Response:
278
- * {
279
- * success: true,
280
- * data: { message: 'Resource deleted successfully' },
281
- * status: 200
282
- * }
283
- * ```
284
- */
285
- delete(context: IRequestContext): Promise<IControllerResponse<{
286
- message: string;
287
- }>>;
288
- }
289
- /**
290
- * Optional: Response formatter utilities
291
- *
292
- * Helper functions to create standardized controller responses.
293
- * Use these to maintain consistent response shapes across your API.
294
- *
295
- * @example
296
- * ```typescript
297
- * class BaseController {
298
- * protected success<T>(data: T, status = 200): IControllerResponse<T> {
299
- * return { success: true, data, status };
300
- * }
301
- *
302
- * protected error(message: string, status = 500, details?: unknown): IControllerResponse {
303
- * return { success: false, error: message, status, details };
304
- * }
305
- *
306
- * protected paginated<T>(result: PaginationResult<T>): IControllerResponse<PaginationResult<T>> {
307
- * return { success: true, data: result, status: 200 };
308
- * }
309
- * }
310
- * ```
311
- */
312
- interface IResponseFormatter {
313
- /**
314
- * Format successful response
315
- * @param data - Response data
316
- * @param status - HTTP status code (default: 200)
317
- */
318
- success<T>(data: T, status?: number): IControllerResponse<T>;
319
- /**
320
- * Format error response
321
- * @param message - Error message
322
- * @param status - HTTP status code (default: 500)
323
- * @param details - Additional error details
324
- */
325
- error(message: string, status?: number, details?: unknown): IControllerResponse;
326
- /**
327
- * Format paginated response
328
- * @param result - Pagination result from Repository.getAll()
329
- */
330
- paginated<T>(result: PaginationResult<T>): IControllerResponse<PaginationResult<T>>;
331
- }
332
-
333
- /** Options for $vectorSearch stage (Atlas only) */
334
- interface VectorSearchOptions {
335
- /** Atlas Search index name */
336
- index: string;
337
- /** Field path containing the vector embedding */
338
- path: string;
339
- /** Query vector to search against */
340
- queryVector: number[];
341
- /** Number of candidates to consider (higher = more accurate, slower) */
342
- numCandidates?: number;
343
- /** Max results to return */
344
- limit: number;
345
- /** Pre-filter documents before vector search */
346
- filter?: Record<string, unknown>;
347
- /** Use exact search instead of ANN (slower but precise) */
348
- exact?: boolean;
349
- }
350
- /** Result from build() including execution options */
351
- interface AggregationPlan {
352
- pipeline: PipelineStage[];
353
- allowDiskUse: boolean;
354
- }
355
- type SortOrder = 1 | -1 | 'asc' | 'desc';
356
- type SortSpec$1 = Record<string, SortOrder>;
357
- type ProjectionSpec = Record<string, 0 | 1 | Expression>;
358
- type GroupSpec = {
359
- _id: string | Record<string, unknown> | null;
360
- [key: string]: unknown;
361
- };
362
- /**
363
- * Fluent builder for MongoDB aggregation pipelines
364
- * Optimized for complex queries at scale
365
- */
366
- declare class AggregationBuilder {
367
- private pipeline;
368
- private _diskUse;
369
- /**
370
- * Get the current pipeline
371
- */
372
- get(): PipelineStage[];
373
- /**
374
- * Build and return the final pipeline
375
- */
376
- build(): PipelineStage[];
377
- /**
378
- * Build pipeline with execution options (allowDiskUse, etc.)
379
- */
380
- plan(): AggregationPlan;
381
- /**
382
- * Build and execute the pipeline against a model
383
- *
384
- * @example
385
- * ```typescript
386
- * const results = await new AggregationBuilder()
387
- * .match({ status: 'active' })
388
- * .allowDiskUse()
389
- * .exec(MyModel);
390
- * ```
391
- */
392
- exec<T = unknown>(model: Model<any>, session?: mongoose.ClientSession): Promise<T[]>;
393
- /**
394
- * Reset the pipeline
395
- */
396
- reset(): this;
397
- /**
398
- * Add a raw pipeline stage
399
- */
400
- addStage(stage: PipelineStage): this;
401
- /**
402
- * Add multiple raw pipeline stages
403
- */
404
- addStages(stages: PipelineStage[]): this;
405
- /**
406
- * $match - Filter documents
407
- * IMPORTANT: Place $match as early as possible for performance
408
- */
409
- match(query: Record<string, unknown>): this;
410
- /**
411
- * $project - Include/exclude fields or compute new fields
412
- */
413
- project(projection: ProjectionSpec): this;
414
- /**
415
- * $group - Group documents and compute aggregations
416
- *
417
- * @example
418
- * ```typescript
419
- * .group({
420
- * _id: '$department',
421
- * count: { $sum: 1 },
422
- * avgSalary: { $avg: '$salary' }
423
- * })
424
- * ```
425
- */
426
- group(groupSpec: GroupSpec): this;
427
- /**
428
- * $sort - Sort documents
429
- */
430
- sort(sortSpec: SortSpec$1 | string): this;
431
- /**
432
- * $limit - Limit number of documents
433
- */
434
- limit(count: number): this;
435
- /**
436
- * $skip - Skip documents
437
- */
438
- skip(count: number): this;
439
- /**
440
- * $unwind - Deconstruct array field
441
- */
442
- unwind(path: string, preserveNullAndEmptyArrays?: boolean): this;
443
- /**
444
- * $addFields - Add new fields or replace existing fields
445
- */
446
- addFields(fields: Record<string, unknown>): this;
447
- /**
448
- * $set - Alias for $addFields
449
- */
450
- set(fields: Record<string, unknown>): this;
451
- /**
452
- * $unset - Remove fields
453
- */
454
- unset(fields: string | string[]): this;
455
- /**
456
- * $replaceRoot - Replace the root document
457
- */
458
- replaceRoot(newRoot: string | Record<string, unknown>): this;
459
- /**
460
- * $lookup - Join with another collection (simple form)
461
- *
462
- * @param from - Collection to join with
463
- * @param localField - Field from source collection
464
- * @param foreignField - Field from target collection
465
- * @param as - Output field name
466
- * @param single - Unwrap array to single object
467
- *
468
- * @example
469
- * ```typescript
470
- * // Join employees with departments by slug
471
- * .lookup('departments', 'deptSlug', 'slug', 'department', true)
472
- * ```
473
- */
474
- lookup(from: string, localField: string, foreignField: string, as?: string, single?: boolean): this;
475
- /**
476
- * $lookup - Join with another collection (advanced form with pipeline)
477
- *
478
- * @example
479
- * ```typescript
480
- * .lookupWithPipeline({
481
- * from: 'products',
482
- * localField: 'productIds',
483
- * foreignField: 'sku',
484
- * as: 'products',
485
- * pipeline: [
486
- * { $match: { status: 'active' } },
487
- * { $project: { name: 1, price: 1 } }
488
- * ]
489
- * })
490
- * ```
491
- */
492
- lookupWithPipeline(options: LookupOptions): this;
493
- /**
494
- * Multiple lookups at once
495
- *
496
- * @example
497
- * ```typescript
498
- * .multiLookup([
499
- * { from: 'departments', localField: 'deptSlug', foreignField: 'slug', single: true },
500
- * { from: 'managers', localField: 'managerId', foreignField: '_id', single: true }
501
- * ])
502
- * ```
503
- */
504
- multiLookup(lookups: LookupOptions[]): this;
505
- /**
506
- * $facet - Process multiple aggregation pipelines in a single stage
507
- * Useful for computing multiple aggregations in parallel
508
- *
509
- * @example
510
- * ```typescript
511
- * .facet({
512
- * totalCount: [{ $count: 'count' }],
513
- * avgPrice: [{ $group: { _id: null, avg: { $avg: '$price' } } }],
514
- * topProducts: [{ $sort: { sales: -1 } }, { $limit: 10 }]
515
- * })
516
- * ```
517
- */
518
- facet(facets: Record<string, PipelineStage[]>): this;
519
- /**
520
- * $bucket - Categorize documents into buckets
521
- *
522
- * @example
523
- * ```typescript
524
- * .bucket({
525
- * groupBy: '$price',
526
- * boundaries: [0, 50, 100, 200],
527
- * default: 'Other',
528
- * output: {
529
- * count: { $sum: 1 },
530
- * products: { $push: '$name' }
531
- * }
532
- * })
533
- * ```
534
- */
535
- bucket(options: {
536
- groupBy: string | Expression;
537
- boundaries: unknown[];
538
- default?: string;
539
- output?: Record<string, unknown>;
540
- }): this;
541
- /**
542
- * $bucketAuto - Automatically determine bucket boundaries
543
- */
544
- bucketAuto(options: {
545
- groupBy: string | Expression;
546
- buckets: number;
547
- output?: Record<string, unknown>;
548
- granularity?: string;
549
- }): this;
550
- /**
551
- * $setWindowFields - Perform window functions (MongoDB 5.0+)
552
- * Useful for rankings, running totals, moving averages
553
- *
554
- * @example
555
- * ```typescript
556
- * .setWindowFields({
557
- * partitionBy: '$department',
558
- * sortBy: { salary: -1 },
559
- * output: {
560
- * rank: { $rank: {} },
561
- * runningTotal: { $sum: '$salary', window: { documents: ['unbounded', 'current'] } }
562
- * }
563
- * })
564
- * ```
565
- */
566
- setWindowFields(options: {
567
- partitionBy?: string | Expression;
568
- sortBy?: SortSpec$1;
569
- output: Record<string, unknown>;
570
- }): this;
571
- /**
572
- * $unionWith - Combine results from multiple collections (MongoDB 4.4+)
573
- *
574
- * @example
575
- * ```typescript
576
- * .unionWith({
577
- * coll: 'archivedOrders',
578
- * pipeline: [{ $match: { year: 2024 } }]
579
- * })
580
- * ```
581
- */
582
- unionWith(options: {
583
- coll: string;
584
- pipeline?: PipelineStage[];
585
- }): this;
586
- /**
587
- * $densify - Fill gaps in data (MongoDB 5.1+)
588
- * Useful for time series data with missing points
589
- */
590
- densify(options: {
591
- field: string;
592
- range: {
593
- step: number;
594
- unit?: 'millisecond' | 'second' | 'minute' | 'hour' | 'day' | 'week' | 'month' | 'quarter' | 'year';
595
- bounds: 'full' | 'partition' | [unknown, unknown];
596
- };
597
- }): this;
598
- /**
599
- * $fill - Fill null or missing field values (MongoDB 5.3+)
600
- */
601
- fill(options: {
602
- sortBy?: SortSpec$1;
603
- output: Record<string, {
604
- method: 'linear' | 'locf' | 'value';
605
- value?: unknown;
606
- }>;
607
- }): this;
608
- /**
609
- * Enable allowDiskUse for large aggregations that exceed 100MB memory limit
610
- *
611
- * @example
612
- * ```typescript
613
- * const results = await new AggregationBuilder()
614
- * .match({ status: 'active' })
615
- * .group({ _id: '$category', total: { $sum: '$amount' } })
616
- * .allowDiskUse()
617
- * .exec(Model);
618
- * ```
619
- */
620
- allowDiskUse(enable?: boolean): this;
621
- /**
622
- * Paginate - Add skip and limit for offset-based pagination
623
- */
624
- paginate(page: number, limit: number): this;
625
- /**
626
- * Count total documents (useful with $facet for pagination metadata)
627
- */
628
- count(outputField?: string): this;
629
- /**
630
- * Sample - Randomly select N documents
631
- */
632
- sample(size: number): this;
633
- /**
634
- * Out - Write results to a collection
635
- */
636
- out(collection: string): this;
637
- /**
638
- * Merge - Merge results into a collection
639
- */
640
- merge(options: string | {
641
- into: string;
642
- on?: string | string[];
643
- whenMatched?: string;
644
- whenNotMatched?: string;
645
- }): this;
646
- /**
647
- * GeoNear - Perform geospatial queries
648
- */
649
- geoNear(options: {
650
- near: {
651
- type: 'Point';
652
- coordinates: [number, number];
653
- };
654
- distanceField: string;
655
- maxDistance?: number;
656
- query?: Record<string, unknown>;
657
- spherical?: boolean;
658
- }): this;
659
- /**
660
- * GraphLookup - Perform recursive search (graph traversal)
661
- */
662
- graphLookup(options: {
663
- from: string;
664
- startWith: string | Expression;
665
- connectFromField: string;
666
- connectToField: string;
667
- as: string;
668
- maxDepth?: number;
669
- depthField?: string;
670
- restrictSearchWithMatch?: Record<string, unknown>;
671
- }): this;
672
- /**
673
- * $search - Atlas Search full-text search (Atlas only)
674
- *
675
- * @example
676
- * ```typescript
677
- * .search({
678
- * index: 'default',
679
- * text: {
680
- * query: 'laptop computer',
681
- * path: ['title', 'description'],
682
- * fuzzy: { maxEdits: 2 }
683
- * }
684
- * })
685
- * ```
686
- */
687
- search(options: {
688
- index?: string;
689
- text?: {
690
- query: string;
691
- path: string | string[];
692
- fuzzy?: {
693
- maxEdits?: number;
694
- prefixLength?: number;
695
- };
696
- score?: {
697
- boost?: {
698
- value?: number;
699
- };
700
- };
701
- };
702
- compound?: {
703
- must?: unknown[];
704
- mustNot?: unknown[];
705
- should?: unknown[];
706
- filter?: unknown[];
707
- };
708
- autocomplete?: unknown;
709
- near?: unknown;
710
- range?: unknown;
711
- }): this;
712
- /**
713
- * $searchMeta - Get Atlas Search metadata (Atlas only)
714
- */
715
- searchMeta(options: Record<string, unknown>): this;
716
- /**
717
- * $vectorSearch - Semantic similarity search using vector embeddings (Atlas only)
718
- *
719
- * Requires an Atlas Vector Search index on the target field.
720
- * Must be the first stage in the pipeline.
721
- *
722
- * @example
723
- * ```typescript
724
- * const results = await new AggregationBuilder()
725
- * .vectorSearch({
726
- * index: 'vector_index',
727
- * path: 'embedding',
728
- * queryVector: await getEmbedding('running shoes'),
729
- * limit: 10,
730
- * numCandidates: 100,
731
- * filter: { category: 'footwear' }
732
- * })
733
- * .project({ embedding: 0, score: { $meta: 'vectorSearchScore' } })
734
- * .exec(ProductModel);
735
- * ```
736
- */
737
- vectorSearch(options: VectorSearchOptions): this;
738
- /**
739
- * Add vectorSearchScore as a field after $vectorSearch
740
- * Convenience for `.addFields({ score: { $meta: 'vectorSearchScore' } })`
741
- */
742
- withVectorScore(fieldName?: string): this;
743
- /**
744
- * Create a builder from an existing pipeline
745
- */
746
- static from(pipeline: PipelineStage[]): AggregationBuilder;
747
- /**
748
- * Create a builder with initial match stage
749
- */
750
- static startWith(query: Record<string, unknown>): AggregationBuilder;
751
- }
752
-
753
- /**
754
- * Repository Pattern - Data Access Layer
755
- *
756
- * Event-driven, plugin-based abstraction for MongoDB operations
757
- * Inspired by Meta & Stripe's repository patterns
758
- *
759
- * @example
760
- * ```typescript
761
- * const userRepo = new Repository(UserModel, [
762
- * timestampPlugin(),
763
- * softDeletePlugin(),
764
- * ]);
765
- *
766
- * // Create
767
- * const user = await userRepo.create({ name: 'John', email: 'john@example.com' });
768
- *
769
- * // Read with pagination
770
- * const users = await userRepo.getAll({ page: 1, limit: 20, filters: { status: 'active' } });
771
- *
772
- * // Update
773
- * const updated = await userRepo.update(user._id, { name: 'John Doe' });
774
- *
775
- * // Delete
776
- * await userRepo.delete(user._id);
777
- * ```
778
- */
779
-
780
- type HookListener = (data: any) => void | Promise<void>;
781
- /**
782
- * Production-grade repository for MongoDB
783
- * Event-driven, plugin-based, with smart pagination
784
- */
785
- declare class Repository<TDoc = AnyDocument> {
786
- readonly Model: Model<TDoc>;
787
- readonly model: string;
788
- readonly _hooks: Map<string, HookListener[]>;
789
- readonly _pagination: PaginationEngine<TDoc>;
790
- private readonly _hookMode;
791
- [key: string]: unknown;
792
- constructor(Model: Model<TDoc, any, any, any>, plugins?: PluginType[], paginationConfig?: PaginationConfig, options?: RepositoryOptions);
793
- /**
794
- * Register a plugin
795
- */
796
- use(plugin: PluginType): this;
797
- /**
798
- * Register event listener
799
- */
800
- on(event: string, listener: HookListener): this;
801
- /**
802
- * Remove a specific event listener
803
- */
804
- off(event: string, listener: HookListener): this;
805
- /**
806
- * Remove all listeners for an event, or all listeners entirely
807
- */
808
- removeAllListeners(event?: string): this;
809
- /**
810
- * Emit event (sync - for backwards compatibility)
811
- */
812
- emit(event: string, data: unknown): void;
813
- /**
814
- * Emit event and await all async handlers
815
- */
816
- emitAsync(event: string, data: unknown): Promise<void>;
817
- private _emitHook;
818
- private _emitErrorHook;
819
- /**
820
- * Create single document
821
- */
822
- create(data: Record<string, unknown>, options?: {
823
- session?: ClientSession;
824
- }): Promise<TDoc>;
825
- /**
826
- * Create multiple documents
827
- */
828
- createMany(dataArray: Record<string, unknown>[], options?: {
829
- session?: ClientSession;
830
- ordered?: boolean;
831
- }): Promise<TDoc[]>;
832
- /**
833
- * Get document by ID
834
- */
835
- getById(id: string | ObjectId, options?: {
836
- select?: SelectSpec;
837
- populate?: PopulateSpec;
838
- populateOptions?: PopulateOptions[];
839
- lean?: boolean;
840
- session?: ClientSession;
841
- throwOnNotFound?: boolean;
842
- skipCache?: boolean;
843
- cacheTtl?: number;
844
- }): Promise<TDoc | null>;
845
- /**
846
- * Get single document by query
847
- */
848
- getByQuery(query: Record<string, unknown>, options?: {
849
- select?: SelectSpec;
850
- populate?: PopulateSpec;
851
- populateOptions?: PopulateOptions[];
852
- lean?: boolean;
853
- session?: ClientSession;
854
- throwOnNotFound?: boolean;
855
- skipCache?: boolean;
856
- cacheTtl?: number;
857
- }): Promise<TDoc | null>;
858
- /**
859
- * Unified pagination - auto-detects offset vs keyset based on params
860
- *
861
- * Auto-detection logic:
862
- * - If params has 'cursor' or 'after' → uses keyset pagination (stream)
863
- * - If params has 'pagination' or 'page' → uses offset pagination (paginate)
864
- * - Else → defaults to offset pagination with page=1
865
- *
866
- * @example
867
- * // Offset pagination (page-based)
868
- * await repo.getAll({ page: 1, limit: 50, filters: { status: 'active' } });
869
- * await repo.getAll({ pagination: { page: 2, limit: 20 } });
870
- *
871
- * // Keyset pagination (cursor-based)
872
- * await repo.getAll({ cursor: 'eyJ2Ij...', limit: 50 });
873
- * await repo.getAll({ after: 'eyJ2Ij...', sort: { createdAt: -1 } });
874
- *
875
- * // Simple query (defaults to page 1)
876
- * await repo.getAll({ filters: { status: 'active' } });
877
- *
878
- * // Skip cache for fresh data
879
- * await repo.getAll({ filters: { status: 'active' } }, { skipCache: true });
880
- */
881
- getAll(params?: {
882
- filters?: Record<string, unknown>;
883
- sort?: SortSpec$2 | string;
884
- cursor?: string;
885
- after?: string;
886
- page?: number;
887
- pagination?: {
888
- page?: number;
889
- limit?: number;
890
- };
891
- limit?: number;
892
- search?: string;
893
- /** Advanced populate options (from QueryParser or Arc's BaseController) */
894
- populateOptions?: PopulateOptions[];
895
- }, options?: {
896
- select?: SelectSpec;
897
- populate?: PopulateSpec;
898
- populateOptions?: PopulateOptions[];
899
- lean?: boolean;
900
- session?: ClientSession;
901
- skipCache?: boolean;
902
- cacheTtl?: number;
903
- }): Promise<OffsetPaginationResult<TDoc> | KeysetPaginationResult<TDoc>>;
904
- /**
905
- * Get or create document
906
- */
907
- getOrCreate(query: Record<string, unknown>, createData: Record<string, unknown>, options?: {
908
- session?: ClientSession;
909
- }): Promise<TDoc | null>;
910
- /**
911
- * Count documents
912
- */
913
- count(query?: Record<string, unknown>, options?: {
914
- session?: ClientSession;
915
- }): Promise<number>;
916
- /**
917
- * Check if document exists
918
- */
919
- exists(query: Record<string, unknown>, options?: {
920
- session?: ClientSession;
921
- }): Promise<{
922
- _id: unknown;
923
- } | null>;
924
- /**
925
- * Update document by ID
926
- */
927
- update(id: string | ObjectId, data: Record<string, unknown>, options?: UpdateOptions): Promise<TDoc>;
928
- /**
929
- * Delete document by ID
930
- */
931
- delete(id: string | ObjectId, options?: {
932
- session?: ClientSession;
933
- }): Promise<{
934
- success: boolean;
935
- message: string;
936
- }>;
937
- /**
938
- * Execute aggregation pipeline
939
- */
940
- aggregate<TResult = unknown>(pipeline: PipelineStage[], options?: {
941
- session?: ClientSession;
942
- }): Promise<TResult[]>;
943
- /**
944
- * Aggregate pipeline with pagination
945
- * Best for: Complex queries, grouping, joins
946
- */
947
- aggregatePaginate(options?: {
948
- pipeline?: PipelineStage[];
949
- page?: number;
950
- limit?: number;
951
- session?: ClientSession;
952
- }): Promise<AggregatePaginationResult<TDoc>>;
953
- /**
954
- * Get distinct values
955
- */
956
- distinct<T = unknown>(field: string, query?: Record<string, unknown>, options?: {
957
- session?: ClientSession;
958
- }): Promise<T[]>;
959
- /**
960
- * Query with custom field lookups ($lookup)
961
- * Best for: Joins on slugs, SKUs, codes, or other indexed custom fields
962
- *
963
- * @example
964
- * ```typescript
965
- * // Join employees with departments using slug instead of ObjectId
966
- * const employees = await employeeRepo.lookupPopulate({
967
- * filters: { status: 'active' },
968
- * lookups: [
969
- * {
970
- * from: 'departments',
971
- * localField: 'departmentSlug',
972
- * foreignField: 'slug',
973
- * as: 'department',
974
- * single: true
975
- * }
976
- * ],
977
- * sort: '-createdAt',
978
- * page: 1,
979
- * limit: 50
980
- * });
981
- * ```
982
- */
983
- lookupPopulate(options: {
984
- filters?: Record<string, unknown>;
985
- lookups: LookupOptions[];
986
- sort?: SortSpec$2 | string;
987
- page?: number;
988
- limit?: number;
989
- select?: SelectSpec;
990
- session?: ClientSession;
991
- }): Promise<{
992
- data: TDoc[];
993
- total?: number;
994
- page?: number;
995
- limit?: number;
996
- }>;
997
- /**
998
- * Create an aggregation builder for this model
999
- * Useful for building complex custom aggregations
1000
- *
1001
- * @example
1002
- * ```typescript
1003
- * const pipeline = repo.buildAggregation()
1004
- * .match({ status: 'active' })
1005
- * .lookup('departments', 'deptSlug', 'slug', 'department', true)
1006
- * .group({ _id: '$department', count: { $sum: 1 } })
1007
- * .sort({ count: -1 })
1008
- * .build();
1009
- *
1010
- * const results = await repo.Model.aggregate(pipeline);
1011
- * ```
1012
- */
1013
- buildAggregation(): AggregationBuilder;
1014
- /**
1015
- * Create a lookup builder
1016
- * Useful for building $lookup stages independently
1017
- *
1018
- * @example
1019
- * ```typescript
1020
- * const lookupStages = repo.buildLookup('departments')
1021
- * .localField('deptSlug')
1022
- * .foreignField('slug')
1023
- * .as('department')
1024
- * .single()
1025
- * .build();
1026
- *
1027
- * const pipeline = [
1028
- * { $match: { status: 'active' } },
1029
- * ...lookupStages
1030
- * ];
1031
- * ```
1032
- */
1033
- buildLookup(from?: string): LookupBuilder;
1034
- /**
1035
- * Execute callback within a transaction with automatic retry on transient failures.
1036
- *
1037
- * Uses the MongoDB driver's `session.withTransaction()` which automatically retries
1038
- * on `TransientTransactionError` and `UnknownTransactionCommitResult`.
1039
- *
1040
- * The callback always receives a `ClientSession`. When `allowFallback` is true
1041
- * and the MongoDB deployment doesn't support transactions (e.g., standalone),
1042
- * the callback runs without a transaction on the same session.
1043
- *
1044
- * @param callback - Receives a `ClientSession` to pass to repository operations
1045
- * @param options.allowFallback - Run without transaction on standalone MongoDB (default: false)
1046
- * @param options.onFallback - Called when falling back to non-transactional execution
1047
- * @param options.transactionOptions - MongoDB driver transaction options (readConcern, writeConcern, etc.)
1048
- *
1049
- * @example
1050
- * ```typescript
1051
- * const result = await repo.withTransaction(async (session) => {
1052
- * const order = await repo.create({ total: 100 }, { session });
1053
- * await paymentRepo.create({ orderId: order._id }, { session });
1054
- * return order;
1055
- * });
1056
- *
1057
- * // With fallback for standalone/dev environments
1058
- * await repo.withTransaction(callback, {
1059
- * allowFallback: true,
1060
- * onFallback: (err) => logger.warn('Running without transaction', err),
1061
- * });
1062
- * ```
1063
- */
1064
- withTransaction<T>(callback: (session: ClientSession) => Promise<T>, options?: WithTransactionOptions): Promise<T>;
1065
- private _isTransactionUnsupported;
1066
- /**
1067
- * Execute custom query with event emission
1068
- */
1069
- _executeQuery<T>(buildQuery: (Model: Model<TDoc>) => Promise<T>): Promise<T>;
1070
- /**
1071
- * Build operation context and run before hooks
1072
- */
1073
- _buildContext(operation: string, options: Record<string, unknown>): Promise<RepositoryContext>;
1074
- /**
1075
- * Parse sort string or object
1076
- */
1077
- _parseSort(sort: SortSpec$2 | string | undefined): SortSpec$2;
1078
- /**
1079
- * Parse populate specification
1080
- */
1081
- _parsePopulate(populate: PopulateSpec | undefined): string[] | PopulateOptions[];
1082
- /**
1083
- * Handle errors with proper HTTP status codes
1084
- */
1085
- _handleError(error: Error): HttpError;
1086
- }
1087
-
1088
- /**
1089
- * Modern Query Parser - URL to MongoDB Query Transpiler
1090
- *
1091
- * Next-generation query parser that converts URL parameters to MongoDB aggregation pipelines.
1092
- * Smarter than Prisma/tRPC for MongoDB with support for:
1093
- * - Custom field lookups ($lookup)
1094
- * - Complex filtering with operators
1095
- * - Full-text search
1096
- * - Aggregations via URL
1097
- * - Security hardening
1098
- *
1099
- * @example
1100
- * ```typescript
1101
- * // Simple usage
1102
- * const parser = new QueryParser();
1103
- * const query = parser.parse(req.query);
1104
- *
1105
- * // URL: ?status=active&lookup[department]=slug&sort=-createdAt&page=1&limit=20
1106
- * // Result: Complete MongoDB query with $lookup, filters, sort, pagination
1107
- * ```
1108
- *
1109
- * ## SECURITY CONSIDERATIONS FOR PRODUCTION
1110
- *
1111
- * ### Aggregation Security (enableAggregations option)
1112
- *
1113
- * **IMPORTANT:** The `enableAggregations` option exposes powerful MongoDB aggregation
1114
- * pipeline capabilities via URL parameters. While this feature includes sanitization
1115
- * (blocks $where, $function, $accumulator), it should be used with caution:
1116
- *
1117
- * **Recommended security practices:**
1118
- * 1. **Disable by default for public endpoints:**
1119
- * ```typescript
1120
- * const parser = new QueryParser({
1121
- * enableAggregations: false // Default: disabled
1122
- * });
1123
- * ```
1124
- *
1125
- * 2. **Use per-route allowlists for trusted clients:**
1126
- * ```typescript
1127
- * // Admin/internal routes only
1128
- * if (req.user?.role === 'admin') {
1129
- * const allowedStages = ['$match', '$project', '$sort', '$limit'];
1130
- * // Validate aggregate parameter against allowlist
1131
- * }
1132
- * ```
1133
- *
1134
- * 3. **Validate stage structure:** Even with sanitization, complex pipelines can
1135
- * cause performance issues. Consider limiting:
1136
- * - Number of pipeline stages (e.g., max 5)
1137
- * - Specific allowed operators per stage
1138
- * - Allowed fields in $project/$match
1139
- *
1140
- * 4. **Monitor resource usage:** Aggregation pipelines can be expensive.
1141
- * Use MongoDB profiling to track slow operations.
1142
- *
1143
- * ### Lookup Security
1144
- *
1145
- * Lookup pipelines are sanitized by default:
1146
- * - Dangerous stages blocked ($out, $merge, $unionWith, $collStats, $currentOp, $listSessions)
1147
- * - Dangerous operators blocked inside $match/$addFields/$set ($where, $function, $accumulator, $expr)
1148
- * - Optional collection whitelist via `allowedLookupCollections`
1149
- * For maximum security, use per-collection field allowlists in your controller layer.
1150
- *
1151
- * ### Filter Security
1152
- *
1153
- * All filters are sanitized:
1154
- * - Dangerous operators blocked ($where, $function, $accumulator, $expr)
1155
- * - Regex patterns validated (ReDoS protection)
1156
- * - Max filter depth enforced (prevents filter bombs)
1157
- * - Max limit enforced (prevents resource exhaustion)
1158
- *
1159
- * @see {@link https://github.com/classytic/mongokit/blob/main/docs/SECURITY.md}
1160
- */
1161
-
1162
- type SortSpec = Record<string, 1 | -1>;
1163
- type FilterQuery = Record<string, unknown>;
1164
- /**
1165
- * Mongoose-compatible populate option
1166
- * Supports advanced populate with select, match, limit, sort, and nested populate
1167
- *
1168
- * @example
1169
- * ```typescript
1170
- * // URL: ?populate[author][select]=name,email&populate[author][match][active]=true
1171
- * // Generates: { path: 'author', select: 'name email', match: { active: true } }
1172
- * ```
1173
- */
1174
- interface PopulateOption {
1175
- /** Field path to populate */
1176
- path: string;
1177
- /** Fields to select (space-separated) */
1178
- select?: string;
1179
- /** Filter conditions for populated documents */
1180
- match?: Record<string, unknown>;
1181
- /** Query options (limit, sort, skip) */
1182
- options?: {
1183
- limit?: number;
1184
- sort?: SortSpec;
1185
- skip?: number;
1186
- };
1187
- /** Nested populate configuration */
1188
- populate?: PopulateOption;
1189
- }
1190
- /** Parsed query result with optional lookup configuration */
1191
- interface ParsedQuery {
1192
- /** MongoDB filter query */
1193
- filters: FilterQuery;
1194
- /** Sort specification */
1195
- sort?: SortSpec;
1196
- /** Fields to populate (simple comma-separated string) */
1197
- populate?: string;
1198
- /**
1199
- * Advanced populate options (Mongoose-compatible)
1200
- * When this is set, `populate` will be undefined
1201
- * @example [{ path: 'author', select: 'name email' }]
1202
- */
1203
- populateOptions?: PopulateOption[];
1204
- /** Page number for offset pagination */
1205
- page?: number;
1206
- /** Cursor for keyset pagination */
1207
- after?: string;
1208
- /** Limit */
1209
- limit: number;
1210
- /** Full-text search query */
1211
- search?: string;
1212
- /** Lookup configurations for custom field joins */
1213
- lookups?: LookupOptions[];
1214
- /** Aggregation pipeline stages (advanced) */
1215
- aggregation?: PipelineStage[];
1216
- /** Select/project fields */
1217
- select?: Record<string, 0 | 1>;
1218
- }
1219
- /** Search mode for query parser */
1220
- type SearchMode = 'text' | 'regex';
1221
- interface QueryParserOptions {
1222
- /** Maximum allowed regex pattern length (default: 500) */
1223
- maxRegexLength?: number;
1224
- /** Maximum allowed text search query length (default: 200) */
1225
- maxSearchLength?: number;
1226
- /** Maximum allowed filter depth (default: 10) */
1227
- maxFilterDepth?: number;
1228
- /** Maximum allowed limit value (default: 1000) */
1229
- maxLimit?: number;
1230
- /** Additional operators to block */
1231
- additionalDangerousOperators?: string[];
1232
- /** Enable lookup parsing (default: true) */
1233
- enableLookups?: boolean;
1234
- /** Enable aggregation parsing (default: false - requires explicit opt-in) */
1235
- enableAggregations?: boolean;
1236
- /**
1237
- * Search mode (default: 'text')
1238
- * - 'text': Uses MongoDB $text search (requires text index)
1239
- * - 'regex': Uses $or with $regex across searchFields (no index required)
1240
- */
1241
- searchMode?: SearchMode;
1242
- /**
1243
- * Fields to search when searchMode is 'regex'
1244
- * Required when searchMode is 'regex'
1245
- * @example ['name', 'description', 'sku', 'tags']
1246
- */
1247
- searchFields?: string[];
1248
- /**
1249
- * Whitelist of collection names allowed in lookups.
1250
- * When set, only these collections can be used in $lookup stages.
1251
- * When undefined, all collection names are allowed.
1252
- * @example ['departments', 'categories', 'users']
1253
- */
1254
- allowedLookupCollections?: string[];
1255
- }
1256
- /**
1257
- * Modern Query Parser
1258
- * Converts URL parameters to MongoDB queries with $lookup support
1259
- */
1260
- declare class QueryParser {
1261
- private readonly options;
1262
- private readonly operators;
1263
- private readonly dangerousOperators;
1264
- /**
1265
- * Regex patterns that can cause catastrophic backtracking (ReDoS attacks)
1266
- * Detects:
1267
- * - Quantifiers: {n,m}
1268
- * - Possessive quantifiers: *+, ++, ?+
1269
- * - Nested quantifiers: (a+)+, (a*)*
1270
- * - Backreferences: \1, \2, etc.
1271
- * - Complex character classes: [...]...[...]
1272
- */
1273
- private readonly dangerousRegexPatterns;
1274
- constructor(options?: QueryParserOptions);
1275
- /**
1276
- * Parse URL query parameters into MongoDB query format
1277
- *
1278
- * @example
1279
- * ```typescript
1280
- * // URL: ?status=active&lookup[department][foreignField]=slug&sort=-createdAt&page=1
1281
- * const query = parser.parse(req.query);
1282
- * // Returns: { filters: {...}, lookups: [...], sort: {...}, page: 1 }
1283
- * ```
1284
- */
1285
- parse(query: Record<string, unknown> | null | undefined): ParsedQuery;
1286
- /**
1287
- * Parse lookup configurations from URL parameters
1288
- *
1289
- * Supported formats:
1290
- * 1. Simple: ?lookup[department]=slug
1291
- * → Join with 'departments' collection on slug field
1292
- *
1293
- * 2. Detailed: ?lookup[department][localField]=deptSlug&lookup[department][foreignField]=slug
1294
- * → Full control over join configuration
1295
- *
1296
- * 3. Multiple: ?lookup[department]=slug&lookup[category]=categorySlug
1297
- * → Multiple lookups
1298
- *
1299
- * @example
1300
- * ```typescript
1301
- * // URL: ?lookup[department][localField]=deptSlug&lookup[department][foreignField]=slug&lookup[department][single]=true
1302
- * const lookups = parser._parseLookups({
1303
- * department: { localField: 'deptSlug', foreignField: 'slug', single: 'true' }
1304
- * });
1305
- * // Returns: [{ from: 'departments', localField: 'deptSlug', foreignField: 'slug', single: true }]
1306
- * ```
1307
- */
1308
- private _parseLookups;
1309
- /**
1310
- * Parse a single lookup configuration
1311
- */
1312
- private _parseSingleLookup;
1313
- /**
1314
- * Parse aggregation pipeline from URL (advanced feature)
1315
- *
1316
- * @example
1317
- * ```typescript
1318
- * // URL: ?aggregate[group][_id]=$status&aggregate[group][count]=$sum:1
1319
- * const pipeline = parser._parseAggregation({
1320
- * group: { _id: '$status', count: '$sum:1' }
1321
- * });
1322
- * ```
1323
- */
1324
- private _parseAggregation;
1325
- /**
1326
- * Parse select/project fields
1327
- *
1328
- * @example
1329
- * ```typescript
1330
- * // URL: ?select=name,email,-password
1331
- * // Returns: { name: 1, email: 1, password: 0 }
1332
- * ```
1333
- */
1334
- private _parseSelect;
1335
- /**
1336
- * Parse populate parameter - handles both simple string and advanced object format
1337
- *
1338
- * @example
1339
- * ```typescript
1340
- * // Simple: ?populate=author,category
1341
- * // Returns: { simplePopulate: 'author,category', populateOptions: undefined }
1342
- *
1343
- * // Advanced: ?populate[author][select]=name,email
1344
- * // Returns: { simplePopulate: undefined, populateOptions: [{ path: 'author', select: 'name email' }] }
1345
- * ```
1346
- */
1347
- private _parsePopulate;
1348
- /**
1349
- * Parse a single populate configuration
1350
- */
1351
- private _parseSinglePopulate;
1352
- /**
1353
- * Convert populate match values (handles boolean strings, etc.)
1354
- */
1355
- private _convertPopulateMatch;
1356
- /**
1357
- * Parse filter parameters
1358
- */
1359
- private _parseFilters;
1360
- /**
1361
- * Handle operator syntax: field[operator]=value
1362
- */
1363
- private _handleOperatorSyntax;
1364
- /**
1365
- * Handle bracket syntax with object value
1366
- */
1367
- private _handleBracketSyntax;
1368
- private _parseSort;
1369
- private _toMongoOperator;
1370
- private _createSafeRegex;
1371
- private _escapeRegex;
1372
- /**
1373
- * Sanitize $match configuration to prevent dangerous operators
1374
- * Recursively filters out operators like $where, $function, $accumulator
1375
- */
1376
- private _sanitizeMatchConfig;
1377
- /**
1378
- * Sanitize pipeline stages for use in $lookup.
1379
- * Blocks dangerous stages ($out, $merge, etc.) and recursively sanitizes
1380
- * operator expressions within $match, $addFields, and $set stages.
1381
- */
1382
- private _sanitizePipeline;
1383
- /**
1384
- * Recursively sanitize expression objects, blocking dangerous operators
1385
- * like $where, $function, $accumulator inside $addFields/$set stages.
1386
- */
1387
- private _sanitizeExpressions;
1388
- private _sanitizeSearch;
1389
- /**
1390
- * Build regex-based multi-field search filters
1391
- * Creates an $or query with case-insensitive regex across all searchFields
1392
- *
1393
- * @example
1394
- * // searchFields: ['name', 'description', 'sku']
1395
- * // search: 'azure'
1396
- * // Returns: [
1397
- * // { name: { $regex: /azure/i } },
1398
- * // { description: { $regex: /azure/i } },
1399
- * // { sku: { $regex: /azure/i } }
1400
- * // ]
1401
- */
1402
- private _buildRegexSearch;
1403
- private _convertValue;
1404
- private _parseOr;
1405
- private _enhanceWithBetween;
1406
- private _pluralize;
1407
- private _capitalize;
1408
- }
1409
-
1410
- /**
1411
- * Factory function to create a repository instance
1412
- *
1413
- * @param Model - Mongoose model
1414
- * @param plugins - Array of plugins to apply
1415
- * @returns Repository instance
1416
- *
1417
- * @example
1418
- * const userRepo = createRepository(UserModel, [timestampPlugin()]);
1419
- */
1420
- declare function createRepository<TDoc>(Model: mongoose.Model<TDoc, any, any, any>, plugins?: PluginType[], paginationConfig?: PaginationConfig, options?: RepositoryOptions): Repository<TDoc>;
1421
-
1422
- export { AggregatePaginationResult, AggregationBuilder, AnyDocument, type FilterQuery, HttpError, type IController, type IControllerResponse, type IRequestContext, type IResponseFormatter, KeysetPaginationResult, LookupBuilder, LookupOptions, ObjectId, OffsetPaginationResult, PaginationConfig, PaginationEngine, PaginationResult, type ParsedQuery, PluginType, type PopulateOption, PopulateSpec, QueryParser, type QueryParserOptions, Repository, RepositoryContext, RepositoryOptions, type SearchMode, SelectSpec, SortSpec$2 as SortSpec, UpdateOptions, WithTransactionOptions, createRepository, Repository as default };