te.js 2.0.3 → 2.1.1

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