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.
Files changed (70) hide show
  1. package/README.md +197 -196
  2. package/auto-docs/analysis/handler-analyzer.js +58 -58
  3. package/auto-docs/analysis/source-resolver.js +101 -101
  4. package/auto-docs/constants.js +37 -37
  5. package/auto-docs/docs-llm/index.js +7 -7
  6. package/auto-docs/docs-llm/prompts.js +222 -222
  7. package/auto-docs/docs-llm/provider.js +132 -132
  8. package/auto-docs/index.js +146 -146
  9. package/auto-docs/openapi/endpoint-processor.js +277 -277
  10. package/auto-docs/openapi/generator.js +107 -107
  11. package/auto-docs/openapi/level3.js +131 -131
  12. package/auto-docs/openapi/spec-builders.js +244 -244
  13. package/auto-docs/ui/docs-ui.js +186 -186
  14. package/auto-docs/utils/logger.js +17 -17
  15. package/auto-docs/utils/strip-usage.js +10 -10
  16. package/cli/docs-command.js +315 -315
  17. package/cli/fly-command.js +71 -71
  18. package/cli/index.js +56 -56
  19. package/cors/index.js +71 -0
  20. package/database/index.js +165 -165
  21. package/database/mongodb.js +146 -146
  22. package/database/redis.js +201 -201
  23. package/docs/README.md +36 -36
  24. package/docs/ammo.md +362 -362
  25. package/docs/api-reference.md +490 -490
  26. package/docs/auto-docs.md +216 -216
  27. package/docs/cli.md +152 -152
  28. package/docs/configuration.md +275 -275
  29. package/docs/database.md +390 -390
  30. package/docs/error-handling.md +438 -438
  31. package/docs/file-uploads.md +333 -333
  32. package/docs/getting-started.md +214 -214
  33. package/docs/middleware.md +355 -355
  34. package/docs/rate-limiting.md +393 -393
  35. package/docs/routing.md +302 -302
  36. package/lib/llm/client.js +73 -0
  37. package/lib/llm/index.js +7 -0
  38. package/lib/llm/parse.js +89 -0
  39. package/package.json +64 -62
  40. package/rate-limit/algorithms/fixed-window.js +141 -141
  41. package/rate-limit/algorithms/sliding-window.js +147 -147
  42. package/rate-limit/algorithms/token-bucket.js +115 -115
  43. package/rate-limit/base.js +165 -165
  44. package/rate-limit/index.js +147 -147
  45. package/rate-limit/storage/base.js +104 -104
  46. package/rate-limit/storage/memory.js +101 -101
  47. package/rate-limit/storage/redis.js +88 -88
  48. package/server/ammo/body-parser.js +220 -220
  49. package/server/ammo/dispatch-helper.js +103 -103
  50. package/server/ammo/enhancer.js +57 -57
  51. package/server/ammo.js +454 -415
  52. package/server/endpoint.js +97 -74
  53. package/server/error.js +9 -9
  54. package/server/errors/code-context.js +125 -125
  55. package/server/errors/llm-error-service.js +140 -140
  56. package/server/files/helper.js +33 -33
  57. package/server/files/uploader.js +143 -143
  58. package/server/handler.js +158 -119
  59. package/server/target.js +185 -175
  60. package/server/targets/middleware-validator.js +22 -22
  61. package/server/targets/path-validator.js +21 -21
  62. package/server/targets/registry.js +160 -160
  63. package/server/targets/shoot-validator.js +21 -21
  64. package/te.js +428 -402
  65. package/utils/auto-register.js +17 -17
  66. package/utils/configuration.js +64 -64
  67. package/utils/errors-llm-config.js +84 -84
  68. package/utils/request-logger.js +43 -43
  69. package/utils/status-codes.js +82 -82
  70. package/utils/tejas-entrypoint-html.js +18 -18
package/docs/ammo.md CHANGED
@@ -1,362 +1,362 @@
1
- # Ammo - Request & Response
2
-
3
- The `Ammo` class is Tejas's unified request/response object. It wraps Node.js's `req` and `res` objects, providing a clean API for handling HTTP interactions.
4
-
5
- ## Overview
6
-
7
- Every route handler receives an `ammo` object:
8
-
9
- ```javascript
10
- target.register('/example', (ammo) => {
11
- // ammo contains everything you need
12
- console.log(ammo.method); // 'GET'
13
- console.log(ammo.payload); // { query: 'params', body: 'data' }
14
- ammo.fire({ success: true });
15
- });
16
- ```
17
-
18
- ## Properties
19
-
20
- ### HTTP Method Flags
21
-
22
- Boolean flags for quick method checking:
23
-
24
- ```javascript
25
- ammo.GET // true if GET request
26
- ammo.POST // true if POST request
27
- ammo.PUT // true if PUT request
28
- ammo.DELETE // true if DELETE request
29
- ammo.PATCH // true if PATCH request
30
- ammo.HEAD // true if HEAD request
31
- ammo.OPTIONS // true if OPTIONS request
32
- ```
33
-
34
- **Usage:**
35
-
36
- ```javascript
37
- target.register('/resource', (ammo) => {
38
- if (ammo.GET) {
39
- return ammo.fire({ items: [] });
40
- }
41
- if (ammo.POST) {
42
- return ammo.fire(201, { created: true });
43
- }
44
- ammo.notAllowed();
45
- });
46
- ```
47
-
48
- ### Request Data
49
-
50
- | Property | Type | Description |
51
- |----------|------|-------------|
52
- | `ammo.method` | string | HTTP method (`'GET'`, `'POST'`, etc.) |
53
- | `ammo.payload` | object | Combined body, query params, and route params |
54
- | `ammo.headers` | object | Request headers (lowercase keys) |
55
- | `ammo.ip` | string | Client IP address |
56
-
57
- ### URL Data
58
-
59
- | Property | Type | Description |
60
- |----------|------|-------------|
61
- | `ammo.path` | string | Full URL path with query string |
62
- | `ammo.endpoint` | string | Path without query string |
63
- | `ammo.protocol` | string | `'http'` or `'https'` |
64
- | `ammo.hostname` | string | Request hostname |
65
- | `ammo.fullURL` | string | Complete URL |
66
-
67
- ### Raw Objects
68
-
69
- | Property | Type | Description |
70
- |----------|------|-------------|
71
- | `ammo.req` | IncomingMessage | Node.js request object |
72
- | `ammo.res` | ServerResponse | Node.js response object |
73
-
74
- ### Response Data
75
-
76
- | Property | Type | Description |
77
- |----------|------|-------------|
78
- | `ammo.dispatchedData` | any | The data sent via the most recent `fire()` call. `undefined` until `fire()` is called |
79
-
80
- ## The Payload Object
81
-
82
- `ammo.payload` is a merged object containing data from three sources, applied in this order (later sources override earlier ones for the same key):
83
-
84
- 1. **Query parameters** — From the URL query string
85
- 2. **Request body** — Parsed JSON, URL-encoded form data, or multipart form data
86
- 3. **Route parameters** — From parameterized routes (`:id`) — highest priority
87
-
88
- ```javascript
89
- // Request: POST /users/123?notify=true
90
- // Body: { "name": "John", "email": "john@example.com" }
91
-
92
- target.register('/users/:id', (ammo) => {
93
- console.log(ammo.payload);
94
- // {
95
- // id: '123', // Route param
96
- // notify: 'true', // Query param
97
- // name: 'John', // Body
98
- // email: 'john@example.com' // Body
99
- // }
100
- });
101
- ```
102
-
103
- ## Methods
104
-
105
- ### fire() — Send Response
106
-
107
- The primary method for sending responses:
108
-
109
- ```javascript
110
- // Send JSON with 200 status (default)
111
- ammo.fire({ message: 'Success' });
112
-
113
- // Send with specific status code
114
- ammo.fire(201, { id: 1, created: true });
115
-
116
- // Send just a status code
117
- ammo.fire(204);
118
-
119
- // Send plain text
120
- ammo.fire('Hello, World!');
121
-
122
- // Send HTML with custom content type
123
- ammo.fire(200, '<h1>Hello</h1>', 'text/html');
124
- ```
125
-
126
- **All signatures:**
127
-
128
- | Call | Status | Body | Content-Type |
129
- |------|--------|------|-------------|
130
- | `fire()` | 204 | *(empty)* | — |
131
- | `fire("text")` | 200 | `text` | `text/plain` |
132
- | `fire({ json })` | 200 | JSON string | `application/json` |
133
- | `fire(201)` | 201 | status message | `text/plain` |
134
- | `fire(201, data)` | 201 | `data` | auto-detected |
135
- | `fire(200, html, "text/html")` | 200 | `html` | `text/html` |
136
-
137
- After `fire()` is called, the sent data is available as `ammo.dispatchedData`.
138
-
139
- ### throw() — Send Error Response
140
-
141
- **One mechanism** for error responses: you don't log the error and send the response separately — `ammo.throw()` takes care of everything. The framework uses the same `ammo.throw()` when it catches an error, so one config, one behaviour. For intentional errors, call `ammo.throw()` (or pass an error); when [LLM-inferred errors](./error-handling.md#llm-inferred-errors) are enabled, call with no arguments and an LLM infers status and message from code context. Explicit code/message always override. See [Error Handling](./error-handling.md) and per-call options (e.g. `messageType`).
142
-
143
- ```javascript
144
- // Explicit: status code and/or message
145
- ammo.throw(404);
146
- ammo.throw(404, 'User not found');
147
- ammo.throw(new TejError(400, 'Invalid input'));
148
-
149
- // When errors.llm.enabled: no args — LLM infers from code context (surrounding + upstream/downstream)
150
- ammo.throw();
151
-
152
- // Optional: pass caught error for secondary signal; LLM still uses code context (error stack) as primary
153
- ammo.throw(caughtErr);
154
-
155
- // Per-call: skip LLM or override message type
156
- ammo.throw({ useLlm: false });
157
- ammo.throw({ messageType: 'developer' });
158
- ```
159
-
160
- **All `throw()` signatures:**
161
-
162
- | Call | Status | Message |
163
- |------|--------|---------|
164
- | `throw()` | 500 or LLM-inferred | Default or LLM-derived from **code context** (see [LLM-inferred errors](./error-handling.md#llm-inferred-errors)) |
165
- | `throw(404)` | 404 | Default message for that status code |
166
- | `throw(404, "msg")` | 404 | `"msg"` |
167
- | `throw(new TejError(code, msg))` | `code` | `msg` |
168
- | `throw(error)` (optional) | LLM-inferred | LLM-derived from code context (error stack used to find call site) |
169
-
170
- > **Note:** You don't need try-catch blocks in your handlers! Tejas automatically catches all errors and converts them to appropriate HTTP responses. Use `throw()` or `TejError` only for intentional, expected error conditions. See [Error Handling](./error-handling.md) for details.
171
-
172
- ### redirect() — HTTP Redirect
173
-
174
- ```javascript
175
- // Temporary redirect (302)
176
- ammo.redirect('/new-location');
177
-
178
- // Permanent redirect (301)
179
- ammo.redirect('/new-location', 301);
180
-
181
- // External redirect
182
- ammo.redirect('https://example.com');
183
- ```
184
-
185
- ### Convenience Error Methods
186
-
187
- ```javascript
188
- ammo.notFound(); // Throws 404 Not Found
189
- ammo.notAllowed(); // Throws 405 Method Not Allowed
190
- ammo.unauthorized(); // Throws 401 Unauthorized
191
- ```
192
-
193
- ## Working with Headers
194
-
195
- ### Reading Headers
196
-
197
- ```javascript
198
- target.register('/example', (ammo) => {
199
- const authHeader = ammo.headers['authorization'];
200
- const contentType = ammo.headers['content-type'];
201
- const userAgent = ammo.headers['user-agent'];
202
-
203
- ammo.fire({ userAgent });
204
- });
205
- ```
206
-
207
- ### Setting Response Headers
208
-
209
- Use the underlying `res` object:
210
-
211
- ```javascript
212
- target.register('/example', (ammo) => {
213
- ammo.res.setHeader('X-Custom-Header', 'value');
214
- ammo.res.setHeader('Cache-Control', 'max-age=3600');
215
-
216
- ammo.fire({ data: 'with headers' });
217
- });
218
- ```
219
-
220
- ## Content Types
221
-
222
- `fire()` automatically sets `Content-Type` based on the data:
223
-
224
- | Data Type | Content-Type |
225
- |-----------|--------------|
226
- | Object/Array | `application/json` |
227
- | String | `text/plain` |
228
- | Buffer | `application/octet-stream` |
229
-
230
- Override with the third parameter:
231
-
232
- ```javascript
233
- ammo.fire(200, htmlString, 'text/html');
234
- ammo.fire(200, xmlString, 'application/xml');
235
- ```
236
-
237
- ## Examples
238
-
239
- ### REST API Resource
240
-
241
- ```javascript
242
- import { Target, TejError } from 'te.js';
243
-
244
- const users = new Target('/users');
245
-
246
- // GET /users - List all
247
- // POST /users - Create new
248
- users.register('/', async (ammo) => {
249
- if (ammo.GET) {
250
- const { page = 1, limit = 10 } = ammo.payload;
251
- const users = await getUsers(page, limit);
252
- return ammo.fire({ users, page, limit });
253
- }
254
-
255
- if (ammo.POST) {
256
- const { name, email } = ammo.payload;
257
-
258
- if (!name || !email) {
259
- throw new TejError(400, 'Name and email are required');
260
- }
261
-
262
- const user = await createUser({ name, email });
263
- return ammo.fire(201, user);
264
- }
265
-
266
- ammo.notAllowed();
267
- });
268
-
269
- // GET /users/:id - Get one
270
- // PUT /users/:id - Update
271
- // DELETE /users/:id - Delete
272
- users.register('/:id', async (ammo) => {
273
- const { id } = ammo.payload;
274
-
275
- if (ammo.GET) {
276
- const user = await getUser(id);
277
- if (!user) throw new TejError(404, 'User not found');
278
- return ammo.fire(user);
279
- }
280
-
281
- if (ammo.PUT) {
282
- const { name, email } = ammo.payload;
283
- const user = await updateUser(id, { name, email });
284
- return ammo.fire(user);
285
- }
286
-
287
- if (ammo.DELETE) {
288
- await deleteUser(id);
289
- return ammo.fire(204);
290
- }
291
-
292
- ammo.notAllowed();
293
- });
294
- ```
295
-
296
- ### File Download
297
-
298
- ```javascript
299
- import fs from 'fs';
300
- import path from 'path';
301
-
302
- target.register('/download/:filename', (ammo) => {
303
- const { filename } = ammo.payload;
304
- const filepath = path.join('uploads', filename);
305
-
306
- if (!fs.existsSync(filepath)) {
307
- throw new TejError(404, 'File not found');
308
- }
309
-
310
- const file = fs.readFileSync(filepath);
311
-
312
- ammo.res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
313
- ammo.fire(200, file, 'application/octet-stream');
314
- });
315
- ```
316
-
317
- ### Streaming Response
318
-
319
- For large responses, use the raw `res` object:
320
-
321
- ```javascript
322
- target.register('/stream', (ammo) => {
323
- ammo.res.setHeader('Content-Type', 'text/event-stream');
324
- ammo.res.setHeader('Cache-Control', 'no-cache');
325
- ammo.res.setHeader('Connection', 'keep-alive');
326
-
327
- let count = 0;
328
- const interval = setInterval(() => {
329
- ammo.res.write(`data: ${JSON.stringify({ count: ++count })}\n\n`);
330
-
331
- if (count >= 10) {
332
- clearInterval(interval);
333
- ammo.res.end();
334
- }
335
- }, 1000);
336
- });
337
- ```
338
-
339
- ## Adding Custom Properties
340
-
341
- Extend `ammo` in middleware:
342
-
343
- ```javascript
344
- // In middleware
345
- const authMiddleware = async (ammo, next) => {
346
- const token = ammo.headers.authorization;
347
- const user = await verifyToken(token);
348
-
349
- ammo.user = user; // Add user
350
- ammo.isAdmin = user.role === 'admin';
351
-
352
- next();
353
- };
354
-
355
- // In handler
356
- target.register('/profile', authMiddleware, (ammo) => {
357
- ammo.fire({
358
- user: ammo.user,
359
- isAdmin: ammo.isAdmin
360
- });
361
- });
362
- ```
1
+ # Ammo - Request & Response
2
+
3
+ The `Ammo` class is Tejas's unified request/response object. It wraps Node.js's `req` and `res` objects, providing a clean API for handling HTTP interactions.
4
+
5
+ ## Overview
6
+
7
+ Every route handler receives an `ammo` object:
8
+
9
+ ```javascript
10
+ target.register('/example', (ammo) => {
11
+ // ammo contains everything you need
12
+ console.log(ammo.method); // 'GET'
13
+ console.log(ammo.payload); // { query: 'params', body: 'data' }
14
+ ammo.fire({ success: true });
15
+ });
16
+ ```
17
+
18
+ ## Properties
19
+
20
+ ### HTTP Method Flags
21
+
22
+ Boolean flags for quick method checking:
23
+
24
+ ```javascript
25
+ ammo.GET // true if GET request
26
+ ammo.POST // true if POST request
27
+ ammo.PUT // true if PUT request
28
+ ammo.DELETE // true if DELETE request
29
+ ammo.PATCH // true if PATCH request
30
+ ammo.HEAD // true if HEAD request
31
+ ammo.OPTIONS // true if OPTIONS request
32
+ ```
33
+
34
+ **Usage:**
35
+
36
+ ```javascript
37
+ target.register('/resource', (ammo) => {
38
+ if (ammo.GET) {
39
+ return ammo.fire({ items: [] });
40
+ }
41
+ if (ammo.POST) {
42
+ return ammo.fire(201, { created: true });
43
+ }
44
+ ammo.notAllowed();
45
+ });
46
+ ```
47
+
48
+ ### Request Data
49
+
50
+ | Property | Type | Description |
51
+ |----------|------|-------------|
52
+ | `ammo.method` | string | HTTP method (`'GET'`, `'POST'`, etc.) |
53
+ | `ammo.payload` | object | Combined body, query params, and route params |
54
+ | `ammo.headers` | object | Request headers (lowercase keys) |
55
+ | `ammo.ip` | string | Client IP address |
56
+
57
+ ### URL Data
58
+
59
+ | Property | Type | Description |
60
+ |----------|------|-------------|
61
+ | `ammo.path` | string | Full URL path with query string |
62
+ | `ammo.endpoint` | string | Path without query string |
63
+ | `ammo.protocol` | string | `'http'` or `'https'` |
64
+ | `ammo.hostname` | string | Request hostname |
65
+ | `ammo.fullURL` | string | Complete URL |
66
+
67
+ ### Raw Objects
68
+
69
+ | Property | Type | Description |
70
+ |----------|------|-------------|
71
+ | `ammo.req` | IncomingMessage | Node.js request object |
72
+ | `ammo.res` | ServerResponse | Node.js response object |
73
+
74
+ ### Response Data
75
+
76
+ | Property | Type | Description |
77
+ |----------|------|-------------|
78
+ | `ammo.dispatchedData` | any | The data sent via the most recent `fire()` call. `undefined` until `fire()` is called |
79
+
80
+ ## The Payload Object
81
+
82
+ `ammo.payload` is a merged object containing data from three sources, applied in this order (later sources override earlier ones for the same key):
83
+
84
+ 1. **Query parameters** — From the URL query string
85
+ 2. **Request body** — Parsed JSON, URL-encoded form data, or multipart form data
86
+ 3. **Route parameters** — From parameterized routes (`:id`) — highest priority
87
+
88
+ ```javascript
89
+ // Request: POST /users/123?notify=true
90
+ // Body: { "name": "John", "email": "john@example.com" }
91
+
92
+ target.register('/users/:id', (ammo) => {
93
+ console.log(ammo.payload);
94
+ // {
95
+ // id: '123', // Route param
96
+ // notify: 'true', // Query param
97
+ // name: 'John', // Body
98
+ // email: 'john@example.com' // Body
99
+ // }
100
+ });
101
+ ```
102
+
103
+ ## Methods
104
+
105
+ ### fire() — Send Response
106
+
107
+ The primary method for sending responses:
108
+
109
+ ```javascript
110
+ // Send JSON with 200 status (default)
111
+ ammo.fire({ message: 'Success' });
112
+
113
+ // Send with specific status code
114
+ ammo.fire(201, { id: 1, created: true });
115
+
116
+ // Send just a status code
117
+ ammo.fire(204);
118
+
119
+ // Send plain text
120
+ ammo.fire('Hello, World!');
121
+
122
+ // Send HTML with custom content type
123
+ ammo.fire(200, '<h1>Hello</h1>', 'text/html');
124
+ ```
125
+
126
+ **All signatures:**
127
+
128
+ | Call | Status | Body | Content-Type |
129
+ |------|--------|------|-------------|
130
+ | `fire()` | 204 | *(empty)* | — |
131
+ | `fire("text")` | 200 | `text` | `text/plain` |
132
+ | `fire({ json })` | 200 | JSON string | `application/json` |
133
+ | `fire(201)` | 201 | status message | `text/plain` |
134
+ | `fire(201, data)` | 201 | `data` | auto-detected |
135
+ | `fire(200, html, "text/html")` | 200 | `html` | `text/html` |
136
+
137
+ After `fire()` is called, the sent data is available as `ammo.dispatchedData`.
138
+
139
+ ### throw() — Send Error Response
140
+
141
+ **One mechanism** for error responses: you don't log the error and send the response separately — `ammo.throw()` takes care of everything. The framework uses the same `ammo.throw()` when it catches an error, so one config, one behaviour. For intentional errors, call `ammo.throw()` (or pass an error); when [LLM-inferred errors](./error-handling.md#llm-inferred-errors) are enabled, call with no arguments and an LLM infers status and message from code context. Explicit code/message always override. See [Error Handling](./error-handling.md) and per-call options (e.g. `messageType`).
142
+
143
+ ```javascript
144
+ // Explicit: status code and/or message
145
+ ammo.throw(404);
146
+ ammo.throw(404, 'User not found');
147
+ ammo.throw(new TejError(400, 'Invalid input'));
148
+
149
+ // When errors.llm.enabled: no args — LLM infers from code context (surrounding + upstream/downstream)
150
+ ammo.throw();
151
+
152
+ // Optional: pass caught error for secondary signal; LLM still uses code context (error stack) as primary
153
+ ammo.throw(caughtErr);
154
+
155
+ // Per-call: skip LLM or override message type
156
+ ammo.throw({ useLlm: false });
157
+ ammo.throw({ messageType: 'developer' });
158
+ ```
159
+
160
+ **All `throw()` signatures:**
161
+
162
+ | Call | Status | Message |
163
+ |------|--------|---------|
164
+ | `throw()` | 500 or LLM-inferred | Default or LLM-derived from **code context** (see [LLM-inferred errors](./error-handling.md#llm-inferred-errors)) |
165
+ | `throw(404)` | 404 | Default message for that status code |
166
+ | `throw(404, "msg")` | 404 | `"msg"` |
167
+ | `throw(new TejError(code, msg))` | `code` | `msg` |
168
+ | `throw(error)` (optional) | LLM-inferred | LLM-derived from code context (error stack used to find call site) |
169
+
170
+ > **Note:** You don't need try-catch blocks in your handlers! Tejas automatically catches all errors and converts them to appropriate HTTP responses. Use `throw()` or `TejError` only for intentional, expected error conditions. See [Error Handling](./error-handling.md) for details.
171
+
172
+ ### redirect() — HTTP Redirect
173
+
174
+ ```javascript
175
+ // Temporary redirect (302)
176
+ ammo.redirect('/new-location');
177
+
178
+ // Permanent redirect (301)
179
+ ammo.redirect('/new-location', 301);
180
+
181
+ // External redirect
182
+ ammo.redirect('https://example.com');
183
+ ```
184
+
185
+ ### Convenience Error Methods
186
+
187
+ ```javascript
188
+ ammo.notFound(); // Throws 404 Not Found
189
+ ammo.notAllowed(); // Throws 405 Method Not Allowed
190
+ ammo.unauthorized(); // Throws 401 Unauthorized
191
+ ```
192
+
193
+ ## Working with Headers
194
+
195
+ ### Reading Headers
196
+
197
+ ```javascript
198
+ target.register('/example', (ammo) => {
199
+ const authHeader = ammo.headers['authorization'];
200
+ const contentType = ammo.headers['content-type'];
201
+ const userAgent = ammo.headers['user-agent'];
202
+
203
+ ammo.fire({ userAgent });
204
+ });
205
+ ```
206
+
207
+ ### Setting Response Headers
208
+
209
+ Use the underlying `res` object:
210
+
211
+ ```javascript
212
+ target.register('/example', (ammo) => {
213
+ ammo.res.setHeader('X-Custom-Header', 'value');
214
+ ammo.res.setHeader('Cache-Control', 'max-age=3600');
215
+
216
+ ammo.fire({ data: 'with headers' });
217
+ });
218
+ ```
219
+
220
+ ## Content Types
221
+
222
+ `fire()` automatically sets `Content-Type` based on the data:
223
+
224
+ | Data Type | Content-Type |
225
+ |-----------|--------------|
226
+ | Object/Array | `application/json` |
227
+ | String | `text/plain` |
228
+ | Buffer | `application/octet-stream` |
229
+
230
+ Override with the third parameter:
231
+
232
+ ```javascript
233
+ ammo.fire(200, htmlString, 'text/html');
234
+ ammo.fire(200, xmlString, 'application/xml');
235
+ ```
236
+
237
+ ## Examples
238
+
239
+ ### REST API Resource
240
+
241
+ ```javascript
242
+ import { Target, TejError } from 'te.js';
243
+
244
+ const users = new Target('/users');
245
+
246
+ // GET /users - List all
247
+ // POST /users - Create new
248
+ users.register('/', async (ammo) => {
249
+ if (ammo.GET) {
250
+ const { page = 1, limit = 10 } = ammo.payload;
251
+ const users = await getUsers(page, limit);
252
+ return ammo.fire({ users, page, limit });
253
+ }
254
+
255
+ if (ammo.POST) {
256
+ const { name, email } = ammo.payload;
257
+
258
+ if (!name || !email) {
259
+ throw new TejError(400, 'Name and email are required');
260
+ }
261
+
262
+ const user = await createUser({ name, email });
263
+ return ammo.fire(201, user);
264
+ }
265
+
266
+ ammo.notAllowed();
267
+ });
268
+
269
+ // GET /users/:id - Get one
270
+ // PUT /users/:id - Update
271
+ // DELETE /users/:id - Delete
272
+ users.register('/:id', async (ammo) => {
273
+ const { id } = ammo.payload;
274
+
275
+ if (ammo.GET) {
276
+ const user = await getUser(id);
277
+ if (!user) throw new TejError(404, 'User not found');
278
+ return ammo.fire(user);
279
+ }
280
+
281
+ if (ammo.PUT) {
282
+ const { name, email } = ammo.payload;
283
+ const user = await updateUser(id, { name, email });
284
+ return ammo.fire(user);
285
+ }
286
+
287
+ if (ammo.DELETE) {
288
+ await deleteUser(id);
289
+ return ammo.fire(204);
290
+ }
291
+
292
+ ammo.notAllowed();
293
+ });
294
+ ```
295
+
296
+ ### File Download
297
+
298
+ ```javascript
299
+ import fs from 'fs';
300
+ import path from 'path';
301
+
302
+ target.register('/download/:filename', (ammo) => {
303
+ const { filename } = ammo.payload;
304
+ const filepath = path.join('uploads', filename);
305
+
306
+ if (!fs.existsSync(filepath)) {
307
+ throw new TejError(404, 'File not found');
308
+ }
309
+
310
+ const file = fs.readFileSync(filepath);
311
+
312
+ ammo.res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
313
+ ammo.fire(200, file, 'application/octet-stream');
314
+ });
315
+ ```
316
+
317
+ ### Streaming Response
318
+
319
+ For large responses, use the raw `res` object:
320
+
321
+ ```javascript
322
+ target.register('/stream', (ammo) => {
323
+ ammo.res.setHeader('Content-Type', 'text/event-stream');
324
+ ammo.res.setHeader('Cache-Control', 'no-cache');
325
+ ammo.res.setHeader('Connection', 'keep-alive');
326
+
327
+ let count = 0;
328
+ const interval = setInterval(() => {
329
+ ammo.res.write(`data: ${JSON.stringify({ count: ++count })}\n\n`);
330
+
331
+ if (count >= 10) {
332
+ clearInterval(interval);
333
+ ammo.res.end();
334
+ }
335
+ }, 1000);
336
+ });
337
+ ```
338
+
339
+ ## Adding Custom Properties
340
+
341
+ Extend `ammo` in middleware:
342
+
343
+ ```javascript
344
+ // In middleware
345
+ const authMiddleware = async (ammo, next) => {
346
+ const token = ammo.headers.authorization;
347
+ const user = await verifyToken(token);
348
+
349
+ ammo.user = user; // Add user
350
+ ammo.isAdmin = user.role === 'admin';
351
+
352
+ next();
353
+ };
354
+
355
+ // In handler
356
+ target.register('/profile', authMiddleware, (ammo) => {
357
+ ammo.fire({
358
+ user: ammo.user,
359
+ isAdmin: ammo.isAdmin
360
+ });
361
+ });
362
+ ```