@tamyla/clodo-framework 4.4.0 → 4.5.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/CHANGELOG.md +2 -1844
- package/README.md +44 -18
- package/dist/cli/commands/add.js +325 -0
- package/dist/config/service-schema-config.js +98 -5
- package/dist/index.js +22 -3
- package/dist/middleware/Composer.js +2 -1
- package/dist/middleware/factories.js +445 -0
- package/dist/middleware/index.js +4 -1
- package/dist/modules/ModuleManager.js +6 -2
- package/dist/routing/EnhancedRouter.js +248 -44
- package/dist/routing/RequestContext.js +393 -0
- package/dist/schema/SchemaManager.js +6 -2
- package/dist/service-management/generators/code/ServiceMiddlewareGenerator.js +79 -223
- package/dist/service-management/generators/code/WorkerIndexGenerator.js +241 -98
- package/dist/service-management/generators/config/WranglerTomlGenerator.js +130 -89
- package/dist/simple-api.js +4 -4
- package/dist/utilities/index.js +134 -1
- package/dist/utils/config/environment-var-normalizer.js +233 -0
- package/dist/validation/environmentGuard.js +172 -0
- package/docs/CHANGELOG.md +1877 -0
- package/docs/api-reference.md +153 -0
- package/package.json +4 -1
- package/scripts/repro-clodo.js +123 -0
- package/templates/ai-worker/package.json +19 -0
- package/templates/ai-worker/src/index.js +160 -0
- package/templates/cron-worker/package.json +19 -0
- package/templates/cron-worker/src/index.js +211 -0
- package/templates/edge-proxy/package.json +18 -0
- package/templates/edge-proxy/src/index.js +150 -0
- package/templates/minimal/package.json +17 -0
- package/templates/minimal/src/index.js +40 -0
- package/templates/queue-processor/package.json +19 -0
- package/templates/queue-processor/src/index.js +213 -0
- package/templates/rest-api/.dev.vars +2 -0
- package/templates/rest-api/package.json +19 -0
- package/templates/rest-api/src/index.js +124 -0
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Middleware Factories — High-level composable middleware for Cloudflare Workers
|
|
3
|
+
*
|
|
4
|
+
* These factory functions create middleware objects compatible with both
|
|
5
|
+
* the MiddlewareComposer lifecycle (preprocess/authenticate/validate/postprocess)
|
|
6
|
+
* and the EnhancedRouter's .use() method.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* import {
|
|
10
|
+
* createCorsMiddleware,
|
|
11
|
+
* createRateLimitGuard,
|
|
12
|
+
* createErrorHandler,
|
|
13
|
+
* createLogger,
|
|
14
|
+
* composeMiddleware
|
|
15
|
+
* } from '@tamyla/clodo-framework';
|
|
16
|
+
*
|
|
17
|
+
* const middleware = composeMiddleware(
|
|
18
|
+
* createCorsMiddleware({ origins: ['*'] }),
|
|
19
|
+
* createLogger({ level: 'info' }),
|
|
20
|
+
* createRateLimitGuard({ maxRequests: 100, windowMs: 60000 }),
|
|
21
|
+
* createErrorHandler({ includeStack: false })
|
|
22
|
+
* );
|
|
23
|
+
*
|
|
24
|
+
* @module @tamyla/clodo-framework/middleware/factories
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
// ─── CORS Middleware ───────────────────────────────────────────────────
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Create CORS middleware
|
|
31
|
+
* @param {Object} [options]
|
|
32
|
+
* @param {string|string[]} [options.origins='*'] - Allowed origins
|
|
33
|
+
* @param {string[]} [options.methods=['GET','POST','PUT','DELETE','PATCH','OPTIONS']] - Allowed methods
|
|
34
|
+
* @param {string[]} [options.headers=['Content-Type','Authorization']] - Allowed headers
|
|
35
|
+
* @param {boolean} [options.credentials=false] - Allow credentials
|
|
36
|
+
* @param {number} [options.maxAge=86400] - Preflight cache duration (seconds)
|
|
37
|
+
* @returns {Object} Middleware object with preprocess and postprocess
|
|
38
|
+
*/
|
|
39
|
+
export function createCorsMiddleware(options = {}) {
|
|
40
|
+
const origins = options.origins || options.origin || '*';
|
|
41
|
+
const allowOrigin = Array.isArray(origins) ? origins : [origins];
|
|
42
|
+
const methods = (options.methods || ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS']).join(', ');
|
|
43
|
+
const headers = (options.headers || ['Content-Type', 'Authorization']).join(', ');
|
|
44
|
+
const credentials = options.credentials || false;
|
|
45
|
+
const maxAge = String(options.maxAge || 86400);
|
|
46
|
+
function getOriginHeader(requestOrigin) {
|
|
47
|
+
if (allowOrigin.includes('*')) return '*';
|
|
48
|
+
if (allowOrigin.includes(requestOrigin)) return requestOrigin;
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
preprocess(request) {
|
|
53
|
+
const requestOrigin = request.headers.get('Origin') || '';
|
|
54
|
+
const origin = getOriginHeader(requestOrigin);
|
|
55
|
+
if (request.method === 'OPTIONS') {
|
|
56
|
+
const h = new Headers();
|
|
57
|
+
if (origin) h.set('Access-Control-Allow-Origin', origin);
|
|
58
|
+
h.set('Access-Control-Allow-Methods', methods);
|
|
59
|
+
h.set('Access-Control-Allow-Headers', headers);
|
|
60
|
+
h.set('Access-Control-Max-Age', maxAge);
|
|
61
|
+
if (credentials) h.set('Access-Control-Allow-Credentials', 'true');
|
|
62
|
+
return new Response(null, {
|
|
63
|
+
status: 204,
|
|
64
|
+
headers: h
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
return null; // pass through to next middleware
|
|
68
|
+
},
|
|
69
|
+
postprocess(response) {
|
|
70
|
+
const h = new Headers(response.headers);
|
|
71
|
+
const origin = allowOrigin.includes('*') ? '*' : allowOrigin[0];
|
|
72
|
+
h.set('Access-Control-Allow-Origin', origin);
|
|
73
|
+
if (credentials) h.set('Access-Control-Allow-Credentials', 'true');
|
|
74
|
+
return new Response(response.body, {
|
|
75
|
+
status: response.status,
|
|
76
|
+
statusText: response.statusText,
|
|
77
|
+
headers: h
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ─── Error Handler Middleware ──────────────────────────────────────────
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Create error handler middleware
|
|
87
|
+
* @param {Object} [options]
|
|
88
|
+
* @param {boolean} [options.includeStack=false] - Include stack trace in response
|
|
89
|
+
* @param {boolean} [options.logErrors=true] - Log errors to console
|
|
90
|
+
* @param {Function} [options.onError] - Custom error handler: (error, request) => Response | null
|
|
91
|
+
* @returns {Object} Middleware object
|
|
92
|
+
*/
|
|
93
|
+
export function createErrorHandler(options = {}) {
|
|
94
|
+
const includeStack = options.includeStack || false;
|
|
95
|
+
const logErrors = options.logErrors !== false;
|
|
96
|
+
const onError = options.onError || null;
|
|
97
|
+
return {
|
|
98
|
+
/**
|
|
99
|
+
* Wraps the handler execution — this is used by the compose function
|
|
100
|
+
* to catch errors from the handler and any downstream middleware.
|
|
101
|
+
*/
|
|
102
|
+
async wrapHandler(request, handler) {
|
|
103
|
+
try {
|
|
104
|
+
return await handler(request);
|
|
105
|
+
} catch (error) {
|
|
106
|
+
if (logErrors) {
|
|
107
|
+
console.error(`[ErrorHandler] ${error.message}`, error.stack);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Custom error handler
|
|
111
|
+
if (onError) {
|
|
112
|
+
const custom = onError(error, request);
|
|
113
|
+
if (custom instanceof Response) return custom;
|
|
114
|
+
}
|
|
115
|
+
const status = error.status || error.statusCode || 500;
|
|
116
|
+
const body = {
|
|
117
|
+
error: error.message || 'Internal Server Error',
|
|
118
|
+
status
|
|
119
|
+
};
|
|
120
|
+
if (includeStack && error.stack) {
|
|
121
|
+
body.stack = error.stack;
|
|
122
|
+
}
|
|
123
|
+
return new Response(JSON.stringify(body), {
|
|
124
|
+
status,
|
|
125
|
+
headers: {
|
|
126
|
+
'Content-Type': 'application/json'
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// ─── Rate Limit Guard Middleware ──────────────────────────────────────
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Create rate-limiting middleware using a simple in-memory token bucket.
|
|
138
|
+
* For production use with multiple Workers instances, back this with KV or Durable Objects.
|
|
139
|
+
*
|
|
140
|
+
* @param {Object} [options]
|
|
141
|
+
* @param {number} [options.maxRequests=100] - Max requests per window
|
|
142
|
+
* @param {number} [options.windowMs=60000] - Window duration in milliseconds
|
|
143
|
+
* @param {Function} [options.keyFn] - Function to extract rate-limit key from request (default: IP)
|
|
144
|
+
* @param {Object} [options.kvBinding] - Optional KV namespace for distributed rate limiting
|
|
145
|
+
* @returns {Object} Middleware object with preprocess
|
|
146
|
+
*/
|
|
147
|
+
export function createRateLimitGuard(options = {}) {
|
|
148
|
+
const maxRequests = options.maxRequests || 100;
|
|
149
|
+
const windowMs = options.windowMs || 60000;
|
|
150
|
+
const keyFn = options.keyFn || (request => request.headers.get('CF-Connecting-IP') || 'unknown');
|
|
151
|
+
const buckets = new Map();
|
|
152
|
+
function getBucket(key) {
|
|
153
|
+
const now = Date.now();
|
|
154
|
+
let bucket = buckets.get(key);
|
|
155
|
+
if (!bucket || now - bucket.windowStart > windowMs) {
|
|
156
|
+
bucket = {
|
|
157
|
+
windowStart: now,
|
|
158
|
+
count: 0
|
|
159
|
+
};
|
|
160
|
+
buckets.set(key, bucket);
|
|
161
|
+
}
|
|
162
|
+
return bucket;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Periodic cleanup to prevent memory leaks (every 10 windows)
|
|
166
|
+
let cleanupCounter = 0;
|
|
167
|
+
function maybeCleanup() {
|
|
168
|
+
if (++cleanupCounter % (maxRequests * 10) === 0) {
|
|
169
|
+
const now = Date.now();
|
|
170
|
+
for (const [key, bucket] of buckets.entries()) {
|
|
171
|
+
if (now - bucket.windowStart > windowMs * 2) {
|
|
172
|
+
buckets.delete(key);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return {
|
|
178
|
+
preprocess(request) {
|
|
179
|
+
const key = keyFn(request);
|
|
180
|
+
const bucket = getBucket(key);
|
|
181
|
+
maybeCleanup();
|
|
182
|
+
bucket.count++;
|
|
183
|
+
if (bucket.count > maxRequests) {
|
|
184
|
+
const retryAfter = Math.ceil((bucket.windowStart + windowMs - Date.now()) / 1000);
|
|
185
|
+
return new Response(JSON.stringify({
|
|
186
|
+
error: 'Too Many Requests',
|
|
187
|
+
retryAfter
|
|
188
|
+
}), {
|
|
189
|
+
status: 429,
|
|
190
|
+
headers: {
|
|
191
|
+
'Content-Type': 'application/json',
|
|
192
|
+
'Retry-After': String(Math.max(retryAfter, 1)),
|
|
193
|
+
'X-RateLimit-Limit': String(maxRequests),
|
|
194
|
+
'X-RateLimit-Remaining': '0',
|
|
195
|
+
'X-RateLimit-Reset': String(Math.ceil((bucket.windowStart + windowMs) / 1000))
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Add rate limit headers to request for downstream use
|
|
201
|
+
request._rateLimitRemaining = maxRequests - bucket.count;
|
|
202
|
+
return null; // pass through
|
|
203
|
+
},
|
|
204
|
+
postprocess(response, request) {
|
|
205
|
+
// Add rate limit info headers to every response
|
|
206
|
+
const h = new Headers(response.headers);
|
|
207
|
+
h.set('X-RateLimit-Limit', String(maxRequests));
|
|
208
|
+
if (request?._rateLimitRemaining !== undefined) {
|
|
209
|
+
h.set('X-RateLimit-Remaining', String(request._rateLimitRemaining));
|
|
210
|
+
}
|
|
211
|
+
return new Response(response.body, {
|
|
212
|
+
status: response.status,
|
|
213
|
+
statusText: response.statusText,
|
|
214
|
+
headers: h
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// ─── Logger Middleware ────────────────────────────────────────────────
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Create a logging middleware
|
|
224
|
+
* @param {Object} [options]
|
|
225
|
+
* @param {string} [options.level='info'] - Log level: 'debug' | 'info' | 'warn' | 'error'
|
|
226
|
+
* @param {string} [options.prefix=''] - Log prefix
|
|
227
|
+
* @param {boolean} [options.includeHeaders=false] - Log request headers
|
|
228
|
+
* @param {boolean} [options.includeLatency=true] - Log response latency
|
|
229
|
+
* @param {Function} [options.logger=console] - Custom logger
|
|
230
|
+
* @returns {Object} Middleware object
|
|
231
|
+
*/
|
|
232
|
+
export function createLogger(options = {}) {
|
|
233
|
+
const level = options.level || 'info';
|
|
234
|
+
const prefix = options.prefix ? `[${options.prefix}] ` : '';
|
|
235
|
+
const includeHeaders = options.includeHeaders || false;
|
|
236
|
+
const includeLatency = options.includeLatency !== false;
|
|
237
|
+
const logger = options.logger || console;
|
|
238
|
+
const levels = {
|
|
239
|
+
debug: 0,
|
|
240
|
+
info: 1,
|
|
241
|
+
warn: 2,
|
|
242
|
+
error: 3
|
|
243
|
+
};
|
|
244
|
+
const currentLevel = levels[level] ?? 1;
|
|
245
|
+
function log(lvl, ...args) {
|
|
246
|
+
if ((levels[lvl] ?? 1) >= currentLevel) {
|
|
247
|
+
(logger[lvl] || logger.log)(...args);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
return {
|
|
251
|
+
preprocess(request) {
|
|
252
|
+
const url = new URL(request.url);
|
|
253
|
+
const logLine = `${prefix}→ ${request.method} ${url.pathname}${url.search}`;
|
|
254
|
+
log('info', logLine);
|
|
255
|
+
if (includeHeaders) {
|
|
256
|
+
const headers = Object.fromEntries(request.headers.entries());
|
|
257
|
+
log('debug', `${prefix} Headers:`, headers);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Store start time for latency calculation
|
|
261
|
+
request._startTime = Date.now();
|
|
262
|
+
return null; // pass through
|
|
263
|
+
},
|
|
264
|
+
postprocess(response) {
|
|
265
|
+
if (includeLatency && response) {
|
|
266
|
+
const latency = Date.now() - (response._startTime || Date.now());
|
|
267
|
+
log('info', `${prefix}← ${response.status} (${latency}ms)`);
|
|
268
|
+
}
|
|
269
|
+
return response;
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// ─── Bearer Auth Middleware ──────────────────────────────────────────
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Create bearer token authentication middleware
|
|
278
|
+
* @param {Object} options
|
|
279
|
+
* @param {string|Function} options.token - Expected token string, or async (token, request) => boolean validator
|
|
280
|
+
* @param {string} [options.realm='API'] - WWW-Authenticate realm
|
|
281
|
+
* @param {string} [options.headerName='Authorization'] - Header to check
|
|
282
|
+
* @returns {Object} Middleware object with authenticate
|
|
283
|
+
*/
|
|
284
|
+
export function createBearerAuth(options = {}) {
|
|
285
|
+
const tokenValidator = typeof options.token === 'function' ? options.token : t => t === options.token;
|
|
286
|
+
const realm = options.realm || 'API';
|
|
287
|
+
const headerName = options.headerName || 'Authorization';
|
|
288
|
+
return {
|
|
289
|
+
async authenticate(request) {
|
|
290
|
+
const header = request.headers.get(headerName);
|
|
291
|
+
if (!header || !header.startsWith('Bearer ')) {
|
|
292
|
+
return new Response(JSON.stringify({
|
|
293
|
+
error: 'Authentication required'
|
|
294
|
+
}), {
|
|
295
|
+
status: 401,
|
|
296
|
+
headers: {
|
|
297
|
+
'Content-Type': 'application/json',
|
|
298
|
+
'WWW-Authenticate': `Bearer realm="${realm}"`
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
const token = header.slice(7); // remove 'Bearer '
|
|
303
|
+
const valid = await tokenValidator(token, request);
|
|
304
|
+
if (!valid) {
|
|
305
|
+
return new Response(JSON.stringify({
|
|
306
|
+
error: 'Invalid token'
|
|
307
|
+
}), {
|
|
308
|
+
status: 403,
|
|
309
|
+
headers: {
|
|
310
|
+
'Content-Type': 'application/json'
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
return null; // authenticated — pass through
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// ─── API Key Middleware ──────────────────────────────────────────────
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Create API key authentication middleware
|
|
323
|
+
* @param {Object} options
|
|
324
|
+
* @param {string|string[]|Function} options.keys - Valid API key(s) or async validator function
|
|
325
|
+
* @param {string} [options.headerName='X-API-Key'] - Header to check
|
|
326
|
+
* @param {string} [options.queryParam] - Optional query parameter name to check
|
|
327
|
+
* @returns {Object} Middleware object with authenticate
|
|
328
|
+
*/
|
|
329
|
+
export function createApiKeyAuth(options = {}) {
|
|
330
|
+
const keys = Array.isArray(options.keys) ? options.keys : [options.keys];
|
|
331
|
+
const validator = typeof options.keys === 'function' ? options.keys : k => keys.includes(k);
|
|
332
|
+
const headerName = options.headerName || 'X-API-Key';
|
|
333
|
+
const queryParam = options.queryParam;
|
|
334
|
+
return {
|
|
335
|
+
async authenticate(request) {
|
|
336
|
+
let key = request.headers.get(headerName);
|
|
337
|
+
|
|
338
|
+
// Fallback to query parameter if configured
|
|
339
|
+
if (!key && queryParam) {
|
|
340
|
+
const url = new URL(request.url);
|
|
341
|
+
key = url.searchParams.get(queryParam);
|
|
342
|
+
}
|
|
343
|
+
if (!key) {
|
|
344
|
+
return new Response(JSON.stringify({
|
|
345
|
+
error: 'API key required'
|
|
346
|
+
}), {
|
|
347
|
+
status: 401,
|
|
348
|
+
headers: {
|
|
349
|
+
'Content-Type': 'application/json'
|
|
350
|
+
}
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
const valid = await validator(key, request);
|
|
354
|
+
if (!valid) {
|
|
355
|
+
return new Response(JSON.stringify({
|
|
356
|
+
error: 'Invalid API key'
|
|
357
|
+
}), {
|
|
358
|
+
status: 403,
|
|
359
|
+
headers: {
|
|
360
|
+
'Content-Type': 'application/json'
|
|
361
|
+
}
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
return null; // authenticated
|
|
365
|
+
}
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// ─── Compose Middleware ──────────────────────────────────────────────
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Compose multiple middleware into a single executable middleware chain.
|
|
373
|
+
* This is the recommended way to combine middleware for use with the router.
|
|
374
|
+
*
|
|
375
|
+
* Execution order:
|
|
376
|
+
* preprocess → authenticate → validate → handler → postprocess (reverse)
|
|
377
|
+
*
|
|
378
|
+
* Any phase returning a Response short-circuits the chain.
|
|
379
|
+
*
|
|
380
|
+
* @param {...Object} middlewares - Middleware objects
|
|
381
|
+
* @returns {Object} Composed middleware with execute(request, handler) method
|
|
382
|
+
*
|
|
383
|
+
* @example
|
|
384
|
+
* const composed = composeMiddleware(
|
|
385
|
+
* createCorsMiddleware({ origins: ['*'] }),
|
|
386
|
+
* createLogger({ prefix: 'api' }),
|
|
387
|
+
* createRateLimitGuard({ maxRequests: 100 }),
|
|
388
|
+
* createErrorHandler()
|
|
389
|
+
* );
|
|
390
|
+
*
|
|
391
|
+
* // Use with router
|
|
392
|
+
* router.use(composed);
|
|
393
|
+
*
|
|
394
|
+
* // Or use directly
|
|
395
|
+
* const response = await composed.execute(request, handler);
|
|
396
|
+
*/
|
|
397
|
+
export function composeMiddleware(...middlewares) {
|
|
398
|
+
const chain = middlewares.filter(Boolean);
|
|
399
|
+
return {
|
|
400
|
+
async execute(request, handler) {
|
|
401
|
+
// Find error handler if one exists (it wraps the handler)
|
|
402
|
+
const errorHandler = chain.find(m => typeof m.wrapHandler === 'function');
|
|
403
|
+
|
|
404
|
+
// Pre-handler phases (in order)
|
|
405
|
+
for (const m of chain) {
|
|
406
|
+
if (typeof m.preprocess === 'function') {
|
|
407
|
+
const res = await m.preprocess(request);
|
|
408
|
+
if (res instanceof Response) return res;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
for (const m of chain) {
|
|
412
|
+
if (typeof m.authenticate === 'function') {
|
|
413
|
+
const res = await m.authenticate(request);
|
|
414
|
+
if (res instanceof Response) return res;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
for (const m of chain) {
|
|
418
|
+
if (typeof m.validate === 'function') {
|
|
419
|
+
const res = await m.validate(request);
|
|
420
|
+
if (res instanceof Response) return res;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// Execute handler (wrapped by error handler if present)
|
|
425
|
+
let response;
|
|
426
|
+
if (errorHandler) {
|
|
427
|
+
response = await errorHandler.wrapHandler(request, handler);
|
|
428
|
+
} else {
|
|
429
|
+
response = await handler(request);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// Post-handler phase (in reverse order)
|
|
433
|
+
for (const m of chain.slice().reverse()) {
|
|
434
|
+
if (typeof m.postprocess === 'function') {
|
|
435
|
+
const updated = await m.postprocess(response);
|
|
436
|
+
if (updated instanceof Response) response = updated;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
return response;
|
|
440
|
+
},
|
|
441
|
+
// Also expose as a preprocess-only middleware for nesting
|
|
442
|
+
preprocess: chain.length === 1 && chain[0].preprocess ? chain[0].preprocess : undefined,
|
|
443
|
+
postprocess: chain.length === 1 && chain[0].postprocess ? chain[0].postprocess : undefined
|
|
444
|
+
};
|
|
445
|
+
}
|
package/dist/middleware/index.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
1
|
export { MiddlewareRegistry } from './Registry.js';
|
|
2
2
|
export { MiddlewareComposer } from './Composer.js';
|
|
3
|
-
export * as Shared from './shared/index.js';
|
|
3
|
+
export * as Shared from './shared/index.js';
|
|
4
|
+
|
|
5
|
+
// High-level middleware factories (recommended API)
|
|
6
|
+
export { createCorsMiddleware, createErrorHandler, createRateLimitGuard, createLogger, createBearerAuth, createApiKeyAuth, composeMiddleware } from './factories.js';
|
|
@@ -60,7 +60,9 @@ export class ModuleManager {
|
|
|
60
60
|
});
|
|
61
61
|
});
|
|
62
62
|
}
|
|
63
|
-
|
|
63
|
+
if (typeof process !== 'undefined' && process.env?.DEBUG) {
|
|
64
|
+
console.log(`✅ Registered module: ${moduleName}`);
|
|
65
|
+
}
|
|
64
66
|
}
|
|
65
67
|
|
|
66
68
|
/**
|
|
@@ -665,4 +667,6 @@ moduleManager.registerModule('logging', {
|
|
|
665
667
|
}
|
|
666
668
|
}
|
|
667
669
|
});
|
|
668
|
-
|
|
670
|
+
|
|
671
|
+
// Module Manager initialized with 3 default modules (auth, files, logging)
|
|
672
|
+
// Set DEBUG=true to see registration logs
|