@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.
- package/README.md +772 -151
- 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 +54 -24
- package/src/Repository.js +0 -225
- package/src/actions/aggregate.js +0 -191
- 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 -156
- package/src/actions/update.js +0 -176
- package/src/hooks/lifecycle.js +0 -146
- package/src/index.js +0 -60
- 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 -313
- 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/utils/field-selection.js +0 -156
- package/src/utils/index.js +0 -12
- package/types/actions/index.d.ts +0 -121
- package/types/index.d.ts +0 -104
- package/types/plugins/index.d.ts +0 -88
- package/types/utils/index.d.ts +0 -24
package/src/hooks/lifecycle.js
DELETED
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Lifecycle Hooks
|
|
3
|
-
* Event system for repository actions
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { EventEmitter } from 'events';
|
|
7
|
-
|
|
8
|
-
export class RepositoryLifecycle extends EventEmitter {
|
|
9
|
-
constructor() {
|
|
10
|
-
super();
|
|
11
|
-
this.hooks = new Map();
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Register hook
|
|
16
|
-
*/
|
|
17
|
-
on(event, handler) {
|
|
18
|
-
if (!this.hooks.has(event)) {
|
|
19
|
-
this.hooks.set(event, []);
|
|
20
|
-
}
|
|
21
|
-
this.hooks.get(event).push(handler);
|
|
22
|
-
return super.on(event, handler);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Execute hooks before action
|
|
27
|
-
*/
|
|
28
|
-
async runBeforeHooks(action, context) {
|
|
29
|
-
const event = `before:${action}`;
|
|
30
|
-
await this.emit(event, context);
|
|
31
|
-
|
|
32
|
-
const hooks = this.hooks.get(event) || [];
|
|
33
|
-
for (const hook of hooks) {
|
|
34
|
-
await hook(context);
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Execute hooks after action
|
|
40
|
-
*/
|
|
41
|
-
async runAfterHooks(action, context, result) {
|
|
42
|
-
const event = `after:${action}`;
|
|
43
|
-
await this.emit(event, context, result);
|
|
44
|
-
|
|
45
|
-
const hooks = this.hooks.get(event) || [];
|
|
46
|
-
for (const hook of hooks) {
|
|
47
|
-
await hook(context, result);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Execute hooks on error
|
|
53
|
-
*/
|
|
54
|
-
async runErrorHooks(action, context, error) {
|
|
55
|
-
const event = `error:${action}`;
|
|
56
|
-
await this.emit(event, context, error);
|
|
57
|
-
|
|
58
|
-
const hooks = this.hooks.get(event) || [];
|
|
59
|
-
for (const hook of hooks) {
|
|
60
|
-
await hook(context, error);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Hook decorators for common patterns
|
|
67
|
-
*/
|
|
68
|
-
export const hooks = {
|
|
69
|
-
/**
|
|
70
|
-
* Auto-timestamp before create/update
|
|
71
|
-
*/
|
|
72
|
-
autoTimestamp: () => ({
|
|
73
|
-
'before:create': (context) => {
|
|
74
|
-
context.data.createdAt = new Date();
|
|
75
|
-
context.data.updatedAt = new Date();
|
|
76
|
-
},
|
|
77
|
-
'before:update': (context) => {
|
|
78
|
-
context.data.updatedAt = new Date();
|
|
79
|
-
},
|
|
80
|
-
}),
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Auto-inject user context
|
|
84
|
-
*/
|
|
85
|
-
autoUser: (userField = 'userId') => ({
|
|
86
|
-
'before:create': (context) => {
|
|
87
|
-
if (context.user && !context.data[userField]) {
|
|
88
|
-
context.data[userField] = context.user._id || context.user.id;
|
|
89
|
-
}
|
|
90
|
-
},
|
|
91
|
-
}),
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Auto-inject organization scope
|
|
95
|
-
*/
|
|
96
|
-
autoOrganization: (orgField = 'organizationId') => ({
|
|
97
|
-
'before:create': (context) => {
|
|
98
|
-
if (context.organizationId && !context.data[orgField]) {
|
|
99
|
-
context.data[orgField] = context.organizationId;
|
|
100
|
-
}
|
|
101
|
-
},
|
|
102
|
-
}),
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Audit log
|
|
106
|
-
*/
|
|
107
|
-
auditLog: (logger) => ({
|
|
108
|
-
'after:create': (context, result) => {
|
|
109
|
-
logger.info('Document created', {
|
|
110
|
-
model: context.model,
|
|
111
|
-
id: result._id,
|
|
112
|
-
user: context.user?.id,
|
|
113
|
-
});
|
|
114
|
-
},
|
|
115
|
-
'after:update': (context, result) => {
|
|
116
|
-
logger.info('Document updated', {
|
|
117
|
-
model: context.model,
|
|
118
|
-
id: result._id,
|
|
119
|
-
user: context.user?.id,
|
|
120
|
-
});
|
|
121
|
-
},
|
|
122
|
-
'after:delete': (context, result) => {
|
|
123
|
-
logger.info('Document deleted', {
|
|
124
|
-
model: context.model,
|
|
125
|
-
user: context.user?.id,
|
|
126
|
-
});
|
|
127
|
-
},
|
|
128
|
-
}),
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* Cache invalidation
|
|
132
|
-
*/
|
|
133
|
-
cacheInvalidation: (cache) => ({
|
|
134
|
-
'after:create': async (context, result) => {
|
|
135
|
-
await cache.invalidate(`${context.model}:*`);
|
|
136
|
-
},
|
|
137
|
-
'after:update': async (context, result) => {
|
|
138
|
-
await cache.invalidate(`${context.model}:${result._id}`);
|
|
139
|
-
await cache.invalidate(`${context.model}:*`);
|
|
140
|
-
},
|
|
141
|
-
'after:delete': async (context) => {
|
|
142
|
-
await cache.invalidate(`${context.model}:*`);
|
|
143
|
-
},
|
|
144
|
-
}),
|
|
145
|
-
};
|
|
146
|
-
|
package/src/index.js
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Repository Pattern - Data Access Layer
|
|
3
|
-
*
|
|
4
|
-
* Event-driven, plugin-based abstraction for MongoDB operations
|
|
5
|
-
* Inspired by Meta & Stripe's repository patterns
|
|
6
|
-
*
|
|
7
|
-
* @module common/repositories
|
|
8
|
-
*
|
|
9
|
-
* Documentation:
|
|
10
|
-
* - README.md - Main documentation (concise overview)
|
|
11
|
-
* - QUICK_REFERENCE.md - One-page cheat sheet
|
|
12
|
-
* - EXAMPLES.md - Detailed examples and patterns
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* MongoKit - Event-driven repository pattern for MongoDB
|
|
17
|
-
*
|
|
18
|
-
* @module @classytic/mongokit
|
|
19
|
-
* @author Sadman Chowdhury (Github: @siam923)
|
|
20
|
-
* @license MIT
|
|
21
|
-
*/
|
|
22
|
-
|
|
23
|
-
export { Repository } from './Repository.js';
|
|
24
|
-
|
|
25
|
-
// Plugins
|
|
26
|
-
export { fieldFilterPlugin } from './plugins/field-filter.plugin.js';
|
|
27
|
-
export { timestampPlugin } from './plugins/timestamp.plugin.js';
|
|
28
|
-
export { auditLogPlugin } from './plugins/audit-log.plugin.js';
|
|
29
|
-
export { softDeletePlugin } from './plugins/soft-delete.plugin.js';
|
|
30
|
-
export { methodRegistryPlugin } from './plugins/method-registry.plugin.js';
|
|
31
|
-
export {
|
|
32
|
-
validationChainPlugin,
|
|
33
|
-
blockIf,
|
|
34
|
-
requireField,
|
|
35
|
-
autoInject,
|
|
36
|
-
immutableField,
|
|
37
|
-
uniqueField,
|
|
38
|
-
} from './plugins/validation-chain.plugin.js';
|
|
39
|
-
export { mongoOperationsPlugin } from './plugins/mongo-operations.plugin.js';
|
|
40
|
-
export { batchOperationsPlugin } from './plugins/batch-operations.plugin.js';
|
|
41
|
-
export { aggregateHelpersPlugin } from './plugins/aggregate-helpers.plugin.js';
|
|
42
|
-
export { subdocumentPlugin } from './plugins/subdocument.plugin.js';
|
|
43
|
-
|
|
44
|
-
// Utilities
|
|
45
|
-
export {
|
|
46
|
-
getFieldsForUser,
|
|
47
|
-
getMongooseProjection,
|
|
48
|
-
filterResponseData,
|
|
49
|
-
createFieldPreset,
|
|
50
|
-
} from './utils/field-selection.js';
|
|
51
|
-
|
|
52
|
-
export * as actions from './actions/index.js';
|
|
53
|
-
|
|
54
|
-
import { Repository } from './Repository.js';
|
|
55
|
-
|
|
56
|
-
export const createRepository = (Model, plugins = []) => {
|
|
57
|
-
return new Repository(Model, plugins);
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
export default Repository;
|
|
@@ -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({ $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;
|