@classytic/mongokit 1.0.1 → 2.0.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 (87) hide show
  1. package/README.md +564 -157
  2. package/package.json +20 -12
  3. package/src/Repository.js +296 -225
  4. package/src/actions/aggregate.js +266 -191
  5. package/src/actions/create.js +59 -58
  6. package/src/actions/delete.js +88 -88
  7. package/src/actions/index.js +11 -11
  8. package/src/actions/read.js +188 -155
  9. package/src/actions/update.js +176 -172
  10. package/src/hooks/lifecycle.js +146 -146
  11. package/src/index.js +71 -60
  12. package/src/pagination/PaginationEngine.js +348 -0
  13. package/src/pagination/utils/cursor.js +119 -0
  14. package/src/pagination/utils/filter.js +42 -0
  15. package/src/pagination/utils/limits.js +82 -0
  16. package/src/pagination/utils/sort.js +101 -0
  17. package/src/plugins/aggregate-helpers.plugin.js +71 -71
  18. package/src/plugins/audit-log.plugin.js +60 -60
  19. package/src/plugins/batch-operations.plugin.js +66 -66
  20. package/src/plugins/field-filter.plugin.js +27 -27
  21. package/src/plugins/index.js +19 -19
  22. package/src/plugins/method-registry.plugin.js +140 -140
  23. package/src/plugins/mongo-operations.plugin.js +317 -313
  24. package/src/plugins/soft-delete.plugin.js +46 -46
  25. package/src/plugins/subdocument.plugin.js +66 -66
  26. package/src/plugins/timestamp.plugin.js +19 -19
  27. package/src/plugins/validation-chain.plugin.js +145 -145
  28. package/src/types.d.ts +87 -0
  29. package/src/utils/error.js +12 -0
  30. package/src/utils/field-selection.js +156 -156
  31. package/src/utils/index.js +12 -12
  32. package/types/Repository.d.ts +95 -0
  33. package/types/Repository.d.ts.map +1 -0
  34. package/types/actions/aggregate.d.ts +112 -0
  35. package/types/actions/aggregate.d.ts.map +1 -0
  36. package/types/actions/create.d.ts +21 -0
  37. package/types/actions/create.d.ts.map +1 -0
  38. package/types/actions/delete.d.ts +37 -0
  39. package/types/actions/delete.d.ts.map +1 -0
  40. package/types/actions/index.d.ts +6 -113
  41. package/types/actions/index.d.ts.map +1 -0
  42. package/types/actions/read.d.ts +135 -0
  43. package/types/actions/read.d.ts.map +1 -0
  44. package/types/actions/update.d.ts +58 -0
  45. package/types/actions/update.d.ts.map +1 -0
  46. package/types/hooks/lifecycle.d.ts +44 -0
  47. package/types/hooks/lifecycle.d.ts.map +1 -0
  48. package/types/index.d.ts +25 -96
  49. package/types/index.d.ts.map +1 -0
  50. package/types/pagination/PaginationEngine.d.ts +386 -0
  51. package/types/pagination/PaginationEngine.d.ts.map +1 -0
  52. package/types/pagination/utils/cursor.d.ts +40 -0
  53. package/types/pagination/utils/cursor.d.ts.map +1 -0
  54. package/types/pagination/utils/filter.d.ts +28 -0
  55. package/types/pagination/utils/filter.d.ts.map +1 -0
  56. package/types/pagination/utils/limits.d.ts +64 -0
  57. package/types/pagination/utils/limits.d.ts.map +1 -0
  58. package/types/pagination/utils/sort.d.ts +41 -0
  59. package/types/pagination/utils/sort.d.ts.map +1 -0
  60. package/types/plugins/aggregate-helpers.plugin.d.ts +6 -0
  61. package/types/plugins/aggregate-helpers.plugin.d.ts.map +1 -0
  62. package/types/plugins/audit-log.plugin.d.ts +6 -0
  63. package/types/plugins/audit-log.plugin.d.ts.map +1 -0
  64. package/types/plugins/batch-operations.plugin.d.ts +6 -0
  65. package/types/plugins/batch-operations.plugin.d.ts.map +1 -0
  66. package/types/plugins/field-filter.plugin.d.ts +6 -0
  67. package/types/plugins/field-filter.plugin.d.ts.map +1 -0
  68. package/types/plugins/index.d.ts +11 -88
  69. package/types/plugins/index.d.ts.map +1 -0
  70. package/types/plugins/method-registry.plugin.d.ts +3 -0
  71. package/types/plugins/method-registry.plugin.d.ts.map +1 -0
  72. package/types/plugins/mongo-operations.plugin.d.ts +4 -0
  73. package/types/plugins/mongo-operations.plugin.d.ts.map +1 -0
  74. package/types/plugins/soft-delete.plugin.d.ts +6 -0
  75. package/types/plugins/soft-delete.plugin.d.ts.map +1 -0
  76. package/types/plugins/subdocument.plugin.d.ts +6 -0
  77. package/types/plugins/subdocument.plugin.d.ts.map +1 -0
  78. package/types/plugins/timestamp.plugin.d.ts +6 -0
  79. package/types/plugins/timestamp.plugin.d.ts.map +1 -0
  80. package/types/plugins/validation-chain.plugin.d.ts +31 -0
  81. package/types/plugins/validation-chain.plugin.d.ts.map +1 -0
  82. package/types/utils/error.d.ts +11 -0
  83. package/types/utils/error.d.ts.map +1 -0
  84. package/types/utils/field-selection.d.ts +9 -0
  85. package/types/utils/field-selection.d.ts.map +1 -0
  86. package/types/utils/index.d.ts +2 -24
  87. package/types/utils/index.d.ts.map +1 -0
@@ -1,191 +1,266 @@
1
- /**
2
- * Aggregate Actions
3
- * MongoDB aggregation pipeline operations
4
- */
5
-
6
- /**
7
- * Execute aggregation pipeline
8
- */
9
- export async function aggregate(Model, pipeline, options = {}) {
10
- const aggregation = Model.aggregate(pipeline);
11
-
12
- if (options.session) {
13
- aggregation.session(options.session);
14
- }
15
-
16
- return aggregation.exec();
17
- }
18
-
19
- /**
20
- * Aggregate with pagination
21
- */
22
- export async function aggregatePaginate(Model, pipeline, options = {}) {
23
- const { page = 1, limit = 10 } = options;
24
-
25
- return Model.aggregatePaginate(Model.aggregate(pipeline), {
26
- page: parseInt(page, 10),
27
- limit: parseInt(limit, 10),
28
- });
29
- }
30
-
31
- /**
32
- * Group by field
33
- */
34
- export async function groupBy(Model, field, options = {}) {
35
- const pipeline = [
36
- { $group: { _id: `$${field}`, count: { $sum: 1 } } },
37
- { $sort: { count: -1 } },
38
- ];
39
-
40
- if (options.limit) {
41
- pipeline.push({ $limit: options.limit });
42
- }
43
-
44
- return aggregate(Model, pipeline, options);
45
- }
46
-
47
- /**
48
- * Count by field values
49
- */
50
- export async function countBy(Model, field, query = {}, options = {}) {
51
- const pipeline = [];
52
-
53
- if (Object.keys(query).length > 0) {
54
- pipeline.push({ $match: query });
55
- }
56
-
57
- pipeline.push(
58
- { $group: { _id: `$${field}`, count: { $sum: 1 } } },
59
- { $sort: { count: -1 } }
60
- );
61
-
62
- return aggregate(Model, pipeline, options);
63
- }
64
-
65
- /**
66
- * Lookup (join) with another collection
67
- */
68
- export async function lookup(Model, {
69
- from,
70
- localField,
71
- foreignField,
72
- as,
73
- pipeline = [],
74
- query = {},
75
- options = {}
76
- }) {
77
- const aggPipeline = [];
78
-
79
- if (Object.keys(query).length > 0) {
80
- aggPipeline.push({ $match: query });
81
- }
82
-
83
- aggPipeline.push({
84
- $lookup: {
85
- from,
86
- localField,
87
- foreignField,
88
- as,
89
- ...(pipeline.length > 0 ? { pipeline } : {}),
90
- },
91
- });
92
-
93
- return aggregate(Model, aggPipeline, options);
94
- }
95
-
96
- /**
97
- * Unwind array field
98
- */
99
- export async function unwind(Model, field, options = {}) {
100
- const pipeline = [
101
- {
102
- $unwind: {
103
- path: `$${field}`,
104
- preserveNullAndEmptyArrays: options.preserveEmpty !== false,
105
- },
106
- },
107
- ];
108
-
109
- return aggregate(Model, pipeline, options);
110
- }
111
-
112
- /**
113
- * Facet search (multiple aggregations in one query)
114
- */
115
- export async function facet(Model, facets, options = {}) {
116
- const pipeline = [{ $facet: facets }];
117
-
118
- return aggregate(Model, pipeline, options);
119
- }
120
-
121
- /**
122
- * Get distinct values
123
- */
124
- export async function distinct(Model, field, query = {}, options = {}) {
125
- return Model.distinct(field, query).session(options.session);
126
- }
127
-
128
- /**
129
- * Calculate sum
130
- */
131
- export async function sum(Model, field, query = {}, options = {}) {
132
- const pipeline = [];
133
-
134
- if (Object.keys(query).length > 0) {
135
- pipeline.push({ $match: query });
136
- }
137
-
138
- pipeline.push({
139
- $group: {
140
- _id: null,
141
- total: { $sum: `$${field}` },
142
- },
143
- });
144
-
145
- const result = await aggregate(Model, pipeline, options);
146
- return result[0]?.total || 0;
147
- }
148
-
149
- /**
150
- * Calculate average
151
- */
152
- export async function average(Model, field, query = {}, options = {}) {
153
- const pipeline = [];
154
-
155
- if (Object.keys(query).length > 0) {
156
- pipeline.push({ $match: query });
157
- }
158
-
159
- pipeline.push({
160
- $group: {
161
- _id: null,
162
- average: { $avg: `$${field}` },
163
- },
164
- });
165
-
166
- const result = await aggregate(Model, pipeline, options);
167
- return result[0]?.average || 0;
168
- }
169
-
170
- /**
171
- * Min/Max
172
- */
173
- export async function minMax(Model, field, query = {}, options = {}) {
174
- const pipeline = [];
175
-
176
- if (Object.keys(query).length > 0) {
177
- pipeline.push({ $match: query });
178
- }
179
-
180
- pipeline.push({
181
- $group: {
182
- _id: null,
183
- min: { $min: `$${field}` },
184
- max: { $max: `$${field}` },
185
- },
186
- });
187
-
188
- const result = await aggregate(Model, pipeline, options);
189
- return result[0] || { min: null, max: null };
190
- }
191
-
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,58 +1,59 @@
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
- }
56
- );
57
- }
58
-
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
+