@classytic/mongokit 1.0.2 → 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 (64) hide show
  1. package/README.md +772 -151
  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 +54 -24
  39. package/src/Repository.js +0 -225
  40. package/src/actions/aggregate.js +0 -191
  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 -156
  45. package/src/actions/update.js +0 -176
  46. package/src/hooks/lifecycle.js +0 -146
  47. package/src/index.js +0 -60
  48. package/src/plugins/aggregate-helpers.plugin.js +0 -71
  49. package/src/plugins/audit-log.plugin.js +0 -60
  50. package/src/plugins/batch-operations.plugin.js +0 -66
  51. package/src/plugins/field-filter.plugin.js +0 -27
  52. package/src/plugins/index.js +0 -19
  53. package/src/plugins/method-registry.plugin.js +0 -140
  54. package/src/plugins/mongo-operations.plugin.js +0 -313
  55. package/src/plugins/soft-delete.plugin.js +0 -46
  56. package/src/plugins/subdocument.plugin.js +0 -66
  57. package/src/plugins/timestamp.plugin.js +0 -19
  58. package/src/plugins/validation-chain.plugin.js +0 -145
  59. package/src/utils/field-selection.js +0 -156
  60. package/src/utils/index.js +0 -12
  61. package/types/actions/index.d.ts +0 -121
  62. package/types/index.d.ts +0 -104
  63. package/types/plugins/index.d.ts +0 -88
  64. package/types/utils/index.d.ts +0 -24
@@ -1,313 +0,0 @@
1
- /**
2
- * MongoDB Operations Plugin
3
- *
4
- * Adds MongoDB-specific operations to repositories.
5
- * Requires method-registry.plugin.js to be loaded first.
6
- *
7
- * **Operations Added:**
8
- * - upsert(query, data, options) - Update or insert
9
- * - increment(id, field, value, options) - Atomic increment
10
- * - decrement(id, field, value, options) - Atomic decrement
11
- * - pushToArray(id, field, value, options) - Add to array
12
- * - pullFromArray(id, field, value, options) - Remove from array
13
- * - addToSet(id, field, value, options) - Add unique to array
14
- *
15
- * **Pattern:** Opt-in MongoDB features
16
- * **Philosophy:** Keep core pure, add database-specific features via plugins
17
- *
18
- * @module common/repositories/plugins/mongo-operations
19
- * @requires method-registry.plugin
20
- *
21
- * @example Basic Usage
22
- * ```js
23
- * import { Repository } from '../Repository.js';
24
- * import { method
25
-
26
- RegistryPlugin } from './method-registry.plugin.js';
27
- * import { mongoOperationsPlugin } from './mongo-operations.plugin.js';
28
- *
29
- * class ProductRepository extends Repository {
30
- * constructor() {
31
- * super(Product, [
32
- * methodRegistryPlugin(),
33
- * mongoOperationsPlugin(),
34
- * ]);
35
- * }
36
- * }
37
- *
38
- * // Now you can use MongoDB operations
39
- * await productRepo.increment(productId, 'views', 1);
40
- * await productRepo.pushToArray(productId, 'tags', 'featured');
41
- * ```
42
- */
43
-
44
- import createError from 'http-errors';
45
- import * as createActions from '../actions/create.js';
46
-
47
- /**
48
- * MongoDB Operations Plugin
49
- *
50
- * Adds common MongoDB atomic operations to repository.
51
- * All operations use repository's update method internally (events/plugins run).
52
- *
53
- * @returns {Object} Plugin configuration
54
- */
55
- export const mongoOperationsPlugin = () => ({
56
- name: 'mongo-operations',
57
-
58
- apply(repo) {
59
- // Check if method-registry is available
60
- if (!repo.registerMethod) {
61
- throw new Error(
62
- 'mongoOperationsPlugin requires methodRegistryPlugin. ' +
63
- 'Add methodRegistryPlugin() before mongoOperationsPlugin() in plugins array.'
64
- );
65
- }
66
-
67
- /**
68
- * Update existing document or insert new one
69
- *
70
- * @param {Object} query - Query to find existing document
71
- * @param {Object} data - Data to insert/update
72
- * @param {Object} [options={}] - Options
73
- * @returns {Promise<Object>} Upserted document
74
- *
75
- * @example
76
- * // Create or update user session
77
- * await repo.upsert(
78
- * { userId, deviceId },
79
- * { lastActive: new Date(), ipAddress },
80
- * { lean: true }
81
- * );
82
- */
83
- repo.registerMethod('upsert', async function (query, data, options = {}) {
84
- return createActions.upsert(this.Model, query, data, options);
85
- });
86
-
87
- // Helper: Validate and perform numeric operation
88
- const validateAndUpdateNumeric = function (id, field, value, operator, operationName, options) {
89
- if (typeof value !== 'number') {
90
- throw createError(400, `${operationName} value must be a number`);
91
- }
92
- return this.update(id, { [operator]: { [field]: value } }, options);
93
- };
94
-
95
- /**
96
- * Atomically increment numeric field
97
- *
98
- * @param {string|ObjectId} id - Document ID
99
- * @param {string} field - Field name to increment
100
- * @param {number} [value=1] - Amount to increment by
101
- * @param {Object} [options={}] - Options
102
- * @returns {Promise<Object>} Updated document
103
- *
104
- * @example
105
- * // Increment product views
106
- * await productRepo.increment(productId, 'views', 1);
107
- *
108
- * @example
109
- * // Increment multiple times
110
- * await productRepo.increment(userId, 'points', 100);
111
- */
112
- repo.registerMethod('increment', async function (id, field, value = 1, options = {}) {
113
- return validateAndUpdateNumeric.call(this, id, field, value, '$inc', 'Increment', options);
114
- });
115
-
116
- /**
117
- * Atomically decrement numeric field
118
- *
119
- * @param {string|ObjectId} id - Document ID
120
- * @param {string} field - Field name to decrement
121
- * @param {number} [value=1] - Amount to decrement by
122
- * @param {Object} [options={}] - Options
123
- * @returns {Promise<Object>} Updated document
124
- *
125
- * @example
126
- * // Decrement product stock
127
- * await productRepo.decrement(productId, 'stock', 1);
128
- */
129
- repo.registerMethod('decrement', async function (id, field, value = 1, options = {}) {
130
- return validateAndUpdateNumeric.call(this, id, field, -value, '$inc', 'Decrement', options);
131
- });
132
-
133
- // Helper: Generic MongoDB operator update
134
- const applyOperator = function (id, field, value, operator, options) {
135
- return this.update(id, { [operator]: { [field]: value } }, options);
136
- };
137
-
138
- /**
139
- * Push value to array field
140
- *
141
- * @param {string|ObjectId} id - Document ID
142
- * @param {string} field - Array field name
143
- * @param {*} value - Value to push
144
- * @param {Object} [options={}] - Options
145
- * @returns {Promise<Object>} Updated document
146
- *
147
- * @example
148
- * // Add tag to product
149
- * await productRepo.pushToArray(productId, 'tags', 'new-arrival');
150
- *
151
- * @example
152
- * // Add multiple items
153
- * await productRepo.pushToArray(userId, 'notifications', {
154
- * message: 'Welcome!',
155
- * createdAt: new Date()
156
- * });
157
- */
158
- repo.registerMethod('pushToArray', async function (id, field, value, options = {}) {
159
- return applyOperator.call(this, id, field, value, '$push', options);
160
- });
161
-
162
- /**
163
- * Remove value from array field
164
- *
165
- * @param {string|ObjectId} id - Document ID
166
- * @param {string} field - Array field name
167
- * @param {*} value - Value to remove (can be query object)
168
- * @param {Object} [options={}] - Options
169
- * @returns {Promise<Object>} Updated document
170
- *
171
- * @example
172
- * // Remove tag from product
173
- * await productRepo.pullFromArray(productId, 'tags', 'old-tag');
174
- *
175
- * @example
176
- * // Remove by query
177
- * await productRepo.pullFromArray(userId, 'notifications', { read: true });
178
- */
179
- repo.registerMethod('pullFromArray', async function (id, field, value, options = {}) {
180
- return applyOperator.call(this, id, field, value, '$pull', options);
181
- });
182
-
183
- /**
184
- * Add value to array only if not already present (unique)
185
- *
186
- * @param {string|ObjectId} id - Document ID
187
- * @param {string} field - Array field name
188
- * @param {*} value - Value to add
189
- * @param {Object} [options={}] - Options
190
- * @returns {Promise<Object>} Updated document
191
- *
192
- * @example
193
- * // Add unique follower
194
- * await userRepo.addToSet(userId, 'followers', followerId);
195
- */
196
- repo.registerMethod('addToSet', async function (id, field, value, options = {}) {
197
- return applyOperator.call(this, id, field, value, '$addToSet', options);
198
- });
199
-
200
- /**
201
- * Set field value (alias for update with $set)
202
- *
203
- * @param {string|ObjectId} id - Document ID
204
- * @param {string} field - Field name
205
- * @param {*} value - New value
206
- * @param {Object} [options={}] - Options
207
- * @returns {Promise<Object>} Updated document
208
- *
209
- * @example
210
- * // Set last login
211
- * await userRepo.setField(userId, 'lastLogin', new Date());
212
- */
213
- repo.registerMethod('setField', async function (id, field, value, options = {}) {
214
- return applyOperator.call(this, id, field, value, '$set', options);
215
- });
216
-
217
- /**
218
- * Unset (remove) field from document
219
- *
220
- * @param {string|ObjectId} id - Document ID
221
- * @param {string|string[]} fields - Field name(s) to remove
222
- * @param {Object} [options={}] - Options
223
- * @returns {Promise<Object>} Updated document
224
- *
225
- * @example
226
- * // Remove temporary field
227
- * await userRepo.unsetField(userId, 'tempToken');
228
- *
229
- * @example
230
- * // Remove multiple fields
231
- * await userRepo.unsetField(userId, ['tempToken', 'tempData']);
232
- */
233
- repo.registerMethod('unsetField', async function (id, fields, options = {}) {
234
- const fieldArray = Array.isArray(fields) ? fields : [fields];
235
- const unsetObj = fieldArray.reduce((acc, field) => {
236
- acc[field] = '';
237
- return acc;
238
- }, {});
239
-
240
- return this.update(id, { $unset: unsetObj }, options);
241
- });
242
-
243
- /**
244
- * Rename field in document
245
- *
246
- * @param {string|ObjectId} id - Document ID
247
- * @param {string} oldName - Current field name
248
- * @param {string} newName - New field name
249
- * @param {Object} [options={}] - Options
250
- * @returns {Promise<Object>} Updated document
251
- *
252
- * @example
253
- * // Rename field
254
- * await userRepo.renameField(userId, 'username', 'displayName');
255
- */
256
- repo.registerMethod('renameField', async function (id, oldName, newName, options = {}) {
257
- return this.update(id, { $rename: { [oldName]: newName } }, options);
258
- });
259
-
260
- /**
261
- * Multiply numeric field by value
262
- *
263
- * @param {string|ObjectId} id - Document ID
264
- * @param {string} field - Field name
265
- * @param {number} multiplier - Multiplier value
266
- * @param {Object} [options={}] - Options
267
- * @returns {Promise<Object>} Updated document
268
- *
269
- * @example
270
- * // Double points
271
- * await userRepo.multiplyField(userId, 'points', 2);
272
- */
273
- repo.registerMethod('multiplyField', async function (id, field, multiplier, options = {}) {
274
- return validateAndUpdateNumeric.call(this, id, field, multiplier, '$mul', 'Multiplier', options);
275
- });
276
-
277
- /**
278
- * Set field to minimum value (only if current value is greater)
279
- *
280
- * @param {string|ObjectId} id - Document ID
281
- * @param {string} field - Field name
282
- * @param {number} value - Minimum value
283
- * @param {Object} [options={}] - Options
284
- * @returns {Promise<Object>} Updated document
285
- *
286
- * @example
287
- * // Set minimum price
288
- * await productRepo.setMin(productId, 'price', 999);
289
- */
290
- repo.registerMethod('setMin', async function (id, field, value, options = {}) {
291
- return applyOperator.call(this, id, field, value, '$min', options);
292
- });
293
-
294
- /**
295
- * Set field to maximum value (only if current value is less)
296
- *
297
- * @param {string|ObjectId} id - Document ID
298
- * @param {string} field - Field name
299
- * @param {number} value - Maximum value
300
- * @param {Object} [options={}] - Options
301
- * @returns {Promise<Object>} Updated document
302
- *
303
- * @example
304
- * // Set maximum score
305
- * await gameRepo.setMax(gameId, 'highScore', newScore);
306
- */
307
- repo.registerMethod('setMax', async function (id, field, value, options = {}) {
308
- return applyOperator.call(this, id, field, value, '$max', options);
309
- });
310
- }
311
- });
312
-
313
- export default mongoOperationsPlugin;
@@ -1,46 +0,0 @@
1
- export const softDeletePlugin = (options = {}) => ({
2
- name: 'softDelete',
3
-
4
- apply(repo) {
5
- const deletedField = options.deletedField || 'deletedAt';
6
- const deletedByField = options.deletedByField || 'deletedBy';
7
-
8
- repo.on('before:delete', async (context) => {
9
- if (options.soft !== false) {
10
- const updateData = {
11
- [deletedField]: new Date(),
12
- };
13
-
14
- if (context.user) {
15
- updateData[deletedByField] = context.user._id || context.user.id;
16
- }
17
-
18
- await repo.Model.findByIdAndUpdate(context.id, updateData, { session: context.session });
19
-
20
- context.softDeleted = true;
21
- }
22
- });
23
-
24
- repo.on('before:getAll', (context) => {
25
- if (!context.includeDeleted && options.soft !== false) {
26
- const queryParams = context.queryParams || {};
27
- queryParams.filters = {
28
- ...(queryParams.filters || {}),
29
- [deletedField]: { $exists: false },
30
- };
31
- context.queryParams = queryParams;
32
- }
33
- });
34
-
35
- repo.on('before:getById', (context) => {
36
- if (!context.includeDeleted && options.soft !== false) {
37
- context.query = {
38
- ...(context.query || {}),
39
- [deletedField]: { $exists: false },
40
- };
41
- }
42
- });
43
- },
44
- });
45
-
46
- export default softDeletePlugin;
@@ -1,66 +0,0 @@
1
- /**
2
- * Subdocument Plugin
3
- * Adds subdocument array operations
4
- */
5
-
6
- import createError from 'http-errors';
7
-
8
- export const subdocumentPlugin = () => ({
9
- name: 'subdocument',
10
-
11
- apply(repo) {
12
- if (!repo.registerMethod) {
13
- throw new Error('subdocumentPlugin requires methodRegistryPlugin');
14
- }
15
-
16
- /**
17
- * Add subdocument to array
18
- */
19
- repo.registerMethod('addSubdocument', async function (parentId, arrayPath, subData, options = {}) {
20
- return this.update(parentId, { $push: { [arrayPath]: subData } }, options);
21
- });
22
-
23
- /**
24
- * Get subdocument from array
25
- */
26
- repo.registerMethod('getSubdocument', async function (parentId, arrayPath, subId, options = {}) {
27
- return this._executeQuery(async (Model) => {
28
- const parent = await Model.findById(parentId).session(options.session).exec();
29
- if (!parent) throw createError(404, 'Parent not found');
30
-
31
- const sub = parent[arrayPath].id(subId);
32
- if (!sub) throw createError(404, 'Subdocument not found');
33
-
34
- return options.lean ? sub.toObject() : sub;
35
- });
36
- });
37
-
38
- /**
39
- * Update subdocument in array
40
- */
41
- repo.registerMethod('updateSubdocument', async function (parentId, arrayPath, subId, updateData, options = {}) {
42
- return this._executeQuery(async (Model) => {
43
- const query = { _id: parentId, [`${arrayPath}._id`]: subId };
44
- const update = { $set: { [`${arrayPath}.$`]: { ...updateData, _id: subId } } };
45
-
46
- const result = await Model.findOneAndUpdate(query, update, {
47
- new: true,
48
- runValidators: true,
49
- session: options.session,
50
- }).exec();
51
-
52
- if (!result) throw createError(404, 'Parent or subdocument not found');
53
- return result;
54
- });
55
- });
56
-
57
- /**
58
- * Delete subdocument from array
59
- */
60
- repo.registerMethod('deleteSubdocument', async function (parentId, arrayPath, subId, options = {}) {
61
- return this.update(parentId, { $pull: { [arrayPath]: { _id: subId } } }, options);
62
- });
63
- }
64
- });
65
-
66
- export default subdocumentPlugin;
@@ -1,19 +0,0 @@
1
- export const timestampPlugin = () => ({
2
- name: 'timestamp',
3
-
4
- apply(repo) {
5
- repo.on('before:create', (context) => {
6
- if (!context.data) return;
7
- const now = new Date();
8
- if (!context.data.createdAt) context.data.createdAt = now;
9
- if (!context.data.updatedAt) context.data.updatedAt = now;
10
- });
11
-
12
- repo.on('before:update', (context) => {
13
- if (!context.data) return;
14
- context.data.updatedAt = new Date();
15
- });
16
- },
17
- });
18
-
19
- export default timestampPlugin;
@@ -1,145 +0,0 @@
1
- import createError from 'http-errors';
2
-
3
- export const validationChainPlugin = (validators = [], options = {}) => {
4
- const { stopOnFirstError = true } = options;
5
-
6
- validators.forEach((v, idx) => {
7
- if (!v.name || typeof v.name !== 'string') {
8
- throw new Error(`Validator at index ${idx} missing 'name' (string)`);
9
- }
10
- if (typeof v.validate !== 'function') {
11
- throw new Error(`Validator '${v.name}' missing 'validate' function`);
12
- }
13
- });
14
-
15
- const validatorsByOperation = { create: [], update: [], delete: [], createMany: [] };
16
- const allOperationsValidators = [];
17
-
18
- validators.forEach(v => {
19
- if (!v.operations || v.operations.length === 0) {
20
- allOperationsValidators.push(v);
21
- } else {
22
- v.operations.forEach(op => {
23
- if (validatorsByOperation[op]) {
24
- validatorsByOperation[op].push(v);
25
- }
26
- });
27
- }
28
- });
29
-
30
- return {
31
- name: 'validation-chain',
32
-
33
- apply(repo) {
34
- const getValidatorsForOperation = (operation) => {
35
- const specific = validatorsByOperation[operation] || [];
36
- return [...allOperationsValidators, ...specific];
37
- };
38
-
39
- const runValidators = async (operation, context) => {
40
- const validators = getValidatorsForOperation(operation);
41
- const errors = [];
42
-
43
- for (const validator of validators) {
44
- try {
45
- await validator.validate(context, repo);
46
- } catch (error) {
47
- if (stopOnFirstError) {
48
- throw error;
49
- }
50
- errors.push({
51
- validator: validator.name,
52
- error: error.message || String(error)
53
- });
54
- }
55
- }
56
-
57
- if (errors.length > 0) {
58
- const error = createError(
59
- 400,
60
- `Validation failed: ${errors.map(e => `[${e.validator}] ${e.error}`).join('; ')}`
61
- );
62
- error.validationErrors = errors;
63
- throw error;
64
- }
65
- };
66
-
67
- repo.on('before:create', async (context) => await runValidators('create', context));
68
- repo.on('before:createMany', async (context) => await runValidators('createMany', context));
69
- repo.on('before:update', async (context) => await runValidators('update', context));
70
- repo.on('before:delete', async (context) => await runValidators('delete', context));
71
- }
72
- };
73
- };
74
-
75
- /**
76
- * Block operation if condition is true
77
- * @param {string} name - Validator name
78
- * @param {string[]} operations - Operations to block on
79
- * @param {Function} condition - Condition function (context) => boolean
80
- * @param {string} errorMessage - Error message to throw
81
- * @example blockIf('block-library', ['delete'], ctx => ctx.data.managed, 'Cannot delete managed records')
82
- */
83
- export const blockIf = (name, operations, condition, errorMessage) => ({
84
- name,
85
- operations,
86
- validate: (context) => {
87
- if (condition(context)) {
88
- throw createError(403, errorMessage);
89
- }
90
- }
91
- });
92
-
93
- export const requireField = (field, operations = ['create']) => ({
94
- name: `require-${field}`,
95
- operations,
96
- validate: (context) => {
97
- if (!context.data || context.data[field] === undefined || context.data[field] === null) {
98
- throw createError(400, `Field '${field}' is required`);
99
- }
100
- }
101
- });
102
-
103
- export const autoInject = (field, getter, operations = ['create']) => ({
104
- name: `auto-inject-${field}`,
105
- operations,
106
- validate: (context) => {
107
- if (context.data && !(field in context.data)) {
108
- const value = getter(context);
109
- if (value !== null && value !== undefined) {
110
- context.data[field] = value;
111
- }
112
- }
113
- }
114
- });
115
-
116
- export const immutableField = (field) => ({
117
- name: `immutable-${field}`,
118
- operations: ['update'],
119
- validate: (context) => {
120
- if (context.data && field in context.data) {
121
- throw createError(400, `Field '${field}' cannot be modified`);
122
- }
123
- }
124
- });
125
-
126
- export const uniqueField = (field, errorMessage) => ({
127
- name: `unique-${field}`,
128
- operations: ['create', 'update'],
129
- validate: async (context, repo) => {
130
- if (!context.data || !context.data[field]) return;
131
-
132
- const query = { [field]: context.data[field] };
133
- const existing = await repo.getByQuery(query, {
134
- select: '_id',
135
- lean: true,
136
- throwOnNotFound: false
137
- });
138
-
139
- if (existing && existing._id.toString() !== context.id?.toString()) {
140
- throw createError(409, errorMessage || `${field} already exists`);
141
- }
142
- }
143
- });
144
-
145
- export default validationChainPlugin;