te.js 2.0.3 → 2.1.1
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 -187
- 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 -0
- package/auto-docs/{llm → docs-llm}/prompts.js +222 -222
- package/auto-docs/{llm → docs-llm}/provider.js +132 -187
- 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/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 -489
- package/docs/auto-docs.md +216 -215
- package/docs/cli.md +152 -152
- package/docs/configuration.md +275 -233
- package/docs/database.md +390 -391
- package/docs/error-handling.md +438 -417
- package/docs/file-uploads.md +333 -334
- package/docs/getting-started.md +214 -215
- package/docs/middleware.md +355 -356
- package/docs/rate-limiting.md +393 -394
- package/docs/routing.md +302 -302
- package/package.json +62 -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 -356
- package/server/endpoint.js +97 -74
- package/server/error.js +9 -9
- package/server/errors/code-context.js +125 -0
- package/server/errors/llm-error-service.js +140 -0
- package/server/files/helper.js +33 -33
- package/server/files/uploader.js +143 -143
- package/server/handler.js +158 -113
- 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 -363
- package/utils/auto-register.js +17 -17
- package/utils/configuration.js +64 -64
- package/utils/errors-llm-config.js +84 -0
- package/utils/request-logger.js +43 -43
- package/utils/status-codes.js +82 -82
- package/utils/tejas-entrypoint-html.js +18 -18
- package/auto-docs/llm/index.js +0 -6
- package/auto-docs/llm/parse.js +0 -88
package/docs/error-handling.md
CHANGED
|
@@ -1,417 +1,438 @@
|
|
|
1
|
-
# Error Handling
|
|
2
|
-
|
|
3
|
-
Tejas
|
|
4
|
-
|
|
5
|
-
## Zero-Config Error Handling
|
|
6
|
-
|
|
7
|
-
**One of Tejas's most powerful features is that you don't need to write any error handling code** — the framework catches all errors automatically at multiple levels.
|
|
8
|
-
|
|
9
|
-
### How It Works
|
|
10
|
-
|
|
11
|
-
Tejas wraps all middleware and route handlers with built-in error catching. Any error thrown in your code is automatically:
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
throw
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
When an error
|
|
89
|
-
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
HTTP
|
|
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
|
-
ammo.
|
|
130
|
-
|
|
131
|
-
//
|
|
132
|
-
ammo.
|
|
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
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
//
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
```
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
});
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
-
|
|
373
|
-
|
|
374
|
-
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
1
|
+
# Error Handling
|
|
2
|
+
|
|
3
|
+
Tejas keeps your application from crashing on unhandled errors. You don't log the error and send the response separately — **`ammo.throw()` is the single mechanism**: it sends the appropriate HTTP response (logging is optional via `log.exceptions`). Whether you call `ammo.throw()` or the framework calls it when it catches an error, the same behaviour applies. When LLM-inferred errors are enabled, call `ammo.throw()` with no arguments and an LLM infers status and message from code context; explicit code and message always override.
|
|
4
|
+
|
|
5
|
+
## Zero-Config Error Handling
|
|
6
|
+
|
|
7
|
+
**One of Tejas's most powerful features is that you don't need to write any error handling code** — the framework catches all errors automatically at multiple levels.
|
|
8
|
+
|
|
9
|
+
### How It Works
|
|
10
|
+
|
|
11
|
+
Tejas wraps all middleware and route handlers with built-in error catching. Any error thrown in your code is automatically passed to `ammo.throw(err)` — the same mechanism you use for intentional errors. So: one place handles everything (response + optional logging via `log.exceptions`). No separate "log then send response"; your app never crashes and clients always receive a proper response.
|
|
12
|
+
|
|
13
|
+
### Write Clean Code Without Try-Catch
|
|
14
|
+
|
|
15
|
+
```javascript
|
|
16
|
+
// ✅ No try-catch needed — Tejas handles errors automatically
|
|
17
|
+
target.register('/users/:id', async (ammo) => {
|
|
18
|
+
const user = await database.findUser(ammo.payload.id); // If this throws, Tejas catches it
|
|
19
|
+
const posts = await database.getUserPosts(user.id); // Same here
|
|
20
|
+
ammo.fire({ user, posts });
|
|
21
|
+
});
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
In other frameworks you typically **log the error and then send the response** (two separate steps). With Tejas, **`ammo.throw()` does both** — and when the framework catches an error it uses the same `ammo.throw()`, so you never define them separately:
|
|
25
|
+
|
|
26
|
+
```javascript
|
|
27
|
+
// ❌ Traditional: log then send response (two separate things)
|
|
28
|
+
app.get('/users/:id', async (req, res) => {
|
|
29
|
+
try {
|
|
30
|
+
const user = await database.findUser(req.params.id);
|
|
31
|
+
res.json(user);
|
|
32
|
+
} catch (error) {
|
|
33
|
+
console.error(error); // 1. log
|
|
34
|
+
res.status(500).json({ error: 'Internal Server Error' }); // 2. send response
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Automatic Error Responses
|
|
40
|
+
|
|
41
|
+
When an unhandled error occurs, the framework calls `ammo.throw(err)` — the same method you use for intentional errors. So one mechanism: explicit `ammo.throw()` or framework-caught, both go through `ammo.throw()`. When [LLM-inferred errors](#llm-inferred-errors) are enabled, status and message are inferred from code context; otherwise or when you pass explicit code/message, those are used.
|
|
42
|
+
|
|
43
|
+
### Enable Error Logging
|
|
44
|
+
|
|
45
|
+
To see caught exceptions in your logs, enable exception logging:
|
|
46
|
+
|
|
47
|
+
```javascript
|
|
48
|
+
const app = new Tejas({
|
|
49
|
+
log: {
|
|
50
|
+
exceptions: true // Log all caught exceptions
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Or via environment variable:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
LOG_EXCEPTIONS=true
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## LLM-Inferred Errors
|
|
64
|
+
|
|
65
|
+
When **`errors.llm.enabled`** is true and you call `ammo.throw()` without an explicit status code or message, Tejas uses an LLM to infer an appropriate HTTP status code and message from **code context** — you do not pass an error object. The framework captures the code surrounding the `ammo.throw()` call (with line numbers) and all **upstream** (callers) and **downstream** (code that would have run next) context, and the LLM infers what went wrong from that. Explicit code and message always override.
|
|
66
|
+
|
|
67
|
+
- **No error object required:** Call `ammo.throw()` with no arguments (or only options). The LLM receives the source code around the call site and upstream call stacks so it can infer status and message from control flow and intent.
|
|
68
|
+
- **Opt-in:** Enable via config: `errors.llm.enabled: true` and configure `errors.llm` (baseURL, apiKey, model), or call **`app.withLLMErrors()`** / **`app.withLLMErrors({ baseURL, apiKey, model, messageType })`** before `takeoff()`. See [Configuration](./configuration.md#error-handling-llm-inferred-errors).
|
|
69
|
+
- **Framework-caught errors:** When the framework catches an unhandled error (in a handler or middleware), it uses the same `ammo.throw(err)` — so the same `errors.llm` config applies. No separate "log then send response"; one mechanism handles everything.
|
|
70
|
+
- **Override:** Whenever you pass a status code or message (e.g. `ammo.throw(404, 'User not found')` or `throw new TejError(404, 'User not found')`), that value is used; the LLM is not called.
|
|
71
|
+
- **Message type:** Configure whether the LLM generates **end-user-friendly** or **developer-friendly** messages via `errors.llm.messageType`; override per call (see [Per-call overrides](#per-call-overrides)).
|
|
72
|
+
- **Non-production:** In non-production, the LLM can also provide developer insight (e.g. bug vs environment, suggested fix), attached to the response as `_dev` or in logs only — never in production.
|
|
73
|
+
|
|
74
|
+
### Per-call overrides
|
|
75
|
+
|
|
76
|
+
For any LLM-eligible `ammo.throw()` call (no explicit status code), you can pass an options object as the last argument to override behaviour for that call only:
|
|
77
|
+
|
|
78
|
+
- **`useLlm`** (boolean): Set to `false` to skip the LLM for this call and respond with a default 500 / "Internal Server Error" (or the error's message when you pass an Error/string). Set to `true` to force using the LLM (same as default when eligible).
|
|
79
|
+
- **`messageType`** (`"endUser"` | `"developer"`): Override the configured default for this call — request an end-user-friendly or developer-friendly message.
|
|
80
|
+
|
|
81
|
+
```javascript
|
|
82
|
+
// Skip LLM for this call; send 500
|
|
83
|
+
ammo.throw({ useLlm: false });
|
|
84
|
+
|
|
85
|
+
// Request a developer-friendly message for this call only
|
|
86
|
+
ammo.throw({ messageType: 'developer' });
|
|
87
|
+
|
|
88
|
+
// When an error was caught and passed in, you can still pass options
|
|
89
|
+
ammo.throw(caughtErr, { useLlm: false });
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## TejError Class
|
|
95
|
+
|
|
96
|
+
Use `TejError` for throwing HTTP errors with status codes. Both status code and message are **optional** when [LLM-inferred errors](#llm-inferred-errors) are enabled and the error is passed through `ammo.throw()`; otherwise, supply them to set the response explicitly.
|
|
97
|
+
|
|
98
|
+
```javascript
|
|
99
|
+
import { TejError } from 'te.js';
|
|
100
|
+
|
|
101
|
+
// Explicit code and message (always used as override)
|
|
102
|
+
throw new TejError(404, 'User not found');
|
|
103
|
+
throw new TejError(400, 'Invalid email format');
|
|
104
|
+
throw new TejError(500, 'Database connection failed');
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Error Response
|
|
108
|
+
|
|
109
|
+
When an error is thrown, Tejas automatically sends the appropriate HTTP response:
|
|
110
|
+
|
|
111
|
+
```javascript
|
|
112
|
+
throw new TejError(404, 'Resource not found');
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
**Response:**
|
|
116
|
+
```
|
|
117
|
+
HTTP/1.1 404 Not Found
|
|
118
|
+
Content-Type: text/plain
|
|
119
|
+
|
|
120
|
+
Resource not found
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Convenience Methods
|
|
124
|
+
|
|
125
|
+
`Ammo` provides shortcut methods for common errors:
|
|
126
|
+
|
|
127
|
+
```javascript
|
|
128
|
+
// 404 Not Found
|
|
129
|
+
ammo.notFound();
|
|
130
|
+
|
|
131
|
+
// 405 Method Not Allowed
|
|
132
|
+
ammo.notAllowed();
|
|
133
|
+
|
|
134
|
+
// 401 Unauthorized
|
|
135
|
+
ammo.unauthorized();
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Using ammo.throw()
|
|
139
|
+
|
|
140
|
+
For more control, use `ammo.throw()`. When [LLM-inferred errors](#llm-inferred-errors) are enabled, you can omit code and message and the LLM will infer them; otherwise, or when you want to override, pass them explicitly.
|
|
141
|
+
|
|
142
|
+
```javascript
|
|
143
|
+
// Explicit: status code and/or message
|
|
144
|
+
ammo.throw(404);
|
|
145
|
+
ammo.throw(404, 'User not found');
|
|
146
|
+
ammo.throw(new TejError(400, 'Bad request'));
|
|
147
|
+
|
|
148
|
+
// When errors.llm.enabled: LLM infers code and message from context
|
|
149
|
+
ammo.throw(new Error('Something went wrong'));
|
|
150
|
+
ammo.throw('Validation failed');
|
|
151
|
+
ammo.throw(); // context still used when available
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
See [Ammo — throw()](./ammo.md#throw--send-error-response) for all signatures and the LLM-inferred row.
|
|
155
|
+
|
|
156
|
+
## Error Handling in Routes
|
|
157
|
+
|
|
158
|
+
### Basic Pattern
|
|
159
|
+
|
|
160
|
+
```javascript
|
|
161
|
+
target.register('/users/:id', async (ammo) => {
|
|
162
|
+
const { id } = ammo.payload;
|
|
163
|
+
|
|
164
|
+
const user = await findUser(id);
|
|
165
|
+
|
|
166
|
+
if (!user) {
|
|
167
|
+
throw new TejError(404, 'User not found');
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
ammo.fire(user);
|
|
171
|
+
});
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Try-Catch Pattern
|
|
175
|
+
|
|
176
|
+
```javascript
|
|
177
|
+
target.register('/data', async (ammo) => {
|
|
178
|
+
try {
|
|
179
|
+
const data = await riskyOperation();
|
|
180
|
+
ammo.fire(data);
|
|
181
|
+
} catch (error) {
|
|
182
|
+
if (error.code === 'TIMEOUT') {
|
|
183
|
+
throw new TejError(504, 'Gateway timeout');
|
|
184
|
+
}
|
|
185
|
+
throw new TejError(500, 'Internal server error');
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## Global Error Handling
|
|
191
|
+
|
|
192
|
+
Errors are automatically caught by Tejas's handler. Enable logging:
|
|
193
|
+
|
|
194
|
+
```javascript
|
|
195
|
+
const app = new Tejas({
|
|
196
|
+
log: {
|
|
197
|
+
exceptions: true // Log all exceptions
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
## Custom Error Middleware
|
|
203
|
+
|
|
204
|
+
Create middleware to customize error handling:
|
|
205
|
+
|
|
206
|
+
```javascript
|
|
207
|
+
// middleware/error-handler.js
|
|
208
|
+
export const errorHandler = (ammo, next) => {
|
|
209
|
+
const originalThrow = ammo.throw.bind(ammo);
|
|
210
|
+
|
|
211
|
+
ammo.throw = (...args) => {
|
|
212
|
+
// Log errors
|
|
213
|
+
console.error('Error:', args);
|
|
214
|
+
|
|
215
|
+
// Send to error tracking service
|
|
216
|
+
errorTracker.capture(args[0]);
|
|
217
|
+
|
|
218
|
+
// Call original throw
|
|
219
|
+
originalThrow(...args);
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
next();
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
// Apply globally
|
|
226
|
+
app.midair(errorHandler);
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
## Structured Error Responses
|
|
230
|
+
|
|
231
|
+
For APIs, return structured error objects:
|
|
232
|
+
|
|
233
|
+
```javascript
|
|
234
|
+
// middleware/api-errors.js
|
|
235
|
+
export const apiErrorHandler = (ammo, next) => {
|
|
236
|
+
const originalThrow = ammo.throw.bind(ammo);
|
|
237
|
+
|
|
238
|
+
ammo.throw = (statusOrError, message) => {
|
|
239
|
+
let status = 500;
|
|
240
|
+
let errorMessage = 'Internal Server Error';
|
|
241
|
+
let errorCode = 'INTERNAL_ERROR';
|
|
242
|
+
|
|
243
|
+
if (typeof statusOrError === 'number') {
|
|
244
|
+
status = statusOrError;
|
|
245
|
+
errorMessage = message || getDefaultMessage(status);
|
|
246
|
+
errorCode = getErrorCode(status);
|
|
247
|
+
} else if (statusOrError instanceof TejError) {
|
|
248
|
+
status = statusOrError.code;
|
|
249
|
+
errorMessage = statusOrError.message;
|
|
250
|
+
errorCode = getErrorCode(status);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
ammo.fire(status, {
|
|
254
|
+
error: {
|
|
255
|
+
code: errorCode,
|
|
256
|
+
message: errorMessage,
|
|
257
|
+
status
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
next();
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
function getDefaultMessage(status) {
|
|
266
|
+
const messages = {
|
|
267
|
+
400: 'Bad Request',
|
|
268
|
+
401: 'Unauthorized',
|
|
269
|
+
403: 'Forbidden',
|
|
270
|
+
404: 'Not Found',
|
|
271
|
+
405: 'Method Not Allowed',
|
|
272
|
+
429: 'Too Many Requests',
|
|
273
|
+
500: 'Internal Server Error'
|
|
274
|
+
};
|
|
275
|
+
return messages[status] || 'Unknown Error';
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function getErrorCode(status) {
|
|
279
|
+
const codes = {
|
|
280
|
+
400: 'BAD_REQUEST',
|
|
281
|
+
401: 'UNAUTHORIZED',
|
|
282
|
+
403: 'FORBIDDEN',
|
|
283
|
+
404: 'NOT_FOUND',
|
|
284
|
+
405: 'METHOD_NOT_ALLOWED',
|
|
285
|
+
429: 'RATE_LIMITED',
|
|
286
|
+
500: 'INTERNAL_ERROR'
|
|
287
|
+
};
|
|
288
|
+
return codes[status] || 'UNKNOWN_ERROR';
|
|
289
|
+
}
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
**Response:**
|
|
293
|
+
```json
|
|
294
|
+
{
|
|
295
|
+
"error": {
|
|
296
|
+
"code": "NOT_FOUND",
|
|
297
|
+
"message": "User not found",
|
|
298
|
+
"status": 404
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
## Validation Errors
|
|
304
|
+
|
|
305
|
+
For input validation, return detailed errors:
|
|
306
|
+
|
|
307
|
+
```javascript
|
|
308
|
+
target.register('/users', (ammo) => {
|
|
309
|
+
if (!ammo.POST) return ammo.notAllowed();
|
|
310
|
+
|
|
311
|
+
const { name, email, age } = ammo.payload;
|
|
312
|
+
const errors = [];
|
|
313
|
+
|
|
314
|
+
if (!name) errors.push({ field: 'name', message: 'Name is required' });
|
|
315
|
+
if (!email) errors.push({ field: 'email', message: 'Email is required' });
|
|
316
|
+
if (email && !isValidEmail(email)) {
|
|
317
|
+
errors.push({ field: 'email', message: 'Invalid email format' });
|
|
318
|
+
}
|
|
319
|
+
if (age && (isNaN(age) || age < 0)) {
|
|
320
|
+
errors.push({ field: 'age', message: 'Age must be a positive number' });
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if (errors.length > 0) {
|
|
324
|
+
return ammo.fire(400, {
|
|
325
|
+
error: {
|
|
326
|
+
code: 'VALIDATION_ERROR',
|
|
327
|
+
message: 'Validation failed',
|
|
328
|
+
details: errors
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Process valid data...
|
|
334
|
+
});
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
## Async Error Handling
|
|
338
|
+
|
|
339
|
+
Tejas automatically catches errors in **both sync and async handlers** — including Promise rejections:
|
|
340
|
+
|
|
341
|
+
```javascript
|
|
342
|
+
// ✅ No try-catch needed — errors are caught automatically
|
|
343
|
+
target.register('/async', async (ammo) => {
|
|
344
|
+
const data = await fetchData(); // If this throws, Tejas catches it
|
|
345
|
+
ammo.fire(data);
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
// ✅ Multiple await calls? Still no try-catch needed
|
|
349
|
+
target.register('/complex', async (ammo) => {
|
|
350
|
+
const user = await getUser(ammo.payload.id);
|
|
351
|
+
const profile = await getProfile(user.profileId);
|
|
352
|
+
const settings = await getSettings(user.id);
|
|
353
|
+
ammo.fire({ user, profile, settings });
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
// 🔧 Use try-catch ONLY when you need custom error handling
|
|
357
|
+
target.register('/async-custom', async (ammo) => {
|
|
358
|
+
try {
|
|
359
|
+
const data = await fetchData();
|
|
360
|
+
ammo.fire(data);
|
|
361
|
+
} catch (error) {
|
|
362
|
+
if (error.code === 'ECONNREFUSED') {
|
|
363
|
+
throw new TejError(503, 'Service temporarily unavailable');
|
|
364
|
+
}
|
|
365
|
+
throw error; // Re-throw unknown errors (Tejas will still catch it)
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
### When You Still Might Want Try-Catch
|
|
371
|
+
|
|
372
|
+
While Tejas catches all errors automatically, you may want try-catch for:
|
|
373
|
+
|
|
374
|
+
1. **Custom error mapping** — Convert database errors to user-friendly messages
|
|
375
|
+
2. **Retry logic** — Attempt an operation multiple times before failing
|
|
376
|
+
3. **Cleanup operations** — Release resources even on failure
|
|
377
|
+
4. **Partial success** — Continue processing after non-critical failures
|
|
378
|
+
|
|
379
|
+
## BodyParserError
|
|
380
|
+
|
|
381
|
+
`BodyParserError` is a subclass of `TejError` thrown automatically during request body parsing. You do not need to handle these yourself — they are caught by the framework and converted to appropriate HTTP responses.
|
|
382
|
+
|
|
383
|
+
| Status | Condition |
|
|
384
|
+
|--------|-----------|
|
|
385
|
+
| **400** | Malformed JSON, invalid URL-encoded data, or corrupted multipart form data |
|
|
386
|
+
| **408** | Body parsing timed out (exceeds `body.timeout`, default 30 seconds) |
|
|
387
|
+
| **413** | Request body exceeds `body.max_size` (default 10 MB) |
|
|
388
|
+
| **415** | Unsupported content type (not JSON, URL-encoded, or multipart) |
|
|
389
|
+
|
|
390
|
+
These limits are configured via [Configuration](./configuration.md) (`body.max_size`, `body.timeout`).
|
|
391
|
+
|
|
392
|
+
Supported content types:
|
|
393
|
+
- `application/json`
|
|
394
|
+
- `application/x-www-form-urlencoded`
|
|
395
|
+
- `multipart/form-data`
|
|
396
|
+
|
|
397
|
+
## Error Flow
|
|
398
|
+
|
|
399
|
+
When any error occurs in your handler or middleware, the framework uses the **same** `ammo.throw(err)` you use for intentional errors — one mechanism:
|
|
400
|
+
|
|
401
|
+
1. The framework's `executeChain()` catches the error
|
|
402
|
+
2. If `LOG_EXCEPTIONS` is enabled, the error is logged
|
|
403
|
+
3. The error is passed to `ammo.throw(err)` (no separate "send response" step — `ammo.throw()` does it)
|
|
404
|
+
4. **TejError** — uses the error's `code` and `message` directly
|
|
405
|
+
5. **When errors.llm.enabled** — LLM infers status and message from code context (same as explicit `ammo.throw()`)
|
|
406
|
+
6. **Otherwise** — 500 with the error message or string representation
|
|
407
|
+
7. `ammo.throw()` sends the HTTP response via `ammo.fire(statusCode, message)`
|
|
408
|
+
|
|
409
|
+
Once a response has been sent (`res.headersSent` is true), no further middleware or handlers execute.
|
|
410
|
+
|
|
411
|
+
## Error Codes Reference
|
|
412
|
+
|
|
413
|
+
| Status | Name | When to Use |
|
|
414
|
+
|--------|------|-------------|
|
|
415
|
+
| 400 | Bad Request | Invalid input, malformed request |
|
|
416
|
+
| 401 | Unauthorized | Missing or invalid authentication |
|
|
417
|
+
| 403 | Forbidden | Authenticated but not authorized |
|
|
418
|
+
| 404 | Not Found | Resource doesn't exist |
|
|
419
|
+
| 405 | Method Not Allowed | HTTP method not supported |
|
|
420
|
+
| 409 | Conflict | Resource conflict (duplicate) |
|
|
421
|
+
| 413 | Payload Too Large | Request body too large |
|
|
422
|
+
| 422 | Unprocessable Entity | Valid syntax but semantic errors |
|
|
423
|
+
| 429 | Too Many Requests | Rate limit exceeded |
|
|
424
|
+
| 500 | Internal Server Error | Unexpected server errors |
|
|
425
|
+
| 502 | Bad Gateway | Upstream server error |
|
|
426
|
+
| 503 | Service Unavailable | Server temporarily unavailable |
|
|
427
|
+
| 504 | Gateway Timeout | Upstream server timeout |
|
|
428
|
+
|
|
429
|
+
## Best Practices
|
|
430
|
+
|
|
431
|
+
1. **Use appropriate status codes** — Don't return 500 for client errors
|
|
432
|
+
2. **Provide useful messages** — Help developers debug issues
|
|
433
|
+
3. **Don't expose internals** — Hide stack traces in production
|
|
434
|
+
4. **Log errors** — Enable exception logging for debugging
|
|
435
|
+
5. **Be consistent** — Use the same error format throughout your API
|
|
436
|
+
6. **Validate early** — Check input before processing
|
|
437
|
+
7. **Use TejError or ammo.throw(code, message)** — For HTTP-specific errors when you want explicit control
|
|
438
|
+
8. **Opt in to LLM-inferred errors when helpful** — Enable via `errors.llm.enabled` in config or **`app.withLLMErrors(config?)`** before `takeoff()`, then configure baseURL, apiKey, and model so you can throw without specifying code or message and let the LLM infer them; see [Configuration](./configuration.md#error-handling-llm-inferred-errors)
|