te.js 2.1.0 → 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 (66) 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/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 -490
  25. package/docs/auto-docs.md +216 -216
  26. package/docs/cli.md +152 -152
  27. package/docs/configuration.md +275 -275
  28. package/docs/database.md +390 -390
  29. package/docs/error-handling.md +438 -438
  30. package/docs/file-uploads.md +333 -333
  31. package/docs/getting-started.md +214 -214
  32. package/docs/middleware.md +355 -355
  33. package/docs/rate-limiting.md +393 -393
  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 -415
  48. package/server/endpoint.js +97 -74
  49. package/server/error.js +9 -9
  50. package/server/errors/code-context.js +125 -125
  51. package/server/errors/llm-error-service.js +140 -140
  52. package/server/files/helper.js +33 -33
  53. package/server/files/uploader.js +143 -143
  54. package/server/handler.js +158 -119
  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 -402
  61. package/utils/auto-register.js +17 -17
  62. package/utils/configuration.js +64 -64
  63. package/utils/errors-llm-config.js +84 -84
  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
@@ -1,244 +1,244 @@
1
- /**
2
- * Pure OpenAPI 3.0 schema and operation builders for auto-documentation.
3
- * No LLM, file I/O, or registry dependencies.
4
- */
5
-
6
- import { ALL_METHODS } from '../analysis/handler-analyzer.js';
7
- import { METHOD_KEYS } from '../constants.js';
8
-
9
- /**
10
- * Returns true if the object is method-keyed metadata (keys are HTTP methods, values are { summary?, description?, request?, response? }).
11
- * @param {object} obj - Parsed LLM response
12
- * @returns {boolean}
13
- */
14
- export function isMethodKeyed(obj) {
15
- if (!obj || typeof obj !== 'object') return false;
16
- for (const key of Object.keys(obj)) {
17
- if (METHOD_KEYS.has(key.toLowerCase())) {
18
- const val = obj[key];
19
- if (val && typeof val === 'object' && (val.summary != null || val.response != null)) return true;
20
- }
21
- }
22
- return false;
23
- }
24
-
25
- /**
26
- * Convert te.js path pattern to OpenAPI path (e.g. /users/:id -> /users/{id}).
27
- * @param {string} path - Route path possibly containing :param segments
28
- * @returns {string}
29
- */
30
- export function toOpenAPIPath(path) {
31
- if (!path || typeof path !== 'string') return '/';
32
- return path.replace(/:([^/]+)/g, '{$1}');
33
- }
34
-
35
- /**
36
- * Extract path parameter definitions from a te.js path for OpenAPI.
37
- * @param {string} path - Route path (e.g. /users/:id)
38
- * @returns {Array<{ name: string, in: string, required: boolean, schema: object }>}
39
- */
40
- export function getPathParameters(path) {
41
- if (!path || typeof path !== 'string') return [];
42
- const params = [];
43
- const segmentRegex = /:([^/]+)/g;
44
- let match;
45
- while ((match = segmentRegex.exec(path)) !== null) {
46
- params.push({
47
- name: match[1],
48
- in: 'path',
49
- required: true,
50
- description: `Path parameter: ${match[1]}`,
51
- schema: { type: 'string' },
52
- });
53
- }
54
- return params;
55
- }
56
-
57
- /**
58
- * Build OpenAPI query parameter list from request.query metadata.
59
- * @param {object} queryMeta - e.g. { limit: { type: 'integer', required: false }, q: { type: 'string', required: true } }
60
- * @returns {Array<{ name: string, in: string, required: boolean, description?: string, schema: object }>}
61
- */
62
- export function getQueryParameters(queryMeta) {
63
- if (!queryMeta || typeof queryMeta !== 'object') return [];
64
- const params = [];
65
- for (const [name, spec] of Object.entries(queryMeta)) {
66
- if (!spec || typeof spec !== 'object' || !spec.type) continue;
67
- params.push({
68
- name,
69
- in: 'query',
70
- required: spec.required === true || spec.required === 'true',
71
- ...(spec.description && { description: spec.description }),
72
- schema: { type: spec.type, ...(spec.format && { format: spec.format }) },
73
- });
74
- }
75
- return params;
76
- }
77
-
78
- /**
79
- * Convert simple metadata schema (field -> { type, description? }) to OpenAPI schema.
80
- * @param {object} meta - e.g. { name: { type: 'string' }, email: { type: 'string' } }
81
- * @returns {object} OpenAPI schema with properties
82
- */
83
- export function buildSchemaFromMetadata(meta) {
84
- if (!meta || typeof meta !== 'object') return {};
85
- const properties = {};
86
- const required = [];
87
- for (const [key, value] of Object.entries(meta)) {
88
- if (value && typeof value === 'object' && value.type) {
89
- properties[key] = {
90
- type: value.type,
91
- ...(value.description && { description: value.description }),
92
- ...(value.format && { format: value.format }),
93
- };
94
- if (value.required === true || value.required === 'true') required.push(key);
95
- }
96
- }
97
- if (Object.keys(properties).length === 0) return {};
98
- return {
99
- type: 'object',
100
- properties,
101
- ...(required.length > 0 && { required }),
102
- };
103
- }
104
-
105
- /**
106
- * Build request body schema for an operation from metadata or empty.
107
- * @param {object} requestMeta - metadata.request (body schema)
108
- * @returns {object|undefined} OpenAPI requestBody or undefined
109
- */
110
- export function buildRequestBody(requestMeta) {
111
- if (!requestMeta?.body || typeof requestMeta.body !== 'object') return undefined;
112
- const schema = buildSchemaFromMetadata(requestMeta.body);
113
- if (!schema || Object.keys(schema).length === 0) return undefined;
114
- return {
115
- required: true,
116
- content: {
117
- 'application/json': {
118
- schema,
119
- },
120
- },
121
- };
122
- }
123
-
124
- /**
125
- * Build response object for an operation from metadata.
126
- * @param {object} responseMeta - metadata.response (e.g. { 200: { description, schema? }, 201: { ... } })
127
- * @returns {object} OpenAPI responses
128
- */
129
- export function buildResponses(responseMeta) {
130
- const responses = {};
131
- if (!responseMeta || typeof responseMeta !== 'object') {
132
- responses['200'] = { description: 'Success' };
133
- return responses;
134
- }
135
- for (const [code, spec] of Object.entries(responseMeta)) {
136
- if (!spec || typeof spec !== 'object') continue;
137
- responses[String(code)] = {
138
- description: spec.description || `Response ${code}`,
139
- ...(spec.schema && {
140
- content: {
141
- 'application/json': {
142
- schema: typeof spec.schema === 'object' && spec.schema.type
143
- ? spec.schema
144
- : { type: 'object' },
145
- },
146
- },
147
- }),
148
- };
149
- }
150
- if (Object.keys(responses).length === 0) {
151
- responses['200'] = { description: 'Success' };
152
- }
153
- return responses;
154
- }
155
-
156
- /**
157
- * Returns true when the endpoint accepts all standard HTTP methods (method-agnostic).
158
- * @param {string[]} methods
159
- * @returns {boolean}
160
- */
161
- export function isMethodAgnostic(methods) {
162
- if (!Array.isArray(methods) || methods.length !== ALL_METHODS.length) return false;
163
- const set = new Set(methods.map((m) => m.toUpperCase()));
164
- return ALL_METHODS.every((m) => set.has(m));
165
- }
166
-
167
- /**
168
- * Build a single OpenAPI operation for one HTTP method.
169
- * @param {string} method - HTTP method (GET, POST, etc.)
170
- * @param {object} meta - Merged metadata (explicit + LLM-enhanced): summary, description?, request?, response?
171
- * @param {Array} pathParams - Path parameters for this path
172
- * @param {object} [options] - { methodAgnostic?: boolean } - when true, description notes all accepted methods
173
- * @returns {object} OpenAPI operation object
174
- */
175
- export function buildOperation(method, meta, pathParams, options = {}) {
176
- const { methodAgnostic = false } = options;
177
- let description = meta.description || '';
178
- if (methodAgnostic) {
179
- const methodList = ALL_METHODS.join(', ');
180
- description = description
181
- ? `${description}\n\nAccepts any HTTP method: ${methodList}.`
182
- : `Accepts any HTTP method: ${methodList}.`;
183
- }
184
- const queryParams = getQueryParameters(meta.request?.query);
185
- const allParams = [...pathParams, ...queryParams];
186
- const op = {
187
- summary: meta.summary || '',
188
- ...(description && { description }),
189
- parameters: allParams.length > 0 ? allParams : undefined,
190
- };
191
- const methodUpper = method.toUpperCase();
192
- const body = buildRequestBody(meta.request);
193
- if (body && (methodAgnostic || (methodUpper !== 'GET' && methodUpper !== 'HEAD'))) {
194
- op.requestBody = body;
195
- }
196
- op.responses = buildResponses(meta.response);
197
- return op;
198
- }
199
-
200
- /**
201
- * Merge explicit endpoint metadata with LLM-enhanced result.
202
- * @param {object} explicit - From endpoint.getMetadata()
203
- * @param {object} enhanced - From llm.enhanceEndpointDocs() or per-method enhancer
204
- * @param {object} [options] - { preferEnhanced?: boolean } - when true (level 2), LLM response wins over explicit
205
- * @returns {object}
206
- */
207
- export function mergeMetadata(explicit, enhanced, options = {}) {
208
- const preferEnhanced = options.preferEnhanced === true;
209
- const summary = preferEnhanced
210
- ? (enhanced?.summary ?? explicit?.summary ?? '')
211
- : (explicit?.summary ?? enhanced?.summary ?? '');
212
- const description = preferEnhanced
213
- ? (enhanced?.description ?? explicit?.description ?? '')
214
- : (explicit?.description ?? enhanced?.description ?? '');
215
- const request = preferEnhanced
216
- ? (enhanced?.request ?? explicit?.request)
217
- : (explicit?.request ?? enhanced?.request);
218
- const response = preferEnhanced
219
- ? (enhanced?.response ?? explicit?.response)
220
- : (explicit?.response ?? enhanced?.response);
221
- return {
222
- summary: summary || 'Endpoint',
223
- description: description || undefined,
224
- ...(request && { request }),
225
- ...(response && { response }),
226
- };
227
- }
228
-
229
- /**
230
- * Merge method-keyed metadata for a method-agnostic endpoint into a single meta (preferred order).
231
- * @param {Map<string,object>} metaByMethod
232
- * @param {string[]} methods
233
- * @param {object} fallbackMeta
234
- * @returns {object}
235
- */
236
- export function mergeMethodAgnosticMeta(metaByMethod, methods, fallbackMeta) {
237
- const preferredOrder = ['post', 'put', 'patch', 'get', 'delete', 'head', 'options'];
238
- for (const k of preferredOrder) {
239
- const m = metaByMethod.get(k);
240
- if (m && (m.summary || m.description)) return m;
241
- }
242
- const first = metaByMethod.values().next().value;
243
- return first || fallbackMeta;
244
- }
1
+ /**
2
+ * Pure OpenAPI 3.0 schema and operation builders for auto-documentation.
3
+ * No LLM, file I/O, or registry dependencies.
4
+ */
5
+
6
+ import { ALL_METHODS } from '../analysis/handler-analyzer.js';
7
+ import { METHOD_KEYS } from '../constants.js';
8
+
9
+ /**
10
+ * Returns true if the object is method-keyed metadata (keys are HTTP methods, values are { summary?, description?, request?, response? }).
11
+ * @param {object} obj - Parsed LLM response
12
+ * @returns {boolean}
13
+ */
14
+ export function isMethodKeyed(obj) {
15
+ if (!obj || typeof obj !== 'object') return false;
16
+ for (const key of Object.keys(obj)) {
17
+ if (METHOD_KEYS.has(key.toLowerCase())) {
18
+ const val = obj[key];
19
+ if (val && typeof val === 'object' && (val.summary != null || val.response != null)) return true;
20
+ }
21
+ }
22
+ return false;
23
+ }
24
+
25
+ /**
26
+ * Convert te.js path pattern to OpenAPI path (e.g. /users/:id -> /users/{id}).
27
+ * @param {string} path - Route path possibly containing :param segments
28
+ * @returns {string}
29
+ */
30
+ export function toOpenAPIPath(path) {
31
+ if (!path || typeof path !== 'string') return '/';
32
+ return path.replace(/:([^/]+)/g, '{$1}');
33
+ }
34
+
35
+ /**
36
+ * Extract path parameter definitions from a te.js path for OpenAPI.
37
+ * @param {string} path - Route path (e.g. /users/:id)
38
+ * @returns {Array<{ name: string, in: string, required: boolean, schema: object }>}
39
+ */
40
+ export function getPathParameters(path) {
41
+ if (!path || typeof path !== 'string') return [];
42
+ const params = [];
43
+ const segmentRegex = /:([^/]+)/g;
44
+ let match;
45
+ while ((match = segmentRegex.exec(path)) !== null) {
46
+ params.push({
47
+ name: match[1],
48
+ in: 'path',
49
+ required: true,
50
+ description: `Path parameter: ${match[1]}`,
51
+ schema: { type: 'string' },
52
+ });
53
+ }
54
+ return params;
55
+ }
56
+
57
+ /**
58
+ * Build OpenAPI query parameter list from request.query metadata.
59
+ * @param {object} queryMeta - e.g. { limit: { type: 'integer', required: false }, q: { type: 'string', required: true } }
60
+ * @returns {Array<{ name: string, in: string, required: boolean, description?: string, schema: object }>}
61
+ */
62
+ export function getQueryParameters(queryMeta) {
63
+ if (!queryMeta || typeof queryMeta !== 'object') return [];
64
+ const params = [];
65
+ for (const [name, spec] of Object.entries(queryMeta)) {
66
+ if (!spec || typeof spec !== 'object' || !spec.type) continue;
67
+ params.push({
68
+ name,
69
+ in: 'query',
70
+ required: spec.required === true || spec.required === 'true',
71
+ ...(spec.description && { description: spec.description }),
72
+ schema: { type: spec.type, ...(spec.format && { format: spec.format }) },
73
+ });
74
+ }
75
+ return params;
76
+ }
77
+
78
+ /**
79
+ * Convert simple metadata schema (field -> { type, description? }) to OpenAPI schema.
80
+ * @param {object} meta - e.g. { name: { type: 'string' }, email: { type: 'string' } }
81
+ * @returns {object} OpenAPI schema with properties
82
+ */
83
+ export function buildSchemaFromMetadata(meta) {
84
+ if (!meta || typeof meta !== 'object') return {};
85
+ const properties = {};
86
+ const required = [];
87
+ for (const [key, value] of Object.entries(meta)) {
88
+ if (value && typeof value === 'object' && value.type) {
89
+ properties[key] = {
90
+ type: value.type,
91
+ ...(value.description && { description: value.description }),
92
+ ...(value.format && { format: value.format }),
93
+ };
94
+ if (value.required === true || value.required === 'true') required.push(key);
95
+ }
96
+ }
97
+ if (Object.keys(properties).length === 0) return {};
98
+ return {
99
+ type: 'object',
100
+ properties,
101
+ ...(required.length > 0 && { required }),
102
+ };
103
+ }
104
+
105
+ /**
106
+ * Build request body schema for an operation from metadata or empty.
107
+ * @param {object} requestMeta - metadata.request (body schema)
108
+ * @returns {object|undefined} OpenAPI requestBody or undefined
109
+ */
110
+ export function buildRequestBody(requestMeta) {
111
+ if (!requestMeta?.body || typeof requestMeta.body !== 'object') return undefined;
112
+ const schema = buildSchemaFromMetadata(requestMeta.body);
113
+ if (!schema || Object.keys(schema).length === 0) return undefined;
114
+ return {
115
+ required: true,
116
+ content: {
117
+ 'application/json': {
118
+ schema,
119
+ },
120
+ },
121
+ };
122
+ }
123
+
124
+ /**
125
+ * Build response object for an operation from metadata.
126
+ * @param {object} responseMeta - metadata.response (e.g. { 200: { description, schema? }, 201: { ... } })
127
+ * @returns {object} OpenAPI responses
128
+ */
129
+ export function buildResponses(responseMeta) {
130
+ const responses = {};
131
+ if (!responseMeta || typeof responseMeta !== 'object') {
132
+ responses['200'] = { description: 'Success' };
133
+ return responses;
134
+ }
135
+ for (const [code, spec] of Object.entries(responseMeta)) {
136
+ if (!spec || typeof spec !== 'object') continue;
137
+ responses[String(code)] = {
138
+ description: spec.description || `Response ${code}`,
139
+ ...(spec.schema && {
140
+ content: {
141
+ 'application/json': {
142
+ schema: typeof spec.schema === 'object' && spec.schema.type
143
+ ? spec.schema
144
+ : { type: 'object' },
145
+ },
146
+ },
147
+ }),
148
+ };
149
+ }
150
+ if (Object.keys(responses).length === 0) {
151
+ responses['200'] = { description: 'Success' };
152
+ }
153
+ return responses;
154
+ }
155
+
156
+ /**
157
+ * Returns true when the endpoint accepts all standard HTTP methods (method-agnostic).
158
+ * @param {string[]} methods
159
+ * @returns {boolean}
160
+ */
161
+ export function isMethodAgnostic(methods) {
162
+ if (!Array.isArray(methods) || methods.length !== ALL_METHODS.length) return false;
163
+ const set = new Set(methods.map((m) => m.toUpperCase()));
164
+ return ALL_METHODS.every((m) => set.has(m));
165
+ }
166
+
167
+ /**
168
+ * Build a single OpenAPI operation for one HTTP method.
169
+ * @param {string} method - HTTP method (GET, POST, etc.)
170
+ * @param {object} meta - Merged metadata (explicit + LLM-enhanced): summary, description?, request?, response?
171
+ * @param {Array} pathParams - Path parameters for this path
172
+ * @param {object} [options] - { methodAgnostic?: boolean } - when true, description notes all accepted methods
173
+ * @returns {object} OpenAPI operation object
174
+ */
175
+ export function buildOperation(method, meta, pathParams, options = {}) {
176
+ const { methodAgnostic = false } = options;
177
+ let description = meta.description || '';
178
+ if (methodAgnostic) {
179
+ const methodList = ALL_METHODS.join(', ');
180
+ description = description
181
+ ? `${description}\n\nAccepts any HTTP method: ${methodList}.`
182
+ : `Accepts any HTTP method: ${methodList}.`;
183
+ }
184
+ const queryParams = getQueryParameters(meta.request?.query);
185
+ const allParams = [...pathParams, ...queryParams];
186
+ const op = {
187
+ summary: meta.summary || '',
188
+ ...(description && { description }),
189
+ parameters: allParams.length > 0 ? allParams : undefined,
190
+ };
191
+ const methodUpper = method.toUpperCase();
192
+ const body = buildRequestBody(meta.request);
193
+ if (body && (methodAgnostic || (methodUpper !== 'GET' && methodUpper !== 'HEAD'))) {
194
+ op.requestBody = body;
195
+ }
196
+ op.responses = buildResponses(meta.response);
197
+ return op;
198
+ }
199
+
200
+ /**
201
+ * Merge explicit endpoint metadata with LLM-enhanced result.
202
+ * @param {object} explicit - From endpoint.getMetadata()
203
+ * @param {object} enhanced - From llm.enhanceEndpointDocs() or per-method enhancer
204
+ * @param {object} [options] - { preferEnhanced?: boolean } - when true (level 2), LLM response wins over explicit
205
+ * @returns {object}
206
+ */
207
+ export function mergeMetadata(explicit, enhanced, options = {}) {
208
+ const preferEnhanced = options.preferEnhanced === true;
209
+ const summary = preferEnhanced
210
+ ? (enhanced?.summary ?? explicit?.summary ?? '')
211
+ : (explicit?.summary ?? enhanced?.summary ?? '');
212
+ const description = preferEnhanced
213
+ ? (enhanced?.description ?? explicit?.description ?? '')
214
+ : (explicit?.description ?? enhanced?.description ?? '');
215
+ const request = preferEnhanced
216
+ ? (enhanced?.request ?? explicit?.request)
217
+ : (explicit?.request ?? enhanced?.request);
218
+ const response = preferEnhanced
219
+ ? (enhanced?.response ?? explicit?.response)
220
+ : (explicit?.response ?? enhanced?.response);
221
+ return {
222
+ summary: summary || 'Endpoint',
223
+ description: description || undefined,
224
+ ...(request && { request }),
225
+ ...(response && { response }),
226
+ };
227
+ }
228
+
229
+ /**
230
+ * Merge method-keyed metadata for a method-agnostic endpoint into a single meta (preferred order).
231
+ * @param {Map<string,object>} metaByMethod
232
+ * @param {string[]} methods
233
+ * @param {object} fallbackMeta
234
+ * @returns {object}
235
+ */
236
+ export function mergeMethodAgnosticMeta(metaByMethod, methods, fallbackMeta) {
237
+ const preferredOrder = ['post', 'put', 'patch', 'get', 'delete', 'head', 'options'];
238
+ for (const k of preferredOrder) {
239
+ const m = metaByMethod.get(k);
240
+ if (m && (m.summary || m.description)) return m;
241
+ }
242
+ const first = metaByMethod.values().next().value;
243
+ return first || fallbackMeta;
244
+ }