@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.
- package/README.md +470 -193
- package/dist/actions/index.d.mts +9 -0
- package/dist/actions/index.mjs +15 -0
- package/dist/aggregate-BAi4Do-X.mjs +767 -0
- package/dist/aggregate-CCHI7F51.d.mts +269 -0
- package/dist/ai/index.d.mts +125 -0
- package/dist/ai/index.mjs +203 -0
- package/dist/cache-keys-C8Z9B5sw.mjs +204 -0
- package/dist/chunk-DQk6qfdC.mjs +18 -0
- package/dist/create-BuO6xt0v.mjs +55 -0
- package/dist/custom-id.plugin-B_zIs6gE.mjs +1818 -0
- package/dist/custom-id.plugin-BzZI4gnE.d.mts +893 -0
- package/dist/index.d.mts +1012 -0
- package/dist/index.mjs +1906 -0
- package/dist/limits-DsNeCx4D.mjs +299 -0
- package/dist/logger-D8ily-PP.mjs +51 -0
- package/dist/mongooseToJsonSchema-COdDEkIJ.mjs +317 -0
- package/dist/{mongooseToJsonSchema-CaRF_bCN.d.ts → mongooseToJsonSchema-Wbvjfwkn.d.mts} +16 -89
- package/dist/pagination/PaginationEngine.d.mts +93 -0
- package/dist/pagination/PaginationEngine.mjs +196 -0
- package/dist/plugins/index.d.mts +3 -0
- package/dist/plugins/index.mjs +3 -0
- package/dist/types-D-gploPr.d.mts +1241 -0
- package/dist/utils/{index.d.ts → index.d.mts} +14 -21
- package/dist/utils/index.mjs +5 -0
- package/package.json +21 -21
- package/dist/actions/index.d.ts +0 -3
- package/dist/actions/index.js +0 -5
- package/dist/ai/index.d.ts +0 -175
- package/dist/ai/index.js +0 -206
- package/dist/chunks/chunk-2ZN65ZOP.js +0 -93
- package/dist/chunks/chunk-44KXLGPO.js +0 -388
- package/dist/chunks/chunk-DEVXDBRL.js +0 -1226
- package/dist/chunks/chunk-I7CWNAJB.js +0 -46
- package/dist/chunks/chunk-JWUAVZ3L.js +0 -8
- package/dist/chunks/chunk-UE2IEXZJ.js +0 -306
- package/dist/chunks/chunk-URLJFIR7.js +0 -22
- package/dist/chunks/chunk-VWKIKZYF.js +0 -737
- package/dist/chunks/chunk-WSFCRVEQ.js +0 -7
- package/dist/index-BDn5fSTE.d.ts +0 -516
- package/dist/index.d.ts +0 -1422
- package/dist/index.js +0 -1893
- package/dist/pagination/PaginationEngine.d.ts +0 -117
- package/dist/pagination/PaginationEngine.js +0 -3
- package/dist/plugins/index.d.ts +0 -922
- package/dist/plugins/index.js +0 -6
- package/dist/types-Jni1KgkP.d.ts +0 -780
- 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 };
|