s3db.js 13.4.0 → 13.6.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 +25 -10
- package/dist/{s3db.cjs.js → s3db.cjs} +38801 -32446
- package/dist/s3db.cjs.map +1 -0
- package/dist/s3db.es.js +38653 -32291
- package/dist/s3db.es.js.map +1 -1
- package/package.json +218 -22
- package/src/concerns/id.js +90 -6
- package/src/concerns/index.js +2 -1
- package/src/concerns/password-hashing.js +150 -0
- package/src/database.class.js +6 -2
- package/src/plugins/api/auth/basic-auth.js +40 -10
- package/src/plugins/api/auth/index.js +49 -3
- package/src/plugins/api/auth/oauth2-auth.js +171 -0
- package/src/plugins/api/auth/oidc-auth.js +789 -0
- package/src/plugins/api/auth/oidc-client.js +462 -0
- package/src/plugins/api/auth/path-auth-matcher.js +284 -0
- package/src/plugins/api/concerns/event-emitter.js +134 -0
- package/src/plugins/api/concerns/failban-manager.js +651 -0
- package/src/plugins/api/concerns/guards-helpers.js +402 -0
- package/src/plugins/api/concerns/metrics-collector.js +346 -0
- package/src/plugins/api/index.js +510 -57
- package/src/plugins/api/middlewares/failban.js +305 -0
- package/src/plugins/api/middlewares/rate-limit.js +301 -0
- package/src/plugins/api/middlewares/request-id.js +74 -0
- package/src/plugins/api/middlewares/security-headers.js +120 -0
- package/src/plugins/api/middlewares/session-tracking.js +194 -0
- package/src/plugins/api/routes/auth-routes.js +119 -78
- package/src/plugins/api/routes/resource-routes.js +73 -30
- package/src/plugins/api/server.js +1139 -45
- package/src/plugins/api/utils/custom-routes.js +102 -0
- package/src/plugins/api/utils/guards.js +213 -0
- package/src/plugins/api/utils/mime-types.js +154 -0
- package/src/plugins/api/utils/openapi-generator.js +91 -12
- package/src/plugins/api/utils/path-matcher.js +173 -0
- package/src/plugins/api/utils/static-filesystem.js +262 -0
- package/src/plugins/api/utils/static-s3.js +231 -0
- package/src/plugins/api/utils/template-engine.js +188 -0
- package/src/plugins/cloud-inventory/drivers/alibaba-driver.js +853 -0
- package/src/plugins/cloud-inventory/drivers/aws-driver.js +2554 -0
- package/src/plugins/cloud-inventory/drivers/azure-driver.js +637 -0
- package/src/plugins/cloud-inventory/drivers/base-driver.js +99 -0
- package/src/plugins/cloud-inventory/drivers/cloudflare-driver.js +620 -0
- package/src/plugins/cloud-inventory/drivers/digitalocean-driver.js +698 -0
- package/src/plugins/cloud-inventory/drivers/gcp-driver.js +645 -0
- package/src/plugins/cloud-inventory/drivers/hetzner-driver.js +559 -0
- package/src/plugins/cloud-inventory/drivers/linode-driver.js +614 -0
- package/src/plugins/cloud-inventory/drivers/mock-drivers.js +449 -0
- package/src/plugins/cloud-inventory/drivers/mongodb-atlas-driver.js +771 -0
- package/src/plugins/cloud-inventory/drivers/oracle-driver.js +768 -0
- package/src/plugins/cloud-inventory/drivers/vultr-driver.js +636 -0
- package/src/plugins/cloud-inventory/index.js +20 -0
- package/src/plugins/cloud-inventory/registry.js +146 -0
- package/src/plugins/cloud-inventory/terraform-exporter.js +362 -0
- package/src/plugins/cloud-inventory.plugin.js +1333 -0
- package/src/plugins/concerns/plugin-dependencies.js +62 -2
- package/src/plugins/eventual-consistency/analytics.js +1 -0
- package/src/plugins/eventual-consistency/consolidation.js +2 -2
- package/src/plugins/eventual-consistency/garbage-collection.js +2 -2
- package/src/plugins/eventual-consistency/install.js +2 -2
- package/src/plugins/identity/README.md +335 -0
- package/src/plugins/identity/concerns/mfa-manager.js +204 -0
- package/src/plugins/identity/concerns/password.js +138 -0
- package/src/plugins/identity/concerns/resource-schemas.js +273 -0
- package/src/plugins/identity/concerns/token-generator.js +172 -0
- package/src/plugins/identity/email-service.js +422 -0
- package/src/plugins/identity/index.js +1052 -0
- package/src/plugins/identity/oauth2-server.js +1033 -0
- package/src/plugins/identity/oidc-discovery.js +285 -0
- package/src/plugins/identity/rsa-keys.js +323 -0
- package/src/plugins/identity/server.js +500 -0
- package/src/plugins/identity/session-manager.js +453 -0
- package/src/plugins/identity/ui/layouts/base.js +251 -0
- package/src/plugins/identity/ui/middleware.js +135 -0
- package/src/plugins/identity/ui/pages/admin/client-form.js +247 -0
- package/src/plugins/identity/ui/pages/admin/clients.js +179 -0
- package/src/plugins/identity/ui/pages/admin/dashboard.js +181 -0
- package/src/plugins/identity/ui/pages/admin/user-form.js +283 -0
- package/src/plugins/identity/ui/pages/admin/users.js +263 -0
- package/src/plugins/identity/ui/pages/consent.js +262 -0
- package/src/plugins/identity/ui/pages/forgot-password.js +104 -0
- package/src/plugins/identity/ui/pages/login.js +144 -0
- package/src/plugins/identity/ui/pages/mfa-backup-codes.js +180 -0
- package/src/plugins/identity/ui/pages/mfa-enrollment.js +187 -0
- package/src/plugins/identity/ui/pages/mfa-verification.js +178 -0
- package/src/plugins/identity/ui/pages/oauth-error.js +225 -0
- package/src/plugins/identity/ui/pages/profile.js +361 -0
- package/src/plugins/identity/ui/pages/register.js +226 -0
- package/src/plugins/identity/ui/pages/reset-password.js +128 -0
- package/src/plugins/identity/ui/pages/verify-email.js +172 -0
- package/src/plugins/identity/ui/routes.js +2541 -0
- package/src/plugins/identity/ui/styles/main.css +465 -0
- package/src/plugins/index.js +4 -1
- package/src/plugins/ml/base-model.class.js +65 -16
- package/src/plugins/ml/classification-model.class.js +1 -1
- package/src/plugins/ml/timeseries-model.class.js +3 -1
- package/src/plugins/ml.plugin.js +584 -31
- package/src/plugins/shared/error-handler.js +147 -0
- package/src/plugins/shared/index.js +9 -0
- package/src/plugins/shared/middlewares/compression.js +117 -0
- package/src/plugins/shared/middlewares/cors.js +49 -0
- package/src/plugins/shared/middlewares/index.js +11 -0
- package/src/plugins/shared/middlewares/logging.js +54 -0
- package/src/plugins/shared/middlewares/rate-limit.js +73 -0
- package/src/plugins/shared/middlewares/security.js +158 -0
- package/src/plugins/shared/response-formatter.js +264 -0
- package/src/plugins/state-machine.plugin.js +57 -2
- package/src/resource.class.js +140 -12
- package/src/schema.class.js +30 -1
- package/src/validator.class.js +57 -6
- package/dist/s3db.cjs.js.map +0 -1
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Path-based Authentication Matcher
|
|
3
|
+
*
|
|
4
|
+
* Provides path-specific authentication rules with precedence by specificity.
|
|
5
|
+
* More specific paths override less specific ones.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* const rules = [
|
|
9
|
+
* { path: '/app/**', methods: ['oidc'], required: true },
|
|
10
|
+
* { path: '/api/v1/**', methods: ['basic', 'oidc'], required: true },
|
|
11
|
+
* { path: '/health', methods: [], required: false },
|
|
12
|
+
* { path: '/**', methods: [], required: false } // default
|
|
13
|
+
* ];
|
|
14
|
+
*
|
|
15
|
+
* const rule = findAuthRule('/app/dashboard', rules);
|
|
16
|
+
* // => { path: '/app/**', methods: ['oidc'], required: true }
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Calculate path specificity score (higher = more specific)
|
|
21
|
+
* @param {string} pattern - Path pattern with wildcards
|
|
22
|
+
* @returns {number} Specificity score
|
|
23
|
+
*/
|
|
24
|
+
function calculateSpecificity(pattern) {
|
|
25
|
+
let score = 0;
|
|
26
|
+
|
|
27
|
+
// Exact matches (no wildcards) are most specific
|
|
28
|
+
if (!pattern.includes('*') && !pattern.includes(':')) {
|
|
29
|
+
score += 10000;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Count path segments (more segments = more specific)
|
|
33
|
+
const segments = pattern.split('/').filter(s => s.length > 0);
|
|
34
|
+
score += segments.length * 100;
|
|
35
|
+
|
|
36
|
+
// Penalize wildcards (fewer wildcards = more specific)
|
|
37
|
+
const singleWildcards = (pattern.match(/(?<!\*)\*(?!\*)/g) || []).length;
|
|
38
|
+
const doubleWildcards = (pattern.match(/\*\*/g) || []).length;
|
|
39
|
+
score -= singleWildcards * 10;
|
|
40
|
+
score -= doubleWildcards * 50;
|
|
41
|
+
|
|
42
|
+
// Penalize route params (e.g., :id)
|
|
43
|
+
const params = (pattern.match(/:[^/]+/g) || []).length;
|
|
44
|
+
score -= params * 5;
|
|
45
|
+
|
|
46
|
+
return score;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Convert glob pattern to regex
|
|
51
|
+
* @param {string} pattern - Glob pattern
|
|
52
|
+
* @returns {RegExp} Regular expression
|
|
53
|
+
*/
|
|
54
|
+
function patternToRegex(pattern) {
|
|
55
|
+
// Escape special regex characters except * and :
|
|
56
|
+
let regexPattern = pattern
|
|
57
|
+
.replace(/[.+?^${}()|[\]\\]/g, '\\$&');
|
|
58
|
+
|
|
59
|
+
// Handle route params (:id, :userId, etc.)
|
|
60
|
+
regexPattern = regexPattern.replace(/:([^/]+)/g, '([^/]+)');
|
|
61
|
+
|
|
62
|
+
// Handle wildcards
|
|
63
|
+
regexPattern = regexPattern
|
|
64
|
+
.replace(/\*\*/g, '___GLOBSTAR___') // Temporary placeholder
|
|
65
|
+
.replace(/\*/g, '[^/]*') // * matches anything except /
|
|
66
|
+
.replace(/___GLOBSTAR___/g, '.*'); // ** matches everything including /
|
|
67
|
+
|
|
68
|
+
// Special case: if pattern ends with /**, it should match with or without trailing content
|
|
69
|
+
// e.g., /app/** should match both /app and /app/dashboard
|
|
70
|
+
if (pattern.endsWith('/**')) {
|
|
71
|
+
// Remove the /.*$ part and make it optional
|
|
72
|
+
regexPattern = regexPattern.replace(/\/\.\*$/, '(?:/.*)?');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Anchor to start and end
|
|
76
|
+
regexPattern = '^' + regexPattern + '$';
|
|
77
|
+
|
|
78
|
+
return new RegExp(regexPattern);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Check if path matches pattern
|
|
83
|
+
* @param {string} path - Request path
|
|
84
|
+
* @param {string} pattern - Path pattern with wildcards
|
|
85
|
+
* @returns {boolean} True if matches
|
|
86
|
+
*/
|
|
87
|
+
export function matchPath(path, pattern) {
|
|
88
|
+
// Exact match (fast path)
|
|
89
|
+
if (path === pattern) return true;
|
|
90
|
+
|
|
91
|
+
// Regex match
|
|
92
|
+
const regex = patternToRegex(pattern);
|
|
93
|
+
return regex.test(path);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Find matching auth rule for path (most specific wins)
|
|
98
|
+
* @param {string} path - Request path
|
|
99
|
+
* @param {Array<Object>} rules - Auth rules
|
|
100
|
+
* @param {string} rules[].path - Path pattern
|
|
101
|
+
* @param {Array<string>} rules[].methods - Allowed auth methods
|
|
102
|
+
* @param {boolean} rules[].required - If true, auth is required
|
|
103
|
+
* @param {string} rules[].strategy - Auth strategy ('any' or 'priority')
|
|
104
|
+
* @param {Object} rules[].priorities - Priority map for 'priority' strategy
|
|
105
|
+
* @returns {Object|null} Matching rule or null
|
|
106
|
+
*/
|
|
107
|
+
export function findAuthRule(path, rules = []) {
|
|
108
|
+
if (!rules || rules.length === 0) {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Find all matching rules
|
|
113
|
+
const matches = rules
|
|
114
|
+
.map(rule => ({
|
|
115
|
+
...rule,
|
|
116
|
+
specificity: calculateSpecificity(rule.path)
|
|
117
|
+
}))
|
|
118
|
+
.filter(rule => matchPath(path, rule.path))
|
|
119
|
+
.sort((a, b) => b.specificity - a.specificity); // Highest specificity first
|
|
120
|
+
|
|
121
|
+
// Return most specific match
|
|
122
|
+
return matches.length > 0 ? matches[0] : null;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Create path-based auth middleware
|
|
127
|
+
* @param {Object} options - Middleware options
|
|
128
|
+
* @param {Array<Object>} options.rules - Path-based auth rules
|
|
129
|
+
* @param {Object} options.authMiddlewares - Available auth middlewares by name
|
|
130
|
+
* @param {Function} options.unauthorizedHandler - Handler for unauthorized requests
|
|
131
|
+
* @returns {Function} Hono middleware
|
|
132
|
+
*/
|
|
133
|
+
export function createPathBasedAuthMiddleware(options = {}) {
|
|
134
|
+
const {
|
|
135
|
+
rules = [],
|
|
136
|
+
authMiddlewares = {},
|
|
137
|
+
unauthorizedHandler = null,
|
|
138
|
+
events = null
|
|
139
|
+
} = options;
|
|
140
|
+
|
|
141
|
+
return async (c, next) => {
|
|
142
|
+
const currentPath = c.req.path;
|
|
143
|
+
|
|
144
|
+
// Find matching rule
|
|
145
|
+
const rule = findAuthRule(currentPath, rules);
|
|
146
|
+
|
|
147
|
+
// No rule = no auth required (default public)
|
|
148
|
+
if (!rule) {
|
|
149
|
+
return await next();
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Rule says auth not required = public
|
|
153
|
+
if (!rule.required) {
|
|
154
|
+
return await next();
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Rule says auth required but no methods = error in config
|
|
158
|
+
if (rule.methods.length === 0 && rule.required) {
|
|
159
|
+
console.error(`[Path Auth] Invalid rule: path "${rule.path}" requires auth but has no methods`);
|
|
160
|
+
if (unauthorizedHandler) {
|
|
161
|
+
return unauthorizedHandler(c, 'Configuration error');
|
|
162
|
+
}
|
|
163
|
+
return c.json({ error: 'Configuration error' }, 500);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Get allowed auth middlewares for this path
|
|
167
|
+
const allowedMiddlewares = rule.methods
|
|
168
|
+
.map(methodName => ({
|
|
169
|
+
name: methodName,
|
|
170
|
+
middleware: authMiddlewares[methodName]
|
|
171
|
+
}))
|
|
172
|
+
.filter(m => m.middleware);
|
|
173
|
+
|
|
174
|
+
if (allowedMiddlewares.length === 0) {
|
|
175
|
+
console.error(`[Path Auth] No middlewares found for methods: ${rule.methods.join(', ')}`);
|
|
176
|
+
if (unauthorizedHandler) {
|
|
177
|
+
return unauthorizedHandler(c, 'No auth methods available');
|
|
178
|
+
}
|
|
179
|
+
return c.json({ error: 'No auth methods available' }, 500);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Sort by priority if strategy is 'priority'
|
|
183
|
+
const strategy = rule.strategy || 'any';
|
|
184
|
+
const priorities = rule.priorities || {};
|
|
185
|
+
|
|
186
|
+
if (strategy === 'priority' && Object.keys(priorities).length > 0) {
|
|
187
|
+
allowedMiddlewares.sort((a, b) => {
|
|
188
|
+
const priorityA = priorities[a.name] || 999;
|
|
189
|
+
const priorityB = priorities[b.name] || 999;
|
|
190
|
+
return priorityA - priorityB; // Lower number = higher priority
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Try each auth method
|
|
195
|
+
for (const { name, middleware } of allowedMiddlewares) {
|
|
196
|
+
let authSuccess = false;
|
|
197
|
+
const tempNext = async () => {
|
|
198
|
+
authSuccess = true;
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
// Try auth method
|
|
202
|
+
await middleware(c, tempNext);
|
|
203
|
+
|
|
204
|
+
// If auth succeeded, continue
|
|
205
|
+
if (authSuccess && c.get('user')) {
|
|
206
|
+
// Emit auth:success event
|
|
207
|
+
if (events) {
|
|
208
|
+
events.emitAuthEvent('success', {
|
|
209
|
+
method: name,
|
|
210
|
+
user: c.get('user'),
|
|
211
|
+
path: currentPath,
|
|
212
|
+
rule: rule.path
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
return await next();
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Emit auth:failure event
|
|
220
|
+
if (events) {
|
|
221
|
+
events.emitAuthEvent('failure', {
|
|
222
|
+
path: currentPath,
|
|
223
|
+
rule: rule.path,
|
|
224
|
+
allowedMethods: rule.methods,
|
|
225
|
+
ip: c.req.header('x-forwarded-for') || c.req.header('x-real-ip')
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// No auth method succeeded - apply content negotiation
|
|
230
|
+
const acceptHeader = c.req.header('accept') || '';
|
|
231
|
+
const acceptsHtml = acceptHeader.includes('text/html');
|
|
232
|
+
|
|
233
|
+
// Get unauthorized behavior from rule (default: 'auto')
|
|
234
|
+
const unauthorizedBehavior = rule.unauthorizedBehavior || 'auto';
|
|
235
|
+
|
|
236
|
+
// Auto mode: HTML → redirect, JSON → 401
|
|
237
|
+
if (unauthorizedBehavior === 'auto') {
|
|
238
|
+
if (acceptsHtml) {
|
|
239
|
+
// Browser request - redirect to login
|
|
240
|
+
const returnTo = encodeURIComponent(c.req.path);
|
|
241
|
+
return c.redirect(`/auth/login?returnTo=${returnTo}`);
|
|
242
|
+
} else {
|
|
243
|
+
// API request - return 401 JSON
|
|
244
|
+
return c.json({
|
|
245
|
+
error: 'Unauthorized',
|
|
246
|
+
message: `Authentication required. Allowed methods: ${rule.methods.join(', ')}`
|
|
247
|
+
}, 401);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Custom behavior object: { html: 'redirect', json: { status: 401 } }
|
|
252
|
+
if (typeof unauthorizedBehavior === 'object') {
|
|
253
|
+
if (acceptsHtml && unauthorizedBehavior.html === 'redirect') {
|
|
254
|
+
const returnTo = encodeURIComponent(c.req.path);
|
|
255
|
+
const loginPath = unauthorizedBehavior.loginPath || '/auth/login';
|
|
256
|
+
return c.redirect(`${loginPath}?returnTo=${returnTo}`);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (!acceptsHtml && unauthorizedBehavior.json) {
|
|
260
|
+
return c.json(
|
|
261
|
+
unauthorizedBehavior.json,
|
|
262
|
+
unauthorizedBehavior.json.status || 401
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Fallback: use custom handler or default 401
|
|
268
|
+
if (unauthorizedHandler) {
|
|
269
|
+
return unauthorizedHandler(c, `Authentication required. Allowed methods: ${rule.methods.join(', ')}`);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return c.json({
|
|
273
|
+
error: 'Unauthorized',
|
|
274
|
+
message: `Authentication required. Allowed methods: ${rule.methods.join(', ')}`
|
|
275
|
+
}, 401);
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
export default {
|
|
280
|
+
matchPath,
|
|
281
|
+
findAuthRule,
|
|
282
|
+
calculateSpecificity,
|
|
283
|
+
createPathBasedAuthMiddleware
|
|
284
|
+
};
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API Event Emitter
|
|
3
|
+
*
|
|
4
|
+
* Provides event hooks throughout the API lifecycle for monitoring,
|
|
5
|
+
* analytics, and custom integrations.
|
|
6
|
+
*
|
|
7
|
+
* Supported Events:
|
|
8
|
+
* - user:created - New user created via OIDC
|
|
9
|
+
* - user:login - User logged in
|
|
10
|
+
* - auth:success - Authentication succeeded
|
|
11
|
+
* - auth:failure - Authentication failed
|
|
12
|
+
* - resource:created - Resource record created
|
|
13
|
+
* - resource:updated - Resource record updated
|
|
14
|
+
* - resource:deleted - Resource record deleted
|
|
15
|
+
* - request:start - Request started
|
|
16
|
+
* - request:end - Request ended
|
|
17
|
+
* - request:error - Request errored
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* const events = new ApiEventEmitter();
|
|
21
|
+
*
|
|
22
|
+
* // Listen to user creation
|
|
23
|
+
* events.on('user:created', (data) => {
|
|
24
|
+
* console.log('New user:', data.user);
|
|
25
|
+
* });
|
|
26
|
+
*
|
|
27
|
+
* // Listen to all resource changes
|
|
28
|
+
* events.on('resource:*', (data) => {
|
|
29
|
+
* console.log('Resource event:', data.event, data.resource);
|
|
30
|
+
* });
|
|
31
|
+
*
|
|
32
|
+
* // Emit events
|
|
33
|
+
* events.emit('user:created', { user: userObject, source: 'oidc' });
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
import { EventEmitter } from 'events';
|
|
37
|
+
|
|
38
|
+
export class ApiEventEmitter extends EventEmitter {
|
|
39
|
+
constructor(options = {}) {
|
|
40
|
+
super();
|
|
41
|
+
|
|
42
|
+
this.options = {
|
|
43
|
+
enabled: options.enabled !== false, // Enabled by default
|
|
44
|
+
verbose: options.verbose || false,
|
|
45
|
+
maxListeners: options.maxListeners || 10
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
this.setMaxListeners(this.options.maxListeners);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Emit event with wildcard support
|
|
53
|
+
* @param {string} event - Event name
|
|
54
|
+
* @param {Object} data - Event data
|
|
55
|
+
*/
|
|
56
|
+
emit(event, data = {}) {
|
|
57
|
+
if (!this.options.enabled) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (this.options.verbose) {
|
|
62
|
+
console.log(`[API Events] ${event}`, data);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Emit specific event
|
|
66
|
+
super.emit(event, { event, ...data, timestamp: new Date().toISOString() });
|
|
67
|
+
|
|
68
|
+
// Emit wildcard pattern (e.g., "resource:*" for "resource:created")
|
|
69
|
+
if (event.includes(':')) {
|
|
70
|
+
const [prefix] = event.split(':');
|
|
71
|
+
const wildcardEvent = `${prefix}:*`;
|
|
72
|
+
super.emit(wildcardEvent, { event, ...data, timestamp: new Date().toISOString() });
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Helper to emit user events
|
|
80
|
+
* @param {string} action - created, login, updated, deleted
|
|
81
|
+
* @param {Object} data - Event data
|
|
82
|
+
*/
|
|
83
|
+
emitUserEvent(action, data) {
|
|
84
|
+
this.emit(`user:${action}`, data);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Helper to emit auth events
|
|
89
|
+
* @param {string} action - success, failure
|
|
90
|
+
* @param {Object} data - Event data
|
|
91
|
+
*/
|
|
92
|
+
emitAuthEvent(action, data) {
|
|
93
|
+
this.emit(`auth:${action}`, data);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Helper to emit resource events
|
|
98
|
+
* @param {string} action - created, updated, deleted
|
|
99
|
+
* @param {Object} data - Event data
|
|
100
|
+
*/
|
|
101
|
+
emitResourceEvent(action, data) {
|
|
102
|
+
this.emit(`resource:${action}`, data);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Helper to emit request events
|
|
107
|
+
* @param {string} action - start, end, error
|
|
108
|
+
* @param {Object} data - Event data
|
|
109
|
+
*/
|
|
110
|
+
emitRequestEvent(action, data) {
|
|
111
|
+
this.emit(`request:${action}`, data);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Get event statistics
|
|
116
|
+
* @returns {Object} Event statistics
|
|
117
|
+
*/
|
|
118
|
+
getStats() {
|
|
119
|
+
const stats = {
|
|
120
|
+
enabled: this.options.enabled,
|
|
121
|
+
maxListeners: this.options.maxListeners,
|
|
122
|
+
listeners: {}
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
// Count listeners per event
|
|
126
|
+
for (const event of this.eventNames()) {
|
|
127
|
+
stats.listeners[event] = this.listenerCount(event);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return stats;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export default ApiEventEmitter;
|