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