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
@@ -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 };