@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
@@ -1,737 +0,0 @@
1
- import { create_exports } from './chunk-I7CWNAJB.js';
2
- import { warn } from './chunk-URLJFIR7.js';
3
- import { createError } from './chunk-JWUAVZ3L.js';
4
- import { __export } from './chunk-WSFCRVEQ.js';
5
-
6
- // src/actions/index.ts
7
- var actions_exports = {};
8
- __export(actions_exports, {
9
- aggregate: () => aggregate_exports,
10
- create: () => create_exports,
11
- deleteActions: () => delete_exports,
12
- read: () => read_exports,
13
- update: () => update_exports
14
- });
15
-
16
- // src/actions/read.ts
17
- var read_exports = {};
18
- __export(read_exports, {
19
- count: () => count,
20
- exists: () => exists,
21
- getAll: () => getAll,
22
- getById: () => getById,
23
- getByQuery: () => getByQuery,
24
- getOrCreate: () => getOrCreate,
25
- tryGetByQuery: () => tryGetByQuery
26
- });
27
- function parsePopulate(populate) {
28
- if (!populate) return [];
29
- if (typeof populate === "string") {
30
- return populate.split(",").map((p) => p.trim());
31
- }
32
- if (Array.isArray(populate)) {
33
- return populate.map((p) => typeof p === "string" ? p.trim() : p);
34
- }
35
- return [populate];
36
- }
37
- async function getById(Model, id, options = {}) {
38
- const query = options.query ? Model.findOne({ _id: id, ...options.query }) : Model.findById(id);
39
- if (options.select) query.select(options.select);
40
- if (options.populate) query.populate(parsePopulate(options.populate));
41
- if (options.lean) query.lean();
42
- if (options.session) query.session(options.session);
43
- const document = await query.exec();
44
- if (!document && options.throwOnNotFound !== false) {
45
- throw createError(404, "Document not found");
46
- }
47
- return document;
48
- }
49
- async function getByQuery(Model, query, options = {}) {
50
- const mongoQuery = Model.findOne(query);
51
- if (options.select) mongoQuery.select(options.select);
52
- if (options.populate) mongoQuery.populate(parsePopulate(options.populate));
53
- if (options.lean) mongoQuery.lean();
54
- if (options.session) mongoQuery.session(options.session);
55
- const document = await mongoQuery.exec();
56
- if (!document && options.throwOnNotFound !== false) {
57
- throw createError(404, "Document not found");
58
- }
59
- return document;
60
- }
61
- async function tryGetByQuery(Model, query, options = {}) {
62
- return getByQuery(Model, query, { ...options, throwOnNotFound: false });
63
- }
64
- async function getAll(Model, query = {}, options = {}) {
65
- let mongoQuery = Model.find(query);
66
- if (options.select) mongoQuery = mongoQuery.select(options.select);
67
- if (options.populate) mongoQuery = mongoQuery.populate(parsePopulate(options.populate));
68
- if (options.sort) mongoQuery = mongoQuery.sort(options.sort);
69
- if (options.limit) mongoQuery = mongoQuery.limit(options.limit);
70
- if (options.skip) mongoQuery = mongoQuery.skip(options.skip);
71
- mongoQuery = mongoQuery.lean(options.lean !== false);
72
- if (options.session) mongoQuery = mongoQuery.session(options.session);
73
- return mongoQuery.exec();
74
- }
75
- async function getOrCreate(Model, query, createData, options = {}) {
76
- return Model.findOneAndUpdate(
77
- query,
78
- { $setOnInsert: createData },
79
- {
80
- upsert: true,
81
- new: true,
82
- runValidators: true,
83
- session: options.session,
84
- ...options.updatePipeline !== void 0 ? { updatePipeline: options.updatePipeline } : {}
85
- }
86
- );
87
- }
88
- async function count(Model, query = {}, options = {}) {
89
- return Model.countDocuments(query).session(options.session ?? null);
90
- }
91
- async function exists(Model, query, options = {}) {
92
- return Model.exists(query).session(options.session ?? null);
93
- }
94
-
95
- // src/actions/update.ts
96
- var update_exports = {};
97
- __export(update_exports, {
98
- increment: () => increment,
99
- pullFromArray: () => pullFromArray,
100
- pushToArray: () => pushToArray,
101
- update: () => update,
102
- updateByQuery: () => updateByQuery,
103
- updateMany: () => updateMany,
104
- updateWithConstraints: () => updateWithConstraints,
105
- updateWithValidation: () => updateWithValidation
106
- });
107
- function assertUpdatePipelineAllowed(update2, updatePipeline) {
108
- if (Array.isArray(update2) && updatePipeline !== true) {
109
- throw createError(
110
- 400,
111
- "Update pipelines (array updates) are disabled by default; pass `{ updatePipeline: true }` to explicitly allow pipeline-style updates."
112
- );
113
- }
114
- }
115
- function parsePopulate2(populate) {
116
- if (!populate) return [];
117
- if (typeof populate === "string") {
118
- return populate.split(",").map((p) => p.trim());
119
- }
120
- if (Array.isArray(populate)) {
121
- return populate.map((p) => typeof p === "string" ? p.trim() : p);
122
- }
123
- return [populate];
124
- }
125
- async function update(Model, id, data, options = {}) {
126
- assertUpdatePipelineAllowed(data, options.updatePipeline);
127
- const query = { _id: id, ...options.query };
128
- const document = await Model.findOneAndUpdate(query, data, {
129
- new: true,
130
- runValidators: true,
131
- session: options.session,
132
- ...options.updatePipeline !== void 0 ? { updatePipeline: options.updatePipeline } : {}
133
- }).select(options.select || "").populate(parsePopulate2(options.populate)).lean(options.lean ?? false);
134
- if (!document) {
135
- throw createError(404, "Document not found");
136
- }
137
- return document;
138
- }
139
- async function updateWithConstraints(Model, id, data, constraints = {}, options = {}) {
140
- assertUpdatePipelineAllowed(data, options.updatePipeline);
141
- const query = { _id: id, ...constraints };
142
- const document = await Model.findOneAndUpdate(query, data, {
143
- new: true,
144
- runValidators: true,
145
- session: options.session,
146
- ...options.updatePipeline !== void 0 ? { updatePipeline: options.updatePipeline } : {}
147
- }).select(options.select || "").populate(parsePopulate2(options.populate)).lean(options.lean ?? false);
148
- return document;
149
- }
150
- async function updateWithValidation(Model, id, data, validationOptions = {}, options = {}) {
151
- const { buildConstraints, validateUpdate } = validationOptions;
152
- assertUpdatePipelineAllowed(data, options.updatePipeline);
153
- if (buildConstraints) {
154
- const constraints = buildConstraints(data);
155
- const document = await updateWithConstraints(Model, id, data, constraints, options);
156
- if (document) {
157
- return { success: true, data: document };
158
- }
159
- }
160
- const existing = await Model.findById(id).select(options.select || "").lean();
161
- if (!existing) {
162
- return {
163
- success: false,
164
- error: {
165
- code: 404,
166
- message: "Document not found"
167
- }
168
- };
169
- }
170
- if (validateUpdate) {
171
- const validation = validateUpdate(existing, data);
172
- if (!validation.valid) {
173
- return {
174
- success: false,
175
- error: {
176
- code: 403,
177
- message: validation.message || "Update not allowed",
178
- violations: validation.violations
179
- }
180
- };
181
- }
182
- }
183
- const updated = await update(Model, id, data, options);
184
- return { success: true, data: updated };
185
- }
186
- async function updateMany(Model, query, data, options = {}) {
187
- assertUpdatePipelineAllowed(data, options.updatePipeline);
188
- const result = await Model.updateMany(query, data, {
189
- runValidators: true,
190
- session: options.session,
191
- ...options.updatePipeline !== void 0 ? { updatePipeline: options.updatePipeline } : {}
192
- });
193
- return {
194
- matchedCount: result.matchedCount,
195
- modifiedCount: result.modifiedCount
196
- };
197
- }
198
- async function updateByQuery(Model, query, data, options = {}) {
199
- assertUpdatePipelineAllowed(data, options.updatePipeline);
200
- const document = await Model.findOneAndUpdate(query, data, {
201
- new: true,
202
- runValidators: true,
203
- session: options.session,
204
- ...options.updatePipeline !== void 0 ? { updatePipeline: options.updatePipeline } : {}
205
- }).select(options.select || "").populate(parsePopulate2(options.populate)).lean(options.lean ?? false);
206
- if (!document && options.throwOnNotFound !== false) {
207
- throw createError(404, "Document not found");
208
- }
209
- return document;
210
- }
211
- async function increment(Model, id, field, value = 1, options = {}) {
212
- return update(Model, id, { $inc: { [field]: value } }, options);
213
- }
214
- async function pushToArray(Model, id, field, value, options = {}) {
215
- return update(Model, id, { $push: { [field]: value } }, options);
216
- }
217
- async function pullFromArray(Model, id, field, value, options = {}) {
218
- return update(Model, id, { $pull: { [field]: value } }, options);
219
- }
220
-
221
- // src/actions/delete.ts
222
- var delete_exports = {};
223
- __export(delete_exports, {
224
- deleteById: () => deleteById,
225
- deleteByQuery: () => deleteByQuery,
226
- deleteMany: () => deleteMany,
227
- restore: () => restore,
228
- softDelete: () => softDelete
229
- });
230
- async function deleteById(Model, id, options = {}) {
231
- const query = { _id: id, ...options.query };
232
- const document = await Model.findOneAndDelete(query).session(options.session ?? null);
233
- if (!document) {
234
- throw createError(404, "Document not found");
235
- }
236
- return { success: true, message: "Deleted successfully" };
237
- }
238
- async function deleteMany(Model, query, options = {}) {
239
- const result = await Model.deleteMany(query).session(options.session ?? null);
240
- return {
241
- success: true,
242
- count: result.deletedCount,
243
- message: "Deleted successfully"
244
- };
245
- }
246
- async function deleteByQuery(Model, query, options = {}) {
247
- const document = await Model.findOneAndDelete(query).session(options.session ?? null);
248
- if (!document && options.throwOnNotFound !== false) {
249
- throw createError(404, "Document not found");
250
- }
251
- return { success: true, message: "Deleted successfully" };
252
- }
253
- async function softDelete(Model, id, options = {}) {
254
- const document = await Model.findByIdAndUpdate(
255
- id,
256
- {
257
- deleted: true,
258
- deletedAt: /* @__PURE__ */ new Date(),
259
- deletedBy: options.userId
260
- },
261
- { new: true, session: options.session }
262
- );
263
- if (!document) {
264
- throw createError(404, "Document not found");
265
- }
266
- return { success: true, message: "Soft deleted successfully" };
267
- }
268
- async function restore(Model, id, options = {}) {
269
- const document = await Model.findByIdAndUpdate(
270
- id,
271
- {
272
- deleted: false,
273
- deletedAt: null,
274
- deletedBy: null
275
- },
276
- { new: true, session: options.session }
277
- );
278
- if (!document) {
279
- throw createError(404, "Document not found");
280
- }
281
- return { success: true, message: "Restored successfully" };
282
- }
283
-
284
- // src/actions/aggregate.ts
285
- var aggregate_exports = {};
286
- __export(aggregate_exports, {
287
- aggregate: () => aggregate,
288
- aggregatePaginate: () => aggregatePaginate,
289
- average: () => average,
290
- countBy: () => countBy,
291
- distinct: () => distinct,
292
- facet: () => facet,
293
- groupBy: () => groupBy,
294
- lookup: () => lookup,
295
- minMax: () => minMax,
296
- sum: () => sum,
297
- unwind: () => unwind
298
- });
299
-
300
- // src/query/LookupBuilder.ts
301
- var BLOCKED_PIPELINE_STAGES = ["$out", "$merge", "$unionWith", "$collStats", "$currentOp", "$listSessions"];
302
- var DANGEROUS_OPERATORS = ["$where", "$function", "$accumulator", "$expr"];
303
- var LookupBuilder = class _LookupBuilder {
304
- options = {};
305
- constructor(from) {
306
- if (from) this.options.from = from;
307
- }
308
- /**
309
- * Set the collection to join with
310
- */
311
- from(collection) {
312
- this.options.from = collection;
313
- return this;
314
- }
315
- /**
316
- * Set the local field (source collection)
317
- * IMPORTANT: This field should be indexed for optimal performance
318
- */
319
- localField(field) {
320
- this.options.localField = field;
321
- return this;
322
- }
323
- /**
324
- * Set the foreign field (target collection)
325
- * IMPORTANT: This field should be indexed (preferably unique) for optimal performance
326
- */
327
- foreignField(field) {
328
- this.options.foreignField = field;
329
- return this;
330
- }
331
- /**
332
- * Set the output field name
333
- * Defaults to the collection name if not specified
334
- */
335
- as(fieldName) {
336
- this.options.as = fieldName;
337
- return this;
338
- }
339
- /**
340
- * Mark this lookup as returning a single document
341
- * Automatically unwraps the array result to a single object or null
342
- */
343
- single(isSingle = true) {
344
- this.options.single = isSingle;
345
- return this;
346
- }
347
- /**
348
- * Add a pipeline to filter/transform joined documents
349
- * Useful for filtering, sorting, or limiting joined results
350
- *
351
- * @example
352
- * ```typescript
353
- * lookup.pipeline([
354
- * { $match: { status: 'active' } },
355
- * { $sort: { priority: -1 } },
356
- * { $limit: 5 }
357
- * ]);
358
- * ```
359
- */
360
- pipeline(stages) {
361
- this.options.pipeline = stages;
362
- return this;
363
- }
364
- /**
365
- * Set let variables for use in pipeline
366
- * Allows referencing local document fields in the pipeline
367
- */
368
- let(variables) {
369
- this.options.let = variables;
370
- return this;
371
- }
372
- /**
373
- * Build the $lookup aggregation stage(s)
374
- * Returns an array of pipeline stages including $lookup and optional $unwind
375
- *
376
- * IMPORTANT: MongoDB $lookup has two mutually exclusive forms:
377
- * 1. Simple form: { from, localField, foreignField, as }
378
- * 2. Pipeline form: { from, let, pipeline, as }
379
- *
380
- * When pipeline or let is specified, we use the pipeline form.
381
- * Otherwise, we use the simpler localField/foreignField form.
382
- */
383
- build() {
384
- const { from, localField, foreignField, as, single, pipeline, let: letVars } = this.options;
385
- if (!from) {
386
- throw new Error('LookupBuilder: "from" collection is required');
387
- }
388
- const outputField = as || from;
389
- const stages = [];
390
- const usePipelineForm = pipeline || letVars;
391
- let lookupStage;
392
- if (usePipelineForm) {
393
- if (!pipeline || pipeline.length === 0) {
394
- if (!localField || !foreignField) {
395
- throw new Error(
396
- "LookupBuilder: When using pipeline form without a custom pipeline, both localField and foreignField are required to auto-generate the pipeline"
397
- );
398
- }
399
- const autoPipeline = [
400
- {
401
- $match: {
402
- $expr: {
403
- $eq: [`$${foreignField}`, `$$${localField}`]
404
- }
405
- }
406
- }
407
- ];
408
- lookupStage = {
409
- $lookup: {
410
- from,
411
- let: { [localField]: `$${localField}`, ...letVars || {} },
412
- pipeline: autoPipeline,
413
- as: outputField
414
- }
415
- };
416
- } else {
417
- const safePipeline = this.options.sanitize !== false ? _LookupBuilder.sanitizePipeline(pipeline) : pipeline;
418
- lookupStage = {
419
- $lookup: {
420
- from,
421
- ...letVars && { let: letVars },
422
- pipeline: safePipeline,
423
- as: outputField
424
- }
425
- };
426
- }
427
- } else {
428
- if (!localField || !foreignField) {
429
- throw new Error("LookupBuilder: localField and foreignField are required for simple lookup");
430
- }
431
- lookupStage = {
432
- $lookup: {
433
- from,
434
- localField,
435
- foreignField,
436
- as: outputField
437
- }
438
- };
439
- }
440
- stages.push(lookupStage);
441
- if (single) {
442
- stages.push({
443
- $unwind: {
444
- path: `$${outputField}`,
445
- preserveNullAndEmptyArrays: true
446
- // Keep documents even if no match found
447
- }
448
- });
449
- }
450
- return stages;
451
- }
452
- /**
453
- * Build and return only the $lookup stage (without $unwind)
454
- * Useful when you want to handle unwrapping yourself
455
- */
456
- buildLookupOnly() {
457
- const stages = this.build();
458
- return stages[0];
459
- }
460
- /**
461
- * Static helper: Create a simple lookup in one line
462
- */
463
- static simple(from, localField, foreignField, options = {}) {
464
- return new _LookupBuilder(from).localField(localField).foreignField(foreignField).as(options.as || from).single(options.single || false).build();
465
- }
466
- /**
467
- * Static helper: Create multiple lookups at once
468
- *
469
- * @example
470
- * ```typescript
471
- * const pipeline = LookupBuilder.multiple([
472
- * { from: 'departments', localField: 'deptSlug', foreignField: 'slug', single: true },
473
- * { from: 'managers', localField: 'managerId', foreignField: '_id', single: true }
474
- * ]);
475
- * ```
476
- */
477
- static multiple(lookups) {
478
- return lookups.flatMap((lookup2) => {
479
- const builder = new _LookupBuilder(lookup2.from).localField(lookup2.localField).foreignField(lookup2.foreignField);
480
- if (lookup2.as) builder.as(lookup2.as);
481
- if (lookup2.single) builder.single(lookup2.single);
482
- if (lookup2.pipeline) builder.pipeline(lookup2.pipeline);
483
- if (lookup2.let) builder.let(lookup2.let);
484
- return builder.build();
485
- });
486
- }
487
- /**
488
- * Static helper: Create a nested lookup (lookup within lookup)
489
- * Useful for multi-level joins like Order -> Product -> Category
490
- *
491
- * @example
492
- * ```typescript
493
- * // Join orders with products, then products with categories
494
- * const pipeline = LookupBuilder.nested([
495
- * { from: 'products', localField: 'productSku', foreignField: 'sku', as: 'product', single: true },
496
- * { from: 'categories', localField: 'product.categorySlug', foreignField: 'slug', as: 'product.category', single: true }
497
- * ]);
498
- * ```
499
- */
500
- static nested(lookups) {
501
- return lookups.flatMap((lookup2, index) => {
502
- const builder = new _LookupBuilder(lookup2.from).localField(lookup2.localField).foreignField(lookup2.foreignField);
503
- if (lookup2.as) builder.as(lookup2.as);
504
- if (lookup2.single !== void 0) builder.single(lookup2.single);
505
- if (lookup2.pipeline) builder.pipeline(lookup2.pipeline);
506
- if (lookup2.let) builder.let(lookup2.let);
507
- return builder.build();
508
- });
509
- }
510
- /**
511
- * Sanitize pipeline stages by blocking dangerous stages and operators.
512
- * Used internally by build() and available for external use (e.g., aggregate.ts).
513
- */
514
- static sanitizePipeline(stages) {
515
- const sanitized = [];
516
- for (const stage of stages) {
517
- if (!stage || typeof stage !== "object") continue;
518
- const entries = Object.entries(stage);
519
- if (entries.length !== 1) continue;
520
- const [op, config] = entries[0];
521
- if (BLOCKED_PIPELINE_STAGES.includes(op)) {
522
- warn(`[mongokit] Blocked dangerous pipeline stage in lookup: ${op}`);
523
- continue;
524
- }
525
- if ((op === "$match" || op === "$addFields" || op === "$set") && typeof config === "object" && config !== null) {
526
- sanitized.push({ [op]: _LookupBuilder._sanitizeDeep(config) });
527
- } else {
528
- sanitized.push(stage);
529
- }
530
- }
531
- return sanitized;
532
- }
533
- /**
534
- * Recursively remove dangerous operators from an expression object.
535
- */
536
- static _sanitizeDeep(config) {
537
- const sanitized = {};
538
- for (const [key, value] of Object.entries(config)) {
539
- if (DANGEROUS_OPERATORS.includes(key)) {
540
- warn(`[mongokit] Blocked dangerous operator in lookup pipeline: ${key}`);
541
- continue;
542
- }
543
- if (value && typeof value === "object" && !Array.isArray(value)) {
544
- sanitized[key] = _LookupBuilder._sanitizeDeep(value);
545
- } else if (Array.isArray(value)) {
546
- sanitized[key] = value.map((item) => {
547
- if (item && typeof item === "object" && !Array.isArray(item)) {
548
- return _LookupBuilder._sanitizeDeep(item);
549
- }
550
- return item;
551
- });
552
- } else {
553
- sanitized[key] = value;
554
- }
555
- }
556
- return sanitized;
557
- }
558
- };
559
-
560
- // src/actions/aggregate.ts
561
- async function aggregate(Model, pipeline, options = {}) {
562
- const aggregation = Model.aggregate(pipeline);
563
- if (options.session) {
564
- aggregation.session(options.session);
565
- }
566
- return aggregation.exec();
567
- }
568
- async function aggregatePaginate(Model, pipeline, options = {}) {
569
- const page = parseInt(String(options.page || 1), 10);
570
- const limit = parseInt(String(options.limit || 10), 10);
571
- const skip = (page - 1) * limit;
572
- const SAFE_LIMIT = 1e3;
573
- if (limit > SAFE_LIMIT) {
574
- warn(
575
- `[mongokit] Large aggregation limit (${limit}). $facet results must be <16MB. Consider using Repository.aggregatePaginate() for safer handling of large datasets.`
576
- );
577
- }
578
- const facetPipeline = [
579
- ...pipeline,
580
- {
581
- $facet: {
582
- docs: [{ $skip: skip }, { $limit: limit }],
583
- total: [{ $count: "count" }]
584
- }
585
- }
586
- ];
587
- const aggregation = Model.aggregate(facetPipeline);
588
- if (options.session) {
589
- aggregation.session(options.session);
590
- }
591
- const [result] = await aggregation.exec();
592
- const docs = result.docs || [];
593
- const total = result.total[0]?.count || 0;
594
- const pages = Math.ceil(total / limit);
595
- return {
596
- docs,
597
- total,
598
- page,
599
- limit,
600
- pages,
601
- hasNext: page < pages,
602
- hasPrev: page > 1
603
- };
604
- }
605
- async function groupBy(Model, field, options = {}) {
606
- const pipeline = [
607
- { $group: { _id: `$${field}`, count: { $sum: 1 } } },
608
- { $sort: { count: -1 } }
609
- ];
610
- if (options.limit) {
611
- pipeline.push({ $limit: options.limit });
612
- }
613
- return aggregate(Model, pipeline, options);
614
- }
615
- async function countBy(Model, field, query = {}, options = {}) {
616
- const pipeline = [];
617
- if (Object.keys(query).length > 0) {
618
- pipeline.push({ $match: query });
619
- }
620
- pipeline.push(
621
- { $group: { _id: `$${field}`, count: { $sum: 1 } } },
622
- { $sort: { count: -1 } }
623
- );
624
- return aggregate(Model, pipeline, options);
625
- }
626
- async function lookup(Model, lookupOptions) {
627
- const { from, localField, foreignField, as, pipeline = [], let: letVars, query = {}, options = {} } = lookupOptions;
628
- const aggPipeline = [];
629
- if (Object.keys(query).length > 0) {
630
- aggPipeline.push({ $match: query });
631
- }
632
- const usePipelineForm = pipeline.length > 0 || letVars;
633
- if (usePipelineForm) {
634
- if (pipeline.length === 0 && localField && foreignField) {
635
- const autoPipeline = [
636
- {
637
- $match: {
638
- $expr: {
639
- $eq: [`$${foreignField}`, `$$${localField}`]
640
- }
641
- }
642
- }
643
- ];
644
- aggPipeline.push({
645
- $lookup: {
646
- from,
647
- let: { [localField]: `$${localField}`, ...letVars || {} },
648
- pipeline: autoPipeline,
649
- as
650
- }
651
- });
652
- } else {
653
- const safePipeline = lookupOptions.sanitize !== false ? LookupBuilder.sanitizePipeline(pipeline) : pipeline;
654
- aggPipeline.push({
655
- $lookup: {
656
- from,
657
- ...letVars && { let: letVars },
658
- pipeline: safePipeline,
659
- as
660
- }
661
- });
662
- }
663
- } else {
664
- aggPipeline.push({
665
- $lookup: {
666
- from,
667
- localField,
668
- foreignField,
669
- as
670
- }
671
- });
672
- }
673
- return aggregate(Model, aggPipeline, options);
674
- }
675
- async function unwind(Model, field, options = {}) {
676
- const pipeline = [
677
- {
678
- $unwind: {
679
- path: `$${field}`,
680
- preserveNullAndEmptyArrays: options.preserveEmpty !== false
681
- }
682
- }
683
- ];
684
- return aggregate(Model, pipeline, { session: options.session });
685
- }
686
- async function facet(Model, facets, options = {}) {
687
- const pipeline = [{ $facet: facets }];
688
- return aggregate(Model, pipeline, options);
689
- }
690
- async function distinct(Model, field, query = {}, options = {}) {
691
- return Model.distinct(field, query).session(options.session ?? null);
692
- }
693
- async function sum(Model, field, query = {}, options = {}) {
694
- const pipeline = [];
695
- if (Object.keys(query).length > 0) {
696
- pipeline.push({ $match: query });
697
- }
698
- pipeline.push({
699
- $group: {
700
- _id: null,
701
- total: { $sum: `$${field}` }
702
- }
703
- });
704
- const result = await aggregate(Model, pipeline, options);
705
- return result[0]?.total || 0;
706
- }
707
- async function average(Model, field, query = {}, options = {}) {
708
- const pipeline = [];
709
- if (Object.keys(query).length > 0) {
710
- pipeline.push({ $match: query });
711
- }
712
- pipeline.push({
713
- $group: {
714
- _id: null,
715
- average: { $avg: `$${field}` }
716
- }
717
- });
718
- const result = await aggregate(Model, pipeline, options);
719
- return result[0]?.average || 0;
720
- }
721
- async function minMax(Model, field, query = {}, options = {}) {
722
- const pipeline = [];
723
- if (Object.keys(query).length > 0) {
724
- pipeline.push({ $match: query });
725
- }
726
- pipeline.push({
727
- $group: {
728
- _id: null,
729
- min: { $min: `$${field}` },
730
- max: { $max: `$${field}` }
731
- }
732
- });
733
- const result = await aggregate(Model, pipeline, options);
734
- return result[0] || { min: null, max: null };
735
- }
736
-
737
- export { LookupBuilder, actions_exports, aggregate, aggregate_exports, count, deleteById, delete_exports, distinct, exists, getById, getByQuery, getOrCreate, read_exports, update, update_exports };