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
@@ -1,220 +1,220 @@
1
- import { env } from 'tej-env';
2
- import TejError from '../error.js';
3
-
4
- async function parseDataBasedOnContentType(req) {
5
- // If no content-type header, return empty object (for requests without body)
6
- if (!req.headers['content-type']) {
7
- return {};
8
- }
9
-
10
- const contentType = req.headers['content-type'].toLowerCase();
11
-
12
- // Check if content type is JSON
13
- if (contentType === 'application/json') {
14
- return await parseJSONRequestBody(req);
15
- }
16
-
17
- // Check if content type is URL encoded
18
- if (contentType === 'application/x-www-form-urlencoded') {
19
- return await parseUrlEncodedData(req);
20
- }
21
-
22
- // Check if content type is multipart form data
23
- if (contentType.startsWith('multipart/form-data')) {
24
- return await parseFormData(req);
25
- }
26
-
27
- throw new BodyParserError(`Unsupported content type: ${contentType}`, 415);
28
- }
29
-
30
- function parseJSONRequestBody(req) {
31
- return new Promise((resolve, reject) => {
32
- let body = '';
33
- let size = 0;
34
- const maxSize = env('BODY_MAX_SIZE');
35
- const timeout = setTimeout(() => {
36
- reject(new BodyParserError('Request timeout', 408));
37
- }, env('BODY_TIMEOUT'));
38
-
39
- req.on('data', (chunk) => {
40
- size += chunk.length;
41
- if (size > maxSize) {
42
- clearTimeout(timeout);
43
- reject(new BodyParserError('Request entity too large', 413));
44
- req.destroy();
45
- return;
46
- }
47
- body += chunk.toString();
48
- });
49
-
50
- req.on('error', (err) => {
51
- clearTimeout(timeout);
52
- reject(new BodyParserError(`Request error: ${err.message}`, 400));
53
- });
54
-
55
- req.on('end', () => {
56
- clearTimeout(timeout);
57
- try {
58
- if (!body) {
59
- resolve({});
60
- return;
61
- }
62
- const jsonData = JSON.parse(body);
63
- if (typeof jsonData !== 'object') {
64
- throw new TejError(400, 'Invalid JSON structure');
65
- }
66
- resolve(jsonData);
67
- } catch (err) {
68
- reject(new TejError(400, `Invalid JSON: ${err.message}`));
69
- }
70
- });
71
- });
72
- }
73
-
74
- function parseUrlEncodedData(req) {
75
- return new Promise((resolve, reject) => {
76
- let body = '';
77
- let size = 0;
78
- const maxSize = env('BODY_MAX_SIZE');
79
- const timeout = setTimeout(() => {
80
- reject(new BodyParserError('Request timeout', 408));
81
- }, env('BODY_TIMEOUT'));
82
-
83
- req.on('data', (chunk) => {
84
- size += chunk.length;
85
- if (size > maxSize) {
86
- clearTimeout(timeout);
87
- reject(new BodyParserError('Request entity too large', 413));
88
- req.destroy();
89
- return;
90
- }
91
- body += chunk.toString();
92
- });
93
-
94
- req.on('error', (err) => {
95
- clearTimeout(timeout);
96
- reject(new BodyParserError(`Request error: ${err.message}`, 400));
97
- });
98
-
99
- req.on('end', () => {
100
- clearTimeout(timeout);
101
- try {
102
- if (!body) {
103
- resolve({});
104
- return;
105
- }
106
- const data = new URLSearchParams(body);
107
- const parsedData = Object.fromEntries(data);
108
- resolve(parsedData);
109
- } catch (err) {
110
- reject(new BodyParserError('Invalid URL encoded data', 400));
111
- }
112
- });
113
- });
114
- }
115
-
116
- function parseFormData(req) {
117
- return new Promise((resolve, reject) => {
118
- let body = '';
119
- let size = 0;
120
- const maxSize = env('BODY_MAX_SIZE');
121
- const timeout = setTimeout(() => {
122
- reject(new BodyParserError('Request timeout', 408));
123
- }, env('BODY_TIMEOUT'));
124
-
125
- req.on('data', (chunk) => {
126
- size += chunk.length;
127
- if (size > maxSize) {
128
- clearTimeout(timeout);
129
- reject(new BodyParserError('Request entity too large', 413));
130
- req.destroy();
131
- return;
132
- }
133
- body += chunk.toString();
134
- });
135
-
136
- req.on('error', (err) => {
137
- clearTimeout(timeout);
138
- reject(new BodyParserError(`Request error: ${err.message}`, 400));
139
- });
140
-
141
- req.on('end', () => {
142
- clearTimeout(timeout);
143
- try {
144
- if (!body.trim()) {
145
- resolve([]);
146
- return;
147
- }
148
-
149
- const contentType = req.headers['content-type'];
150
- const boundaryMatch = contentType.match(
151
- /boundary=(?:"([^"]+)"|([^;]+))/i,
152
- );
153
-
154
- if (!boundaryMatch) {
155
- throw new TejError(400, 'Missing boundary in content-type');
156
- }
157
-
158
- const boundary = '--' + (boundaryMatch[1] || boundaryMatch[2]);
159
- const parts = body
160
- .split(boundary)
161
- .filter((part) => part.trim() !== '' && part.trim() !== '--');
162
-
163
- const parsedData = parts.map((part) => {
164
- const [headerString, ...contentParts] = part.split('\r\n\r\n');
165
- if (!headerString || contentParts.length === 0) {
166
- throw new TejError(400, 'Malformed multipart part');
167
- }
168
-
169
- const headers = {};
170
- const headerLines = headerString.trim().split('\r\n');
171
-
172
- headerLines.forEach((line) => {
173
- const [key, ...valueParts] = line.split(': ');
174
- if (!key || valueParts.length === 0) {
175
- throw new TejError(400, 'Malformed header');
176
- }
177
- headers[key.toLowerCase()] = valueParts.join(': ');
178
- });
179
-
180
- const value = contentParts.join('\r\n\r\n').replace(/\r\n$/, '');
181
-
182
- // Parse content-disposition
183
- const disposition = headers['content-disposition'];
184
- if (!disposition) {
185
- throw new TejError(400, 'Missing content-disposition header');
186
- }
187
-
188
- const nameMatch = disposition.match(/name="([^"]+)"/);
189
- const filename = disposition.match(/filename="([^"]+)"/);
190
-
191
- return {
192
- name: nameMatch ? nameMatch[1] : undefined,
193
- filename: filename ? filename[1] : undefined,
194
- headers,
195
- value,
196
- };
197
- });
198
-
199
- resolve(parsedData);
200
- } catch (err) {
201
- reject(
202
- new BodyParserError(
203
- `Invalid multipart form data: ${err.message}`,
204
- 400,
205
- ),
206
- );
207
- }
208
- });
209
- });
210
- }
211
-
212
- class BodyParserError extends TejError {
213
- constructor(message, statusCode = 400) {
214
- super(statusCode, message);
215
- this.name = 'BodyParserError';
216
- }
217
- }
218
-
219
- export { BodyParserError };
220
- export default parseDataBasedOnContentType;
1
+ import { env } from 'tej-env';
2
+ import TejError from '../error.js';
3
+
4
+ async function parseDataBasedOnContentType(req) {
5
+ // If no content-type header, return empty object (for requests without body)
6
+ if (!req.headers['content-type']) {
7
+ return {};
8
+ }
9
+
10
+ const contentType = req.headers['content-type'].toLowerCase();
11
+
12
+ // Check if content type is JSON
13
+ if (contentType === 'application/json') {
14
+ return await parseJSONRequestBody(req);
15
+ }
16
+
17
+ // Check if content type is URL encoded
18
+ if (contentType === 'application/x-www-form-urlencoded') {
19
+ return await parseUrlEncodedData(req);
20
+ }
21
+
22
+ // Check if content type is multipart form data
23
+ if (contentType.startsWith('multipart/form-data')) {
24
+ return await parseFormData(req);
25
+ }
26
+
27
+ throw new BodyParserError(`Unsupported content type: ${contentType}`, 415);
28
+ }
29
+
30
+ function parseJSONRequestBody(req) {
31
+ return new Promise((resolve, reject) => {
32
+ let body = '';
33
+ let size = 0;
34
+ const maxSize = env('BODY_MAX_SIZE');
35
+ const timeout = setTimeout(() => {
36
+ reject(new BodyParserError('Request timeout', 408));
37
+ }, env('BODY_TIMEOUT'));
38
+
39
+ req.on('data', (chunk) => {
40
+ size += chunk.length;
41
+ if (size > maxSize) {
42
+ clearTimeout(timeout);
43
+ reject(new BodyParserError('Request entity too large', 413));
44
+ req.destroy();
45
+ return;
46
+ }
47
+ body += chunk.toString();
48
+ });
49
+
50
+ req.on('error', (err) => {
51
+ clearTimeout(timeout);
52
+ reject(new BodyParserError(`Request error: ${err.message}`, 400));
53
+ });
54
+
55
+ req.on('end', () => {
56
+ clearTimeout(timeout);
57
+ try {
58
+ if (!body) {
59
+ resolve({});
60
+ return;
61
+ }
62
+ const jsonData = JSON.parse(body);
63
+ if (typeof jsonData !== 'object') {
64
+ throw new TejError(400, 'Invalid JSON structure');
65
+ }
66
+ resolve(jsonData);
67
+ } catch (err) {
68
+ reject(new TejError(400, `Invalid JSON: ${err.message}`));
69
+ }
70
+ });
71
+ });
72
+ }
73
+
74
+ function parseUrlEncodedData(req) {
75
+ return new Promise((resolve, reject) => {
76
+ let body = '';
77
+ let size = 0;
78
+ const maxSize = env('BODY_MAX_SIZE');
79
+ const timeout = setTimeout(() => {
80
+ reject(new BodyParserError('Request timeout', 408));
81
+ }, env('BODY_TIMEOUT'));
82
+
83
+ req.on('data', (chunk) => {
84
+ size += chunk.length;
85
+ if (size > maxSize) {
86
+ clearTimeout(timeout);
87
+ reject(new BodyParserError('Request entity too large', 413));
88
+ req.destroy();
89
+ return;
90
+ }
91
+ body += chunk.toString();
92
+ });
93
+
94
+ req.on('error', (err) => {
95
+ clearTimeout(timeout);
96
+ reject(new BodyParserError(`Request error: ${err.message}`, 400));
97
+ });
98
+
99
+ req.on('end', () => {
100
+ clearTimeout(timeout);
101
+ try {
102
+ if (!body) {
103
+ resolve({});
104
+ return;
105
+ }
106
+ const data = new URLSearchParams(body);
107
+ const parsedData = Object.fromEntries(data);
108
+ resolve(parsedData);
109
+ } catch (err) {
110
+ reject(new BodyParserError('Invalid URL encoded data', 400));
111
+ }
112
+ });
113
+ });
114
+ }
115
+
116
+ function parseFormData(req) {
117
+ return new Promise((resolve, reject) => {
118
+ let body = '';
119
+ let size = 0;
120
+ const maxSize = env('BODY_MAX_SIZE');
121
+ const timeout = setTimeout(() => {
122
+ reject(new BodyParserError('Request timeout', 408));
123
+ }, env('BODY_TIMEOUT'));
124
+
125
+ req.on('data', (chunk) => {
126
+ size += chunk.length;
127
+ if (size > maxSize) {
128
+ clearTimeout(timeout);
129
+ reject(new BodyParserError('Request entity too large', 413));
130
+ req.destroy();
131
+ return;
132
+ }
133
+ body += chunk.toString();
134
+ });
135
+
136
+ req.on('error', (err) => {
137
+ clearTimeout(timeout);
138
+ reject(new BodyParserError(`Request error: ${err.message}`, 400));
139
+ });
140
+
141
+ req.on('end', () => {
142
+ clearTimeout(timeout);
143
+ try {
144
+ if (!body.trim()) {
145
+ resolve([]);
146
+ return;
147
+ }
148
+
149
+ const contentType = req.headers['content-type'];
150
+ const boundaryMatch = contentType.match(
151
+ /boundary=(?:"([^"]+)"|([^;]+))/i,
152
+ );
153
+
154
+ if (!boundaryMatch) {
155
+ throw new TejError(400, 'Missing boundary in content-type');
156
+ }
157
+
158
+ const boundary = '--' + (boundaryMatch[1] || boundaryMatch[2]);
159
+ const parts = body
160
+ .split(boundary)
161
+ .filter((part) => part.trim() !== '' && part.trim() !== '--');
162
+
163
+ const parsedData = parts.map((part) => {
164
+ const [headerString, ...contentParts] = part.split('\r\n\r\n');
165
+ if (!headerString || contentParts.length === 0) {
166
+ throw new TejError(400, 'Malformed multipart part');
167
+ }
168
+
169
+ const headers = {};
170
+ const headerLines = headerString.trim().split('\r\n');
171
+
172
+ headerLines.forEach((line) => {
173
+ const [key, ...valueParts] = line.split(': ');
174
+ if (!key || valueParts.length === 0) {
175
+ throw new TejError(400, 'Malformed header');
176
+ }
177
+ headers[key.toLowerCase()] = valueParts.join(': ');
178
+ });
179
+
180
+ const value = contentParts.join('\r\n\r\n').replace(/\r\n$/, '');
181
+
182
+ // Parse content-disposition
183
+ const disposition = headers['content-disposition'];
184
+ if (!disposition) {
185
+ throw new TejError(400, 'Missing content-disposition header');
186
+ }
187
+
188
+ const nameMatch = disposition.match(/name="([^"]+)"/);
189
+ const filename = disposition.match(/filename="([^"]+)"/);
190
+
191
+ return {
192
+ name: nameMatch ? nameMatch[1] : undefined,
193
+ filename: filename ? filename[1] : undefined,
194
+ headers,
195
+ value,
196
+ };
197
+ });
198
+
199
+ resolve(parsedData);
200
+ } catch (err) {
201
+ reject(
202
+ new BodyParserError(
203
+ `Invalid multipart form data: ${err.message}`,
204
+ 400,
205
+ ),
206
+ );
207
+ }
208
+ });
209
+ });
210
+ }
211
+
212
+ class BodyParserError extends TejError {
213
+ constructor(message, statusCode = 400) {
214
+ super(statusCode, message);
215
+ this.name = 'BodyParserError';
216
+ }
217
+ }
218
+
219
+ export { BodyParserError };
220
+ export default parseDataBasedOnContentType;
@@ -1,103 +1,103 @@
1
- import status from 'statuses';
2
-
3
- const formattedData = (data) => {
4
- if (data === null || data === undefined) return '';
5
-
6
- if (typeof data === 'object') {
7
- try {
8
- return JSON.stringify(data);
9
- } catch (error) {
10
- return String(data);
11
- }
12
- }
13
-
14
- if (typeof data === 'string') return data;
15
- if (typeof data === 'number') return status[data] || String(data);
16
-
17
- return String(data);
18
- };
19
-
20
- const statusAndData = (args) => {
21
- // Handle no arguments
22
- if (!args || args.length === 0) {
23
- return {
24
- statusCode: 204,
25
- data: status(204),
26
- contentType: 'text/plain',
27
- };
28
- }
29
-
30
- // Handle single argument
31
- if (args.length === 1) {
32
- const arg = args[0];
33
-
34
- // If it's a number, treat as status code
35
- if (typeof arg === 'number') {
36
- return {
37
- statusCode: arg,
38
- data: status(arg) || String(arg),
39
- contentType: 'text/plain',
40
- };
41
- }
42
-
43
- // Otherwise treat as data
44
- return {
45
- statusCode: 200,
46
- data: formattedData(arg),
47
- contentType: contentType(arg),
48
- };
49
- }
50
-
51
- // Handle multiple arguments
52
- let statusCode = 200;
53
- let data = args[0];
54
-
55
- // If first argument is a number, treat as status code
56
- if (typeof args[0] === 'number') {
57
- statusCode = args[0];
58
- data = args[1];
59
- } else {
60
- // If first argument is not a number, check if second is
61
- if (typeof args[1] === 'number') {
62
- statusCode = args[1];
63
- }
64
- }
65
-
66
- // If data is undefined, use status message
67
- if (data === undefined) {
68
- data = status[statusCode] || String(statusCode);
69
- }
70
-
71
- // If third argument is provided, it's the content type
72
- const customContentType = args.length > 2 ? args[2] : null;
73
-
74
- return {
75
- statusCode,
76
- data: formattedData(data),
77
- contentType: customContentType || contentType(data),
78
- };
79
- };
80
-
81
- const contentType = (data) => {
82
- if (data === null || data === undefined) return 'text/plain';
83
-
84
- switch (typeof data) {
85
- case 'object':
86
- return 'application/json';
87
- case 'string':
88
- // Check if string is HTML
89
- if (
90
- data.trim().toLowerCase().startsWith('<!DOCTYPE') ||
91
- data.trim().toLowerCase().startsWith('<html')
92
- ) {
93
- return 'text/html';
94
- }
95
- return 'text/plain';
96
- case 'number':
97
- return 'text/plain';
98
- default:
99
- return 'text/plain';
100
- }
101
- };
102
-
103
- export { statusAndData, contentType, formattedData };
1
+ import status from 'statuses';
2
+
3
+ const formattedData = (data) => {
4
+ if (data === null || data === undefined) return '';
5
+
6
+ if (typeof data === 'object') {
7
+ try {
8
+ return JSON.stringify(data);
9
+ } catch (error) {
10
+ return String(data);
11
+ }
12
+ }
13
+
14
+ if (typeof data === 'string') return data;
15
+ if (typeof data === 'number') return status[data] || String(data);
16
+
17
+ return String(data);
18
+ };
19
+
20
+ const statusAndData = (args) => {
21
+ // Handle no arguments
22
+ if (!args || args.length === 0) {
23
+ return {
24
+ statusCode: 204,
25
+ data: status(204),
26
+ contentType: 'text/plain',
27
+ };
28
+ }
29
+
30
+ // Handle single argument
31
+ if (args.length === 1) {
32
+ const arg = args[0];
33
+
34
+ // If it's a number, treat as status code
35
+ if (typeof arg === 'number') {
36
+ return {
37
+ statusCode: arg,
38
+ data: status(arg) || String(arg),
39
+ contentType: 'text/plain',
40
+ };
41
+ }
42
+
43
+ // Otherwise treat as data
44
+ return {
45
+ statusCode: 200,
46
+ data: formattedData(arg),
47
+ contentType: contentType(arg),
48
+ };
49
+ }
50
+
51
+ // Handle multiple arguments
52
+ let statusCode = 200;
53
+ let data = args[0];
54
+
55
+ // If first argument is a number, treat as status code
56
+ if (typeof args[0] === 'number') {
57
+ statusCode = args[0];
58
+ data = args[1];
59
+ } else {
60
+ // If first argument is not a number, check if second is
61
+ if (typeof args[1] === 'number') {
62
+ statusCode = args[1];
63
+ }
64
+ }
65
+
66
+ // If data is undefined, use status message
67
+ if (data === undefined) {
68
+ data = status[statusCode] || String(statusCode);
69
+ }
70
+
71
+ // If third argument is provided, it's the content type
72
+ const customContentType = args.length > 2 ? args[2] : null;
73
+
74
+ return {
75
+ statusCode,
76
+ data: formattedData(data),
77
+ contentType: customContentType || contentType(data),
78
+ };
79
+ };
80
+
81
+ const contentType = (data) => {
82
+ if (data === null || data === undefined) return 'text/plain';
83
+
84
+ switch (typeof data) {
85
+ case 'object':
86
+ return 'application/json';
87
+ case 'string':
88
+ // Check if string is HTML
89
+ if (
90
+ data.trim().toLowerCase().startsWith('<!DOCTYPE') ||
91
+ data.trim().toLowerCase().startsWith('<html')
92
+ ) {
93
+ return 'text/html';
94
+ }
95
+ return 'text/plain';
96
+ case 'number':
97
+ return 'text/plain';
98
+ default:
99
+ return 'text/plain';
100
+ }
101
+ };
102
+
103
+ export { statusAndData, contentType, formattedData };