@classytic/mongokit 2.0.0 → 2.1.0

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 (123) hide show
  1. package/README.md +221 -7
  2. package/dist/actions/index.cjs +479 -0
  3. package/dist/actions/index.cjs.map +1 -0
  4. package/dist/actions/index.d.cts +3 -0
  5. package/dist/actions/index.d.ts +3 -0
  6. package/dist/actions/index.js +473 -0
  7. package/dist/actions/index.js.map +1 -0
  8. package/dist/index-BfVJZF-3.d.cts +337 -0
  9. package/dist/index-CgOJ2pqz.d.ts +337 -0
  10. package/dist/index.cjs +2142 -0
  11. package/dist/index.cjs.map +1 -0
  12. package/dist/index.d.cts +239 -0
  13. package/dist/index.d.ts +239 -0
  14. package/dist/index.js +2108 -0
  15. package/dist/index.js.map +1 -0
  16. package/dist/memory-cache-DG2oSSbx.d.ts +142 -0
  17. package/dist/memory-cache-DqfFfKes.d.cts +142 -0
  18. package/dist/pagination/PaginationEngine.cjs +375 -0
  19. package/dist/pagination/PaginationEngine.cjs.map +1 -0
  20. package/dist/pagination/PaginationEngine.d.cts +117 -0
  21. package/dist/pagination/PaginationEngine.d.ts +117 -0
  22. package/dist/pagination/PaginationEngine.js +369 -0
  23. package/dist/pagination/PaginationEngine.js.map +1 -0
  24. package/dist/plugins/index.cjs +874 -0
  25. package/dist/plugins/index.cjs.map +1 -0
  26. package/dist/plugins/index.d.cts +275 -0
  27. package/dist/plugins/index.d.ts +275 -0
  28. package/dist/plugins/index.js +857 -0
  29. package/dist/plugins/index.js.map +1 -0
  30. package/dist/types-Nxhmi1aI.d.cts +510 -0
  31. package/dist/types-Nxhmi1aI.d.ts +510 -0
  32. package/dist/utils/index.cjs +667 -0
  33. package/dist/utils/index.cjs.map +1 -0
  34. package/dist/utils/index.d.cts +189 -0
  35. package/dist/utils/index.d.ts +189 -0
  36. package/dist/utils/index.js +643 -0
  37. package/dist/utils/index.js.map +1 -0
  38. package/package.json +44 -21
  39. package/src/Repository.js +0 -296
  40. package/src/actions/aggregate.js +0 -266
  41. package/src/actions/create.js +0 -59
  42. package/src/actions/delete.js +0 -88
  43. package/src/actions/index.js +0 -11
  44. package/src/actions/read.js +0 -188
  45. package/src/actions/update.js +0 -176
  46. package/src/hooks/lifecycle.js +0 -146
  47. package/src/index.js +0 -71
  48. package/src/pagination/PaginationEngine.js +0 -348
  49. package/src/pagination/utils/cursor.js +0 -119
  50. package/src/pagination/utils/filter.js +0 -42
  51. package/src/pagination/utils/limits.js +0 -82
  52. package/src/pagination/utils/sort.js +0 -101
  53. package/src/plugins/aggregate-helpers.plugin.js +0 -71
  54. package/src/plugins/audit-log.plugin.js +0 -60
  55. package/src/plugins/batch-operations.plugin.js +0 -66
  56. package/src/plugins/field-filter.plugin.js +0 -27
  57. package/src/plugins/index.js +0 -19
  58. package/src/plugins/method-registry.plugin.js +0 -140
  59. package/src/plugins/mongo-operations.plugin.js +0 -317
  60. package/src/plugins/soft-delete.plugin.js +0 -46
  61. package/src/plugins/subdocument.plugin.js +0 -66
  62. package/src/plugins/timestamp.plugin.js +0 -19
  63. package/src/plugins/validation-chain.plugin.js +0 -145
  64. package/src/types.d.ts +0 -87
  65. package/src/utils/error.js +0 -12
  66. package/src/utils/field-selection.js +0 -156
  67. package/src/utils/index.js +0 -12
  68. package/types/Repository.d.ts +0 -95
  69. package/types/Repository.d.ts.map +0 -1
  70. package/types/actions/aggregate.d.ts +0 -112
  71. package/types/actions/aggregate.d.ts.map +0 -1
  72. package/types/actions/create.d.ts +0 -21
  73. package/types/actions/create.d.ts.map +0 -1
  74. package/types/actions/delete.d.ts +0 -37
  75. package/types/actions/delete.d.ts.map +0 -1
  76. package/types/actions/index.d.ts +0 -6
  77. package/types/actions/index.d.ts.map +0 -1
  78. package/types/actions/read.d.ts +0 -135
  79. package/types/actions/read.d.ts.map +0 -1
  80. package/types/actions/update.d.ts +0 -58
  81. package/types/actions/update.d.ts.map +0 -1
  82. package/types/hooks/lifecycle.d.ts +0 -44
  83. package/types/hooks/lifecycle.d.ts.map +0 -1
  84. package/types/index.d.ts +0 -25
  85. package/types/index.d.ts.map +0 -1
  86. package/types/pagination/PaginationEngine.d.ts +0 -386
  87. package/types/pagination/PaginationEngine.d.ts.map +0 -1
  88. package/types/pagination/utils/cursor.d.ts +0 -40
  89. package/types/pagination/utils/cursor.d.ts.map +0 -1
  90. package/types/pagination/utils/filter.d.ts +0 -28
  91. package/types/pagination/utils/filter.d.ts.map +0 -1
  92. package/types/pagination/utils/limits.d.ts +0 -64
  93. package/types/pagination/utils/limits.d.ts.map +0 -1
  94. package/types/pagination/utils/sort.d.ts +0 -41
  95. package/types/pagination/utils/sort.d.ts.map +0 -1
  96. package/types/plugins/aggregate-helpers.plugin.d.ts +0 -6
  97. package/types/plugins/aggregate-helpers.plugin.d.ts.map +0 -1
  98. package/types/plugins/audit-log.plugin.d.ts +0 -6
  99. package/types/plugins/audit-log.plugin.d.ts.map +0 -1
  100. package/types/plugins/batch-operations.plugin.d.ts +0 -6
  101. package/types/plugins/batch-operations.plugin.d.ts.map +0 -1
  102. package/types/plugins/field-filter.plugin.d.ts +0 -6
  103. package/types/plugins/field-filter.plugin.d.ts.map +0 -1
  104. package/types/plugins/index.d.ts +0 -11
  105. package/types/plugins/index.d.ts.map +0 -1
  106. package/types/plugins/method-registry.plugin.d.ts +0 -3
  107. package/types/plugins/method-registry.plugin.d.ts.map +0 -1
  108. package/types/plugins/mongo-operations.plugin.d.ts +0 -4
  109. package/types/plugins/mongo-operations.plugin.d.ts.map +0 -1
  110. package/types/plugins/soft-delete.plugin.d.ts +0 -6
  111. package/types/plugins/soft-delete.plugin.d.ts.map +0 -1
  112. package/types/plugins/subdocument.plugin.d.ts +0 -6
  113. package/types/plugins/subdocument.plugin.d.ts.map +0 -1
  114. package/types/plugins/timestamp.plugin.d.ts +0 -6
  115. package/types/plugins/timestamp.plugin.d.ts.map +0 -1
  116. package/types/plugins/validation-chain.plugin.d.ts +0 -31
  117. package/types/plugins/validation-chain.plugin.d.ts.map +0 -1
  118. package/types/utils/error.d.ts +0 -11
  119. package/types/utils/error.d.ts.map +0 -1
  120. package/types/utils/field-selection.d.ts +0 -9
  121. package/types/utils/field-selection.d.ts.map +0 -1
  122. package/types/utils/index.d.ts +0 -2
  123. package/types/utils/index.d.ts.map +0 -1
@@ -1,266 +0,0 @@
1
- /**
2
- * Aggregate Actions
3
- * MongoDB aggregation pipeline operations
4
- */
5
-
6
- /**
7
- * @typedef {import('mongoose').Model} Model
8
- * @typedef {import('mongoose').ClientSession} ClientSession
9
- */
10
-
11
- /**
12
- * Execute aggregation pipeline
13
- *
14
- * @param {Model} Model - Mongoose model
15
- * @param {any[]} pipeline - Aggregation pipeline stages
16
- * @param {Object} [options={}] - Aggregation options
17
- * @param {ClientSession} [options.session] - MongoDB session
18
- * @returns {Promise<any[]>} Aggregation results
19
- */
20
- export async function aggregate(Model, pipeline, options = {}) {
21
- const aggregation = Model.aggregate(pipeline);
22
-
23
- if (options.session) {
24
- aggregation.session(options.session);
25
- }
26
-
27
- return aggregation.exec();
28
- }
29
-
30
- /**
31
- * Aggregate with pagination using native MongoDB $facet
32
- * WARNING: $facet results must be <16MB. For larger results (limit >1000),
33
- * consider using Repository.aggregatePaginate() or splitting into separate queries.
34
- *
35
- * @param {Model} Model - Mongoose model
36
- * @param {any[]} pipeline - Aggregation pipeline stages (before pagination)
37
- * @param {Object} [options={}] - Pagination options
38
- * @param {number} [options.page=1] - Page number (1-indexed)
39
- * @param {number} [options.limit=10] - Documents per page
40
- * @param {ClientSession} [options.session] - MongoDB session
41
- * @returns {Promise<{docs: any[], total: number, page: number, limit: number, pages: number, hasNext: boolean, hasPrev: boolean}>} Paginated results
42
- *
43
- * @example
44
- * const result = await aggregatePaginate(UserModel, [
45
- * { $match: { status: 'active' } },
46
- * { $group: { _id: '$category', count: { $sum: 1 } } }
47
- * ], { page: 1, limit: 20 });
48
- */
49
- export async function aggregatePaginate(Model, pipeline, options = {}) {
50
- const page = parseInt(String(options.page || 1), 10);
51
- const limit = parseInt(String(options.limit || 10), 10);
52
- const skip = (page - 1) * limit;
53
-
54
- // 16MB MongoDB document size limit safety check
55
- const SAFE_LIMIT = 1000;
56
- if (limit > SAFE_LIMIT) {
57
- console.warn(
58
- `[mongokit] Large aggregation limit (${limit}). $facet results must be <16MB. ` +
59
- `Consider using Repository.aggregatePaginate() for safer handling of large datasets.`
60
- );
61
- }
62
-
63
- const facetPipeline = [
64
- ...pipeline,
65
- {
66
- $facet: {
67
- docs: [
68
- { $skip: skip },
69
- { $limit: limit }
70
- ],
71
- total: [
72
- { $count: 'count' }
73
- ]
74
- }
75
- }
76
- ];
77
-
78
- const aggregation = Model.aggregate(facetPipeline);
79
- if (options.session) {
80
- aggregation.session(options.session);
81
- }
82
-
83
- const [result] = await aggregation.exec();
84
- const docs = result.docs || [];
85
- const total = result.total[0]?.count || 0;
86
- const pages = Math.ceil(total / limit);
87
-
88
- return {
89
- docs,
90
- total,
91
- page,
92
- limit,
93
- pages,
94
- hasNext: page < pages,
95
- hasPrev: page > 1
96
- };
97
- }
98
-
99
- /**
100
- * Group documents by field value
101
- *
102
- * @param {Model} Model - Mongoose model
103
- * @param {string} field - Field name to group by
104
- * @param {Object} [options={}] - Options
105
- * @param {number} [options.limit] - Maximum groups to return
106
- * @param {ClientSession} [options.session] - MongoDB session
107
- * @returns {Promise<Array<{_id: any, count: number}>>} Grouped results
108
- */
109
- export async function groupBy(Model, field, options = {}) {
110
- const pipeline = [
111
- { $group: { _id: `$${field}`, count: { $sum: 1 } } },
112
- { $sort: { count: -1 } },
113
- ];
114
-
115
- if (options.limit) {
116
- pipeline.push(/** @type {any} */({ $limit: options.limit }));
117
- }
118
-
119
- return aggregate(Model, pipeline, options);
120
- }
121
-
122
- /**
123
- * Count by field values
124
- */
125
- export async function countBy(Model, field, query = {}, options = {}) {
126
- const pipeline = [];
127
-
128
- if (Object.keys(query).length > 0) {
129
- pipeline.push({ $match: query });
130
- }
131
-
132
- pipeline.push(
133
- { $group: { _id: `$${field}`, count: { $sum: 1 } } },
134
- { $sort: { count: -1 } }
135
- );
136
-
137
- return aggregate(Model, pipeline, options);
138
- }
139
-
140
- /**
141
- * Lookup (join) with another collection
142
- */
143
- export async function lookup(Model, {
144
- from,
145
- localField,
146
- foreignField,
147
- as,
148
- pipeline = [],
149
- query = {},
150
- options = {}
151
- }) {
152
- const aggPipeline = [];
153
-
154
- if (Object.keys(query).length > 0) {
155
- aggPipeline.push({ $match: query });
156
- }
157
-
158
- aggPipeline.push({
159
- $lookup: {
160
- from,
161
- localField,
162
- foreignField,
163
- as,
164
- ...(pipeline.length > 0 ? { pipeline } : {}),
165
- },
166
- });
167
-
168
- return aggregate(Model, aggPipeline, options);
169
- }
170
-
171
- /**
172
- * Unwind array field
173
- */
174
- export async function unwind(Model, field, options = {}) {
175
- const pipeline = [
176
- {
177
- $unwind: {
178
- path: `$${field}`,
179
- preserveNullAndEmptyArrays: options.preserveEmpty !== false,
180
- },
181
- },
182
- ];
183
-
184
- return aggregate(Model, pipeline, options);
185
- }
186
-
187
- /**
188
- * Facet search (multiple aggregations in one query)
189
- */
190
- export async function facet(Model, facets, options = {}) {
191
- const pipeline = [{ $facet: facets }];
192
-
193
- return aggregate(Model, pipeline, options);
194
- }
195
-
196
- /**
197
- * Get distinct values
198
- */
199
- export async function distinct(Model, field, query = {}, options = {}) {
200
- return Model.distinct(field, query).session(options.session);
201
- }
202
-
203
- /**
204
- * Calculate sum
205
- */
206
- export async function sum(Model, field, query = {}, options = {}) {
207
- const pipeline = [];
208
-
209
- if (Object.keys(query).length > 0) {
210
- pipeline.push({ $match: query });
211
- }
212
-
213
- pipeline.push({
214
- $group: {
215
- _id: null,
216
- total: { $sum: `$${field}` },
217
- },
218
- });
219
-
220
- const result = await aggregate(Model, pipeline, options);
221
- return result[0]?.total || 0;
222
- }
223
-
224
- /**
225
- * Calculate average
226
- */
227
- export async function average(Model, field, query = {}, options = {}) {
228
- const pipeline = [];
229
-
230
- if (Object.keys(query).length > 0) {
231
- pipeline.push({ $match: query });
232
- }
233
-
234
- pipeline.push({
235
- $group: {
236
- _id: null,
237
- average: { $avg: `$${field}` },
238
- },
239
- });
240
-
241
- const result = await aggregate(Model, pipeline, options);
242
- return result[0]?.average || 0;
243
- }
244
-
245
- /**
246
- * Min/Max
247
- */
248
- export async function minMax(Model, field, query = {}, options = {}) {
249
- const pipeline = [];
250
-
251
- if (Object.keys(query).length > 0) {
252
- pipeline.push({ $match: query });
253
- }
254
-
255
- pipeline.push({
256
- $group: {
257
- _id: null,
258
- min: { $min: `$${field}` },
259
- max: { $max: `$${field}` },
260
- },
261
- });
262
-
263
- const result = await aggregate(Model, pipeline, options);
264
- return result[0] || { min: null, max: null };
265
- }
266
-
@@ -1,59 +0,0 @@
1
- /**
2
- * Create Actions
3
- * Pure functions for document creation
4
- */
5
-
6
- /**
7
- * Create single document
8
- */
9
- export async function create(Model, data, options = {}) {
10
- const document = new Model(data);
11
- await document.save({ session: options.session });
12
- return document;
13
- }
14
-
15
- /**
16
- * Create multiple documents
17
- */
18
- export async function createMany(Model, dataArray, options = {}) {
19
- return Model.insertMany(dataArray, {
20
- session: options.session,
21
- ordered: options.ordered !== false,
22
- });
23
- }
24
-
25
- /**
26
- * Create with defaults (useful for initialization)
27
- */
28
- export async function createDefault(Model, overrides = {}, options = {}) {
29
- const defaults = {};
30
-
31
- // Extract defaults from schema
32
- Model.schema.eachPath((path, schemaType) => {
33
- if (schemaType.options.default !== undefined && path !== '_id') {
34
- defaults[path] = typeof schemaType.options.default === 'function'
35
- ? schemaType.options.default()
36
- : schemaType.options.default;
37
- }
38
- });
39
-
40
- return create(Model, { ...defaults, ...overrides }, options);
41
- }
42
-
43
- /**
44
- * Upsert (create or update)
45
- */
46
- export async function upsert(Model, query, data, options = {}) {
47
- return Model.findOneAndUpdate(
48
- query,
49
- { $setOnInsert: data },
50
- {
51
- upsert: true,
52
- new: true,
53
- runValidators: true,
54
- session: options.session,
55
- ...(options.updatePipeline !== undefined ? { updatePipeline: options.updatePipeline } : {}),
56
- }
57
- );
58
- }
59
-
@@ -1,88 +0,0 @@
1
- /**
2
- * Delete Actions
3
- * Pure functions for document deletion
4
- */
5
-
6
- import { createError } from '../utils/error.js';
7
-
8
- /**
9
- * Delete by ID
10
- */
11
- export async function deleteById(Model, id, options = {}) {
12
- const document = await Model.findByIdAndDelete(id).session(options.session);
13
-
14
- if (!document) {
15
- throw createError(404, 'Document not found');
16
- }
17
-
18
- return { success: true, message: 'Deleted successfully' };
19
- }
20
-
21
- /**
22
- * Delete many documents
23
- */
24
- export async function deleteMany(Model, query, options = {}) {
25
- const result = await Model.deleteMany(query).session(options.session);
26
-
27
- return {
28
- success: true,
29
- count: result.deletedCount,
30
- message: 'Deleted successfully',
31
- };
32
- }
33
-
34
- /**
35
- * Delete by query
36
- */
37
- export async function deleteByQuery(Model, query, options = {}) {
38
- const document = await Model.findOneAndDelete(query).session(options.session);
39
-
40
- if (!document && options.throwOnNotFound !== false) {
41
- throw createError(404, 'Document not found');
42
- }
43
-
44
- return { success: true, message: 'Deleted successfully' };
45
- }
46
-
47
- /**
48
- * Soft delete (set deleted flag)
49
- */
50
- export async function softDelete(Model, id, options = {}) {
51
- const document = await Model.findByIdAndUpdate(
52
- id,
53
- {
54
- deleted: true,
55
- deletedAt: new Date(),
56
- deletedBy: options.userId,
57
- },
58
- { new: true, session: options.session }
59
- );
60
-
61
- if (!document) {
62
- throw createError(404, 'Document not found');
63
- }
64
-
65
- return { success: true, message: 'Soft deleted successfully' };
66
- }
67
-
68
- /**
69
- * Restore soft deleted document
70
- */
71
- export async function restore(Model, id, options = {}) {
72
- const document = await Model.findByIdAndUpdate(
73
- id,
74
- {
75
- deleted: false,
76
- deletedAt: null,
77
- deletedBy: null,
78
- },
79
- { new: true, session: options.session }
80
- );
81
-
82
- if (!document) {
83
- throw createError(404, 'Document not found');
84
- }
85
-
86
- return { success: true, message: 'Restored successfully' };
87
- }
88
-
@@ -1,11 +0,0 @@
1
- /**
2
- * Repository Actions
3
- * Modular, composable data access operations
4
- */
5
-
6
- export * as create from './create.js';
7
- export * as read from './read.js';
8
- export * as update from './update.js';
9
- export * as deleteActions from './delete.js';
10
- export * as aggregate from './aggregate.js';
11
-
@@ -1,188 +0,0 @@
1
- /**
2
- * Read Actions
3
- * Pure functions for document retrieval
4
- */
5
-
6
- import { createError } from '../utils/error.js';
7
-
8
- /**
9
- * @typedef {import('mongoose').Model} Model
10
- * @typedef {import('mongoose').PopulateOptions} PopulateOptions
11
- * @typedef {import('mongoose').ClientSession} ClientSession
12
- */
13
-
14
- /**
15
- * Get document by ID
16
- *
17
- * @param {Model} Model - Mongoose model
18
- * @param {string} id - Document ID
19
- * @param {Object} [options={}] - Query options
20
- * @param {string|string[]} [options.select] - Fields to select
21
- * @param {string|string[]|PopulateOptions|PopulateOptions[]} [options.populate] - Fields to populate
22
- * @param {boolean} [options.lean] - Return plain JavaScript object
23
- * @param {ClientSession} [options.session] - MongoDB session
24
- * @param {boolean} [options.throwOnNotFound=true] - Throw error if not found
25
- * @returns {Promise<any>} Document or null
26
- * @throws {Error} If document not found and throwOnNotFound is true
27
- */
28
- export async function getById(Model, id, options = {}) {
29
- const query = Model.findById(id);
30
-
31
- if (options.select) query.select(options.select);
32
- if (options.populate) query.populate(parsePopulate(options.populate));
33
- if (options.lean) query.lean();
34
- if (options.session) query.session(options.session);
35
-
36
- const document = await query.exec();
37
- if (!document && options.throwOnNotFound !== false) {
38
- throw createError(404, 'Document not found');
39
- }
40
-
41
- return document;
42
- }
43
-
44
- /**
45
- * Get document by query
46
- *
47
- * @param {Model} Model - Mongoose model
48
- * @param {Record<string, any>} query - MongoDB query
49
- * @param {Object} [options={}] - Query options
50
- * @param {string|string[]} [options.select] - Fields to select
51
- * @param {string|string[]|PopulateOptions|PopulateOptions[]} [options.populate] - Fields to populate
52
- * @param {boolean} [options.lean] - Return plain JavaScript object
53
- * @param {ClientSession} [options.session] - MongoDB session
54
- * @param {boolean} [options.throwOnNotFound=true] - Throw error if not found
55
- * @returns {Promise<any>} Document or null
56
- * @throws {Error} If document not found and throwOnNotFound is true
57
- */
58
- export async function getByQuery(Model, query, options = {}) {
59
- const mongoQuery = Model.findOne(query);
60
-
61
- if (options.select) mongoQuery.select(options.select);
62
- if (options.populate) mongoQuery.populate(parsePopulate(options.populate));
63
- if (options.lean) mongoQuery.lean();
64
- if (options.session) mongoQuery.session(options.session);
65
-
66
- const document = await mongoQuery.exec();
67
- if (!document && options.throwOnNotFound !== false) {
68
- throw createError(404, 'Document not found');
69
- }
70
-
71
- return document;
72
- }
73
-
74
- /**
75
- * Get document by query without throwing (returns null if not found)
76
- *
77
- * @param {Model} Model - Mongoose model
78
- * @param {Record<string, any>} query - MongoDB query
79
- * @param {Object} [options={}] - Query options
80
- * @param {string|string[]} [options.select] - Fields to select
81
- * @param {string|string[]|PopulateOptions|PopulateOptions[]} [options.populate] - Fields to populate
82
- * @param {boolean} [options.lean] - Return plain JavaScript object
83
- * @param {ClientSession} [options.session] - MongoDB session
84
- * @returns {Promise<any|null>} Document or null
85
- */
86
- export async function tryGetByQuery(Model, query, options = {}) {
87
- return getByQuery(Model, query, { ...options, throwOnNotFound: false });
88
- }
89
-
90
- /**
91
- * Get all documents (basic query without pagination)
92
- * For pagination, use Repository.paginate() or Repository.stream()
93
- *
94
- * @param {Model} Model - Mongoose model
95
- * @param {Record<string, any>} [query={}] - MongoDB query
96
- * @param {Object} [options={}] - Query options
97
- * @param {string|string[]} [options.select] - Fields to select
98
- * @param {string|string[]|PopulateOptions|PopulateOptions[]} [options.populate] - Fields to populate
99
- * @param {Record<string, 1|-1>} [options.sort] - Sort specification
100
- * @param {number} [options.limit] - Maximum documents to return
101
- * @param {number} [options.skip] - Documents to skip
102
- * @param {boolean} [options.lean=true] - Return plain JavaScript objects
103
- * @param {ClientSession} [options.session] - MongoDB session
104
- * @returns {Promise<any[]>} Array of documents
105
- */
106
- export async function getAll(Model, query = {}, options = {}) {
107
- let mongoQuery = Model.find(query);
108
-
109
- if (options.select) mongoQuery = mongoQuery.select(options.select);
110
- if (options.populate) mongoQuery = mongoQuery.populate(parsePopulate(options.populate));
111
- if (options.sort) mongoQuery = mongoQuery.sort(options.sort);
112
- if (options.limit) mongoQuery = mongoQuery.limit(options.limit);
113
- if (options.skip) mongoQuery = mongoQuery.skip(options.skip);
114
-
115
- mongoQuery = mongoQuery.lean(options.lean !== false);
116
- if (options.session) mongoQuery = mongoQuery.session(options.session);
117
-
118
- return mongoQuery.exec();
119
- }
120
-
121
- /**
122
- * Get or create document (upsert)
123
- *
124
- * @param {Model} Model - Mongoose model
125
- * @param {Record<string, any>} query - Query to find document
126
- * @param {Record<string, any>} createData - Data to insert if not found
127
- * @param {Object} [options={}] - Query options
128
- * @param {ClientSession} [options.session] - MongoDB session
129
- * @param {boolean} [options.updatePipeline] - Use update pipeline
130
- * @returns {Promise<any>} Created or found document
131
- */
132
- export async function getOrCreate(Model, query, createData, options = {}) {
133
- return Model.findOneAndUpdate(
134
- query,
135
- { $setOnInsert: createData },
136
- {
137
- upsert: true,
138
- new: true,
139
- runValidators: true,
140
- session: options.session,
141
- ...(options.updatePipeline !== undefined ? { updatePipeline: options.updatePipeline } : {}),
142
- }
143
- );
144
- }
145
-
146
- /**
147
- * Count documents matching query
148
- *
149
- * @param {Model} Model - Mongoose model
150
- * @param {Record<string, any>} [query={}] - MongoDB query
151
- * @param {Object} [options={}] - Query options
152
- * @param {ClientSession} [options.session] - MongoDB session
153
- * @returns {Promise<number>} Document count
154
- */
155
- export async function count(Model, query = {}, options = {}) {
156
- return Model.countDocuments(query).session(options.session);
157
- }
158
-
159
- /**
160
- * Check if document exists
161
- *
162
- * @param {Model} Model - Mongoose model
163
- * @param {Record<string, any>} query - MongoDB query
164
- * @param {Object} [options={}] - Query options
165
- * @param {ClientSession} [options.session] - MongoDB session
166
- * @returns {Promise<{_id: any} | null>} Document ID if exists, null otherwise
167
- */
168
- export async function exists(Model, query, options = {}) {
169
- return Model.exists(query).session(options.session);
170
- }
171
-
172
- /**
173
- * Parses populate parameter into Mongoose-compatible format
174
- *
175
- * @param {string|string[]|PopulateOptions|PopulateOptions[]} populate - Populate specification
176
- * @returns {(string|PopulateOptions)[]} Normalized populate array
177
- */
178
- function parsePopulate(populate) {
179
- if (!populate) return [];
180
- if (typeof populate === 'string') {
181
- return populate.split(',').map(/** @param {string} p */ (p) => p.trim());
182
- }
183
- if (Array.isArray(populate)) {
184
- return populate.map(/** @param {string|PopulateOptions} p */ (p) => typeof p === 'string' ? p.trim() : p);
185
- }
186
- return [populate];
187
- }
188
-