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.
Files changed (68) hide show
  1. package/README.md +197 -187
  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 -0
  6. package/auto-docs/{llm → docs-llm}/prompts.js +222 -222
  7. package/auto-docs/{llm → docs-llm}/provider.js +132 -187
  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/database/index.js +165 -165
  20. package/database/mongodb.js +146 -146
  21. package/database/redis.js +201 -201
  22. package/docs/README.md +36 -36
  23. package/docs/ammo.md +362 -362
  24. package/docs/api-reference.md +490 -489
  25. package/docs/auto-docs.md +216 -215
  26. package/docs/cli.md +152 -152
  27. package/docs/configuration.md +275 -233
  28. package/docs/database.md +390 -391
  29. package/docs/error-handling.md +438 -417
  30. package/docs/file-uploads.md +333 -334
  31. package/docs/getting-started.md +214 -215
  32. package/docs/middleware.md +355 -356
  33. package/docs/rate-limiting.md +393 -394
  34. package/docs/routing.md +302 -302
  35. package/package.json +62 -62
  36. package/rate-limit/algorithms/fixed-window.js +141 -141
  37. package/rate-limit/algorithms/sliding-window.js +147 -147
  38. package/rate-limit/algorithms/token-bucket.js +115 -115
  39. package/rate-limit/base.js +165 -165
  40. package/rate-limit/index.js +147 -147
  41. package/rate-limit/storage/base.js +104 -104
  42. package/rate-limit/storage/memory.js +101 -101
  43. package/rate-limit/storage/redis.js +88 -88
  44. package/server/ammo/body-parser.js +220 -220
  45. package/server/ammo/dispatch-helper.js +103 -103
  46. package/server/ammo/enhancer.js +57 -57
  47. package/server/ammo.js +454 -356
  48. package/server/endpoint.js +97 -74
  49. package/server/error.js +9 -9
  50. package/server/errors/code-context.js +125 -0
  51. package/server/errors/llm-error-service.js +140 -0
  52. package/server/files/helper.js +33 -33
  53. package/server/files/uploader.js +143 -143
  54. package/server/handler.js +158 -113
  55. package/server/target.js +185 -175
  56. package/server/targets/middleware-validator.js +22 -22
  57. package/server/targets/path-validator.js +21 -21
  58. package/server/targets/registry.js +160 -160
  59. package/server/targets/shoot-validator.js +21 -21
  60. package/te.js +428 -363
  61. package/utils/auto-register.js +17 -17
  62. package/utils/configuration.js +64 -64
  63. package/utils/errors-llm-config.js +84 -0
  64. package/utils/request-logger.js +43 -43
  65. package/utils/status-codes.js +82 -82
  66. package/utils/tejas-entrypoint-html.js +18 -18
  67. package/auto-docs/llm/index.js +0 -6
  68. package/auto-docs/llm/parse.js +0 -88
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
- For intentional error responses:
142
-
143
- ```javascript
144
- // Send 500 Internal Server Error
145
- ammo.throw();
146
-
147
- // Send specific error code
148
- ammo.throw(404);
149
- ammo.throw(404, 'User not found');
150
-
151
- // Throw from Error object
152
- ammo.throw(new Error('Something went wrong'));
153
-
154
- // Throw TejError
155
- import { TejError } from 'te.js';
156
- throw new TejError(400, 'Invalid input');
157
- ```
158
-
159
- **All `throw()` signatures:**
160
-
161
- | Call | Status | Message |
162
- |------|--------|---------|
163
- | `throw()` | 500 | `"Internal Server Error"` |
164
- | `throw(404)` | 404 | Default message for that status code |
165
- | `throw(404, "msg")` | 404 | `"msg"` |
166
- | `throw(new TejError(code, msg))` | `code` | `msg` |
167
- | `throw(new Error("msg"))` | 500 | `"msg"` (or parses numeric messages as status codes) |
168
-
169
- > **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.
170
-
171
- ### redirect() — HTTP Redirect
172
-
173
- ```javascript
174
- // Temporary redirect (302)
175
- ammo.redirect('/new-location');
176
-
177
- // Permanent redirect (301)
178
- ammo.redirect('/new-location', 301);
179
-
180
- // External redirect
181
- ammo.redirect('https://example.com');
182
- ```
183
-
184
- ### Convenience Error Methods
185
-
186
- ```javascript
187
- ammo.notFound(); // Throws 404 Not Found
188
- ammo.notAllowed(); // Throws 405 Method Not Allowed
189
- ammo.unauthorized(); // Throws 401 Unauthorized
190
- ```
191
-
192
- ## Working with Headers
193
-
194
- ### Reading Headers
195
-
196
- ```javascript
197
- target.register('/example', (ammo) => {
198
- const authHeader = ammo.headers['authorization'];
199
- const contentType = ammo.headers['content-type'];
200
- const userAgent = ammo.headers['user-agent'];
201
-
202
- ammo.fire({ userAgent });
203
- });
204
- ```
205
-
206
- ### Setting Response Headers
207
-
208
- Use the underlying `res` object:
209
-
210
- ```javascript
211
- target.register('/example', (ammo) => {
212
- ammo.res.setHeader('X-Custom-Header', 'value');
213
- ammo.res.setHeader('Cache-Control', 'max-age=3600');
214
-
215
- ammo.fire({ data: 'with headers' });
216
- });
217
- ```
218
-
219
- ## Content Types
220
-
221
- `fire()` automatically sets `Content-Type` based on the data:
222
-
223
- | Data Type | Content-Type |
224
- |-----------|--------------|
225
- | Object/Array | `application/json` |
226
- | String | `text/plain` |
227
- | Buffer | `application/octet-stream` |
228
-
229
- Override with the third parameter:
230
-
231
- ```javascript
232
- ammo.fire(200, htmlString, 'text/html');
233
- ammo.fire(200, xmlString, 'application/xml');
234
- ```
235
-
236
- ## Examples
237
-
238
- ### REST API Resource
239
-
240
- ```javascript
241
- import { Target, TejError } from 'te.js';
242
-
243
- const users = new Target('/users');
244
-
245
- // GET /users - List all
246
- // POST /users - Create new
247
- users.register('/', async (ammo) => {
248
- if (ammo.GET) {
249
- const { page = 1, limit = 10 } = ammo.payload;
250
- const users = await getUsers(page, limit);
251
- return ammo.fire({ users, page, limit });
252
- }
253
-
254
- if (ammo.POST) {
255
- const { name, email } = ammo.payload;
256
-
257
- if (!name || !email) {
258
- throw new TejError(400, 'Name and email are required');
259
- }
260
-
261
- const user = await createUser({ name, email });
262
- return ammo.fire(201, user);
263
- }
264
-
265
- ammo.notAllowed();
266
- });
267
-
268
- // GET /users/:id - Get one
269
- // PUT /users/:id - Update
270
- // DELETE /users/:id - Delete
271
- users.register('/:id', async (ammo) => {
272
- const { id } = ammo.payload;
273
-
274
- if (ammo.GET) {
275
- const user = await getUser(id);
276
- if (!user) throw new TejError(404, 'User not found');
277
- return ammo.fire(user);
278
- }
279
-
280
- if (ammo.PUT) {
281
- const { name, email } = ammo.payload;
282
- const user = await updateUser(id, { name, email });
283
- return ammo.fire(user);
284
- }
285
-
286
- if (ammo.DELETE) {
287
- await deleteUser(id);
288
- return ammo.fire(204);
289
- }
290
-
291
- ammo.notAllowed();
292
- });
293
- ```
294
-
295
- ### File Download
296
-
297
- ```javascript
298
- import fs from 'fs';
299
- import path from 'path';
300
-
301
- target.register('/download/:filename', (ammo) => {
302
- const { filename } = ammo.payload;
303
- const filepath = path.join('uploads', filename);
304
-
305
- if (!fs.existsSync(filepath)) {
306
- throw new TejError(404, 'File not found');
307
- }
308
-
309
- const file = fs.readFileSync(filepath);
310
-
311
- ammo.res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
312
- ammo.fire(200, file, 'application/octet-stream');
313
- });
314
- ```
315
-
316
- ### Streaming Response
317
-
318
- For large responses, use the raw `res` object:
319
-
320
- ```javascript
321
- target.register('/stream', (ammo) => {
322
- ammo.res.setHeader('Content-Type', 'text/event-stream');
323
- ammo.res.setHeader('Cache-Control', 'no-cache');
324
- ammo.res.setHeader('Connection', 'keep-alive');
325
-
326
- let count = 0;
327
- const interval = setInterval(() => {
328
- ammo.res.write(`data: ${JSON.stringify({ count: ++count })}\n\n`);
329
-
330
- if (count >= 10) {
331
- clearInterval(interval);
332
- ammo.res.end();
333
- }
334
- }, 1000);
335
- });
336
- ```
337
-
338
- ## Adding Custom Properties
339
-
340
- Extend `ammo` in middleware:
341
-
342
- ```javascript
343
- // In middleware
344
- const authMiddleware = async (ammo, next) => {
345
- const token = ammo.headers.authorization;
346
- const user = await verifyToken(token);
347
-
348
- ammo.user = user; // Add user
349
- ammo.isAdmin = user.role === 'admin';
350
-
351
- next();
352
- };
353
-
354
- // In handler
355
- target.register('/profile', authMiddleware, (ammo) => {
356
- ammo.fire({
357
- user: ammo.user,
358
- isAdmin: ammo.isAdmin
359
- });
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
+ ```