@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.
- package/README.md +564 -157
- package/package.json +20 -12
- package/src/Repository.js +296 -225
- package/src/actions/aggregate.js +266 -191
- package/src/actions/create.js +59 -58
- package/src/actions/delete.js +88 -88
- package/src/actions/index.js +11 -11
- package/src/actions/read.js +188 -155
- package/src/actions/update.js +176 -172
- 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 -113
- 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 -96
- 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
package/src/actions/update.js
CHANGED
|
@@ -1,172 +1,176 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Update Actions
|
|
3
|
-
* Pure functions for document updates with optimizations
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import createError from '
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Update by ID
|
|
10
|
-
*/
|
|
11
|
-
export async function update(Model, id, data, options = {}) {
|
|
12
|
-
const document = await Model.findByIdAndUpdate(id, data, {
|
|
13
|
-
new: true,
|
|
14
|
-
runValidators: true,
|
|
15
|
-
session: options.session,
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
.
|
|
19
|
-
.
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
*
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
.
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
if (
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Update Actions
|
|
3
|
+
* Pure functions for document updates with optimizations
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { createError } from '../utils/error.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Update by ID
|
|
10
|
+
*/
|
|
11
|
+
export async function update(Model, id, data, options = {}) {
|
|
12
|
+
const document = await Model.findByIdAndUpdate(id, data, {
|
|
13
|
+
new: true,
|
|
14
|
+
runValidators: true,
|
|
15
|
+
session: options.session,
|
|
16
|
+
...(options.updatePipeline !== undefined ? { updatePipeline: options.updatePipeline } : {}),
|
|
17
|
+
})
|
|
18
|
+
.select(options.select)
|
|
19
|
+
.populate(parsePopulate(options.populate))
|
|
20
|
+
.lean(options.lean);
|
|
21
|
+
|
|
22
|
+
if (!document) {
|
|
23
|
+
throw createError(404, 'Document not found');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return document;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Update with query constraints (optimized)
|
|
31
|
+
* Returns null if constraints not met (not an error)
|
|
32
|
+
*/
|
|
33
|
+
export async function updateWithConstraints(Model, id, data, constraints = {}, options = {}) {
|
|
34
|
+
const query = { _id: id, ...constraints };
|
|
35
|
+
|
|
36
|
+
const document = await Model.findOneAndUpdate(query, data, {
|
|
37
|
+
new: true,
|
|
38
|
+
runValidators: true,
|
|
39
|
+
session: options.session,
|
|
40
|
+
...(options.updatePipeline !== undefined ? { updatePipeline: options.updatePipeline } : {}),
|
|
41
|
+
})
|
|
42
|
+
.select(options.select)
|
|
43
|
+
.populate(parsePopulate(options.populate))
|
|
44
|
+
.lean(options.lean);
|
|
45
|
+
|
|
46
|
+
return document;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Update with validation (smart optimization)
|
|
51
|
+
* 1-query on success, 2-queries for detailed errors
|
|
52
|
+
*/
|
|
53
|
+
export async function updateWithValidation(
|
|
54
|
+
Model,
|
|
55
|
+
id,
|
|
56
|
+
data,
|
|
57
|
+
validationOptions = {},
|
|
58
|
+
options = {}
|
|
59
|
+
) {
|
|
60
|
+
const { buildConstraints, validateUpdate } = validationOptions;
|
|
61
|
+
|
|
62
|
+
// Try optimized update with constraints
|
|
63
|
+
if (buildConstraints) {
|
|
64
|
+
const constraints = buildConstraints(data);
|
|
65
|
+
const document = await updateWithConstraints(Model, id, data, constraints, options);
|
|
66
|
+
|
|
67
|
+
if (document) {
|
|
68
|
+
return { success: true, data: document };
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Fetch for validation
|
|
73
|
+
const existing = await Model.findById(id)
|
|
74
|
+
.select(options.select)
|
|
75
|
+
.lean();
|
|
76
|
+
|
|
77
|
+
if (!existing) {
|
|
78
|
+
return {
|
|
79
|
+
success: false,
|
|
80
|
+
error: {
|
|
81
|
+
code: 404,
|
|
82
|
+
message: 'Document not found',
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Run custom validation
|
|
88
|
+
if (validateUpdate) {
|
|
89
|
+
const validation = validateUpdate(existing, data);
|
|
90
|
+
if (!validation.valid) {
|
|
91
|
+
return {
|
|
92
|
+
success: false,
|
|
93
|
+
error: {
|
|
94
|
+
code: 403,
|
|
95
|
+
message: validation.message || 'Update not allowed',
|
|
96
|
+
violations: validation.violations,
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Validation passed - perform update
|
|
103
|
+
const updated = await update(Model, id, data, options);
|
|
104
|
+
return { success: true, data: updated };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Update many documents
|
|
109
|
+
*/
|
|
110
|
+
export async function updateMany(Model, query, data, options = {}) {
|
|
111
|
+
const result = await Model.updateMany(query, data, {
|
|
112
|
+
runValidators: true,
|
|
113
|
+
session: options.session,
|
|
114
|
+
...(options.updatePipeline !== undefined ? { updatePipeline: options.updatePipeline } : {}),
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
matchedCount: result.matchedCount,
|
|
119
|
+
modifiedCount: result.modifiedCount,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Update by query
|
|
125
|
+
*/
|
|
126
|
+
export async function updateByQuery(Model, query, data, options = {}) {
|
|
127
|
+
const document = await Model.findOneAndUpdate(query, data, {
|
|
128
|
+
new: true,
|
|
129
|
+
runValidators: true,
|
|
130
|
+
session: options.session,
|
|
131
|
+
...(options.updatePipeline !== undefined ? { updatePipeline: options.updatePipeline } : {}),
|
|
132
|
+
})
|
|
133
|
+
.select(options.select)
|
|
134
|
+
.populate(parsePopulate(options.populate))
|
|
135
|
+
.lean(options.lean);
|
|
136
|
+
|
|
137
|
+
if (!document && options.throwOnNotFound !== false) {
|
|
138
|
+
throw createError(404, 'Document not found');
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return document;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Increment field
|
|
146
|
+
*/
|
|
147
|
+
export async function increment(Model, id, field, value = 1, options = {}) {
|
|
148
|
+
return update(Model, id, { $inc: { [field]: value } }, options);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Push to array
|
|
153
|
+
*/
|
|
154
|
+
export async function pushToArray(Model, id, field, value, options = {}) {
|
|
155
|
+
return update(Model, id, { $push: { [field]: value } }, options);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Pull from array
|
|
160
|
+
*/
|
|
161
|
+
export async function pullFromArray(Model, id, field, value, options = {}) {
|
|
162
|
+
return update(Model, id, { $pull: { [field]: value } }, options);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Utilities
|
|
166
|
+
function parsePopulate(populate) {
|
|
167
|
+
if (!populate) return [];
|
|
168
|
+
if (typeof populate === 'string') {
|
|
169
|
+
return populate.split(',').map(p => p.trim());
|
|
170
|
+
}
|
|
171
|
+
if (Array.isArray(populate)) {
|
|
172
|
+
return populate.map(p => typeof p === 'string' ? p.trim() : p);
|
|
173
|
+
}
|
|
174
|
+
return [populate];
|
|
175
|
+
}
|
|
176
|
+
|
package/src/hooks/lifecycle.js
CHANGED
|
@@ -1,146 +1,146 @@
|
|
|
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
|
-
|
|
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
|
+
|