te.js 2.1.6 → 2.2.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 (55) hide show
  1. package/README.md +1 -12
  2. package/auto-docs/analysis/handler-analyzer.test.js +106 -0
  3. package/auto-docs/analysis/source-resolver.test.js +58 -0
  4. package/auto-docs/constants.js +13 -2
  5. package/auto-docs/openapi/generator.js +7 -5
  6. package/auto-docs/openapi/generator.test.js +132 -0
  7. package/auto-docs/openapi/spec-builders.js +39 -19
  8. package/cli/docs-command.js +44 -36
  9. package/cors/index.test.js +82 -0
  10. package/docs/README.md +1 -2
  11. package/docs/api-reference.md +124 -186
  12. package/docs/configuration.md +0 -13
  13. package/docs/getting-started.md +19 -21
  14. package/docs/rate-limiting.md +59 -58
  15. package/lib/llm/client.js +7 -2
  16. package/lib/llm/index.js +14 -1
  17. package/lib/llm/parse.test.js +60 -0
  18. package/package.json +3 -1
  19. package/radar/index.js +382 -0
  20. package/rate-limit/base.js +12 -15
  21. package/rate-limit/index.js +19 -22
  22. package/rate-limit/index.test.js +93 -0
  23. package/rate-limit/storage/memory.js +13 -13
  24. package/rate-limit/storage/redis-install.js +70 -0
  25. package/rate-limit/storage/redis.js +94 -52
  26. package/server/ammo/body-parser.js +156 -152
  27. package/server/ammo/body-parser.test.js +79 -0
  28. package/server/ammo/enhancer.js +8 -4
  29. package/server/ammo.js +138 -12
  30. package/server/context/request-context.js +51 -0
  31. package/server/context/request-context.test.js +53 -0
  32. package/server/endpoint.js +15 -0
  33. package/server/error.js +56 -3
  34. package/server/error.test.js +45 -0
  35. package/server/errors/channels/channels.test.js +148 -0
  36. package/server/errors/channels/index.js +1 -1
  37. package/server/errors/llm-cache.js +1 -1
  38. package/server/errors/llm-cache.test.js +160 -0
  39. package/server/errors/llm-error-service.js +1 -1
  40. package/server/errors/llm-rate-limiter.test.js +105 -0
  41. package/server/files/uploader.js +38 -26
  42. package/server/handler.js +1 -1
  43. package/server/targets/registry.js +3 -3
  44. package/server/targets/registry.test.js +108 -0
  45. package/te.js +233 -183
  46. package/utils/auto-register.js +1 -1
  47. package/utils/configuration.js +23 -9
  48. package/utils/configuration.test.js +58 -0
  49. package/utils/errors-llm-config.js +74 -8
  50. package/utils/request-logger.js +49 -3
  51. package/utils/startup.js +80 -0
  52. package/database/index.js +0 -165
  53. package/database/mongodb.js +0 -146
  54. package/database/redis.js +0 -201
  55. package/docs/database.md +0 -390
package/database/redis.js DELETED
@@ -1,201 +0,0 @@
1
- import { spawnSync } from 'child_process';
2
- import fs from 'fs';
3
- import path from 'path';
4
- import TejError from '../server/error.js';
5
- import TejLogger from 'tej-logger';
6
- import { pathToFileURL } from 'node:url';
7
-
8
- const packageJsonPath = path.join(process.cwd(), 'package.json');
9
- const packagePath = `${process.cwd()}/node_modules/redis/dist/index.js`;
10
-
11
- const logger = new TejLogger('RedisConnectionManager');
12
-
13
- function checkRedisInstallation() {
14
- try {
15
- // Check if redis exists in package.json
16
- const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
17
- const inPackageJson = !!packageJson.dependencies?.redis;
18
-
19
- // Check if redis exists in node_modules
20
- const inNodeModules = fs.existsSync(packagePath);
21
-
22
- return {
23
- needsInstall: !inPackageJson || !inNodeModules,
24
- reason: !inPackageJson
25
- ? 'not in package.json'
26
- : !inNodeModules
27
- ? 'not in node_modules'
28
- : null,
29
- };
30
- } catch (error) {
31
- logger.error(error, true);
32
- return { needsInstall: true, reason: 'error checking installation' };
33
- }
34
- }
35
-
36
- function installRedisSync() {
37
- const spinner = ['|', '/', '-', '\\'];
38
- let current = 0;
39
- let intervalId;
40
-
41
- try {
42
- const { needsInstall, reason } = checkRedisInstallation();
43
-
44
- if (!needsInstall) {
45
- return true;
46
- }
47
-
48
- // Start the spinner
49
- intervalId = setInterval(() => {
50
- process.stdout.write(`\r${spinner[current]} Installing redis...`);
51
- current = (current + 1) % spinner.length;
52
- }, 100);
53
-
54
- logger.info(`Tejas will install redis (${reason})...`);
55
-
56
- const command = process.platform === 'win32' ? 'npm.cmd' : 'npm';
57
- const result = spawnSync(command, ['install', 'redis'], {
58
- stdio: 'inherit',
59
- shell: true,
60
- });
61
-
62
- process.stdout.write('\r');
63
- clearInterval(intervalId);
64
-
65
- if (result.status === 0) {
66
- logger.info('Redis installed successfully');
67
- return true;
68
- } else {
69
- logger.error('Redis installation failed');
70
- return false;
71
- }
72
- } catch (error) {
73
- if (intervalId) {
74
- process.stdout.write('\r');
75
- clearInterval(intervalId);
76
- }
77
- logger.error(error, true);
78
- return false;
79
- }
80
- }
81
-
82
- /**
83
- * Create a new Redis client or cluster
84
- * @param {Object} config - Redis configuration
85
- * @param {boolean} [config.isCluster=false] - Whether to use Redis Cluster
86
- * @param {Object} [config.options={}] - Additional Redis options
87
- * @returns {Promise<RedisClient|RedisCluster>} Redis client or cluster instance
88
- */
89
- async function createConnection(config) {
90
- const { needsInstall } = checkRedisInstallation();
91
-
92
- if (needsInstall) {
93
- const installed = installRedisSync();
94
- if (!installed) {
95
- throw new TejError(500, 'Failed to install required redis package');
96
- }
97
- }
98
-
99
- const { isCluster = false, options = {} } = config;
100
- let client;
101
-
102
- try {
103
- const { createClient, createCluster } = await import(
104
- pathToFileURL(packagePath)
105
- );
106
-
107
- if (isCluster) {
108
- client = createCluster({
109
- ...options,
110
- });
111
- } else {
112
- client = createClient({
113
- ...options,
114
- });
115
- }
116
-
117
- let connectionTimeout;
118
- let hasConnected = false;
119
- let connectionAttempts = 0;
120
- const maxRetries = options.maxRetries || 3;
121
-
122
- // Create a promise that will resolve when connected or reject on fatal errors
123
- const connectionPromise = new Promise((resolve, reject) => {
124
- connectionTimeout = setTimeout(() => {
125
- if (!hasConnected) {
126
- client.quit().catch(() => {});
127
- reject(new TejError(500, 'Redis connection timeout'));
128
- }
129
- }, options.connectTimeout || 10000);
130
-
131
- client.on('error', (err) => {
132
- logger.error(`Redis connection error: ${err}`, true);
133
- if (!hasConnected && connectionAttempts >= maxRetries) {
134
- clearTimeout(connectionTimeout);
135
- client.quit().catch(() => {});
136
- reject(
137
- new TejError(
138
- 500,
139
- `Redis connection failed after ${maxRetries} attempts: ${err.message}`,
140
- ),
141
- );
142
- }
143
- connectionAttempts++;
144
- });
145
-
146
- client.on('connect', () => {
147
- hasConnected = true;
148
- clearTimeout(connectionTimeout);
149
- logger.info(
150
- `Redis connected on ${client?.options?.url ?? client?.options?.socket?.host}`,
151
- );
152
- });
153
-
154
- client.on('ready', () => {
155
- logger.info('Redis ready');
156
- resolve(client);
157
- });
158
-
159
- client.on('end', () => {
160
- logger.info('Redis connection closed');
161
- });
162
- });
163
-
164
- await client.connect();
165
- await connectionPromise;
166
-
167
- return client;
168
- } catch (error) {
169
- if (client) {
170
- try {
171
- await client.quit();
172
- } catch (quitError) {
173
- logger.error(
174
- `Error while cleaning up Redis connection: ${quitError}`,
175
- true,
176
- );
177
- }
178
- }
179
- logger.error(`Failed to create Redis connection: ${error}`, true);
180
- throw new TejError(
181
- 500,
182
- `Failed to create Redis connection: ${error.message}`,
183
- );
184
- }
185
- }
186
-
187
- /**
188
- * Close a Redis connection
189
- * @param {RedisClient|RedisCluster} client - Redis client to close
190
- * @returns {Promise<void>}
191
- */
192
- async function closeConnection(client) {
193
- if (client) {
194
- await client.quit();
195
- }
196
- }
197
-
198
- export default {
199
- createConnection,
200
- closeConnection,
201
- };
package/docs/database.md DELETED
@@ -1,390 +0,0 @@
1
- # Database Integration
2
-
3
- Tejas provides built-in support for **MongoDB** and **Redis** databases through a centralized `DatabaseManager`.
4
-
5
- ## Quick Start
6
-
7
- ### Redis
8
-
9
- ```javascript
10
- import Tejas from 'te.js';
11
-
12
- const app = new Tejas();
13
-
14
- app.takeoff({
15
- withRedis: { url: 'redis://localhost:6379' }
16
- });
17
- ```
18
-
19
- ### MongoDB
20
-
21
- ```javascript
22
- app.takeoff({
23
- withMongo: { uri: 'mongodb://localhost:27017/myapp' }
24
- });
25
- ```
26
-
27
- ### Both Together
28
-
29
- ```javascript
30
- app.takeoff({
31
- withRedis: { url: 'redis://localhost:6379' },
32
- withMongo: { uri: 'mongodb://localhost:27017/myapp' }
33
- });
34
- ```
35
-
36
- > **Auto-install:** Tejas automatically installs the `redis` or `mongoose` npm packages on first use if they are not already in your `node_modules`. No manual `npm install` is required for database drivers.
37
-
38
- ## Redis Configuration
39
-
40
- ### Basic Connection
41
-
42
- ```javascript
43
- app.withRedis({
44
- url: 'redis://localhost:6379'
45
- });
46
- ```
47
-
48
- ### With Authentication
49
-
50
- ```javascript
51
- app.withRedis({
52
- url: 'redis://username:password@hostname:6379'
53
- });
54
-
55
- // Or using socket options
56
- app.withRedis({
57
- socket: {
58
- host: 'localhost',
59
- port: 6379
60
- },
61
- password: 'your-password'
62
- });
63
- ```
64
-
65
- ### TLS Connection
66
-
67
- ```javascript
68
- app.withRedis({
69
- socket: {
70
- host: 'your-redis-host.com',
71
- port: 6379,
72
- tls: true
73
- }
74
- });
75
- ```
76
-
77
- ### Redis Cluster
78
-
79
- ```javascript
80
- app.withRedis({
81
- isCluster: true,
82
- url: 'redis://node1:6379'
83
- });
84
- ```
85
-
86
- ### All Options
87
-
88
- ```javascript
89
- app.withRedis({
90
- url: 'redis://localhost:6379', // Connection URL
91
- isCluster: false, // Use Redis Cluster
92
- socket: {
93
- host: 'localhost',
94
- port: 6379,
95
- tls: false
96
- },
97
- password: 'secret', // Redis password
98
- database: 0, // Database number
99
- // ... any other node-redis options
100
- });
101
- ```
102
-
103
- ## MongoDB Configuration
104
-
105
- ### Basic Connection
106
-
107
- ```javascript
108
- app.withMongo({
109
- uri: 'mongodb://localhost:27017/myapp'
110
- });
111
- ```
112
-
113
- ### With Options
114
-
115
- ```javascript
116
- app.withMongo({
117
- uri: 'mongodb://localhost:27017/myapp',
118
- options: {
119
- maxPoolSize: 10,
120
- serverSelectionTimeoutMS: 5000,
121
- socketTimeoutMS: 45000
122
- }
123
- });
124
- ```
125
-
126
- ### MongoDB Atlas
127
-
128
- ```javascript
129
- app.withMongo({
130
- uri: 'mongodb+srv://username:password@cluster.mongodb.net/myapp'
131
- });
132
- ```
133
-
134
- ### Replica Set
135
-
136
- ```javascript
137
- app.withMongo({
138
- uri: 'mongodb://host1:27017,host2:27017,host3:27017/myapp?replicaSet=myReplicaSet'
139
- });
140
- ```
141
-
142
- ## Using Database Connections
143
-
144
- ### Getting Connections
145
-
146
- Import the database manager to access connections:
147
-
148
- ```javascript
149
- import dbManager from 'te.js/database/index.js';
150
-
151
- // Get Redis client
152
- const redis = dbManager.getConnection('redis');
153
-
154
- // Get MongoDB client
155
- const mongo = dbManager.getConnection('mongodb');
156
- ```
157
-
158
- ### In Route Handlers
159
-
160
- ```javascript
161
- import { Target } from 'te.js';
162
- import dbManager from 'te.js/database/index.js';
163
-
164
- const cache = new Target('/cache');
165
-
166
- cache.register('/get/:key', async (ammo) => {
167
- const redis = dbManager.getConnection('redis');
168
- const { key } = ammo.payload;
169
-
170
- const value = await redis.get(key);
171
-
172
- if (!value) {
173
- return ammo.fire(404, { error: 'Key not found' });
174
- }
175
-
176
- ammo.fire({ key, value });
177
- });
178
-
179
- cache.register('/set', async (ammo) => {
180
- if (!ammo.POST) return ammo.notAllowed();
181
-
182
- const redis = dbManager.getConnection('redis');
183
- const { key, value, ttl } = ammo.payload;
184
-
185
- if (ttl) {
186
- await redis.setEx(key, ttl, value);
187
- } else {
188
- await redis.set(key, value);
189
- }
190
-
191
- ammo.fire(201, { message: 'Cached successfully' });
192
- });
193
- ```
194
-
195
- ### MongoDB Example
196
-
197
- ```javascript
198
- import { Target, TejError } from 'te.js';
199
- import dbManager from 'te.js/database/index.js';
200
-
201
- const users = new Target('/users');
202
-
203
- users.register('/', async (ammo) => {
204
- const mongo = dbManager.getConnection('mongodb');
205
- const db = mongo.db('myapp');
206
- const collection = db.collection('users');
207
-
208
- if (ammo.GET) {
209
- const users = await collection.find({}).toArray();
210
- return ammo.fire(users);
211
- }
212
-
213
- if (ammo.POST) {
214
- const { name, email } = ammo.payload;
215
- const result = await collection.insertOne({ name, email, createdAt: new Date() });
216
- return ammo.fire(201, { id: result.insertedId, name, email });
217
- }
218
-
219
- ammo.notAllowed();
220
- });
221
-
222
- users.register('/:id', async (ammo) => {
223
- const mongo = dbManager.getConnection('mongodb');
224
- const db = mongo.db('myapp');
225
- const collection = db.collection('users');
226
-
227
- const { id } = ammo.payload;
228
- const { ObjectId } = await import('mongodb');
229
-
230
- const user = await collection.findOne({ _id: new ObjectId(id) });
231
-
232
- if (!user) {
233
- throw new TejError(404, 'User not found');
234
- }
235
-
236
- ammo.fire(user);
237
- });
238
- ```
239
-
240
- ## Database Manager API
241
-
242
- Access the database manager directly for advanced usage:
243
-
244
- ```javascript
245
- import dbManager from 'te.js/database/index.js';
246
- ```
247
-
248
- ### Check Connection Status
249
-
250
- ```javascript
251
- const status = dbManager.hasConnection('redis', {});
252
- // Returns: { exists: boolean, initializing: boolean }
253
-
254
- if (status.exists) {
255
- const redis = dbManager.getConnection('redis');
256
- }
257
- ```
258
-
259
- The `initializing` flag is `true` while the connection is being established.
260
-
261
- ### Get All Active Connections
262
-
263
- ```javascript
264
- const connections = dbManager.getActiveConnections();
265
- // Returns a Map: { 'redis' => { type, client, config }, 'mongodb' => { type, client, config } }
266
- ```
267
-
268
- ### Close Connections
269
-
270
- ```javascript
271
- // Close a specific connection
272
- await dbManager.closeConnection('redis');
273
-
274
- // Close all connections
275
- await dbManager.closeAllConnections();
276
- ```
277
-
278
- ## Caching Pattern
279
-
280
- A common pattern using Redis for caching:
281
-
282
- ```javascript
283
- import { Target } from 'te.js';
284
- import dbManager from 'te.js/database/index.js';
285
-
286
- const api = new Target('/api');
287
-
288
- // Cache middleware
289
- const withCache = (ttl = 60) => async (ammo, next) => {
290
- if (!ammo.GET) return next();
291
-
292
- const redis = dbManager.getConnection('redis');
293
- const cacheKey = `cache:${ammo.endpoint}`;
294
-
295
- // Try cache first
296
- const cached = await redis.get(cacheKey);
297
- if (cached) {
298
- ammo.res.setHeader('X-Cache', 'HIT');
299
- return ammo.fire(JSON.parse(cached));
300
- }
301
-
302
- // Store original fire to intercept
303
- const originalFire = ammo.fire.bind(ammo);
304
- ammo.fire = async (...args) => {
305
- const data = args[0];
306
- if (typeof data === 'object') {
307
- await redis.setEx(cacheKey, ttl, JSON.stringify(data));
308
- }
309
- ammo.res.setHeader('X-Cache', 'MISS');
310
- originalFire(...args);
311
- };
312
-
313
- next();
314
- };
315
-
316
- api.register('/expensive-data', withCache(300), async (ammo) => {
317
- // This expensive operation result will be cached for 5 minutes
318
- const data = await expensiveOperation();
319
- ammo.fire(data);
320
- });
321
- ```
322
-
323
- ## Session Storage with Redis
324
-
325
- ```javascript
326
- import { v4 as uuidv4 } from 'uuid';
327
- import dbManager from 'te.js/database/index.js';
328
-
329
- const sessionMiddleware = async (ammo, next) => {
330
- const redis = dbManager.getConnection('redis');
331
- let sessionId = ammo.headers['x-session-id'];
332
-
333
- if (!sessionId) {
334
- sessionId = uuidv4();
335
- ammo.res.setHeader('X-Session-ID', sessionId);
336
- ammo.session = {};
337
- } else {
338
- const sessionData = await redis.get(`session:${sessionId}`);
339
- ammo.session = sessionData ? JSON.parse(sessionData) : {};
340
- }
341
-
342
- // Save session after response
343
- const originalFire = ammo.fire.bind(ammo);
344
- ammo.fire = async (...args) => {
345
- await redis.setEx(
346
- `session:${sessionId}`,
347
- 3600, // 1 hour TTL
348
- JSON.stringify(ammo.session)
349
- );
350
- originalFire(...args);
351
- };
352
-
353
- next();
354
- };
355
- ```
356
-
357
- ## Connection Events
358
-
359
- The underlying clients emit events you can listen to:
360
-
361
- ```javascript
362
- const redis = dbManager.getConnection('redis');
363
-
364
- redis.on('error', (err) => {
365
- console.error('Redis error:', err);
366
- });
367
-
368
- redis.on('connect', () => {
369
- console.log('Redis connected');
370
- });
371
-
372
- redis.on('reconnecting', () => {
373
- console.log('Redis reconnecting...');
374
- });
375
- ```
376
-
377
- ## Best Practices
378
-
379
- 1. **Initialize early** — Set up connections before `takeoff()`
380
- 2. **Handle errors** — Always wrap database operations in try/catch
381
- 3. **Use connection pooling** — MongoDB handles this automatically
382
- 4. **Close on shutdown** — Clean up connections when app terminates
383
-
384
- ```javascript
385
- // Graceful shutdown
386
- process.on('SIGTERM', async () => {
387
- await dbManager.closeAllConnections();
388
- process.exit(0);
389
- });
390
- ```