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/server/ammo.js CHANGED
@@ -1,415 +1,454 @@
1
- import { statusAndData } from './ammo/dispatch-helper.js';
2
- import {
3
- isStatusCode,
4
- toStatusCode,
5
- toStatusMessage,
6
- } from '../utils/status-codes.js';
7
- import html from '../utils/tejas-entrypoint-html.js';
8
- import ammoEnhancer from './ammo/enhancer.js';
9
- import TejError from './error.js';
10
- import { getErrorsLlmConfig } from '../utils/errors-llm-config.js';
11
- import { inferErrorFromContext } from './errors/llm-error-service.js';
12
- import { captureCodeContext } from './errors/code-context.js';
13
-
14
- /**
15
- * Detect if the value is a throw() options object (per-call overrides).
16
- * @param {unknown} v
17
- * @returns {v is { useLlm?: boolean, messageType?: 'endUser'|'developer' }}
18
- */
19
- function isThrowOptions(v) {
20
- if (!v || typeof v !== 'object' || Array.isArray(v)) return false;
21
- const o = /** @type {Record<string, unknown>} */ (v);
22
- const hasUseLlm = 'useLlm' in o;
23
- const hasMessageType =
24
- 'messageType' in o &&
25
- (o.messageType === 'endUser' || o.messageType === 'developer');
26
- return hasUseLlm || hasMessageType === true;
27
- }
28
-
29
- /**
30
- * Ammo class for handling HTTP requests and responses.
31
- *
32
- * @description
33
- * Ammo is a utility class that simplifies HTTP request handling and response generation.
34
- * It provides methods for processing requests, sending responses, and handling errors.
35
- *
36
- * @example
37
- *
38
- * if (ammo.GET) {
39
- * ammo.fire(200, { message: 'Hello World' });
40
- * } else {
41
- * ammo.notAllowed();
42
- * }
43
- */
44
- class Ammo {
45
- /**
46
- * Creates a new Ammo instance.
47
- *
48
- * @param {http.IncomingMessage} req - The HTTP request object
49
- * @param {http.ServerResponse} res - The HTTP response object
50
- *
51
- * @description
52
- * Initializes a new Ammo instance with the provided request and response objects.
53
- * Sets up default values for various properties that will be populated by the enhance method.
54
- */
55
- constructor(req, res) {
56
- this.req = req;
57
- this.res = res;
58
-
59
- this.GET = false;
60
- this.POST = false;
61
- this.PUT = false;
62
- this.DELETE = false;
63
- this.PATCH = false;
64
- this.HEAD = false;
65
- this.OPTIONS = false;
66
-
67
- // Request related data
68
- this.ip = undefined;
69
- this.headers = undefined;
70
- this.payload = undefined;
71
- this.method = undefined;
72
-
73
- // URL related data
74
- this.protocol = undefined;
75
- this.hostname = undefined;
76
- this.path = undefined;
77
- this.endpoint = undefined;
78
-
79
- this.fullURL = undefined;
80
-
81
- // Response related data
82
- this.dispatchedData = undefined;
83
- }
84
-
85
- /**
86
- * Enhances the Ammo instance with request data and sets HTTP method flags.
87
- *
88
- * @description
89
- * This method processes the request and sets various properties on the Ammo instance:
90
- * - HTTP method flags (GET, POST, PUT, etc.)
91
- * - Request data (IP, headers, payload, method)
92
- * - URL data (protocol, hostname, path, endpoint, fullURL)
93
- *
94
- * This method should be called before using any other Ammo methods.
95
- *
96
- * @returns {Promise<void>} A promise that resolves when enhancement is complete
97
- *
98
- * @example
99
- * const ammo = new Ammo(req, res);
100
- * await ammo.enhance();
101
- * // Now you can use ammo.GET, ammo.path, etc.
102
- */
103
- async enhance() {
104
- await ammoEnhancer(this);
105
-
106
- this.GET = this.method === 'GET';
107
- this.POST = this.method === 'POST';
108
- this.PUT = this.method === 'PUT';
109
- this.DELETE = this.method === 'DELETE';
110
- this.PATCH = this.method === 'PATCH';
111
- this.HEAD = this.method === 'HEAD';
112
- this.OPTIONS = this.method === 'OPTIONS';
113
- }
114
-
115
- /**
116
- * Sends a response to the client with the specified data and status code.
117
- *
118
- * @param {number|any} [arg1] - If a number, treated as status code. Otherwise treated as data to send.
119
- * @param {any} [arg2] - If arg1 is a number, this is the data to send. Otherwise ignored.
120
- * @param {string} [arg3] - Optional content type override.
121
- *
122
- * @description
123
- * The fire method is flexible and can handle different argument patterns:
124
- *
125
- * 1. No arguments: Sends a 204 No Content response
126
- * 2. Single number: Sends a response with the given status code
127
- * 3. Single non-number: Sends a 200 OK response with the given data
128
- * 4. Two arguments (number, data): Sends a response with the given status code and data
129
- * 5. Three arguments: Sends a response with the given status code, data, and content type
130
- *
131
- * The fire method can be used with any HTTP status code, including error codes (4xx, 5xx).
132
- * For error responses, you can use either fire() or throw(). The main difference is that
133
- * throw() can accept an Error instance and has special handling for it, while fire() only
134
- * accepts status codes, strings, or other data types.
135
- *
136
- * @example
137
- * // Send a 200 OK response with JSON data
138
- * ammo.fire(200, { message: 'Success' });
139
- *
140
- * @example
141
- * // Send a 404 Not Found response with custom message
142
- * ammo.fire(404, 'Resource not found');
143
- *
144
- * @example
145
- * // Send a 500 Internal Server Error response
146
- * ammo.fire(500, 'Something went wrong');
147
- *
148
- * @example
149
- * // Send HTML content with custom content type
150
- * ammo.fire(200, '<html><body>Hello</body></html>', 'text/html');
151
- *
152
- * @example
153
- * // Send just a status code (will use default status message)
154
- * ammo.fire(204);
155
- *
156
- * @example
157
- * // Send just data (will use 200 status code)
158
- * ammo.fire({ message: 'Success' });
159
- * ammo.fire('Hello World');
160
- */
161
- fire() {
162
- const { statusCode, data, contentType } = statusAndData(arguments);
163
- const contentTypeHeader = { 'Content-Type': contentType };
164
-
165
- this.dispatchedData = data;
166
-
167
- this.res.writeHead(statusCode, contentTypeHeader);
168
- this.res.write(data ?? '');
169
- this.res.end();
170
- }
171
-
172
- /**
173
- * Redirects to the specified URL.
174
- *
175
- * @param {string} url - The URL to redirect to
176
- * @param {number} [statusCode=302] - HTTP status code for redirect (default: 302)
177
- *
178
- * @description
179
- * Sends an HTTP redirect response to the specified URL.
180
- * Uses 302 (Found/Temporary Redirect) by default.
181
- * Common status codes:
182
- * - 301: Moved Permanently
183
- * - 302: Found (Temporary Redirect)
184
- * - 303: See Other
185
- * - 307: Temporary Redirect (maintains HTTP method)
186
- * - 308: Permanent Redirect (maintains HTTP method)
187
- *
188
- * @example
189
- * // Temporary redirect (302)
190
- * ammo.redirect('/new-location');
191
- *
192
- * @example
193
- * // Permanent redirect (301)
194
- * ammo.redirect('/new-location', 301);
195
- */
196
- redirect(url, statusCode = 302) {
197
- this.res.writeHead(statusCode, { Location: url });
198
- this.res.end();
199
- }
200
-
201
- /**
202
- * Throws a 404 Not Found error.
203
- *
204
- * @description
205
- * This is a convenience method that throws a 404 Not Found error.
206
- * It's equivalent to calling `throw(404) ` or `fire(404)`.
207
- *
208
- * @throws {TejError} Always throws a TejError with status code 404
209
- *
210
- * @example
211
- * // If resource not found
212
- * if (!resource) {
213
- * ammo.notFound();
214
- * }
215
- */
216
- notFound() {
217
- throw new TejError(404, 'Not Found');
218
- }
219
-
220
- /**
221
- * Throws a 405 Method Not Allowed error.
222
- *
223
- * @description
224
- * This is a convenience method that throws a 405 Method Not Allowed error.
225
- * It's equivalent to calling `throw(405)` or `fire(405)`.
226
- *
227
- * @throws {TejError} Always throws a TejError with status code 405
228
- *
229
- * @example
230
- * // If method not allowed
231
- * if (!allowedMethods.includes(ammo.method)) {
232
- * ammo.notAllowed();
233
- * }
234
- */
235
- notAllowed() {
236
- throw new TejError(405, 'Method Not Allowed');
237
- }
238
-
239
- /**
240
- * Throws a 401 Unauthorized error.
241
- *
242
- * @description
243
- * This is a convenience method that throws a 401 Unauthorized error.
244
- * It's equivalent to calling `throw(401) ` or `fire(401)`.
245
- *
246
- * @throws {TejError} Always throws a TejError with status code 401
247
- *
248
- * @example
249
- * // If user is not authenticated
250
- * if (!user) {
251
- * ammo.unauthorized();
252
- * }
253
- */
254
- unauthorized() {
255
- throw new TejError(401, 'Unauthorized');
256
- }
257
-
258
- /**
259
- * Sends the default entry point HTML.
260
- *
261
- * @description
262
- * This method sends the default HTML entry point for the application.
263
- * It's typically used as a fallback when no specific route is matched.
264
- *
265
- * @example
266
- * // In a catch-all route
267
- * ammo.defaultEntry();
268
- */
269
- defaultEntry() {
270
- this.fire(html);
271
- }
272
-
273
- /**
274
- * Throws an error response with appropriate status code and message.
275
- *
276
- * @param {number|Error|string|object} [arg1] - Status code, Error object, error message, or (when no code) options
277
- * @param {string|object} [arg2] - Error message (when arg1 is status code) or options (when arg1 is error/empty)
278
- *
279
- * @description
280
- * The throw method is flexible and can handle different argument patterns:
281
- *
282
- * 1. No arguments: Sends a 500 Internal Server Error response
283
- * 2. Status code: Sends a response with the given status code and default message
284
- * 3. Status code and message: Sends a response with the given status code and message
285
- * 4. Error object: Extracts status code and message from the error
286
- * 5. String: Treats as error message with 500 status code
287
- *
288
- * When errors.llm.enabled is true and no explicit code/message is given (no args,
289
- * Error, or string/other), an LLM infers statusCode and message from context.
290
- * In that case throw() returns a Promise; otherwise it returns undefined.
291
- *
292
- * Per-call options (last argument, only when no explicit status code): pass an object
293
- * with `useLlm` (boolean) and/or `messageType` ('endUser' | 'developer'). Use
294
- * `useLlm: false` to skip the LLM for this call; use `messageType` to override
295
- * errors.llm.messageType for this call (end-user-friendly vs developer-friendly message).
296
- *
297
- * @example
298
- * // Throw a 404 Not Found error
299
- * ammo.throw(404);
300
- *
301
- * @example
302
- * // Throw a 404 Not Found error with custom message
303
- * ammo.throw(404, 'Resource not found');
304
- *
305
- * @example
306
- * // Throw an error from an Error object
307
- * ammo.throw(new Error('Something went wrong'));
308
- *
309
- * @example
310
- * // Throw an error with a custom message
311
- * ammo.throw('Something went wrong');
312
- *
313
- * @example
314
- * // Skip LLM for this call; use default 500
315
- * ammo.throw(err, { useLlm: false });
316
- *
317
- * @example
318
- * // Force developer-friendly message for this call
319
- * ammo.throw(err, { messageType: 'developer' });
320
- *
321
- * @returns {Promise<void>|void} Promise when LLM path is used; otherwise void
322
- */
323
- throw() {
324
- let args = Array.from(arguments);
325
- const { enabled: llmEnabled } = getErrorsLlmConfig();
326
-
327
- // Per-call options: last arg can be { useLlm?, messageType? } when call is LLM-eligible (no explicit code).
328
- const llmEligible =
329
- args.length === 0 ||
330
- (!isStatusCode(args[0]) && !(args[0] instanceof TejError));
331
- let throwOpts = /** @type {{ useLlm?: boolean, messageType?: 'endUser'|'developer' } | null} */ (null);
332
- if (llmEligible && args.length > 0 && isThrowOptions(args[args.length - 1])) {
333
- throwOpts = /** @type {{ useLlm?: boolean, messageType?: 'endUser'|'developer' } } */ (args.pop());
334
- }
335
-
336
- const useLlm =
337
- llmEnabled &&
338
- llmEligible &&
339
- throwOpts?.useLlm !== false;
340
-
341
- if (useLlm) {
342
- // Use stack from thrown error when available (e.g. handler caught and called ammo.throw(err)) so we capture user code; else current call site.
343
- const stack =
344
- args[0] instanceof Error && args[0].stack
345
- ? args[0].stack
346
- : new Error().stack;
347
- return captureCodeContext(stack)
348
- .then((codeContext) => {
349
- const context = {
350
- codeContext,
351
- method: this.method,
352
- path: this.path,
353
- includeDevInsight: true,
354
- ...(throwOpts?.messageType && { messageType: throwOpts.messageType }),
355
- };
356
- if (args[0] !== undefined && args[0] !== null) context.error = args[0];
357
- return inferErrorFromContext(context);
358
- })
359
- .then(({ statusCode, message, devInsight }) => {
360
- const isProduction = process.env.NODE_ENV === 'production';
361
- const data =
362
- !isProduction && devInsight
363
- ? { message, _dev: devInsight }
364
- : message;
365
- this.fire(statusCode, data);
366
- });
367
- }
368
-
369
- // Sync path: explicit code/message or useLlm: false
370
- if (args.length === 0) {
371
- this.fire(500, 'Internal Server Error');
372
- return;
373
- }
374
-
375
- if (isStatusCode(args[0])) {
376
- const statusCode = args[0];
377
- const message = args[1] || toStatusMessage(statusCode);
378
- this.fire(statusCode, message);
379
- return;
380
- }
381
-
382
- if (args[0] instanceof TejError) {
383
- const error = args[0];
384
- this.fire(error.code, error.message);
385
- return;
386
- }
387
-
388
- if (args[0] instanceof Error) {
389
- const error = args[0];
390
- if (!isNaN(parseInt(error.message))) {
391
- const statusCode = parseInt(error.message);
392
- const message = toStatusMessage(statusCode) || toStatusMessage(500);
393
- this.fire(statusCode, message);
394
- return;
395
- }
396
- const statusCode = toStatusCode(error.message);
397
- if (statusCode) {
398
- this.fire(statusCode, error.message);
399
- return;
400
- }
401
- this.fire(500, error.message);
402
- return;
403
- }
404
-
405
- const errorValue = args[0];
406
- const statusCode = toStatusCode(errorValue);
407
- if (statusCode) {
408
- this.fire(statusCode, toStatusMessage(statusCode));
409
- return;
410
- }
411
- this.fire(500, errorValue.toString());
412
- }
413
- }
414
-
415
- export default Ammo;
1
+ import { statusAndData } from './ammo/dispatch-helper.js';
2
+ import {
3
+ isStatusCode,
4
+ toStatusCode,
5
+ toStatusMessage,
6
+ } from '../utils/status-codes.js';
7
+ import html from '../utils/tejas-entrypoint-html.js';
8
+ import ammoEnhancer from './ammo/enhancer.js';
9
+ import TejError from './error.js';
10
+ import { getErrorsLlmConfig } from '../utils/errors-llm-config.js';
11
+ import { inferErrorFromContext } from './errors/llm-error-service.js';
12
+ import { captureCodeContext } from './errors/code-context.js';
13
+
14
+ /**
15
+ * Detect if the value is a throw() options object (per-call overrides).
16
+ * @param {unknown} v
17
+ * @returns {v is { useLlm?: boolean, messageType?: 'endUser'|'developer' }}
18
+ */
19
+ function isThrowOptions(v) {
20
+ if (!v || typeof v !== 'object' || Array.isArray(v)) return false;
21
+ const o = /** @type {Record<string, unknown>} */ (v);
22
+ const hasUseLlm = 'useLlm' in o;
23
+ const hasMessageType =
24
+ 'messageType' in o &&
25
+ (o.messageType === 'endUser' || o.messageType === 'developer');
26
+ return hasUseLlm || hasMessageType === true;
27
+ }
28
+
29
+ /**
30
+ * Ammo class for handling HTTP requests and responses.
31
+ *
32
+ * @description
33
+ * Ammo is a utility class that simplifies HTTP request handling and response generation.
34
+ * It provides methods for processing requests, sending responses, and handling errors.
35
+ *
36
+ * @example
37
+ *
38
+ * if (ammo.GET) {
39
+ * ammo.fire(200, { message: 'Hello World' });
40
+ * } else {
41
+ * ammo.notAllowed();
42
+ * }
43
+ */
44
+ class Ammo {
45
+ /**
46
+ * Creates a new Ammo instance.
47
+ *
48
+ * @param {http.IncomingMessage} req - The HTTP request object
49
+ * @param {http.ServerResponse} res - The HTTP response object
50
+ *
51
+ * @description
52
+ * Initializes a new Ammo instance with the provided request and response objects.
53
+ * Sets up default values for various properties that will be populated by the enhance method.
54
+ */
55
+ constructor(req, res) {
56
+ this.req = req;
57
+ this.res = res;
58
+
59
+ this.GET = false;
60
+ this.POST = false;
61
+ this.PUT = false;
62
+ this.DELETE = false;
63
+ this.PATCH = false;
64
+ this.HEAD = false;
65
+ this.OPTIONS = false;
66
+
67
+ // Request related data
68
+ this.ip = undefined;
69
+ this.headers = undefined;
70
+ this.payload = undefined;
71
+ this.method = undefined;
72
+
73
+ // URL related data
74
+ this.protocol = undefined;
75
+ this.hostname = undefined;
76
+ this.path = undefined;
77
+ this.endpoint = undefined;
78
+
79
+ this.fullURL = undefined;
80
+
81
+ // Response related data
82
+ this.dispatchedData = undefined;
83
+ }
84
+
85
+ /**
86
+ * Enhances the Ammo instance with request data and sets HTTP method flags.
87
+ *
88
+ * @description
89
+ * This method processes the request and sets various properties on the Ammo instance:
90
+ * - HTTP method flags (GET, POST, PUT, etc.)
91
+ * - Request data (IP, headers, payload, method)
92
+ * - URL data (protocol, hostname, path, endpoint, fullURL)
93
+ *
94
+ * This method should be called before using any other Ammo methods.
95
+ *
96
+ * @returns {Promise<void>} A promise that resolves when enhancement is complete
97
+ *
98
+ * @example
99
+ * const ammo = new Ammo(req, res);
100
+ * await ammo.enhance();
101
+ * // Now you can use ammo.GET, ammo.path, etc.
102
+ */
103
+ async enhance() {
104
+ await ammoEnhancer(this);
105
+
106
+ this.GET = this.method === 'GET' || this.method === 'HEAD';
107
+ this.POST = this.method === 'POST';
108
+ this.PUT = this.method === 'PUT';
109
+ this.DELETE = this.method === 'DELETE';
110
+ this.PATCH = this.method === 'PATCH';
111
+ this.HEAD = this.method === 'HEAD';
112
+ this.OPTIONS = this.method === 'OPTIONS';
113
+ }
114
+
115
+ /**
116
+ * Sends a response to the client with the specified data and status code.
117
+ *
118
+ * @param {number|any} [arg1] - If a number, treated as status code. Otherwise treated as data to send.
119
+ * @param {any} [arg2] - If arg1 is a number, this is the data to send. Otherwise ignored.
120
+ * @param {string} [arg3] - Optional content type override.
121
+ *
122
+ * @description
123
+ * The fire method is flexible and can handle different argument patterns:
124
+ *
125
+ * 1. No arguments: Sends a 204 No Content response
126
+ * 2. Single number: Sends a response with the given status code
127
+ * 3. Single non-number: Sends a 200 OK response with the given data
128
+ * 4. Two arguments (number, data): Sends a response with the given status code and data
129
+ * 5. Three arguments: Sends a response with the given status code, data, and content type
130
+ *
131
+ * The fire method can be used with any HTTP status code, including error codes (4xx, 5xx).
132
+ * For error responses, you can use either fire() or throw(). The main difference is that
133
+ * throw() can accept an Error instance and has special handling for it, while fire() only
134
+ * accepts status codes, strings, or other data types.
135
+ *
136
+ * @example
137
+ * // Send a 200 OK response with JSON data
138
+ * ammo.fire(200, { message: 'Success' });
139
+ *
140
+ * @example
141
+ * // Send a 404 Not Found response with custom message
142
+ * ammo.fire(404, 'Resource not found');
143
+ *
144
+ * @example
145
+ * // Send a 500 Internal Server Error response
146
+ * ammo.fire(500, 'Something went wrong');
147
+ *
148
+ * @example
149
+ * // Send HTML content with custom content type
150
+ * ammo.fire(200, '<html><body>Hello</body></html>', 'text/html');
151
+ *
152
+ * @example
153
+ * // Send just a status code (will use default status message)
154
+ * ammo.fire(204);
155
+ *
156
+ * @example
157
+ * // Send just data (will use 200 status code)
158
+ * ammo.fire({ message: 'Success' });
159
+ * ammo.fire('Hello World');
160
+ */
161
+ fire() {
162
+ const { statusCode, data, contentType } = statusAndData(arguments);
163
+ const contentTypeHeader = { 'Content-Type': contentType };
164
+
165
+ this.dispatchedData = data;
166
+
167
+ this.res.writeHead(statusCode, contentTypeHeader);
168
+ this.res.write(data ?? '');
169
+ this.res.end();
170
+ }
171
+
172
+ /**
173
+ * Redirects to the specified URL.
174
+ *
175
+ * @param {string} url - The URL to redirect to
176
+ * @param {number} [statusCode=302] - HTTP status code for redirect (default: 302)
177
+ *
178
+ * @description
179
+ * Sends an HTTP redirect response to the specified URL.
180
+ * Uses 302 (Found/Temporary Redirect) by default.
181
+ * Common status codes:
182
+ * - 301: Moved Permanently
183
+ * - 302: Found (Temporary Redirect)
184
+ * - 303: See Other
185
+ * - 307: Temporary Redirect (maintains HTTP method)
186
+ * - 308: Permanent Redirect (maintains HTTP method)
187
+ *
188
+ * @example
189
+ * // Temporary redirect (302)
190
+ * ammo.redirect('/new-location');
191
+ *
192
+ * @example
193
+ * // Permanent redirect (301)
194
+ * ammo.redirect('/new-location', 301);
195
+ */
196
+ redirect(url, statusCode = 302) {
197
+ this.res.writeHead(statusCode, { Location: url });
198
+ this.res.end();
199
+ }
200
+
201
+ /**
202
+ * Throws a 404 Not Found error.
203
+ *
204
+ * @description
205
+ * This is a convenience method that throws a 404 Not Found error.
206
+ * It's equivalent to calling `throw(404) ` or `fire(404)`.
207
+ *
208
+ * @throws {TejError} Always throws a TejError with status code 404
209
+ *
210
+ * @example
211
+ * // If resource not found
212
+ * if (!resource) {
213
+ * ammo.notFound();
214
+ * }
215
+ */
216
+ notFound() {
217
+ throw new TejError(404, 'Not Found');
218
+ }
219
+
220
+ /**
221
+ * Throws a 405 Method Not Allowed error.
222
+ *
223
+ * @description
224
+ * This is a convenience method that throws a 405 Method Not Allowed error.
225
+ * It's equivalent to calling `throw(405)` or `fire(405)`.
226
+ *
227
+ * @throws {TejError} Always throws a TejError with status code 405
228
+ *
229
+ * @example
230
+ * // If method not allowed
231
+ * if (!allowedMethods.includes(ammo.method)) {
232
+ * ammo.notAllowed();
233
+ * }
234
+ */
235
+ /**
236
+ * Restricts the handler to the given HTTP method(s). If the request method is not in the list,
237
+ * sets the Allow header and throws 405 Method Not Allowed.
238
+ *
239
+ * @param {...string} methods - Allowed methods (e.g. 'GET', 'POST'). Case-insensitive.
240
+ * @throws {TejError} 405 when the request method is not allowed
241
+ *
242
+ * @example
243
+ * target.register('/health', (ammo) => {
244
+ * ammo.only('GET');
245
+ * ammo.fire({ status: 'ok' });
246
+ * });
247
+ */
248
+ only(...methods) {
249
+ const allowed = methods.map((m) => String(m).toUpperCase());
250
+ const method = this.method ? String(this.method).toUpperCase() : '';
251
+ if (!method || !allowed.includes(method)) {
252
+ this.res.setHeader('Allow', allowed.join(', '));
253
+ throw new TejError(405, 'Method Not Allowed');
254
+ }
255
+ }
256
+
257
+ /**
258
+ * Throws a 405 Method Not Allowed error. Optionally sets the Allow header when allowed methods are provided.
259
+ *
260
+ * @param {...string} [allowedMethods] - Allowed methods for the Allow header (e.g. 'GET', 'POST'). Omit for no header.
261
+ * @throws {TejError} Always throws a TejError with status code 405
262
+ *
263
+ * @example
264
+ * if (!allowedMethods.includes(ammo.method)) {
265
+ * ammo.notAllowed('GET', 'POST');
266
+ * }
267
+ */
268
+ notAllowed(...allowedMethods) {
269
+ if (allowedMethods.length > 0) {
270
+ this.res.setHeader(
271
+ 'Allow',
272
+ allowedMethods.map((m) => String(m).toUpperCase()).join(', '),
273
+ );
274
+ }
275
+ throw new TejError(405, 'Method Not Allowed');
276
+ }
277
+
278
+ /**
279
+ * Throws a 401 Unauthorized error.
280
+ *
281
+ * @description
282
+ * This is a convenience method that throws a 401 Unauthorized error.
283
+ * It's equivalent to calling `throw(401) ` or `fire(401)`.
284
+ *
285
+ * @throws {TejError} Always throws a TejError with status code 401
286
+ *
287
+ * @example
288
+ * // If user is not authenticated
289
+ * if (!user) {
290
+ * ammo.unauthorized();
291
+ * }
292
+ */
293
+ unauthorized() {
294
+ throw new TejError(401, 'Unauthorized');
295
+ }
296
+
297
+ /**
298
+ * Sends the default entry point HTML.
299
+ *
300
+ * @description
301
+ * This method sends the default HTML entry point for the application.
302
+ * It's typically used as a fallback when no specific route is matched.
303
+ *
304
+ * @example
305
+ * // In a catch-all route
306
+ * ammo.defaultEntry();
307
+ */
308
+ defaultEntry() {
309
+ this.fire(html);
310
+ }
311
+
312
+ /**
313
+ * Throws an error response with appropriate status code and message.
314
+ *
315
+ * @param {number|Error|string|object} [arg1] - Status code, Error object, error message, or (when no code) options
316
+ * @param {string|object} [arg2] - Error message (when arg1 is status code) or options (when arg1 is error/empty)
317
+ *
318
+ * @description
319
+ * The throw method is flexible and can handle different argument patterns:
320
+ *
321
+ * 1. No arguments: Sends a 500 Internal Server Error response
322
+ * 2. Status code: Sends a response with the given status code and default message
323
+ * 3. Status code and message: Sends a response with the given status code and message
324
+ * 4. Error object: Extracts status code and message from the error
325
+ * 5. String: Treats as error message with 500 status code
326
+ *
327
+ * When errors.llm.enabled is true and no explicit code/message is given (no args,
328
+ * Error, or string/other), an LLM infers statusCode and message from context.
329
+ * In that case throw() returns a Promise; otherwise it returns undefined.
330
+ *
331
+ * Per-call options (last argument, only when no explicit status code): pass an object
332
+ * with `useLlm` (boolean) and/or `messageType` ('endUser' | 'developer'). Use
333
+ * `useLlm: false` to skip the LLM for this call; use `messageType` to override
334
+ * errors.llm.messageType for this call (end-user-friendly vs developer-friendly message).
335
+ *
336
+ * @example
337
+ * // Throw a 404 Not Found error
338
+ * ammo.throw(404);
339
+ *
340
+ * @example
341
+ * // Throw a 404 Not Found error with custom message
342
+ * ammo.throw(404, 'Resource not found');
343
+ *
344
+ * @example
345
+ * // Throw an error from an Error object
346
+ * ammo.throw(new Error('Something went wrong'));
347
+ *
348
+ * @example
349
+ * // Throw an error with a custom message
350
+ * ammo.throw('Something went wrong');
351
+ *
352
+ * @example
353
+ * // Skip LLM for this call; use default 500
354
+ * ammo.throw(err, { useLlm: false });
355
+ *
356
+ * @example
357
+ * // Force developer-friendly message for this call
358
+ * ammo.throw(err, { messageType: 'developer' });
359
+ *
360
+ * @returns {Promise<void>|void} Promise when LLM path is used; otherwise void
361
+ */
362
+ throw() {
363
+ let args = Array.from(arguments);
364
+ const { enabled: llmEnabled } = getErrorsLlmConfig();
365
+
366
+ // Per-call options: last arg can be { useLlm?, messageType? } when call is LLM-eligible (no explicit code).
367
+ const llmEligible =
368
+ args.length === 0 ||
369
+ (!isStatusCode(args[0]) && !(args[0] instanceof TejError));
370
+ let throwOpts = /** @type {{ useLlm?: boolean, messageType?: 'endUser'|'developer' } | null} */ (null);
371
+ if (llmEligible && args.length > 0 && isThrowOptions(args[args.length - 1])) {
372
+ throwOpts = /** @type {{ useLlm?: boolean, messageType?: 'endUser'|'developer' } } */ (args.pop());
373
+ }
374
+
375
+ const useLlm =
376
+ llmEnabled &&
377
+ llmEligible &&
378
+ throwOpts?.useLlm !== false;
379
+
380
+ if (useLlm) {
381
+ // Use stack from thrown error when available (e.g. handler caught and called ammo.throw(err)) so we capture user code; else current call site.
382
+ const stack =
383
+ args[0] instanceof Error && args[0].stack
384
+ ? args[0].stack
385
+ : new Error().stack;
386
+ return captureCodeContext(stack)
387
+ .then((codeContext) => {
388
+ const context = {
389
+ codeContext,
390
+ method: this.method,
391
+ path: this.path,
392
+ includeDevInsight: true,
393
+ ...(throwOpts?.messageType && { messageType: throwOpts.messageType }),
394
+ };
395
+ if (args[0] !== undefined && args[0] !== null) context.error = args[0];
396
+ return inferErrorFromContext(context);
397
+ })
398
+ .then(({ statusCode, message, devInsight }) => {
399
+ const isProduction = process.env.NODE_ENV === 'production';
400
+ const data =
401
+ !isProduction && devInsight
402
+ ? { message, _dev: devInsight }
403
+ : message;
404
+ this.fire(statusCode, data);
405
+ });
406
+ }
407
+
408
+ // Sync path: explicit code/message or useLlm: false
409
+ if (args.length === 0) {
410
+ this.fire(500, 'Internal Server Error');
411
+ return;
412
+ }
413
+
414
+ if (isStatusCode(args[0])) {
415
+ const statusCode = args[0];
416
+ const message = args[1] || toStatusMessage(statusCode);
417
+ this.fire(statusCode, message);
418
+ return;
419
+ }
420
+
421
+ if (args[0] instanceof TejError) {
422
+ const error = args[0];
423
+ this.fire(error.code, error.message);
424
+ return;
425
+ }
426
+
427
+ if (args[0] instanceof Error) {
428
+ const error = args[0];
429
+ if (!isNaN(parseInt(error.message))) {
430
+ const statusCode = parseInt(error.message);
431
+ const message = toStatusMessage(statusCode) || toStatusMessage(500);
432
+ this.fire(statusCode, message);
433
+ return;
434
+ }
435
+ const statusCode = toStatusCode(error.message);
436
+ if (statusCode) {
437
+ this.fire(statusCode, error.message);
438
+ return;
439
+ }
440
+ this.fire(500, error.message);
441
+ return;
442
+ }
443
+
444
+ const errorValue = args[0];
445
+ const statusCode = toStatusCode(errorValue);
446
+ if (statusCode) {
447
+ this.fire(statusCode, toStatusMessage(statusCode));
448
+ return;
449
+ }
450
+ this.fire(500, errorValue.toString());
451
+ }
452
+ }
453
+
454
+ export default Ammo;