te.js 2.1.0 → 2.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +197 -196
- package/auto-docs/analysis/handler-analyzer.js +58 -58
- package/auto-docs/analysis/source-resolver.js +101 -101
- package/auto-docs/constants.js +37 -37
- package/auto-docs/docs-llm/index.js +7 -7
- package/auto-docs/docs-llm/prompts.js +222 -222
- package/auto-docs/docs-llm/provider.js +132 -132
- package/auto-docs/index.js +146 -146
- package/auto-docs/openapi/endpoint-processor.js +277 -277
- package/auto-docs/openapi/generator.js +107 -107
- package/auto-docs/openapi/level3.js +131 -131
- package/auto-docs/openapi/spec-builders.js +244 -244
- package/auto-docs/ui/docs-ui.js +186 -186
- package/auto-docs/utils/logger.js +17 -17
- package/auto-docs/utils/strip-usage.js +10 -10
- package/cli/docs-command.js +315 -315
- package/cli/fly-command.js +71 -71
- package/cli/index.js +56 -56
- package/cors/index.js +71 -0
- package/database/index.js +165 -165
- package/database/mongodb.js +146 -146
- package/database/redis.js +201 -201
- package/docs/README.md +36 -36
- package/docs/ammo.md +362 -362
- package/docs/api-reference.md +490 -490
- package/docs/auto-docs.md +216 -216
- package/docs/cli.md +152 -152
- package/docs/configuration.md +275 -275
- package/docs/database.md +390 -390
- package/docs/error-handling.md +438 -438
- package/docs/file-uploads.md +333 -333
- package/docs/getting-started.md +214 -214
- package/docs/middleware.md +355 -355
- package/docs/rate-limiting.md +393 -393
- package/docs/routing.md +302 -302
- package/lib/llm/client.js +73 -0
- package/lib/llm/index.js +7 -0
- package/lib/llm/parse.js +89 -0
- package/package.json +64 -62
- package/rate-limit/algorithms/fixed-window.js +141 -141
- package/rate-limit/algorithms/sliding-window.js +147 -147
- package/rate-limit/algorithms/token-bucket.js +115 -115
- package/rate-limit/base.js +165 -165
- package/rate-limit/index.js +147 -147
- package/rate-limit/storage/base.js +104 -104
- package/rate-limit/storage/memory.js +101 -101
- package/rate-limit/storage/redis.js +88 -88
- package/server/ammo/body-parser.js +220 -220
- package/server/ammo/dispatch-helper.js +103 -103
- package/server/ammo/enhancer.js +57 -57
- package/server/ammo.js +454 -415
- package/server/endpoint.js +97 -74
- package/server/error.js +9 -9
- package/server/errors/code-context.js +125 -125
- package/server/errors/llm-error-service.js +140 -140
- package/server/files/helper.js +33 -33
- package/server/files/uploader.js +143 -143
- package/server/handler.js +158 -119
- package/server/target.js +185 -175
- package/server/targets/middleware-validator.js +22 -22
- package/server/targets/path-validator.js +21 -21
- package/server/targets/registry.js +160 -160
- package/server/targets/shoot-validator.js +21 -21
- package/te.js +428 -402
- package/utils/auto-register.js +17 -17
- package/utils/configuration.js +64 -64
- package/utils/errors-llm-config.js +84 -84
- package/utils/request-logger.js +43 -43
- package/utils/status-codes.js +82 -82
- package/utils/tejas-entrypoint-html.js +18 -18
package/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
|
-
|
|
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
|
-
|
|
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;
|