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