@classytic/mongokit 1.0.2 → 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.
- package/README.md +562 -155
- package/package.json +17 -10
- package/src/Repository.js +296 -225
- package/src/actions/aggregate.js +266 -191
- package/src/actions/create.js +47 -47
- package/src/actions/delete.js +88 -88
- package/src/actions/index.js +11 -11
- package/src/actions/read.js +176 -144
- package/src/actions/update.js +144 -144
- package/src/hooks/lifecycle.js +146 -146
- package/src/index.js +71 -60
- package/src/pagination/PaginationEngine.js +348 -0
- package/src/pagination/utils/cursor.js +119 -0
- package/src/pagination/utils/filter.js +42 -0
- package/src/pagination/utils/limits.js +82 -0
- package/src/pagination/utils/sort.js +101 -0
- package/src/plugins/aggregate-helpers.plugin.js +71 -71
- package/src/plugins/audit-log.plugin.js +60 -60
- package/src/plugins/batch-operations.plugin.js +66 -66
- package/src/plugins/field-filter.plugin.js +27 -27
- package/src/plugins/index.js +19 -19
- package/src/plugins/method-registry.plugin.js +140 -140
- package/src/plugins/mongo-operations.plugin.js +317 -313
- package/src/plugins/soft-delete.plugin.js +46 -46
- package/src/plugins/subdocument.plugin.js +66 -66
- package/src/plugins/timestamp.plugin.js +19 -19
- package/src/plugins/validation-chain.plugin.js +145 -145
- package/src/types.d.ts +87 -0
- package/src/utils/error.js +12 -0
- package/src/utils/field-selection.js +156 -156
- package/src/utils/index.js +12 -12
- package/types/Repository.d.ts +95 -0
- package/types/Repository.d.ts.map +1 -0
- package/types/actions/aggregate.d.ts +112 -0
- package/types/actions/aggregate.d.ts.map +1 -0
- package/types/actions/create.d.ts +21 -0
- package/types/actions/create.d.ts.map +1 -0
- package/types/actions/delete.d.ts +37 -0
- package/types/actions/delete.d.ts.map +1 -0
- package/types/actions/index.d.ts +6 -121
- package/types/actions/index.d.ts.map +1 -0
- package/types/actions/read.d.ts +135 -0
- package/types/actions/read.d.ts.map +1 -0
- package/types/actions/update.d.ts +58 -0
- package/types/actions/update.d.ts.map +1 -0
- package/types/hooks/lifecycle.d.ts +44 -0
- package/types/hooks/lifecycle.d.ts.map +1 -0
- package/types/index.d.ts +25 -104
- package/types/index.d.ts.map +1 -0
- package/types/pagination/PaginationEngine.d.ts +386 -0
- package/types/pagination/PaginationEngine.d.ts.map +1 -0
- package/types/pagination/utils/cursor.d.ts +40 -0
- package/types/pagination/utils/cursor.d.ts.map +1 -0
- package/types/pagination/utils/filter.d.ts +28 -0
- package/types/pagination/utils/filter.d.ts.map +1 -0
- package/types/pagination/utils/limits.d.ts +64 -0
- package/types/pagination/utils/limits.d.ts.map +1 -0
- package/types/pagination/utils/sort.d.ts +41 -0
- package/types/pagination/utils/sort.d.ts.map +1 -0
- package/types/plugins/aggregate-helpers.plugin.d.ts +6 -0
- package/types/plugins/aggregate-helpers.plugin.d.ts.map +1 -0
- package/types/plugins/audit-log.plugin.d.ts +6 -0
- package/types/plugins/audit-log.plugin.d.ts.map +1 -0
- package/types/plugins/batch-operations.plugin.d.ts +6 -0
- package/types/plugins/batch-operations.plugin.d.ts.map +1 -0
- package/types/plugins/field-filter.plugin.d.ts +6 -0
- package/types/plugins/field-filter.plugin.d.ts.map +1 -0
- package/types/plugins/index.d.ts +11 -88
- package/types/plugins/index.d.ts.map +1 -0
- package/types/plugins/method-registry.plugin.d.ts +3 -0
- package/types/plugins/method-registry.plugin.d.ts.map +1 -0
- package/types/plugins/mongo-operations.plugin.d.ts +4 -0
- package/types/plugins/mongo-operations.plugin.d.ts.map +1 -0
- package/types/plugins/soft-delete.plugin.d.ts +6 -0
- package/types/plugins/soft-delete.plugin.d.ts.map +1 -0
- package/types/plugins/subdocument.plugin.d.ts +6 -0
- package/types/plugins/subdocument.plugin.d.ts.map +1 -0
- package/types/plugins/timestamp.plugin.d.ts +6 -0
- package/types/plugins/timestamp.plugin.d.ts.map +1 -0
- package/types/plugins/validation-chain.plugin.d.ts +31 -0
- package/types/plugins/validation-chain.plugin.d.ts.map +1 -0
- package/types/utils/error.d.ts +11 -0
- package/types/utils/error.d.ts.map +1 -0
- package/types/utils/field-selection.d.ts +9 -0
- package/types/utils/field-selection.d.ts.map +1 -0
- package/types/utils/index.d.ts +2 -24
- package/types/utils/index.d.ts.map +1 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {Object} PaginationConfig
|
|
3
|
+
* @property {number} defaultLimit - Default limit value
|
|
4
|
+
* @property {number} maxLimit - Maximum allowed limit
|
|
5
|
+
* @property {number} maxPage - Maximum allowed page number
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Validates and sanitizes limit value
|
|
10
|
+
* Parses strings to numbers and prevents NaN bugs
|
|
11
|
+
*
|
|
12
|
+
* @param {number|string} limit - Requested limit
|
|
13
|
+
* @param {PaginationConfig} config - Pagination configuration
|
|
14
|
+
* @returns {number} Sanitized limit between 1 and maxLimit
|
|
15
|
+
*/
|
|
16
|
+
export function validateLimit(limit, config) {
|
|
17
|
+
const parsed = Number(limit);
|
|
18
|
+
|
|
19
|
+
if (!Number.isFinite(parsed) || parsed < 1) {
|
|
20
|
+
return config.defaultLimit || 10;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return Math.min(Math.floor(parsed), config.maxLimit);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Validates and sanitizes page number
|
|
28
|
+
* Parses strings to numbers and prevents NaN bugs
|
|
29
|
+
*
|
|
30
|
+
* @param {number|string} page - Requested page (1-indexed)
|
|
31
|
+
* @param {PaginationConfig} config - Pagination configuration
|
|
32
|
+
* @returns {number} Sanitized page number >= 1
|
|
33
|
+
* @throws {Error} If page exceeds maxPage
|
|
34
|
+
*/
|
|
35
|
+
export function validatePage(page, config) {
|
|
36
|
+
const parsed = Number(page);
|
|
37
|
+
|
|
38
|
+
if (!Number.isFinite(parsed) || parsed < 1) {
|
|
39
|
+
return 1;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const sanitized = Math.floor(parsed);
|
|
43
|
+
|
|
44
|
+
if (sanitized > config.maxPage) {
|
|
45
|
+
throw new Error(`Page ${sanitized} exceeds maximum ${config.maxPage}`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return sanitized;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Checks if page number should trigger deep pagination warning
|
|
53
|
+
*
|
|
54
|
+
* @param {number} page - Current page number
|
|
55
|
+
* @param {number} threshold - Warning threshold
|
|
56
|
+
* @returns {boolean} True if warning should be shown
|
|
57
|
+
*/
|
|
58
|
+
export function shouldWarnDeepPagination(page, threshold) {
|
|
59
|
+
return page > threshold;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Calculates number of documents to skip for offset pagination
|
|
64
|
+
*
|
|
65
|
+
* @param {number} page - Page number (1-indexed)
|
|
66
|
+
* @param {number} limit - Documents per page
|
|
67
|
+
* @returns {number} Number of documents to skip
|
|
68
|
+
*/
|
|
69
|
+
export function calculateSkip(page, limit) {
|
|
70
|
+
return (page - 1) * limit;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Calculates total number of pages
|
|
75
|
+
*
|
|
76
|
+
* @param {number} total - Total document count
|
|
77
|
+
* @param {number} limit - Documents per page
|
|
78
|
+
* @returns {number} Total number of pages
|
|
79
|
+
*/
|
|
80
|
+
export function calculateTotalPages(total, limit) {
|
|
81
|
+
return Math.ceil(total / limit);
|
|
82
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalizes sort object to ensure stable key order
|
|
3
|
+
* Primary fields first, _id last (not alphabetical)
|
|
4
|
+
*
|
|
5
|
+
* @param {Record<string, 1|-1>} sort - Sort specification
|
|
6
|
+
* @returns {Record<string, 1|-1>} Normalized sort with stable key order
|
|
7
|
+
*/
|
|
8
|
+
export function normalizeSort(sort) {
|
|
9
|
+
/** @type {Record<string, 1|-1>} */
|
|
10
|
+
const normalized = {};
|
|
11
|
+
|
|
12
|
+
Object.keys(sort).forEach(key => {
|
|
13
|
+
if (key !== '_id') normalized[key] = sort[key];
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
if (sort._id !== undefined) {
|
|
17
|
+
normalized._id = sort._id;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return normalized;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Validates and normalizes sort for keyset pagination
|
|
25
|
+
* Auto-adds _id tie-breaker if needed
|
|
26
|
+
* Ensures _id direction matches primary field
|
|
27
|
+
*
|
|
28
|
+
* @param {Record<string, 1|-1>} sort - Sort specification
|
|
29
|
+
* @returns {Record<string, 1|-1>} Validated and normalized sort
|
|
30
|
+
* @throws {Error} If sort is invalid for keyset pagination
|
|
31
|
+
*/
|
|
32
|
+
export function validateKeysetSort(sort) {
|
|
33
|
+
const keys = Object.keys(sort);
|
|
34
|
+
|
|
35
|
+
if (keys.length === 1 && keys[0] !== '_id') {
|
|
36
|
+
const field = keys[0];
|
|
37
|
+
const direction = sort[field];
|
|
38
|
+
return normalizeSort({ [field]: direction, _id: direction });
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (keys.length === 1 && keys[0] === '_id') {
|
|
42
|
+
return normalizeSort(sort);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (keys.length === 2) {
|
|
46
|
+
if (!keys.includes('_id')) {
|
|
47
|
+
throw new Error('Keyset pagination requires _id as tie-breaker');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const primaryField = keys.find(k => k !== '_id');
|
|
51
|
+
const primaryDirection = sort[primaryField];
|
|
52
|
+
const idDirection = sort._id;
|
|
53
|
+
|
|
54
|
+
if (primaryDirection !== idDirection) {
|
|
55
|
+
throw new Error('_id direction must match primary field direction');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return normalizeSort(sort);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
throw new Error('Keyset pagination only supports single field + _id');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Inverts sort directions (1 becomes -1, -1 becomes 1)
|
|
66
|
+
*
|
|
67
|
+
* @param {Record<string, 1|-1>} sort - Sort specification
|
|
68
|
+
* @returns {Record<string, 1|-1>} Inverted sort
|
|
69
|
+
*/
|
|
70
|
+
export function invertSort(sort) {
|
|
71
|
+
/** @type {Record<string, 1|-1>} */
|
|
72
|
+
const inverted = {};
|
|
73
|
+
|
|
74
|
+
Object.keys(sort).forEach(key => {
|
|
75
|
+
inverted[key] = sort[key] === 1 ? -1 : 1;
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
return inverted;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Extracts primary sort field (first non-_id field)
|
|
83
|
+
*
|
|
84
|
+
* @param {Record<string, 1|-1>} sort - Sort specification
|
|
85
|
+
* @returns {string} Primary field name
|
|
86
|
+
*/
|
|
87
|
+
export function getPrimaryField(sort) {
|
|
88
|
+
const keys = Object.keys(sort);
|
|
89
|
+
return keys.find(k => k !== '_id') || '_id';
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Gets sort direction for a specific field
|
|
94
|
+
*
|
|
95
|
+
* @param {Record<string, 1|-1>} sort - Sort specification
|
|
96
|
+
* @param {string} field - Field name
|
|
97
|
+
* @returns {1|-1|undefined} Sort direction
|
|
98
|
+
*/
|
|
99
|
+
export function getDirection(sort, field) {
|
|
100
|
+
return sort[field];
|
|
101
|
+
}
|
|
@@ -1,71 +1,71 @@
|
|
|
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
|
+
/**
|
|
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 +1,60 @@
|
|
|
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
|
+
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 +1,66 @@
|
|
|
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
|
+
/**
|
|
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 +1,27 @@
|
|
|
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;
|
|
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
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
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
|
+
// 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
|
+
|