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
@@ -32,25 +32,25 @@ function mask(value) {
32
32
 
33
33
  function ask(question, fallback = '') {
34
34
  const hint = fallback ? c.dim(` (${fallback})`) : '';
35
- return new Promise((resolve) => {
36
- rl.question(`${c.cyan('?')} ${question}${hint}${c.dim(': ')}`, (answer) => {
37
- resolve(answer.trim() || fallback);
38
- });
35
+ const { promise, resolve } = Promise.withResolvers();
36
+ rl.question(`${c.cyan('?')} ${question}${hint}${c.dim(': ')}`, (answer) => {
37
+ resolve(answer.trim() || fallback);
39
38
  });
39
+ return promise;
40
40
  }
41
41
 
42
42
  function askYesNo(question, fallback = false) {
43
43
  const hint = fallback ? 'Y/n' : 'y/N';
44
- return new Promise((resolve) => {
45
- rl.question(
46
- `${c.cyan('?')} ${question} ${c.dim(`(${hint})`)}${c.dim(': ')}`,
47
- (answer) => {
48
- const val = answer.trim().toLowerCase();
49
- if (!val) return resolve(fallback);
50
- resolve(val === 'y' || val === 'yes');
51
- },
52
- );
53
- });
44
+ const { promise, resolve } = Promise.withResolvers();
45
+ rl.question(
46
+ `${c.cyan('?')} ${question} ${c.dim(`(${hint})`)}${c.dim(': ')}`,
47
+ (answer) => {
48
+ const val = answer.trim().toLowerCase();
49
+ if (!val) return resolve(fallback);
50
+ resolve(val === 'y' || val === 'yes');
51
+ },
52
+ );
53
+ return promise;
54
54
  }
55
55
 
56
56
  async function loadTargetFiles(dirTargets = 'targets') {
@@ -88,13 +88,15 @@ async function loadTargetFiles(dirTargets = 'targets') {
88
88
  function getDocsOptionsFromConfig(config = {}) {
89
89
  const docs = config.docs || config.generateDocs || {};
90
90
  const e = process.env;
91
- const baseURL = docs.llm?.baseURL ?? e.LLM_BASE_URL ?? 'https://api.openai.com/v1';
91
+ const baseURL =
92
+ docs.llm?.baseURL ?? e.LLM_BASE_URL ?? 'https://api.openai.com/v1';
92
93
  const apiKey = docs.llm?.apiKey ?? e.LLM_API_KEY ?? e.OPENAI_API_KEY;
93
94
  const model = docs.llm?.model ?? e.LLM_MODEL ?? 'gpt-4o-mini';
94
95
  if (!apiKey && !e.OPENAI_API_KEY) {
95
96
  return null;
96
97
  }
97
- const dirTargets = docs.dirTargets ?? docs.dir?.targets ?? config.dir?.targets ?? 'targets';
98
+ const dirTargets =
99
+ docs.dirTargets ?? docs.dir?.targets ?? config.dir?.targets ?? 'targets';
98
100
  const output = docs.output ?? './openapi.json';
99
101
  const title = docs.title ?? 'API';
100
102
  const version = docs.version ?? '1.0.0';
@@ -116,7 +118,7 @@ function getDocsOptionsFromConfig(config = {}) {
116
118
  * @returns {Promise<void>}
117
119
  */
118
120
  export async function runDocsCommandCI() {
119
- const config = loadConfigFile();
121
+ const config = await loadConfigFile();
120
122
  const options = getDocsOptionsFromConfig(config);
121
123
  if (!options) {
122
124
  console.error(
@@ -128,7 +130,9 @@ export async function runDocsCommandCI() {
128
130
  process.stdout.write(`${c.yellow('⏳')} Loading targets...`);
129
131
  const fileCount = await loadTargetFiles(dirTargets);
130
132
  const endpointCount = targetRegistry.targets?.length ?? 0;
131
- process.stdout.write(`\r${c.green('✓')} Loaded ${c.bold(fileCount)} target file(s) — ${c.bold(endpointCount)} endpoint(s)\n`);
133
+ process.stdout.write(
134
+ `\r${c.green('✓')} Loaded ${c.bold(fileCount)} target file(s) — ${c.bold(endpointCount)} endpoint(s)\n`,
135
+ );
132
136
  if (endpointCount === 0) {
133
137
  console.log(c.yellow(' No endpoints found. Skipping doc generation.\n'));
134
138
  return;
@@ -156,19 +160,19 @@ export async function runDocsCommandCI() {
156
160
  * @returns {Promise<string[]>} e.g. ['refs/heads/main', 'refs/heads/feature/x']
157
161
  */
158
162
  function readPrePushRefs() {
159
- return new Promise((resolve) => {
160
- const chunks = [];
161
- stdin.on('data', (chunk) => chunks.push(chunk));
162
- stdin.on('end', () => {
163
- const lines = Buffer.concat(chunks).toString('utf8').trim().split('\n');
164
- const refs = [];
165
- for (let i = 0; i < lines.length; i++) {
166
- const parts = lines[i].split(/\s+/);
167
- if (parts.length >= 4) refs.push(parts[2]); // remote_ref
168
- }
169
- resolve(refs);
170
- });
163
+ const { promise, resolve } = Promise.withResolvers();
164
+ const chunks = [];
165
+ stdin.on('data', (chunk) => chunks.push(chunk));
166
+ stdin.on('end', () => {
167
+ const lines = Buffer.concat(chunks).toString('utf8').trim().split('\n');
168
+ const refs = [];
169
+ for (let i = 0; i < lines.length; i++) {
170
+ const parts = lines[i].split(/\s+/);
171
+ if (parts.length >= 4) refs.push(parts[2]);
172
+ }
173
+ resolve(refs);
171
174
  });
175
+ return promise;
172
176
  }
173
177
 
174
178
  /**
@@ -178,7 +182,7 @@ function readPrePushRefs() {
178
182
  * Configure via tejas.config.json docs.productionBranch or env DOCS_PRODUCTION_BRANCH (default: main).
179
183
  */
180
184
  export async function runDocsOnPush() {
181
- const config = loadConfigFile();
185
+ const config = await loadConfigFile();
182
186
  const docs = config.docs || config.generateDocs || {};
183
187
  const productionBranch =
184
188
  docs.productionBranch ?? process.env.DOCS_PRODUCTION_BRANCH ?? 'main';
@@ -186,7 +190,11 @@ export async function runDocsOnPush() {
186
190
  const productionRef = `refs/heads/${productionBranch}`;
187
191
  const isPushingToProduction = remoteRefs.some((ref) => ref === productionRef);
188
192
  if (!isPushingToProduction) return;
189
- console.log(c.dim(` Docs: pushing to ${productionBranch} — generating documentation...\n`));
193
+ console.log(
194
+ c.dim(
195
+ ` Docs: pushing to ${productionBranch} — generating documentation...\n`,
196
+ ),
197
+ );
190
198
  await runDocsCommandCI();
191
199
  }
192
200
 
@@ -207,13 +215,13 @@ function serveDocsPreview(spec, port = 3333) {
207
215
  res.writeHead(404);
208
216
  res.end('Not found');
209
217
  });
210
- return new Promise((resolve) => {
211
- server.listen(port, () => resolve(server));
212
- });
218
+ const { promise, resolve } = Promise.withResolvers();
219
+ server.listen(port, () => resolve(server));
220
+ return promise;
213
221
  }
214
222
 
215
223
  export async function runDocsCommand() {
216
- const config = loadConfigFile();
224
+ const config = await loadConfigFile();
217
225
  const e = process.env;
218
226
 
219
227
  console.log();
@@ -0,0 +1,82 @@
1
+ /**
2
+ * @fileoverview Tests for CORS middleware.
3
+ */
4
+ import { describe, it, expect, vi } from 'vitest';
5
+ import corsMiddleware from './index.js';
6
+
7
+ function makeAmmo(method = 'GET', origin = 'https://example.com') {
8
+ const headers = {};
9
+ return {
10
+ req: { method, headers: { origin } },
11
+ res: {
12
+ _headers: {},
13
+ setHeader(name, value) {
14
+ this._headers[name.toLowerCase()] = value;
15
+ },
16
+ writeHead(code) {
17
+ this._status = code;
18
+ },
19
+ end() {
20
+ this._ended = true;
21
+ },
22
+ },
23
+ };
24
+ }
25
+
26
+ describe('corsMiddleware', () => {
27
+ it('should set Access-Control-Allow-Origin to * by default', async () => {
28
+ const mw = corsMiddleware();
29
+ const ammo = makeAmmo();
30
+ const next = vi.fn();
31
+ await mw(ammo, next);
32
+ expect(ammo.res._headers['access-control-allow-origin']).toBe('*');
33
+ expect(next).toHaveBeenCalled();
34
+ });
35
+
36
+ it('should respond 204 and not call next for OPTIONS preflight', async () => {
37
+ const mw = corsMiddleware();
38
+ const ammo = makeAmmo('OPTIONS');
39
+ const next = vi.fn();
40
+ await mw(ammo, next);
41
+ expect(ammo.res._status).toBe(204);
42
+ expect(ammo.res._ended).toBe(true);
43
+ expect(next).not.toHaveBeenCalled();
44
+ });
45
+
46
+ it('should allow specific origin from array', async () => {
47
+ const mw = corsMiddleware({ origin: ['https://example.com'] });
48
+ const ammo = makeAmmo('GET', 'https://example.com');
49
+ const next = vi.fn();
50
+ await mw(ammo, next);
51
+ expect(ammo.res._headers['access-control-allow-origin']).toBe(
52
+ 'https://example.com',
53
+ );
54
+ });
55
+
56
+ it('should block origins not in array', async () => {
57
+ const mw = corsMiddleware({ origin: ['https://example.com'] });
58
+ const ammo = makeAmmo('GET', 'https://evil.com');
59
+ const next = vi.fn();
60
+ await mw(ammo, next);
61
+ expect(ammo.res._headers['access-control-allow-origin']).toBeUndefined();
62
+ });
63
+
64
+ it('should set Access-Control-Max-Age when maxAge provided', async () => {
65
+ const mw = corsMiddleware({ maxAge: 86400 });
66
+ const ammo = makeAmmo();
67
+ const next = vi.fn();
68
+ await mw(ammo, next);
69
+ expect(ammo.res._headers['access-control-max-age']).toBe('86400');
70
+ });
71
+
72
+ it('should set Access-Control-Allow-Credentials when credentials=true', async () => {
73
+ const mw = corsMiddleware({
74
+ credentials: true,
75
+ origin: 'https://example.com',
76
+ });
77
+ const ammo = makeAmmo('GET', 'https://example.com');
78
+ const next = vi.fn();
79
+ await mw(ammo, next);
80
+ expect(ammo.res._headers['access-control-allow-credentials']).toBe('true');
81
+ });
82
+ });
package/docs/README.md CHANGED
@@ -18,8 +18,7 @@ Welcome to the documentation for **Tejas** — a Node.js framework for building
18
18
 
19
19
  ### Features
20
20
 
21
- - [Database Integration](./database.md) — Redis and MongoDB connections with auto-install
22
- - [Rate Limiting](./rate-limiting.md) — Three algorithms, two storage backends, custom headers
21
+ - [Rate Limiting](./rate-limiting.md) — Three algorithms, memory or Redis storage, custom headers
23
22
  - [File Uploads](./file-uploads.md) — Single and multiple file handling with validation
24
23
 
25
24
  ### Tooling