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/te.js
CHANGED
|
@@ -1,402 +1,428 @@
|
|
|
1
|
-
import { createServer } from 'node:http';
|
|
2
|
-
import { env, setEnv } from 'tej-env';
|
|
3
|
-
import TejLogger from 'tej-logger';
|
|
4
|
-
import rateLimiter from './rate-limit/index.js';
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
import
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
import
|
|
13
|
-
import
|
|
14
|
-
import
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* @
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* @param {
|
|
36
|
-
* @param {
|
|
37
|
-
* @param {
|
|
38
|
-
* @param {boolean} [args.log.
|
|
39
|
-
* @param {
|
|
40
|
-
* @param {Object} [args.
|
|
41
|
-
* @param {
|
|
42
|
-
* @param {
|
|
43
|
-
* @param {
|
|
44
|
-
* @param {
|
|
45
|
-
* @param {
|
|
46
|
-
* @param {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
Tejas.instance
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
this.
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
* @
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
const
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
if
|
|
86
|
-
if (!env('
|
|
87
|
-
if (!env('
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
* @
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
* @
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
.replace(
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
*
|
|
164
|
-
*
|
|
165
|
-
* @param {Object} [options
|
|
166
|
-
* @param {Object} [options.
|
|
167
|
-
* @
|
|
168
|
-
*
|
|
169
|
-
*
|
|
170
|
-
*
|
|
171
|
-
* For
|
|
172
|
-
*
|
|
173
|
-
*
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
-
*
|
|
177
|
-
*
|
|
178
|
-
*
|
|
179
|
-
*
|
|
180
|
-
*
|
|
181
|
-
*
|
|
182
|
-
*
|
|
183
|
-
* }
|
|
184
|
-
*
|
|
185
|
-
*
|
|
186
|
-
*
|
|
187
|
-
*
|
|
188
|
-
* }
|
|
189
|
-
*
|
|
190
|
-
*
|
|
191
|
-
*
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
this.engine
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
if (
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
*
|
|
217
|
-
*
|
|
218
|
-
* @param {
|
|
219
|
-
* @param {
|
|
220
|
-
* @param {
|
|
221
|
-
* @param {
|
|
222
|
-
* @param {
|
|
223
|
-
* @param {
|
|
224
|
-
* @
|
|
225
|
-
*
|
|
226
|
-
*
|
|
227
|
-
*
|
|
228
|
-
*
|
|
229
|
-
*
|
|
230
|
-
*
|
|
231
|
-
*
|
|
232
|
-
*
|
|
233
|
-
*
|
|
234
|
-
*
|
|
235
|
-
*
|
|
236
|
-
*
|
|
237
|
-
*
|
|
238
|
-
*
|
|
239
|
-
*
|
|
240
|
-
*
|
|
241
|
-
*
|
|
242
|
-
*
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
*
|
|
259
|
-
*
|
|
260
|
-
* @param {
|
|
261
|
-
* @param {
|
|
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
|
-
* @param {
|
|
312
|
-
* @param {string} [config.
|
|
313
|
-
* @param {string} [config.
|
|
314
|
-
* @param {
|
|
315
|
-
* @
|
|
316
|
-
*
|
|
317
|
-
*
|
|
318
|
-
*
|
|
319
|
-
* app.
|
|
320
|
-
*
|
|
321
|
-
*
|
|
322
|
-
*
|
|
323
|
-
* app.
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
if (config.
|
|
330
|
-
if (config.
|
|
331
|
-
if (config.
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
*
|
|
339
|
-
*
|
|
340
|
-
* @param {
|
|
341
|
-
* @param {number} [config.
|
|
342
|
-
* @param {
|
|
343
|
-
* @param {
|
|
344
|
-
* @param {Object} [config.
|
|
345
|
-
* @param {
|
|
346
|
-
* @param {
|
|
347
|
-
* @
|
|
348
|
-
*
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
*
|
|
365
|
-
*
|
|
366
|
-
*
|
|
367
|
-
* @param {
|
|
368
|
-
* @param {string} [config.
|
|
369
|
-
* @param {
|
|
370
|
-
* @
|
|
371
|
-
*
|
|
372
|
-
* @
|
|
373
|
-
*
|
|
374
|
-
*
|
|
375
|
-
* app.
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
return this;
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
1
|
+
import { createServer } from 'node:http';
|
|
2
|
+
import { env, setEnv } from 'tej-env';
|
|
3
|
+
import TejLogger from 'tej-logger';
|
|
4
|
+
import rateLimiter from './rate-limit/index.js';
|
|
5
|
+
import corsMiddleware from './cors/index.js';
|
|
6
|
+
|
|
7
|
+
import targetRegistry from './server/targets/registry.js';
|
|
8
|
+
import dbManager from './database/index.js';
|
|
9
|
+
|
|
10
|
+
import { loadConfigFile, standardizeObj } from './utils/configuration.js';
|
|
11
|
+
|
|
12
|
+
import targetHandler from './server/handler.js';
|
|
13
|
+
import { getErrorsLlmConfig, validateErrorsLlmAtTakeoff } from './utils/errors-llm-config.js';
|
|
14
|
+
import path from 'node:path';
|
|
15
|
+
import { pathToFileURL } from 'node:url';
|
|
16
|
+
import { readFile } from 'node:fs/promises';
|
|
17
|
+
import { findTargetFiles } from './utils/auto-register.js';
|
|
18
|
+
import { registerDocRoutes } from './auto-docs/ui/docs-ui.js';
|
|
19
|
+
|
|
20
|
+
const logger = new TejLogger('Tejas');
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Main Tejas Framework Class
|
|
24
|
+
*
|
|
25
|
+
* @class
|
|
26
|
+
* @description
|
|
27
|
+
* Tejas is a Node.js framework for building powerful backend services.
|
|
28
|
+
* It provides features like routing, middleware support, database connections,
|
|
29
|
+
* and automatic target (route) registration.
|
|
30
|
+
*/
|
|
31
|
+
class Tejas {
|
|
32
|
+
/**
|
|
33
|
+
* Creates a new Tejas instance with the specified configuration
|
|
34
|
+
*
|
|
35
|
+
* @param {Object} [args] - Configuration options for Tejas
|
|
36
|
+
* @param {number} [args.port] - Port number to run the server on (defaults to 1403)
|
|
37
|
+
* @param {Object} [args.log] - Logging configuration
|
|
38
|
+
* @param {boolean} [args.log.http_requests] - Whether to log incoming HTTP requests
|
|
39
|
+
* @param {boolean} [args.log.exceptions] - Whether to log exceptions
|
|
40
|
+
* @param {Object} [args.db] - Database configuration options
|
|
41
|
+
* @param {Object} [args.withRedis] - Redis connection configuration
|
|
42
|
+
* @param {boolean} [args.withRedis.isCluster=false] - Whether to use Redis Cluster
|
|
43
|
+
* @param {Object} [args.withRedis.socket] - Redis socket connection options
|
|
44
|
+
* @param {string} [args.withRedis.socket.host] - Redis server hostname
|
|
45
|
+
* @param {number} [args.withRedis.socket.port] - Redis server port
|
|
46
|
+
* @param {boolean} [args.withRedis.socket.tls] - Whether to use TLS for connection
|
|
47
|
+
* @param {string} [args.withRedis.url] - Redis connection URL (alternative to socket config)
|
|
48
|
+
*/
|
|
49
|
+
constructor(args) {
|
|
50
|
+
if (Tejas.instance) return Tejas.instance;
|
|
51
|
+
Tejas.instance = this;
|
|
52
|
+
|
|
53
|
+
this.options = args || {};
|
|
54
|
+
|
|
55
|
+
this.generateConfiguration();
|
|
56
|
+
this.registerTargetsDir();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Generates and loads configuration from multiple sources
|
|
61
|
+
*
|
|
62
|
+
* @private
|
|
63
|
+
* @description
|
|
64
|
+
* Loads and merges configuration from:
|
|
65
|
+
* 1. tejas.config.json file (lowest priority)
|
|
66
|
+
* 2. Environment variables
|
|
67
|
+
* 3. Constructor options (highest priority)
|
|
68
|
+
*
|
|
69
|
+
* All configuration keys are standardized to uppercase and flattened.
|
|
70
|
+
* Sets default values for required configuration if not provided.
|
|
71
|
+
*/
|
|
72
|
+
generateConfiguration() {
|
|
73
|
+
const configVars = standardizeObj(loadConfigFile());
|
|
74
|
+
const envVars = standardizeObj(process.env);
|
|
75
|
+
const userVars = standardizeObj(this.options);
|
|
76
|
+
|
|
77
|
+
const config = { ...configVars, ...envVars, ...userVars };
|
|
78
|
+
|
|
79
|
+
for (const key in config) {
|
|
80
|
+
if (config.hasOwnProperty(key)) {
|
|
81
|
+
setEnv(key, config[key]);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Set default values for required configuration if not provided
|
|
86
|
+
if (!env('PORT')) setEnv('PORT', 1403);
|
|
87
|
+
if (!env('BODY_MAX_SIZE')) setEnv('BODY_MAX_SIZE', 10 * 1024 * 1024); // 10MB default
|
|
88
|
+
if (!env('BODY_TIMEOUT')) setEnv('BODY_TIMEOUT', 30000); // 30 seconds default
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Registers global middleware functions
|
|
93
|
+
*
|
|
94
|
+
* @param {...Function} arguments - Middleware functions to register globally
|
|
95
|
+
* @description
|
|
96
|
+
* Middleware functions are executed in order for all incoming requests.
|
|
97
|
+
* Each middleware should have the signature (ammo, next) or (req, res, next).
|
|
98
|
+
*
|
|
99
|
+
* @example
|
|
100
|
+
* app.midair(
|
|
101
|
+
* (ammo, next) => {
|
|
102
|
+
* console.log('Request received');
|
|
103
|
+
* next();
|
|
104
|
+
* },
|
|
105
|
+
* authenticationMiddleware
|
|
106
|
+
* );
|
|
107
|
+
*/
|
|
108
|
+
midair() {
|
|
109
|
+
if (arguments.length === 0) return;
|
|
110
|
+
targetRegistry.addGlobalMiddleware(...arguments);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Automatically registers target files from the configured directory
|
|
115
|
+
*
|
|
116
|
+
* @private
|
|
117
|
+
* @description
|
|
118
|
+
* Searches for and registers all files ending in 'target.js' from the
|
|
119
|
+
* directory specified by DIR_TARGETS environment variable.
|
|
120
|
+
* Target files define routes and their handlers.
|
|
121
|
+
*
|
|
122
|
+
* @throws {Error} If target files cannot be registered
|
|
123
|
+
*/
|
|
124
|
+
registerTargetsDir() {
|
|
125
|
+
const baseDir = path.join(process.cwd(), process.env.DIR_TARGETS || '');
|
|
126
|
+
findTargetFiles()
|
|
127
|
+
.then((targetFiles) => {
|
|
128
|
+
if (!targetFiles?.length) return;
|
|
129
|
+
(async () => {
|
|
130
|
+
for (const file of targetFiles) {
|
|
131
|
+
const parentPath = file.path || '';
|
|
132
|
+
const fullPath = path.isAbsolute(parentPath)
|
|
133
|
+
? path.join(parentPath, file.name)
|
|
134
|
+
: path.join(baseDir, parentPath, file.name);
|
|
135
|
+
const relativePath = path.relative(baseDir, fullPath);
|
|
136
|
+
const groupId = relativePath
|
|
137
|
+
.replace(/\.target\.js$/i, '')
|
|
138
|
+
.replace(/\\/g, '/')
|
|
139
|
+
|| 'index';
|
|
140
|
+
targetRegistry.setCurrentSourceGroup(groupId);
|
|
141
|
+
try {
|
|
142
|
+
await import(pathToFileURL(fullPath).href);
|
|
143
|
+
} finally {
|
|
144
|
+
targetRegistry.setCurrentSourceGroup(null);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
})().catch((err) => {
|
|
148
|
+
logger.error(
|
|
149
|
+
`Tejas could not register target files. Error: ${err}`,
|
|
150
|
+
false,
|
|
151
|
+
);
|
|
152
|
+
});
|
|
153
|
+
})
|
|
154
|
+
.catch((err) => {
|
|
155
|
+
logger.error(
|
|
156
|
+
`Tejas could not register target files. Error: ${err}`,
|
|
157
|
+
false,
|
|
158
|
+
);
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Starts the Tejas server
|
|
164
|
+
*
|
|
165
|
+
* @param {Object} [options] - Server configuration options
|
|
166
|
+
* @param {Object} [options.withRedis] - Redis connection options
|
|
167
|
+
* @param {Object} [options.withMongo] - MongoDB connection options (https://www.mongodb.com/docs/drivers/node/current/fundamentals/connection/)
|
|
168
|
+
* @description
|
|
169
|
+
* Creates and starts an HTTP server on the configured port.
|
|
170
|
+
* Optionally initializes Redis and/or MongoDB connections if configuration is provided.
|
|
171
|
+
* For Redis, accepts cluster flag and all connection options supported by node-redis package.
|
|
172
|
+
* For MongoDB, accepts all connection options supported by the official MongoDB Node.js driver.
|
|
173
|
+
*
|
|
174
|
+
* @example
|
|
175
|
+
* const app = new Tejas();
|
|
176
|
+
*
|
|
177
|
+
* // Start server with Redis and MongoDB
|
|
178
|
+
* app.takeoff({
|
|
179
|
+
* withRedis: {
|
|
180
|
+
* url: 'redis://alice:foobared@awesome.redis.server:6380',
|
|
181
|
+
* isCluster: false
|
|
182
|
+
* },
|
|
183
|
+
* withMongo: { url: 'mongodb://localhost:27017/mydatabase' }
|
|
184
|
+
* });
|
|
185
|
+
*
|
|
186
|
+
* // Start server with only Redis using defaults
|
|
187
|
+
* app.takeoff({
|
|
188
|
+
* withRedis: { url: 'redis://localhost:6379' }
|
|
189
|
+
* });
|
|
190
|
+
*
|
|
191
|
+
* // Start server without databases
|
|
192
|
+
* app.takeoff(); // Server starts on default port 1403
|
|
193
|
+
*/
|
|
194
|
+
takeoff({ withRedis, withMongo } = {}) {
|
|
195
|
+
validateErrorsLlmAtTakeoff();
|
|
196
|
+
const errorsLlm = getErrorsLlmConfig();
|
|
197
|
+
if (errorsLlm.enabled) {
|
|
198
|
+
logger.info(
|
|
199
|
+
`errors.llm enabled successfully — baseURL: ${errorsLlm.baseURL}, model: ${errorsLlm.model}, messageType: ${errorsLlm.messageType}, apiKey: ${errorsLlm.apiKey ? '***' : '(missing)'}`,
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
this.engine = createServer(targetHandler);
|
|
203
|
+
this.engine.listen(env('PORT'), async () => {
|
|
204
|
+
logger.info(`Took off from port ${env('PORT')}`);
|
|
205
|
+
|
|
206
|
+
if (withRedis) await this.withRedis(withRedis);
|
|
207
|
+
if (withMongo) await this.withMongo(withMongo);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
this.engine.on('error', (err) => {
|
|
211
|
+
logger.error(`Server error: ${err}`);
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Initializes a Redis connection
|
|
217
|
+
*
|
|
218
|
+
* @param {Object} [config] - Redis connection configuration
|
|
219
|
+
* @param {boolean} [config.isCluster=false] - Whether to use Redis Cluster
|
|
220
|
+
* @param {Object} [config.socket] - Redis socket connection options
|
|
221
|
+
* @param {string} [config.socket.host] - Redis server hostname
|
|
222
|
+
* @param {number} [config.socket.port] - Redis server port
|
|
223
|
+
* @param {boolean} [config.socket.tls] - Whether to use TLS for connection
|
|
224
|
+
* @param {string} [config.url] - Redis connection URL (alternative to socket config)
|
|
225
|
+
* @returns {Promise<Tejas>} Returns a Promise that resolves to this instance for chaining
|
|
226
|
+
*
|
|
227
|
+
* @example
|
|
228
|
+
* // Initialize Redis with URL
|
|
229
|
+
* await app.withRedis({
|
|
230
|
+
* url: 'redis://localhost:6379'
|
|
231
|
+
* }).withRateLimit({
|
|
232
|
+
* maxRequests: 100,
|
|
233
|
+
* store: 'redis'
|
|
234
|
+
* });
|
|
235
|
+
*
|
|
236
|
+
* @example
|
|
237
|
+
* // Initialize Redis with socket options
|
|
238
|
+
* await app.withRedis({
|
|
239
|
+
* socket: {
|
|
240
|
+
* host: 'localhost',
|
|
241
|
+
* port: 6379
|
|
242
|
+
* }
|
|
243
|
+
* });
|
|
244
|
+
*/
|
|
245
|
+
async withRedis(config) {
|
|
246
|
+
if (config) {
|
|
247
|
+
await dbManager.initializeConnection('redis', config);
|
|
248
|
+
} else {
|
|
249
|
+
logger.warn(
|
|
250
|
+
'No Redis configuration provided. Skipping Redis connection.',
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return this;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Initializes a MongoDB connection
|
|
259
|
+
*
|
|
260
|
+
* @param {Object} [config] - MongoDB connection configuration
|
|
261
|
+
* @param {string} [config.uri] - MongoDB connection URI
|
|
262
|
+
* @param {Object} [config.options] - Additional MongoDB connection options
|
|
263
|
+
* @returns {Tejas} Returns a Promise that resolves to this instance for chaining
|
|
264
|
+
*
|
|
265
|
+
* @example
|
|
266
|
+
* // Initialize MongoDB with URI
|
|
267
|
+
* await app.withMongo({
|
|
268
|
+
* uri: 'mongodb://localhost:27017/myapp'
|
|
269
|
+
* });
|
|
270
|
+
*
|
|
271
|
+
* @example
|
|
272
|
+
* // Initialize MongoDB with options
|
|
273
|
+
* await app.withMongo({
|
|
274
|
+
* uri: 'mongodb://localhost:27017/myapp',
|
|
275
|
+
* options: {
|
|
276
|
+
* useNewUrlParser: true,
|
|
277
|
+
* useUnifiedTopology: true
|
|
278
|
+
* }
|
|
279
|
+
* });
|
|
280
|
+
*
|
|
281
|
+
* @example
|
|
282
|
+
* // Chain database connections
|
|
283
|
+
* await app
|
|
284
|
+
* .withMongo({
|
|
285
|
+
* uri: 'mongodb://localhost:27017/myapp'
|
|
286
|
+
* })
|
|
287
|
+
* .withRedis({
|
|
288
|
+
* url: 'redis://localhost:6379'
|
|
289
|
+
* })
|
|
290
|
+
* .withRateLimit({
|
|
291
|
+
* maxRequests: 100,
|
|
292
|
+
* store: 'redis'
|
|
293
|
+
* });
|
|
294
|
+
*/
|
|
295
|
+
withMongo(config) {
|
|
296
|
+
if (config) {
|
|
297
|
+
dbManager.initializeConnection('mongodb', config);
|
|
298
|
+
} else {
|
|
299
|
+
logger.warn(
|
|
300
|
+
'No MongoDB configuration provided. Skipping MongoDB connection.',
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
return this;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Enables LLM-inferred error codes and messages for ammo.throw() and framework-caught errors.
|
|
308
|
+
* Call before takeoff(). Remaining options (baseURL, apiKey, model, messageType) can come from
|
|
309
|
+
* config, or from env/tejas.config.json (LLM_* / ERRORS_LLM_*). Validation runs at takeoff.
|
|
310
|
+
*
|
|
311
|
+
* @param {Object} [config] - Optional errors.llm overrides
|
|
312
|
+
* @param {string} [config.baseURL] - LLM provider endpoint (e.g. https://api.openai.com/v1)
|
|
313
|
+
* @param {string} [config.apiKey] - LLM provider API key
|
|
314
|
+
* @param {string} [config.model] - Model name (e.g. gpt-4o-mini)
|
|
315
|
+
* @param {'endUser'|'developer'} [config.messageType] - Default message tone
|
|
316
|
+
* @returns {Tejas} The Tejas instance for chaining
|
|
317
|
+
*
|
|
318
|
+
* @example
|
|
319
|
+
* app.withLLMErrors();
|
|
320
|
+
* app.takeoff();
|
|
321
|
+
*
|
|
322
|
+
* @example
|
|
323
|
+
* app.withLLMErrors({ baseURL: 'https://api.openai.com/v1', apiKey: process.env.OPENAI_KEY, model: 'gpt-4o-mini' });
|
|
324
|
+
* app.takeoff();
|
|
325
|
+
*/
|
|
326
|
+
withLLMErrors(config) {
|
|
327
|
+
setEnv('ERRORS_LLM_ENABLED', true);
|
|
328
|
+
if (config && typeof config === 'object') {
|
|
329
|
+
if (config.baseURL != null) setEnv('ERRORS_LLM_BASE_URL', config.baseURL);
|
|
330
|
+
if (config.apiKey != null) setEnv('ERRORS_LLM_API_KEY', config.apiKey);
|
|
331
|
+
if (config.model != null) setEnv('ERRORS_LLM_MODEL', config.model);
|
|
332
|
+
if (config.messageType != null) setEnv('ERRORS_LLM_MESSAGE_TYPE', config.messageType);
|
|
333
|
+
}
|
|
334
|
+
return this;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Adds global rate limiting to all endpoints
|
|
339
|
+
*
|
|
340
|
+
* @param {Object} config - Rate limiting configuration
|
|
341
|
+
* @param {number} [config.maxRequests=60] - Maximum number of requests allowed in the time window
|
|
342
|
+
* @param {number} [config.timeWindowSeconds=60] - Time window in seconds
|
|
343
|
+
* @param {string} [config.algorithm='sliding-window'] - Rate-limiting algorithm ('token-bucket', 'sliding-window', or 'fixed-window')
|
|
344
|
+
* @param {Object} [config.algorithmOptions] - Algorithm-specific options
|
|
345
|
+
* @param {Object} [config.redis] - Redis configuration for distributed rate limiting
|
|
346
|
+
* @param {Function} [config.keyGenerator] - Function to generate unique identifiers (defaults to IP-based)
|
|
347
|
+
* @param {Object} [config.headerFormat] - Rate limit header format configuration
|
|
348
|
+
* @returns {Tejas} The Tejas instance for chaining
|
|
349
|
+
*
|
|
350
|
+
*/
|
|
351
|
+
withRateLimit(config) {
|
|
352
|
+
if (!config) {
|
|
353
|
+
logger.warn(
|
|
354
|
+
'No rate limit configuration provided. Skipping rate limit setup.',
|
|
355
|
+
);
|
|
356
|
+
return this;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
this.midair(rateLimiter(config));
|
|
360
|
+
return this;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Adds CORS middleware. Sets Access-Control-* headers and responds to OPTIONS preflight with 204.
|
|
365
|
+
*
|
|
366
|
+
* @param {Object} [config] - CORS configuration
|
|
367
|
+
* @param {string|string[]|((origin: string) => boolean)} [config.origin='*'] - Allowed origin(s)
|
|
368
|
+
* @param {string[]} [config.methods] - Allowed methods (default: GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS)
|
|
369
|
+
* @param {string[]} [config.allowedHeaders=['Content-Type','Authorization']] - Allowed request headers
|
|
370
|
+
* @param {boolean} [config.credentials=false] - Allow credentials
|
|
371
|
+
* @param {number} [config.maxAge] - Preflight cache max age in seconds
|
|
372
|
+
* @returns {Tejas} The Tejas instance for chaining
|
|
373
|
+
*
|
|
374
|
+
* @example
|
|
375
|
+
* app.withCORS({
|
|
376
|
+
* origin: ['https://example.com'],
|
|
377
|
+
* methods: ['GET', 'POST', 'PUT', 'DELETE'],
|
|
378
|
+
* allowedHeaders: ['Content-Type', 'Authorization'],
|
|
379
|
+
* credentials: true,
|
|
380
|
+
* maxAge: 86400,
|
|
381
|
+
* });
|
|
382
|
+
*/
|
|
383
|
+
withCORS(config = {}) {
|
|
384
|
+
this.midair(corsMiddleware(config));
|
|
385
|
+
return this;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Serves the API documentation at GET /docs and GET /docs/openapi.json from a pre-generated spec file.
|
|
390
|
+
* Generate the spec with `tejas generate:docs`, then call this to serve it on your app.
|
|
391
|
+
* Uses Scalar API Reference; default layout is 'classic' so the test request appears on the same page (not in a dialog).
|
|
392
|
+
*
|
|
393
|
+
* @param {Object} [config] - Configuration
|
|
394
|
+
* @param {string} [config.specPath='./openapi.json'] - Path to the OpenAPI spec JSON file (relative to process.cwd())
|
|
395
|
+
* @param {object} [config.scalarConfig] - Optional Scalar API Reference config (e.g. { layout: 'modern' } for dialog try-it)
|
|
396
|
+
* @returns {Tejas} The Tejas instance for chaining
|
|
397
|
+
*
|
|
398
|
+
* @example
|
|
399
|
+
* app.serveDocs({ specPath: './openapi.json' });
|
|
400
|
+
* app.serveDocs({ specPath: './openapi.json', scalarConfig: { layout: 'modern' } });
|
|
401
|
+
* app.takeoff();
|
|
402
|
+
*/
|
|
403
|
+
serveDocs(config = {}) {
|
|
404
|
+
const specPath = path.resolve(process.cwd(), config.specPath || './openapi.json');
|
|
405
|
+
const { scalarConfig } = config;
|
|
406
|
+
const getSpec = async () => {
|
|
407
|
+
const content = await readFile(specPath, 'utf8');
|
|
408
|
+
return JSON.parse(content);
|
|
409
|
+
};
|
|
410
|
+
registerDocRoutes({ getSpec, specUrl: '/docs/openapi.json', scalarConfig }, targetRegistry);
|
|
411
|
+
return this;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
const listAllEndpoints = (grouped = false) => {
|
|
417
|
+
return targetRegistry.getAllEndpoints(grouped);
|
|
418
|
+
};
|
|
419
|
+
|
|
420
|
+
export { default as Target } from './server/target.js';
|
|
421
|
+
export { default as TejFileUploader } from './server/files/uploader.js';
|
|
422
|
+
export { default as TejError } from './server/error.js';
|
|
423
|
+
export { listAllEndpoints };
|
|
424
|
+
|
|
425
|
+
export default Tejas;
|
|
426
|
+
|
|
427
|
+
// TODO Ability to register a target (route) from tejas instance
|
|
428
|
+
// TODO tejas as CLI tool
|