te.js 2.1.0 → 2.1.2
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 +197 -196
- package/auto-docs/analysis/handler-analyzer.js +58 -58
- package/auto-docs/analysis/source-resolver.js +101 -101
- package/auto-docs/constants.js +37 -37
- package/auto-docs/docs-llm/index.js +7 -7
- package/auto-docs/docs-llm/prompts.js +222 -222
- package/auto-docs/docs-llm/provider.js +132 -132
- package/auto-docs/index.js +146 -146
- package/auto-docs/openapi/endpoint-processor.js +277 -277
- package/auto-docs/openapi/generator.js +107 -107
- package/auto-docs/openapi/level3.js +131 -131
- package/auto-docs/openapi/spec-builders.js +244 -244
- package/auto-docs/ui/docs-ui.js +186 -186
- package/auto-docs/utils/logger.js +17 -17
- package/auto-docs/utils/strip-usage.js +10 -10
- package/cli/docs-command.js +315 -315
- package/cli/fly-command.js +71 -71
- package/cli/index.js +56 -56
- package/cors/index.js +71 -0
- package/database/index.js +165 -165
- package/database/mongodb.js +146 -146
- package/database/redis.js +201 -201
- package/docs/README.md +36 -36
- package/docs/ammo.md +362 -362
- package/docs/api-reference.md +490 -490
- package/docs/auto-docs.md +216 -216
- package/docs/cli.md +152 -152
- package/docs/configuration.md +275 -275
- package/docs/database.md +390 -390
- package/docs/error-handling.md +438 -438
- package/docs/file-uploads.md +333 -333
- package/docs/getting-started.md +214 -214
- package/docs/middleware.md +355 -355
- package/docs/rate-limiting.md +393 -393
- package/docs/routing.md +302 -302
- package/lib/llm/client.js +73 -0
- package/lib/llm/index.js +7 -0
- package/lib/llm/parse.js +89 -0
- package/package.json +64 -62
- package/rate-limit/algorithms/fixed-window.js +141 -141
- package/rate-limit/algorithms/sliding-window.js +147 -147
- package/rate-limit/algorithms/token-bucket.js +115 -115
- package/rate-limit/base.js +165 -165
- package/rate-limit/index.js +147 -147
- package/rate-limit/storage/base.js +104 -104
- package/rate-limit/storage/memory.js +101 -101
- package/rate-limit/storage/redis.js +88 -88
- package/server/ammo/body-parser.js +220 -220
- package/server/ammo/dispatch-helper.js +103 -103
- package/server/ammo/enhancer.js +57 -57
- package/server/ammo.js +454 -415
- package/server/endpoint.js +97 -74
- package/server/error.js +9 -9
- package/server/errors/code-context.js +125 -125
- package/server/errors/llm-error-service.js +140 -140
- package/server/files/helper.js +33 -33
- package/server/files/uploader.js +143 -143
- package/server/handler.js +158 -119
- package/server/target.js +185 -175
- package/server/targets/middleware-validator.js +22 -22
- package/server/targets/path-validator.js +21 -21
- package/server/targets/registry.js +160 -160
- package/server/targets/shoot-validator.js +21 -21
- package/te.js +428 -402
- package/utils/auto-register.js +17 -17
- package/utils/configuration.js +64 -64
- package/utils/errors-llm-config.js +84 -84
- package/utils/request-logger.js +43 -43
- package/utils/status-codes.js +82 -82
- package/utils/tejas-entrypoint-html.js +18 -18
package/docs/middleware.md
CHANGED
|
@@ -1,355 +1,355 @@
|
|
|
1
|
-
# Middleware
|
|
2
|
-
|
|
3
|
-
Middleware functions in Tejas intercept requests before they reach your route handlers. They can modify the request, perform checks, or terminate the request early.
|
|
4
|
-
|
|
5
|
-
## Middleware Signature
|
|
6
|
-
|
|
7
|
-
Tejas supports two middleware signatures:
|
|
8
|
-
|
|
9
|
-
### Tejas Style (Recommended)
|
|
10
|
-
|
|
11
|
-
```javascript
|
|
12
|
-
const middleware = (ammo, next) => {
|
|
13
|
-
// Do something
|
|
14
|
-
next(); // Continue to next middleware/handler
|
|
15
|
-
};
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
### Express Style (Compatible)
|
|
19
|
-
|
|
20
|
-
```javascript
|
|
21
|
-
const middleware = (req, res, next) => {
|
|
22
|
-
// Express-style middleware
|
|
23
|
-
next();
|
|
24
|
-
};
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
Tejas automatically detects which style you're using based on the function's argument count: functions with 3 parameters are treated as Express-style `(req, res, next)`, while functions with 2 parameters are treated as Tejas-style `(ammo, next)`.
|
|
28
|
-
|
|
29
|
-
## Middleware Levels
|
|
30
|
-
|
|
31
|
-
### 1. Global Middleware
|
|
32
|
-
|
|
33
|
-
Applied to **all routes** in your application:
|
|
34
|
-
|
|
35
|
-
```javascript
|
|
36
|
-
import Tejas from 'te.js';
|
|
37
|
-
|
|
38
|
-
const app = new Tejas();
|
|
39
|
-
|
|
40
|
-
// Add global middleware
|
|
41
|
-
app.midair((ammo, next) => {
|
|
42
|
-
console.log(`[${new Date().toISOString()}] ${ammo.method} ${ammo.path}`);
|
|
43
|
-
next();
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
// Multiple middleware in one call
|
|
47
|
-
app.midair(
|
|
48
|
-
loggingMiddleware,
|
|
49
|
-
corsMiddleware,
|
|
50
|
-
compressionMiddleware
|
|
51
|
-
);
|
|
52
|
-
|
|
53
|
-
app.takeoff();
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
### 2. Target Middleware
|
|
57
|
-
|
|
58
|
-
Applied to **all routes in a Target**:
|
|
59
|
-
|
|
60
|
-
```javascript
|
|
61
|
-
import { Target } from 'te.js';
|
|
62
|
-
|
|
63
|
-
const api = new Target('/api');
|
|
64
|
-
|
|
65
|
-
// All /api/* routes require authentication
|
|
66
|
-
api.midair(authMiddleware);
|
|
67
|
-
|
|
68
|
-
api.register('/users', handler); // Protected
|
|
69
|
-
api.register('/posts', handler); // Protected
|
|
70
|
-
api.register('/comments', handler); // Protected
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
### 3. Route Middleware
|
|
74
|
-
|
|
75
|
-
Applied to a **specific route** only:
|
|
76
|
-
|
|
77
|
-
```javascript
|
|
78
|
-
// Only /admin routes require admin privileges
|
|
79
|
-
target.register('/admin', authMiddleware, adminMiddleware, (ammo) => {
|
|
80
|
-
ammo.fire({ admin: 'panel' });
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
// Public route - no middleware
|
|
84
|
-
target.register('/public', (ammo) => {
|
|
85
|
-
ammo.fire({ public: true });
|
|
86
|
-
});
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
## Execution Order
|
|
90
|
-
|
|
91
|
-
Middleware executes in this order:
|
|
92
|
-
|
|
93
|
-
```
|
|
94
|
-
Request
|
|
95
|
-
│
|
|
96
|
-
▼
|
|
97
|
-
┌──────────────────┐
|
|
98
|
-
│ Global Middleware │ (app.midair)
|
|
99
|
-
└──────────────────┘
|
|
100
|
-
│
|
|
101
|
-
▼
|
|
102
|
-
┌──────────────────┐
|
|
103
|
-
│ Target Middleware │ (target.midair)
|
|
104
|
-
└──────────────────┘
|
|
105
|
-
│
|
|
106
|
-
▼
|
|
107
|
-
┌──────────────────┐
|
|
108
|
-
│ Route Middleware │ (in register())
|
|
109
|
-
└──────────────────┘
|
|
110
|
-
│
|
|
111
|
-
▼
|
|
112
|
-
┌──────────────────┐
|
|
113
|
-
│ Route Handler │
|
|
114
|
-
└──────────────────┘
|
|
115
|
-
│
|
|
116
|
-
▼
|
|
117
|
-
Response
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
## Common Middleware Patterns
|
|
121
|
-
|
|
122
|
-
### Authentication
|
|
123
|
-
|
|
124
|
-
```javascript
|
|
125
|
-
// middleware/auth.js
|
|
126
|
-
import { TejError } from 'te.js';
|
|
127
|
-
|
|
128
|
-
export const authMiddleware = async (ammo, next) => {
|
|
129
|
-
const token = ammo.headers.authorization?.replace('Bearer ', '');
|
|
130
|
-
|
|
131
|
-
if (!token) {
|
|
132
|
-
throw new TejError(401, 'No token provided');
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
try {
|
|
136
|
-
const user = await verifyToken(token);
|
|
137
|
-
ammo.user = user; // Attach user to ammo
|
|
138
|
-
next();
|
|
139
|
-
} catch (error) {
|
|
140
|
-
throw new TejError(401, 'Invalid token');
|
|
141
|
-
}
|
|
142
|
-
};
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
### Logging
|
|
146
|
-
|
|
147
|
-
```javascript
|
|
148
|
-
// middleware/logging.js
|
|
149
|
-
export const loggingMiddleware = (ammo, next) => {
|
|
150
|
-
const start = Date.now();
|
|
151
|
-
|
|
152
|
-
// Store original fire method
|
|
153
|
-
const originalFire = ammo.fire.bind(ammo);
|
|
154
|
-
|
|
155
|
-
// Override to log after response
|
|
156
|
-
ammo.fire = (...args) => {
|
|
157
|
-
const duration = Date.now() - start;
|
|
158
|
-
console.log(`${ammo.method} ${ammo.path} - ${duration}ms`);
|
|
159
|
-
originalFire(...args);
|
|
160
|
-
};
|
|
161
|
-
|
|
162
|
-
next();
|
|
163
|
-
};
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
### CORS
|
|
167
|
-
|
|
168
|
-
```javascript
|
|
169
|
-
// middleware/cors.js
|
|
170
|
-
export const corsMiddleware = (ammo, next) => {
|
|
171
|
-
ammo.res.setHeader('Access-Control-Allow-Origin', '*');
|
|
172
|
-
ammo.res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
|
|
173
|
-
ammo.res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
|
|
174
|
-
|
|
175
|
-
// Handle preflight
|
|
176
|
-
if (ammo.OPTIONS) {
|
|
177
|
-
return ammo.fire(204);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
next();
|
|
181
|
-
};
|
|
182
|
-
```
|
|
183
|
-
|
|
184
|
-
### Rate Limiting Check
|
|
185
|
-
|
|
186
|
-
```javascript
|
|
187
|
-
// middleware/rate-check.js
|
|
188
|
-
export const rateLimitCheck = (ammo, next) => {
|
|
189
|
-
const remaining = ammo.res.getHeader('RateLimit-Remaining');
|
|
190
|
-
|
|
191
|
-
if (remaining && parseInt(remaining) < 10) {
|
|
192
|
-
console.warn(`Low rate limit remaining for ${ammo.ip}`);
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
next();
|
|
196
|
-
};
|
|
197
|
-
```
|
|
198
|
-
|
|
199
|
-
### Request Validation
|
|
200
|
-
|
|
201
|
-
```javascript
|
|
202
|
-
// middleware/validate.js
|
|
203
|
-
import { TejError } from 'te.js';
|
|
204
|
-
|
|
205
|
-
export const validateBody = (schema) => {
|
|
206
|
-
return (ammo, next) => {
|
|
207
|
-
const errors = [];
|
|
208
|
-
|
|
209
|
-
for (const [field, rules] of Object.entries(schema)) {
|
|
210
|
-
const value = ammo.payload[field];
|
|
211
|
-
|
|
212
|
-
if (rules.required && !value) {
|
|
213
|
-
errors.push(`${field} is required`);
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
if (rules.type && typeof value !== rules.type) {
|
|
217
|
-
errors.push(`${field} must be a ${rules.type}`);
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
if (errors.length > 0) {
|
|
222
|
-
throw new TejError(400, errors.join(', '));
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
next();
|
|
226
|
-
};
|
|
227
|
-
};
|
|
228
|
-
|
|
229
|
-
// Usage
|
|
230
|
-
target.register('/users',
|
|
231
|
-
validateBody({
|
|
232
|
-
name: { required: true, type: 'string' },
|
|
233
|
-
email: { required: true, type: 'string' }
|
|
234
|
-
}),
|
|
235
|
-
(ammo) => {
|
|
236
|
-
// Payload is validated
|
|
237
|
-
ammo.fire(201, { created: true });
|
|
238
|
-
}
|
|
239
|
-
);
|
|
240
|
-
```
|
|
241
|
-
|
|
242
|
-
## Using Express Middleware
|
|
243
|
-
|
|
244
|
-
Tejas is compatible with Express middleware:
|
|
245
|
-
|
|
246
|
-
```javascript
|
|
247
|
-
import cors from 'cors';
|
|
248
|
-
import helmet from 'helmet';
|
|
249
|
-
import compression from 'compression';
|
|
250
|
-
|
|
251
|
-
const app = new Tejas();
|
|
252
|
-
|
|
253
|
-
// Express middleware works directly
|
|
254
|
-
app.midair(cors());
|
|
255
|
-
app.midair(helmet());
|
|
256
|
-
app.midair(compression());
|
|
257
|
-
|
|
258
|
-
app.takeoff();
|
|
259
|
-
```
|
|
260
|
-
|
|
261
|
-
### Passport.js Integration
|
|
262
|
-
|
|
263
|
-
```javascript
|
|
264
|
-
import passport from 'passport';
|
|
265
|
-
|
|
266
|
-
const app = new Tejas();
|
|
267
|
-
|
|
268
|
-
// Initialize passport
|
|
269
|
-
app.midair(passport.initialize());
|
|
270
|
-
|
|
271
|
-
// In your target
|
|
272
|
-
target.register('/auth/google',
|
|
273
|
-
passport.authenticate('google', { scope: ['profile', 'email'] }),
|
|
274
|
-
(ammo) => {
|
|
275
|
-
// Redirect handled by passport
|
|
276
|
-
}
|
|
277
|
-
);
|
|
278
|
-
|
|
279
|
-
target.register('/auth/google/callback',
|
|
280
|
-
passport.authenticate('google', { session: false }),
|
|
281
|
-
(ammo) => {
|
|
282
|
-
ammo.fire({ user: ammo.req.user });
|
|
283
|
-
}
|
|
284
|
-
);
|
|
285
|
-
```
|
|
286
|
-
|
|
287
|
-
## Async Middleware
|
|
288
|
-
|
|
289
|
-
Middleware can be async. Errors thrown inside async middleware are automatically caught by the framework:
|
|
290
|
-
|
|
291
|
-
```javascript
|
|
292
|
-
const asyncMiddleware = async (ammo, next) => {
|
|
293
|
-
const result = await someAsyncOperation();
|
|
294
|
-
ammo.asyncResult = result;
|
|
295
|
-
next();
|
|
296
|
-
};
|
|
297
|
-
```
|
|
298
|
-
|
|
299
|
-
## Terminating Early
|
|
300
|
-
|
|
301
|
-
To stop the middleware chain, send a response without calling `next()`. Once a response is sent (`ammo.fire()`, `ammo.throw()`, etc.), the framework detects that `res.headersSent` is `true` and stops executing further middleware or the handler:
|
|
302
|
-
|
|
303
|
-
```javascript
|
|
304
|
-
const earlyReturn = (ammo, next) => {
|
|
305
|
-
if (someCondition) {
|
|
306
|
-
return ammo.fire(403, 'Forbidden');
|
|
307
|
-
// next() is not called, chain stops
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
next(); // Continue chain
|
|
311
|
-
};
|
|
312
|
-
```
|
|
313
|
-
|
|
314
|
-
## Middleware Factory Pattern
|
|
315
|
-
|
|
316
|
-
Create configurable middleware:
|
|
317
|
-
|
|
318
|
-
```javascript
|
|
319
|
-
// middleware/cache.js
|
|
320
|
-
export const cache = (ttl = 60) => {
|
|
321
|
-
const store = new Map();
|
|
322
|
-
|
|
323
|
-
return (ammo, next) => {
|
|
324
|
-
const key = ammo.path;
|
|
325
|
-
const cached = store.get(key);
|
|
326
|
-
|
|
327
|
-
if (cached && Date.now() - cached.time < ttl * 1000) {
|
|
328
|
-
return ammo.fire(cached.data);
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
const originalFire = ammo.fire.bind(ammo);
|
|
332
|
-
ammo.fire = (...args) => {
|
|
333
|
-
store.set(key, { data: args[0], time: Date.now() });
|
|
334
|
-
originalFire(...args);
|
|
335
|
-
};
|
|
336
|
-
|
|
337
|
-
next();
|
|
338
|
-
};
|
|
339
|
-
};
|
|
340
|
-
|
|
341
|
-
// Usage
|
|
342
|
-
target.register('/expensive', cache(300), (ammo) => {
|
|
343
|
-
const data = expensiveComputation();
|
|
344
|
-
ammo.fire(data);
|
|
345
|
-
});
|
|
346
|
-
```
|
|
347
|
-
|
|
348
|
-
## Best Practices
|
|
349
|
-
|
|
350
|
-
1. **Keep middleware focused** — Each middleware should do one thing
|
|
351
|
-
2. **Always call next()** — Unless intentionally terminating the chain
|
|
352
|
-
3. **Handle errors** — Use try/catch in async middleware
|
|
353
|
-
4. **Use factories** — For configurable middleware
|
|
354
|
-
5. **Order matters** — Place authentication before authorization
|
|
355
|
-
6. **Don't mutate payload directly** — Add new properties instead
|
|
1
|
+
# Middleware
|
|
2
|
+
|
|
3
|
+
Middleware functions in Tejas intercept requests before they reach your route handlers. They can modify the request, perform checks, or terminate the request early.
|
|
4
|
+
|
|
5
|
+
## Middleware Signature
|
|
6
|
+
|
|
7
|
+
Tejas supports two middleware signatures:
|
|
8
|
+
|
|
9
|
+
### Tejas Style (Recommended)
|
|
10
|
+
|
|
11
|
+
```javascript
|
|
12
|
+
const middleware = (ammo, next) => {
|
|
13
|
+
// Do something
|
|
14
|
+
next(); // Continue to next middleware/handler
|
|
15
|
+
};
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
### Express Style (Compatible)
|
|
19
|
+
|
|
20
|
+
```javascript
|
|
21
|
+
const middleware = (req, res, next) => {
|
|
22
|
+
// Express-style middleware
|
|
23
|
+
next();
|
|
24
|
+
};
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Tejas automatically detects which style you're using based on the function's argument count: functions with 3 parameters are treated as Express-style `(req, res, next)`, while functions with 2 parameters are treated as Tejas-style `(ammo, next)`.
|
|
28
|
+
|
|
29
|
+
## Middleware Levels
|
|
30
|
+
|
|
31
|
+
### 1. Global Middleware
|
|
32
|
+
|
|
33
|
+
Applied to **all routes** in your application:
|
|
34
|
+
|
|
35
|
+
```javascript
|
|
36
|
+
import Tejas from 'te.js';
|
|
37
|
+
|
|
38
|
+
const app = new Tejas();
|
|
39
|
+
|
|
40
|
+
// Add global middleware
|
|
41
|
+
app.midair((ammo, next) => {
|
|
42
|
+
console.log(`[${new Date().toISOString()}] ${ammo.method} ${ammo.path}`);
|
|
43
|
+
next();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// Multiple middleware in one call
|
|
47
|
+
app.midair(
|
|
48
|
+
loggingMiddleware,
|
|
49
|
+
corsMiddleware,
|
|
50
|
+
compressionMiddleware
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
app.takeoff();
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### 2. Target Middleware
|
|
57
|
+
|
|
58
|
+
Applied to **all routes in a Target**:
|
|
59
|
+
|
|
60
|
+
```javascript
|
|
61
|
+
import { Target } from 'te.js';
|
|
62
|
+
|
|
63
|
+
const api = new Target('/api');
|
|
64
|
+
|
|
65
|
+
// All /api/* routes require authentication
|
|
66
|
+
api.midair(authMiddleware);
|
|
67
|
+
|
|
68
|
+
api.register('/users', handler); // Protected
|
|
69
|
+
api.register('/posts', handler); // Protected
|
|
70
|
+
api.register('/comments', handler); // Protected
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### 3. Route Middleware
|
|
74
|
+
|
|
75
|
+
Applied to a **specific route** only:
|
|
76
|
+
|
|
77
|
+
```javascript
|
|
78
|
+
// Only /admin routes require admin privileges
|
|
79
|
+
target.register('/admin', authMiddleware, adminMiddleware, (ammo) => {
|
|
80
|
+
ammo.fire({ admin: 'panel' });
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// Public route - no middleware
|
|
84
|
+
target.register('/public', (ammo) => {
|
|
85
|
+
ammo.fire({ public: true });
|
|
86
|
+
});
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Execution Order
|
|
90
|
+
|
|
91
|
+
Middleware executes in this order:
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
Request
|
|
95
|
+
│
|
|
96
|
+
▼
|
|
97
|
+
┌──────────────────┐
|
|
98
|
+
│ Global Middleware │ (app.midair)
|
|
99
|
+
└──────────────────┘
|
|
100
|
+
│
|
|
101
|
+
▼
|
|
102
|
+
┌──────────────────┐
|
|
103
|
+
│ Target Middleware │ (target.midair)
|
|
104
|
+
└──────────────────┘
|
|
105
|
+
│
|
|
106
|
+
▼
|
|
107
|
+
┌──────────────────┐
|
|
108
|
+
│ Route Middleware │ (in register())
|
|
109
|
+
└──────────────────┘
|
|
110
|
+
│
|
|
111
|
+
▼
|
|
112
|
+
┌──────────────────┐
|
|
113
|
+
│ Route Handler │
|
|
114
|
+
└──────────────────┘
|
|
115
|
+
│
|
|
116
|
+
▼
|
|
117
|
+
Response
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Common Middleware Patterns
|
|
121
|
+
|
|
122
|
+
### Authentication
|
|
123
|
+
|
|
124
|
+
```javascript
|
|
125
|
+
// middleware/auth.js
|
|
126
|
+
import { TejError } from 'te.js';
|
|
127
|
+
|
|
128
|
+
export const authMiddleware = async (ammo, next) => {
|
|
129
|
+
const token = ammo.headers.authorization?.replace('Bearer ', '');
|
|
130
|
+
|
|
131
|
+
if (!token) {
|
|
132
|
+
throw new TejError(401, 'No token provided');
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
try {
|
|
136
|
+
const user = await verifyToken(token);
|
|
137
|
+
ammo.user = user; // Attach user to ammo
|
|
138
|
+
next();
|
|
139
|
+
} catch (error) {
|
|
140
|
+
throw new TejError(401, 'Invalid token');
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Logging
|
|
146
|
+
|
|
147
|
+
```javascript
|
|
148
|
+
// middleware/logging.js
|
|
149
|
+
export const loggingMiddleware = (ammo, next) => {
|
|
150
|
+
const start = Date.now();
|
|
151
|
+
|
|
152
|
+
// Store original fire method
|
|
153
|
+
const originalFire = ammo.fire.bind(ammo);
|
|
154
|
+
|
|
155
|
+
// Override to log after response
|
|
156
|
+
ammo.fire = (...args) => {
|
|
157
|
+
const duration = Date.now() - start;
|
|
158
|
+
console.log(`${ammo.method} ${ammo.path} - ${duration}ms`);
|
|
159
|
+
originalFire(...args);
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
next();
|
|
163
|
+
};
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### CORS
|
|
167
|
+
|
|
168
|
+
```javascript
|
|
169
|
+
// middleware/cors.js
|
|
170
|
+
export const corsMiddleware = (ammo, next) => {
|
|
171
|
+
ammo.res.setHeader('Access-Control-Allow-Origin', '*');
|
|
172
|
+
ammo.res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
|
|
173
|
+
ammo.res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
|
|
174
|
+
|
|
175
|
+
// Handle preflight
|
|
176
|
+
if (ammo.OPTIONS) {
|
|
177
|
+
return ammo.fire(204);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
next();
|
|
181
|
+
};
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Rate Limiting Check
|
|
185
|
+
|
|
186
|
+
```javascript
|
|
187
|
+
// middleware/rate-check.js
|
|
188
|
+
export const rateLimitCheck = (ammo, next) => {
|
|
189
|
+
const remaining = ammo.res.getHeader('RateLimit-Remaining');
|
|
190
|
+
|
|
191
|
+
if (remaining && parseInt(remaining) < 10) {
|
|
192
|
+
console.warn(`Low rate limit remaining for ${ammo.ip}`);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
next();
|
|
196
|
+
};
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### Request Validation
|
|
200
|
+
|
|
201
|
+
```javascript
|
|
202
|
+
// middleware/validate.js
|
|
203
|
+
import { TejError } from 'te.js';
|
|
204
|
+
|
|
205
|
+
export const validateBody = (schema) => {
|
|
206
|
+
return (ammo, next) => {
|
|
207
|
+
const errors = [];
|
|
208
|
+
|
|
209
|
+
for (const [field, rules] of Object.entries(schema)) {
|
|
210
|
+
const value = ammo.payload[field];
|
|
211
|
+
|
|
212
|
+
if (rules.required && !value) {
|
|
213
|
+
errors.push(`${field} is required`);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (rules.type && typeof value !== rules.type) {
|
|
217
|
+
errors.push(`${field} must be a ${rules.type}`);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (errors.length > 0) {
|
|
222
|
+
throw new TejError(400, errors.join(', '));
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
next();
|
|
226
|
+
};
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
// Usage
|
|
230
|
+
target.register('/users',
|
|
231
|
+
validateBody({
|
|
232
|
+
name: { required: true, type: 'string' },
|
|
233
|
+
email: { required: true, type: 'string' }
|
|
234
|
+
}),
|
|
235
|
+
(ammo) => {
|
|
236
|
+
// Payload is validated
|
|
237
|
+
ammo.fire(201, { created: true });
|
|
238
|
+
}
|
|
239
|
+
);
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
## Using Express Middleware
|
|
243
|
+
|
|
244
|
+
Tejas is compatible with Express middleware:
|
|
245
|
+
|
|
246
|
+
```javascript
|
|
247
|
+
import cors from 'cors';
|
|
248
|
+
import helmet from 'helmet';
|
|
249
|
+
import compression from 'compression';
|
|
250
|
+
|
|
251
|
+
const app = new Tejas();
|
|
252
|
+
|
|
253
|
+
// Express middleware works directly
|
|
254
|
+
app.midair(cors());
|
|
255
|
+
app.midair(helmet());
|
|
256
|
+
app.midair(compression());
|
|
257
|
+
|
|
258
|
+
app.takeoff();
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### Passport.js Integration
|
|
262
|
+
|
|
263
|
+
```javascript
|
|
264
|
+
import passport from 'passport';
|
|
265
|
+
|
|
266
|
+
const app = new Tejas();
|
|
267
|
+
|
|
268
|
+
// Initialize passport
|
|
269
|
+
app.midair(passport.initialize());
|
|
270
|
+
|
|
271
|
+
// In your target
|
|
272
|
+
target.register('/auth/google',
|
|
273
|
+
passport.authenticate('google', { scope: ['profile', 'email'] }),
|
|
274
|
+
(ammo) => {
|
|
275
|
+
// Redirect handled by passport
|
|
276
|
+
}
|
|
277
|
+
);
|
|
278
|
+
|
|
279
|
+
target.register('/auth/google/callback',
|
|
280
|
+
passport.authenticate('google', { session: false }),
|
|
281
|
+
(ammo) => {
|
|
282
|
+
ammo.fire({ user: ammo.req.user });
|
|
283
|
+
}
|
|
284
|
+
);
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
## Async Middleware
|
|
288
|
+
|
|
289
|
+
Middleware can be async. Errors thrown inside async middleware are automatically caught by the framework:
|
|
290
|
+
|
|
291
|
+
```javascript
|
|
292
|
+
const asyncMiddleware = async (ammo, next) => {
|
|
293
|
+
const result = await someAsyncOperation();
|
|
294
|
+
ammo.asyncResult = result;
|
|
295
|
+
next();
|
|
296
|
+
};
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
## Terminating Early
|
|
300
|
+
|
|
301
|
+
To stop the middleware chain, send a response without calling `next()`. Once a response is sent (`ammo.fire()`, `ammo.throw()`, etc.), the framework detects that `res.headersSent` is `true` and stops executing further middleware or the handler:
|
|
302
|
+
|
|
303
|
+
```javascript
|
|
304
|
+
const earlyReturn = (ammo, next) => {
|
|
305
|
+
if (someCondition) {
|
|
306
|
+
return ammo.fire(403, 'Forbidden');
|
|
307
|
+
// next() is not called, chain stops
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
next(); // Continue chain
|
|
311
|
+
};
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
## Middleware Factory Pattern
|
|
315
|
+
|
|
316
|
+
Create configurable middleware:
|
|
317
|
+
|
|
318
|
+
```javascript
|
|
319
|
+
// middleware/cache.js
|
|
320
|
+
export const cache = (ttl = 60) => {
|
|
321
|
+
const store = new Map();
|
|
322
|
+
|
|
323
|
+
return (ammo, next) => {
|
|
324
|
+
const key = ammo.path;
|
|
325
|
+
const cached = store.get(key);
|
|
326
|
+
|
|
327
|
+
if (cached && Date.now() - cached.time < ttl * 1000) {
|
|
328
|
+
return ammo.fire(cached.data);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const originalFire = ammo.fire.bind(ammo);
|
|
332
|
+
ammo.fire = (...args) => {
|
|
333
|
+
store.set(key, { data: args[0], time: Date.now() });
|
|
334
|
+
originalFire(...args);
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
next();
|
|
338
|
+
};
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
// Usage
|
|
342
|
+
target.register('/expensive', cache(300), (ammo) => {
|
|
343
|
+
const data = expensiveComputation();
|
|
344
|
+
ammo.fire(data);
|
|
345
|
+
});
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
## Best Practices
|
|
349
|
+
|
|
350
|
+
1. **Keep middleware focused** — Each middleware should do one thing
|
|
351
|
+
2. **Always call next()** — Unless intentionally terminating the chain
|
|
352
|
+
3. **Handle errors** — Use try/catch in async middleware
|
|
353
|
+
4. **Use factories** — For configurable middleware
|
|
354
|
+
5. **Order matters** — Place authentication before authorization
|
|
355
|
+
6. **Don't mutate payload directly** — Add new properties instead
|