@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.
- package/README.md +221 -7
- package/dist/actions/index.cjs +479 -0
- package/dist/actions/index.cjs.map +1 -0
- package/dist/actions/index.d.cts +3 -0
- package/dist/actions/index.d.ts +3 -0
- package/dist/actions/index.js +473 -0
- package/dist/actions/index.js.map +1 -0
- package/dist/index-BfVJZF-3.d.cts +337 -0
- package/dist/index-CgOJ2pqz.d.ts +337 -0
- package/dist/index.cjs +2142 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +239 -0
- package/dist/index.d.ts +239 -0
- package/dist/index.js +2108 -0
- package/dist/index.js.map +1 -0
- package/dist/memory-cache-DG2oSSbx.d.ts +142 -0
- package/dist/memory-cache-DqfFfKes.d.cts +142 -0
- package/dist/pagination/PaginationEngine.cjs +375 -0
- package/dist/pagination/PaginationEngine.cjs.map +1 -0
- package/dist/pagination/PaginationEngine.d.cts +117 -0
- package/dist/pagination/PaginationEngine.d.ts +117 -0
- package/dist/pagination/PaginationEngine.js +369 -0
- package/dist/pagination/PaginationEngine.js.map +1 -0
- package/dist/plugins/index.cjs +874 -0
- package/dist/plugins/index.cjs.map +1 -0
- package/dist/plugins/index.d.cts +275 -0
- package/dist/plugins/index.d.ts +275 -0
- package/dist/plugins/index.js +857 -0
- package/dist/plugins/index.js.map +1 -0
- package/dist/types-Nxhmi1aI.d.cts +510 -0
- package/dist/types-Nxhmi1aI.d.ts +510 -0
- package/dist/utils/index.cjs +667 -0
- package/dist/utils/index.cjs.map +1 -0
- package/dist/utils/index.d.cts +189 -0
- package/dist/utils/index.d.ts +189 -0
- package/dist/utils/index.js +643 -0
- package/dist/utils/index.js.map +1 -0
- package/package.json +44 -21
- package/src/Repository.js +0 -296
- package/src/actions/aggregate.js +0 -266
- package/src/actions/create.js +0 -59
- package/src/actions/delete.js +0 -88
- package/src/actions/index.js +0 -11
- package/src/actions/read.js +0 -188
- package/src/actions/update.js +0 -176
- package/src/hooks/lifecycle.js +0 -146
- package/src/index.js +0 -71
- package/src/pagination/PaginationEngine.js +0 -348
- package/src/pagination/utils/cursor.js +0 -119
- package/src/pagination/utils/filter.js +0 -42
- package/src/pagination/utils/limits.js +0 -82
- package/src/pagination/utils/sort.js +0 -101
- package/src/plugins/aggregate-helpers.plugin.js +0 -71
- package/src/plugins/audit-log.plugin.js +0 -60
- package/src/plugins/batch-operations.plugin.js +0 -66
- package/src/plugins/field-filter.plugin.js +0 -27
- package/src/plugins/index.js +0 -19
- package/src/plugins/method-registry.plugin.js +0 -140
- package/src/plugins/mongo-operations.plugin.js +0 -317
- package/src/plugins/soft-delete.plugin.js +0 -46
- package/src/plugins/subdocument.plugin.js +0 -66
- package/src/plugins/timestamp.plugin.js +0 -19
- package/src/plugins/validation-chain.plugin.js +0 -145
- package/src/types.d.ts +0 -87
- package/src/utils/error.js +0 -12
- package/src/utils/field-selection.js +0 -156
- package/src/utils/index.js +0 -12
- package/types/Repository.d.ts +0 -95
- package/types/Repository.d.ts.map +0 -1
- package/types/actions/aggregate.d.ts +0 -112
- package/types/actions/aggregate.d.ts.map +0 -1
- package/types/actions/create.d.ts +0 -21
- package/types/actions/create.d.ts.map +0 -1
- package/types/actions/delete.d.ts +0 -37
- package/types/actions/delete.d.ts.map +0 -1
- package/types/actions/index.d.ts +0 -6
- package/types/actions/index.d.ts.map +0 -1
- package/types/actions/read.d.ts +0 -135
- package/types/actions/read.d.ts.map +0 -1
- package/types/actions/update.d.ts +0 -58
- package/types/actions/update.d.ts.map +0 -1
- package/types/hooks/lifecycle.d.ts +0 -44
- package/types/hooks/lifecycle.d.ts.map +0 -1
- package/types/index.d.ts +0 -25
- package/types/index.d.ts.map +0 -1
- package/types/pagination/PaginationEngine.d.ts +0 -386
- package/types/pagination/PaginationEngine.d.ts.map +0 -1
- package/types/pagination/utils/cursor.d.ts +0 -40
- package/types/pagination/utils/cursor.d.ts.map +0 -1
- package/types/pagination/utils/filter.d.ts +0 -28
- package/types/pagination/utils/filter.d.ts.map +0 -1
- package/types/pagination/utils/limits.d.ts +0 -64
- package/types/pagination/utils/limits.d.ts.map +0 -1
- package/types/pagination/utils/sort.d.ts +0 -41
- package/types/pagination/utils/sort.d.ts.map +0 -1
- package/types/plugins/aggregate-helpers.plugin.d.ts +0 -6
- package/types/plugins/aggregate-helpers.plugin.d.ts.map +0 -1
- package/types/plugins/audit-log.plugin.d.ts +0 -6
- package/types/plugins/audit-log.plugin.d.ts.map +0 -1
- package/types/plugins/batch-operations.plugin.d.ts +0 -6
- package/types/plugins/batch-operations.plugin.d.ts.map +0 -1
- package/types/plugins/field-filter.plugin.d.ts +0 -6
- package/types/plugins/field-filter.plugin.d.ts.map +0 -1
- package/types/plugins/index.d.ts +0 -11
- package/types/plugins/index.d.ts.map +0 -1
- package/types/plugins/method-registry.plugin.d.ts +0 -3
- package/types/plugins/method-registry.plugin.d.ts.map +0 -1
- package/types/plugins/mongo-operations.plugin.d.ts +0 -4
- package/types/plugins/mongo-operations.plugin.d.ts.map +0 -1
- package/types/plugins/soft-delete.plugin.d.ts +0 -6
- package/types/plugins/soft-delete.plugin.d.ts.map +0 -1
- package/types/plugins/subdocument.plugin.d.ts +0 -6
- package/types/plugins/subdocument.plugin.d.ts.map +0 -1
- package/types/plugins/timestamp.plugin.d.ts +0 -6
- package/types/plugins/timestamp.plugin.d.ts.map +0 -1
- package/types/plugins/validation-chain.plugin.d.ts +0 -31
- package/types/plugins/validation-chain.plugin.d.ts.map +0 -1
- package/types/utils/error.d.ts +0 -11
- package/types/utils/error.d.ts.map +0 -1
- package/types/utils/field-selection.d.ts +0 -9
- package/types/utils/field-selection.d.ts.map +0 -1
- package/types/utils/index.d.ts +0 -2
- package/types/utils/index.d.ts.map +0 -1
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Aggregate Helpers Plugin
|
|
3
|
-
* Adds common aggregation helper methods
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
export const aggregateHelpersPlugin = () => ({
|
|
7
|
-
name: 'aggregate-helpers',
|
|
8
|
-
|
|
9
|
-
apply(repo) {
|
|
10
|
-
if (!repo.registerMethod) {
|
|
11
|
-
throw new Error('aggregateHelpersPlugin requires methodRegistryPlugin');
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Group by field
|
|
16
|
-
*/
|
|
17
|
-
repo.registerMethod('groupBy', async function (field, options = {}) {
|
|
18
|
-
const pipeline = [
|
|
19
|
-
{ $group: { _id: `$${field}`, count: { $sum: 1 } } },
|
|
20
|
-
{ $sort: { count: -1 } }
|
|
21
|
-
];
|
|
22
|
-
|
|
23
|
-
if (options.limit) {
|
|
24
|
-
pipeline.push(/** @type {any} */({ $limit: options.limit }));
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
return this.aggregate(pipeline, options);
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
// Helper: Generic aggregation operation
|
|
31
|
-
const aggregateOperation = async function (field, operator, resultKey, query = {}, options = {}) {
|
|
32
|
-
const pipeline = [
|
|
33
|
-
{ $match: query },
|
|
34
|
-
{ $group: { _id: null, [resultKey]: { [operator]: `$${field}` } } }
|
|
35
|
-
];
|
|
36
|
-
|
|
37
|
-
const result = await this.aggregate(pipeline, options);
|
|
38
|
-
return result[0]?.[resultKey] || 0;
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Sum field values
|
|
43
|
-
*/
|
|
44
|
-
repo.registerMethod('sum', async function (field, query = {}, options = {}) {
|
|
45
|
-
return aggregateOperation.call(this, field, '$sum', 'total', query, options);
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Average field values
|
|
50
|
-
*/
|
|
51
|
-
repo.registerMethod('average', async function (field, query = {}, options = {}) {
|
|
52
|
-
return aggregateOperation.call(this, field, '$avg', 'avg', query, options);
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Get minimum value
|
|
57
|
-
*/
|
|
58
|
-
repo.registerMethod('min', async function (field, query = {}, options = {}) {
|
|
59
|
-
return aggregateOperation.call(this, field, '$min', 'min', query, options);
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Get maximum value
|
|
64
|
-
*/
|
|
65
|
-
repo.registerMethod('max', async function (field, query = {}, options = {}) {
|
|
66
|
-
return aggregateOperation.call(this, field, '$max', 'max', query, options);
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
export default aggregateHelpersPlugin;
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
export const auditLogPlugin = (logger) => ({
|
|
2
|
-
name: 'auditLog',
|
|
3
|
-
|
|
4
|
-
apply(repo) {
|
|
5
|
-
repo.on('after:create', ({ context, result }) => {
|
|
6
|
-
logger?.info?.('Document created', {
|
|
7
|
-
model: context.model || repo.model,
|
|
8
|
-
id: result._id,
|
|
9
|
-
userId: context.user?._id || context.user?.id,
|
|
10
|
-
organizationId: context.organizationId,
|
|
11
|
-
});
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
repo.on('after:update', ({ context, result }) => {
|
|
15
|
-
logger?.info?.('Document updated', {
|
|
16
|
-
model: context.model || repo.model,
|
|
17
|
-
id: context.id || result._id,
|
|
18
|
-
userId: context.user?._id || context.user?.id,
|
|
19
|
-
organizationId: context.organizationId,
|
|
20
|
-
});
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
repo.on('after:delete', ({ context, result }) => {
|
|
24
|
-
logger?.info?.('Document deleted', {
|
|
25
|
-
model: context.model || repo.model,
|
|
26
|
-
id: context.id,
|
|
27
|
-
userId: context.user?._id || context.user?.id,
|
|
28
|
-
organizationId: context.organizationId,
|
|
29
|
-
});
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
repo.on('error:create', ({ context, error }) => {
|
|
33
|
-
logger?.error?.('Create failed', {
|
|
34
|
-
model: context.model || repo.model,
|
|
35
|
-
error: error.message,
|
|
36
|
-
userId: context.user?._id || context.user?.id,
|
|
37
|
-
});
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
repo.on('error:update', ({ context, error }) => {
|
|
41
|
-
logger?.error?.('Update failed', {
|
|
42
|
-
model: context.model || repo.model,
|
|
43
|
-
id: context.id,
|
|
44
|
-
error: error.message,
|
|
45
|
-
userId: context.user?._id || context.user?.id,
|
|
46
|
-
});
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
repo.on('error:delete', ({ context, error }) => {
|
|
50
|
-
logger?.error?.('Delete failed', {
|
|
51
|
-
model: context.model || repo.model,
|
|
52
|
-
id: context.id,
|
|
53
|
-
error: error.message,
|
|
54
|
-
userId: context.user?._id || context.user?.id,
|
|
55
|
-
});
|
|
56
|
-
});
|
|
57
|
-
},
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
export default auditLogPlugin;
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Batch Operations Plugin
|
|
3
|
-
* Adds bulk update/delete operations with proper event emission
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
export const batchOperationsPlugin = () => ({
|
|
7
|
-
name: 'batch-operations',
|
|
8
|
-
|
|
9
|
-
apply(repo) {
|
|
10
|
-
if (!repo.registerMethod) {
|
|
11
|
-
throw new Error('batchOperationsPlugin requires methodRegistryPlugin');
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Update multiple documents
|
|
16
|
-
* @param {Object} query - MongoDB query to match documents
|
|
17
|
-
* @param {Object} data - Update data
|
|
18
|
-
* @param {Object} options - Additional options (session, context)
|
|
19
|
-
* @returns {Promise<Object>} MongoDB update result
|
|
20
|
-
*/
|
|
21
|
-
repo.registerMethod('updateMany', async function (query, data, options = {}) {
|
|
22
|
-
const context = await this._buildContext('updateMany', { query, data, options });
|
|
23
|
-
|
|
24
|
-
try {
|
|
25
|
-
this.emit('before:updateMany', context);
|
|
26
|
-
|
|
27
|
-
const result = await this.Model.updateMany(query, data, {
|
|
28
|
-
runValidators: true,
|
|
29
|
-
session: options.session,
|
|
30
|
-
}).exec();
|
|
31
|
-
|
|
32
|
-
this.emit('after:updateMany', { context, result });
|
|
33
|
-
return result;
|
|
34
|
-
} catch (error) {
|
|
35
|
-
this.emit('error:updateMany', { context, error });
|
|
36
|
-
throw this._handleError(error);
|
|
37
|
-
}
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Delete multiple documents
|
|
42
|
-
* @param {Object} query - MongoDB query to match documents
|
|
43
|
-
* @param {Object} options - Additional options (session, context)
|
|
44
|
-
* @returns {Promise<Object>} MongoDB delete result
|
|
45
|
-
*/
|
|
46
|
-
repo.registerMethod('deleteMany', async function (query, options = {}) {
|
|
47
|
-
const context = await this._buildContext('deleteMany', { query, options });
|
|
48
|
-
|
|
49
|
-
try {
|
|
50
|
-
this.emit('before:deleteMany', context);
|
|
51
|
-
|
|
52
|
-
const result = await this.Model.deleteMany(query, {
|
|
53
|
-
session: options.session,
|
|
54
|
-
}).exec();
|
|
55
|
-
|
|
56
|
-
this.emit('after:deleteMany', { context, result });
|
|
57
|
-
return result;
|
|
58
|
-
} catch (error) {
|
|
59
|
-
this.emit('error:deleteMany', { context, error });
|
|
60
|
-
throw this._handleError(error);
|
|
61
|
-
}
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
export default batchOperationsPlugin;
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { getFieldsForUser } from '../utils/field-selection.js';
|
|
2
|
-
|
|
3
|
-
export const fieldFilterPlugin = (fieldPreset) => ({
|
|
4
|
-
name: 'fieldFilter',
|
|
5
|
-
|
|
6
|
-
apply(repo) {
|
|
7
|
-
const applyFieldFiltering = (context) => {
|
|
8
|
-
if (!fieldPreset) return;
|
|
9
|
-
|
|
10
|
-
const user = context.context?.user || context.user;
|
|
11
|
-
const fields = getFieldsForUser(user, fieldPreset);
|
|
12
|
-
const presetSelect = fields.join(' ');
|
|
13
|
-
|
|
14
|
-
if (context.select) {
|
|
15
|
-
context.select = `${presetSelect} ${context.select}`;
|
|
16
|
-
} else {
|
|
17
|
-
context.select = presetSelect;
|
|
18
|
-
}
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
repo.on('before:getAll', applyFieldFiltering);
|
|
22
|
-
repo.on('before:getById', applyFieldFiltering);
|
|
23
|
-
repo.on('before:getByQuery', applyFieldFiltering);
|
|
24
|
-
},
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
export default fieldFilterPlugin;
|
package/src/plugins/index.js
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
// Core plugins
|
|
2
|
-
export { fieldFilterPlugin } from './field-filter.plugin.js';
|
|
3
|
-
export { timestampPlugin } from './timestamp.plugin.js';
|
|
4
|
-
export { auditLogPlugin } from './audit-log.plugin.js';
|
|
5
|
-
export { softDeletePlugin } from './soft-delete.plugin.js';
|
|
6
|
-
export { methodRegistryPlugin } from './method-registry.plugin.js';
|
|
7
|
-
export {
|
|
8
|
-
validationChainPlugin,
|
|
9
|
-
blockIf,
|
|
10
|
-
requireField,
|
|
11
|
-
autoInject,
|
|
12
|
-
immutableField,
|
|
13
|
-
uniqueField,
|
|
14
|
-
} from './validation-chain.plugin.js';
|
|
15
|
-
export { mongoOperationsPlugin } from './mongo-operations.plugin.js';
|
|
16
|
-
export { batchOperationsPlugin } from './batch-operations.plugin.js';
|
|
17
|
-
export { aggregateHelpersPlugin } from './aggregate-helpers.plugin.js';
|
|
18
|
-
export { subdocumentPlugin } from './subdocument.plugin.js';
|
|
19
|
-
|
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Method Registry Plugin
|
|
3
|
-
*
|
|
4
|
-
* Enables plugins to dynamically add methods to repository instances.
|
|
5
|
-
* Foundation for extensibility - allows other plugins to extend repositories
|
|
6
|
-
* with custom methods while maintaining type safety and proper binding.
|
|
7
|
-
*
|
|
8
|
-
* **Pattern:** Inspired by Stripe's extension system
|
|
9
|
-
* **Philosophy:** Repositories start minimal, plugins add capabilities
|
|
10
|
-
*
|
|
11
|
-
* @module common/repositories/plugins/method-registry
|
|
12
|
-
*
|
|
13
|
-
* @example Basic Usage
|
|
14
|
-
* ```js
|
|
15
|
-
* import { Repository } from '../Repository.js';
|
|
16
|
-
* import { methodRegistryPlugin } from './method-registry.plugin.js';
|
|
17
|
-
*
|
|
18
|
-
* class UserRepository extends Repository {
|
|
19
|
-
* constructor() {
|
|
20
|
-
* super(User, [methodRegistryPlugin()]);
|
|
21
|
-
*
|
|
22
|
-
* // Now you can register custom methods
|
|
23
|
-
* this.registerMethod('findActive', async function() {
|
|
24
|
-
* return this.getAll({ filters: { status: 'active' } });
|
|
25
|
-
* });
|
|
26
|
-
* }
|
|
27
|
-
* }
|
|
28
|
-
* ```
|
|
29
|
-
*
|
|
30
|
-
* @example Plugin Using Method Registry
|
|
31
|
-
* ```js
|
|
32
|
-
* // Other plugins can use registerMethod to add functionality
|
|
33
|
-
* export const mongoOperationsPlugin = () => ({
|
|
34
|
-
* name: 'mongo-operations',
|
|
35
|
-
* apply(repo) {
|
|
36
|
-
* repo.registerMethod('increment', async function(id, field, value = 1, options = {}) {
|
|
37
|
-
* return this.update(id, { $inc: { [field]: value } }, options);
|
|
38
|
-
* });
|
|
39
|
-
* }
|
|
40
|
-
* });
|
|
41
|
-
* ```
|
|
42
|
-
*/
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Method Registry Plugin
|
|
46
|
-
*
|
|
47
|
-
* Adds `registerMethod()` to repository instance, allowing dynamic method addition.
|
|
48
|
-
*
|
|
49
|
-
* @returns {Object} Plugin configuration
|
|
50
|
-
*/
|
|
51
|
-
export const methodRegistryPlugin = () => ({
|
|
52
|
-
name: 'method-registry',
|
|
53
|
-
|
|
54
|
-
apply(repo) {
|
|
55
|
-
/**
|
|
56
|
-
* Register a new method on the repository instance
|
|
57
|
-
*
|
|
58
|
-
* **Rules:**
|
|
59
|
-
* - Method name must not conflict with existing methods
|
|
60
|
-
* - Method is automatically bound to repository instance
|
|
61
|
-
* - Method has access to all repository methods via `this`
|
|
62
|
-
* - Async methods are recommended for consistency
|
|
63
|
-
*
|
|
64
|
-
* @param {string} name - Method name
|
|
65
|
-
* @param {Function} fn - Method implementation (will be bound to repo)
|
|
66
|
-
* @throws {Error} If method name already exists
|
|
67
|
-
*
|
|
68
|
-
* @example
|
|
69
|
-
* repo.registerMethod('findByEmail', async function(email) {
|
|
70
|
-
* return this.getByQuery({ email }, { lean: true });
|
|
71
|
-
* });
|
|
72
|
-
*
|
|
73
|
-
* @example With options
|
|
74
|
-
* repo.registerMethod('incrementViews', async function(id, amount = 1) {
|
|
75
|
-
* return this.update(id, { $inc: { views: amount } });
|
|
76
|
-
* });
|
|
77
|
-
*/
|
|
78
|
-
repo.registerMethod = function (name, fn) {
|
|
79
|
-
// Check for naming conflicts
|
|
80
|
-
if (repo[name]) {
|
|
81
|
-
throw new Error(
|
|
82
|
-
`Cannot register method '${name}': Method already exists on repository. ` +
|
|
83
|
-
`Choose a different name or use a plugin that doesn't conflict.`
|
|
84
|
-
);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// Validate method name
|
|
88
|
-
if (!name || typeof name !== 'string') {
|
|
89
|
-
throw new Error('Method name must be a non-empty string');
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// Validate function
|
|
93
|
-
if (typeof fn !== 'function') {
|
|
94
|
-
throw new Error(`Method '${name}' must be a function`);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Bind function to repository instance
|
|
98
|
-
repo[name] = fn.bind(repo);
|
|
99
|
-
|
|
100
|
-
// Emit event for plugin system awareness
|
|
101
|
-
repo.emit('method:registered', { name, fn });
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Check if a method is registered
|
|
106
|
-
*
|
|
107
|
-
* @param {string} name - Method name to check
|
|
108
|
-
* @returns {boolean} True if method exists
|
|
109
|
-
*
|
|
110
|
-
* @example
|
|
111
|
-
* if (repo.hasMethod('increment')) {
|
|
112
|
-
* await repo.increment(id, 'count', 1);
|
|
113
|
-
* }
|
|
114
|
-
*/
|
|
115
|
-
repo.hasMethod = function (name) {
|
|
116
|
-
return typeof repo[name] === 'function';
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Get list of all dynamically registered methods
|
|
121
|
-
*
|
|
122
|
-
* @returns {Array<string>} Array of method names
|
|
123
|
-
*
|
|
124
|
-
* @example
|
|
125
|
-
* const methods = repo.getRegisteredMethods();
|
|
126
|
-
* console.log('Available methods:', methods);
|
|
127
|
-
*/
|
|
128
|
-
repo.getRegisteredMethods = function () {
|
|
129
|
-
const registeredMethods = [];
|
|
130
|
-
|
|
131
|
-
repo.on('method:registered', ({ name }) => {
|
|
132
|
-
registeredMethods.push(name);
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
return registeredMethods;
|
|
136
|
-
};
|
|
137
|
-
}
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
export default methodRegistryPlugin;
|
|
@@ -1,317 +0,0 @@
|
|
|
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;
|