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.
- package/README.md +197 -196
- package/auto-docs/analysis/handler-analyzer.js +58 -58
- package/auto-docs/analysis/source-resolver.js +101 -101
- package/auto-docs/constants.js +37 -37
- package/auto-docs/docs-llm/index.js +7 -7
- package/auto-docs/docs-llm/prompts.js +222 -222
- package/auto-docs/docs-llm/provider.js +132 -132
- package/auto-docs/index.js +146 -146
- package/auto-docs/openapi/endpoint-processor.js +277 -277
- package/auto-docs/openapi/generator.js +107 -107
- package/auto-docs/openapi/level3.js +131 -131
- package/auto-docs/openapi/spec-builders.js +244 -244
- package/auto-docs/ui/docs-ui.js +186 -186
- package/auto-docs/utils/logger.js +17 -17
- package/auto-docs/utils/strip-usage.js +10 -10
- package/cli/docs-command.js +315 -315
- package/cli/fly-command.js +71 -71
- package/cli/index.js +56 -56
- package/database/index.js +165 -165
- package/database/mongodb.js +146 -146
- package/database/redis.js +201 -201
- package/docs/README.md +36 -36
- package/docs/ammo.md +362 -362
- package/docs/api-reference.md +490 -490
- package/docs/auto-docs.md +216 -216
- package/docs/cli.md +152 -152
- package/docs/configuration.md +275 -275
- package/docs/database.md +390 -390
- package/docs/error-handling.md +438 -438
- package/docs/file-uploads.md +333 -333
- package/docs/getting-started.md +214 -214
- package/docs/middleware.md +355 -355
- package/docs/rate-limiting.md +393 -393
- package/docs/routing.md +302 -302
- package/package.json +62 -62
- package/rate-limit/algorithms/fixed-window.js +141 -141
- package/rate-limit/algorithms/sliding-window.js +147 -147
- package/rate-limit/algorithms/token-bucket.js +115 -115
- package/rate-limit/base.js +165 -165
- package/rate-limit/index.js +147 -147
- package/rate-limit/storage/base.js +104 -104
- package/rate-limit/storage/memory.js +101 -101
- package/rate-limit/storage/redis.js +88 -88
- package/server/ammo/body-parser.js +220 -220
- package/server/ammo/dispatch-helper.js +103 -103
- package/server/ammo/enhancer.js +57 -57
- package/server/ammo.js +454 -415
- package/server/endpoint.js +97 -74
- package/server/error.js +9 -9
- package/server/errors/code-context.js +125 -125
- package/server/errors/llm-error-service.js +140 -140
- package/server/files/helper.js +33 -33
- package/server/files/uploader.js +143 -143
- package/server/handler.js +158 -119
- package/server/target.js +185 -175
- package/server/targets/middleware-validator.js +22 -22
- package/server/targets/path-validator.js +21 -21
- package/server/targets/registry.js +160 -160
- package/server/targets/shoot-validator.js +21 -21
- package/te.js +428 -402
- package/utils/auto-register.js +17 -17
- package/utils/configuration.js +64 -64
- package/utils/errors-llm-config.js +84 -84
- package/utils/request-logger.js +43 -43
- package/utils/status-codes.js +82 -82
- 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
|
+
}
|