@moostjs/swagger 0.5.32 → 0.6.0
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 +79 -0
- package/dist/index.cjs +767 -333
- package/dist/index.d.ts +289 -20
- package/dist/index.mjs +761 -335
- package/package.json +32 -32
package/dist/index.cjs
CHANGED
|
@@ -23,42 +23,56 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
23
23
|
//#endregion
|
|
24
24
|
const moost = __toESM(require("moost"));
|
|
25
25
|
const __moostjs_event_http = __toESM(require("@moostjs/event-http"));
|
|
26
|
-
const __moostjs_zod = __toESM(require("@moostjs/zod"));
|
|
27
26
|
const __wooksjs_event_http = __toESM(require("@wooksjs/event-http"));
|
|
28
27
|
const __wooksjs_http_static = __toESM(require("@wooksjs/http-static"));
|
|
29
28
|
const path = __toESM(require("path"));
|
|
30
29
|
const swagger_ui_dist = __toESM(require("swagger-ui-dist"));
|
|
31
|
-
const zod_parser = __toESM(require("zod-parser"));
|
|
32
30
|
|
|
33
31
|
//#region packages/swagger/src/swagger.mate.ts
|
|
34
|
-
function getSwaggerMate() {
|
|
32
|
+
/** Returns the shared `Mate` instance extended with Swagger/OpenAPI metadata fields. */ function getSwaggerMate() {
|
|
35
33
|
return (0, moost.getMoostMate)();
|
|
36
34
|
}
|
|
37
35
|
|
|
38
36
|
//#endregion
|
|
39
37
|
//#region packages/swagger/src/decorators.ts
|
|
40
|
-
const SwaggerTag = (tag) => getSwaggerMate().decorate("swaggerTags", tag, true);
|
|
41
|
-
const SwaggerExclude = () => getSwaggerMate().decorate("swaggerExclude", true);
|
|
42
|
-
const SwaggerDescription = (descr) => getSwaggerMate().decorate("swaggerDescription", descr);
|
|
38
|
+
/** Adds an OpenAPI tag to a controller or handler for grouping in the Swagger UI. */ const SwaggerTag = (tag) => getSwaggerMate().decorate("swaggerTags", tag, true);
|
|
39
|
+
/** Excludes a controller or handler from the generated OpenAPI spec. */ const SwaggerExclude = () => getSwaggerMate().decorate("swaggerExclude", true);
|
|
40
|
+
/** Sets the OpenAPI description for a handler. */ const SwaggerDescription = (descr) => getSwaggerMate().decorate("swaggerDescription", descr);
|
|
43
41
|
function SwaggerResponse(code, opts, example) {
|
|
44
42
|
return getSwaggerMate().decorate((meta) => {
|
|
45
43
|
let ex;
|
|
46
44
|
if (example) ex = example;
|
|
47
45
|
if (typeof code !== "number" && opts) ex = opts;
|
|
46
|
+
if (ex === void 0) {
|
|
47
|
+
ex = typeof code === "number" ? opts : code;
|
|
48
|
+
ex = ex?.example;
|
|
49
|
+
}
|
|
48
50
|
meta.swaggerResponses = meta.swaggerResponses || {};
|
|
49
51
|
const keyCode = typeof code === "number" ? code : 0;
|
|
50
52
|
const opt = typeof code === "number" ? opts : code;
|
|
51
53
|
const contentType = typeof opt.contentType === "string" ? opt.contentType : "*/*";
|
|
54
|
+
const description = typeof opt.description === "string" ? opt.description : void 0;
|
|
55
|
+
const headers = opt.headers;
|
|
52
56
|
const response = ["object", "function"].includes(typeof opt.response) ? opt.response : opt;
|
|
53
|
-
meta.swaggerResponses[keyCode] = meta.swaggerResponses[keyCode] || {};
|
|
54
|
-
meta.swaggerResponses[keyCode][contentType] = {
|
|
57
|
+
meta.swaggerResponses[keyCode] = meta.swaggerResponses[keyCode] || { content: {} };
|
|
58
|
+
meta.swaggerResponses[keyCode].content[contentType] = {
|
|
55
59
|
response,
|
|
56
60
|
example: ex
|
|
57
61
|
};
|
|
62
|
+
if (description) meta.swaggerResponses[keyCode].description = description;
|
|
63
|
+
if (headers) meta.swaggerResponses[keyCode].headers = {
|
|
64
|
+
...meta.swaggerResponses[keyCode].headers,
|
|
65
|
+
...headers
|
|
66
|
+
};
|
|
58
67
|
return meta;
|
|
59
68
|
});
|
|
60
69
|
}
|
|
61
|
-
|
|
70
|
+
/**
|
|
71
|
+
* Defines the request body schema in the OpenAPI spec.
|
|
72
|
+
*
|
|
73
|
+
* @param opt - Schema definition. Use `{ response: MyDto }` for a typed schema,
|
|
74
|
+
* or `{ response: MyDto, contentType: 'multipart/form-data' }` for a specific content type.
|
|
75
|
+
*/ function SwaggerRequestBody(opt) {
|
|
62
76
|
return getSwaggerMate().decorate((meta) => {
|
|
63
77
|
meta.swaggerRequestBody = meta.swaggerRequestBody || {};
|
|
64
78
|
const contentType = typeof opt.contentType === "string" ? opt.contentType : "application/json";
|
|
@@ -67,26 +81,155 @@ function SwaggerRequestBody(opt) {
|
|
|
67
81
|
return meta;
|
|
68
82
|
});
|
|
69
83
|
}
|
|
70
|
-
function SwaggerParam(opts) {
|
|
84
|
+
/** Defines a parameter (query, path, header, cookie) in the OpenAPI spec. */ function SwaggerParam(opts) {
|
|
71
85
|
return getSwaggerMate().decorate("swaggerParams", opts, true);
|
|
72
86
|
}
|
|
73
|
-
function SwaggerExample(example) {
|
|
87
|
+
/** Attaches an example value to a handler's OpenAPI documentation. */ function SwaggerExample(example) {
|
|
74
88
|
return getSwaggerMate().decorate("swaggerExample", example);
|
|
75
89
|
}
|
|
90
|
+
/** Marks a handler or controller as public, opting out of inherited security requirements. */ const SwaggerPublic = () => getSwaggerMate().decorate("swaggerPublic", true);
|
|
91
|
+
/** Marks a handler or controller as deprecated in the OpenAPI spec. */ const SwaggerDeprecated = () => getSwaggerMate().decorate("swaggerDeprecated", true);
|
|
92
|
+
/** Overrides the auto-generated operationId for an endpoint. */ const SwaggerOperationId = (id) => getSwaggerMate().decorate("swaggerOperationId", id);
|
|
93
|
+
/** Links an operation to external documentation. */ const SwaggerExternalDocs = (url, description) => getSwaggerMate().decorate("swaggerExternalDocs", {
|
|
94
|
+
url,
|
|
95
|
+
...description ? { description } : {}
|
|
96
|
+
});
|
|
97
|
+
/**
|
|
98
|
+
* Attaches a security requirement to a handler or controller (OR semantics).
|
|
99
|
+
* Multiple calls add alternative requirements — any one suffices.
|
|
100
|
+
*
|
|
101
|
+
* @param schemeName - The name of the security scheme (must match a key in securitySchemes)
|
|
102
|
+
* @param scopes - OAuth2/OIDC scopes required (default: [])
|
|
103
|
+
*/ function SwaggerSecurity(schemeName, scopes = []) {
|
|
104
|
+
return getSwaggerMate().decorate("swaggerSecurity", { [schemeName]: scopes }, true);
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Attaches a combined security requirement (AND semantics).
|
|
108
|
+
* All schemes in the requirement must be satisfied simultaneously.
|
|
109
|
+
*/ function SwaggerSecurityAll(requirement) {
|
|
110
|
+
return getSwaggerMate().decorate("swaggerSecurity", requirement, true);
|
|
111
|
+
}
|
|
112
|
+
function SwaggerLink(codeOrName, nameOrOptions, maybeOptions) {
|
|
113
|
+
const statusCode = typeof codeOrName === "number" ? codeOrName : 0;
|
|
114
|
+
const name = typeof codeOrName === "string" ? codeOrName : nameOrOptions;
|
|
115
|
+
const options = typeof codeOrName === "string" ? nameOrOptions : maybeOptions;
|
|
116
|
+
const config = {
|
|
117
|
+
statusCode,
|
|
118
|
+
name,
|
|
119
|
+
..."operationId" in options && options.operationId ? { operationId: options.operationId } : {},
|
|
120
|
+
..."operationRef" in options && options.operationRef ? { operationRef: options.operationRef } : {},
|
|
121
|
+
..."handler" in options && options.handler ? { handler: options.handler } : {},
|
|
122
|
+
...options.parameters ? { parameters: options.parameters } : {},
|
|
123
|
+
...options.requestBody ? { requestBody: options.requestBody } : {},
|
|
124
|
+
...options.description ? { description: options.description } : {},
|
|
125
|
+
...options.server ? { server: options.server } : {}
|
|
126
|
+
};
|
|
127
|
+
return getSwaggerMate().decorate("swaggerLinks", config, true);
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Documents an OpenAPI callback (webhook) on an operation.
|
|
131
|
+
* Describes a request your server sends to a client-provided URL.
|
|
132
|
+
*
|
|
133
|
+
* @example
|
|
134
|
+
* ```ts
|
|
135
|
+
* @SwaggerCallback('onEvent', {
|
|
136
|
+
* expression: '{$request.body#/callbackUrl}',
|
|
137
|
+
* requestBody: EventPayloadDto,
|
|
138
|
+
* description: 'Event notification sent to subscriber',
|
|
139
|
+
* })
|
|
140
|
+
* @Post('subscribe')
|
|
141
|
+
* subscribe() { ... }
|
|
142
|
+
* ```
|
|
143
|
+
*/ function SwaggerCallback(name, options) {
|
|
144
|
+
const config = {
|
|
145
|
+
name,
|
|
146
|
+
expression: options.expression,
|
|
147
|
+
...options.method ? { method: options.method } : {},
|
|
148
|
+
...options.requestBody ? { requestBody: options.requestBody } : {},
|
|
149
|
+
...options.contentType ? { contentType: options.contentType } : {},
|
|
150
|
+
...options.description ? { description: options.description } : {},
|
|
151
|
+
...options.responseStatus ? { responseStatus: options.responseStatus } : {},
|
|
152
|
+
...options.responseDescription ? { responseDescription: options.responseDescription } : {}
|
|
153
|
+
};
|
|
154
|
+
return getSwaggerMate().decorate("swaggerCallbacks", config, true);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
//#endregion
|
|
158
|
+
//#region packages/swagger/src/json-to-yaml.ts
|
|
159
|
+
const YAML_SPECIAL = /^[\s#!&*|>'{}[\],?:@`-]|[:#]\s|[\n\r]|\s$/;
|
|
160
|
+
function quoteString(str) {
|
|
161
|
+
if (str === "") return "''";
|
|
162
|
+
if (YAML_SPECIAL.test(str) || str === "true" || str === "false" || str === "null") return JSON.stringify(str);
|
|
163
|
+
const num = Number(str);
|
|
164
|
+
if (str.length > 0 && !Number.isNaN(num) && String(num) === str) return JSON.stringify(str);
|
|
165
|
+
return str;
|
|
166
|
+
}
|
|
167
|
+
function serializeValue(value, indent) {
|
|
168
|
+
if (value === null || value === void 0) return "null";
|
|
169
|
+
if (typeof value === "boolean") return value ? "true" : "false";
|
|
170
|
+
if (typeof value === "number") return Number.isFinite(value) ? String(value) : "null";
|
|
171
|
+
if (typeof value === "string") return quoteString(value);
|
|
172
|
+
const pad = " ".repeat(indent);
|
|
173
|
+
const childPad = " ".repeat(indent + 1);
|
|
174
|
+
if (Array.isArray(value)) {
|
|
175
|
+
if (value.length === 0) return "[]";
|
|
176
|
+
const lines = [];
|
|
177
|
+
for (const item of value) if (isObject(item) || Array.isArray(item)) {
|
|
178
|
+
const nested = serializeValue(item, indent + 1);
|
|
179
|
+
lines.push(`${pad}- ${nested.slice(childPad.length)}`);
|
|
180
|
+
} else lines.push(`${pad}- ${serializeValue(item, 0)}`);
|
|
181
|
+
return lines.join("\n");
|
|
182
|
+
}
|
|
183
|
+
if (isObject(value)) {
|
|
184
|
+
const entries = Object.entries(value);
|
|
185
|
+
if (entries.length === 0) return "{}";
|
|
186
|
+
const lines = [];
|
|
187
|
+
for (const [key, val] of entries) {
|
|
188
|
+
const yamlKey = quoteString(key);
|
|
189
|
+
if (isObject(val) || Array.isArray(val)) {
|
|
190
|
+
const nested = serializeValue(val, indent + 1);
|
|
191
|
+
if (nested === "[]" || nested === "{}") lines.push(`${pad}${yamlKey}: ${nested}`);
|
|
192
|
+
else lines.push(`${pad}${yamlKey}:\n${nested}`);
|
|
193
|
+
} else lines.push(`${pad}${yamlKey}: ${serializeValue(val, 0)}`);
|
|
194
|
+
}
|
|
195
|
+
return lines.join("\n");
|
|
196
|
+
}
|
|
197
|
+
return String(value);
|
|
198
|
+
}
|
|
199
|
+
function isObject(value) {
|
|
200
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
201
|
+
}
|
|
202
|
+
function jsonToYaml(value) {
|
|
203
|
+
return `${serializeValue(value, 0)}\n`;
|
|
204
|
+
}
|
|
76
205
|
|
|
77
206
|
//#endregion
|
|
78
207
|
//#region packages/swagger/src/mapping.ts
|
|
208
|
+
const globalSchemas = {};
|
|
209
|
+
let schemaRefs = /* @__PURE__ */ new WeakMap();
|
|
210
|
+
const nameToType = /* @__PURE__ */ new Map();
|
|
79
211
|
function mapToSwaggerSpec(metadata, options, logger) {
|
|
212
|
+
resetSchemaRegistry();
|
|
213
|
+
const is31 = options?.openapiVersion === "3.1";
|
|
214
|
+
const collectedSecuritySchemes = { ...options?.securitySchemes };
|
|
80
215
|
const swaggerSpec = {
|
|
81
|
-
openapi: "3.0.0",
|
|
216
|
+
openapi: is31 ? "3.1.0" : "3.0.0",
|
|
82
217
|
info: {
|
|
83
218
|
title: options?.title || "API Documentation",
|
|
84
|
-
|
|
219
|
+
...options?.description ? { description: options.description } : {},
|
|
220
|
+
version: options?.version || "1.0.0",
|
|
221
|
+
...options?.contact ? { contact: options.contact } : {},
|
|
222
|
+
...options?.license ? { license: options.license } : {},
|
|
223
|
+
...options?.termsOfService ? { termsOfService: options.termsOfService } : {}
|
|
85
224
|
},
|
|
86
225
|
paths: {},
|
|
87
226
|
tags: [],
|
|
227
|
+
...options?.servers?.length ? { servers: options.servers } : {},
|
|
228
|
+
...options?.externalDocs ? { externalDocs: options.externalDocs } : {},
|
|
229
|
+
...options?.security ? { security: options.security } : {},
|
|
88
230
|
components: { schemas: globalSchemas }
|
|
89
231
|
};
|
|
232
|
+
const deferredLinks = [];
|
|
90
233
|
for (const controller of metadata) {
|
|
91
234
|
const cmeta = controller.meta;
|
|
92
235
|
if (cmeta?.swaggerExclude) continue;
|
|
@@ -98,67 +241,93 @@ function mapToSwaggerSpec(metadata, options, logger) {
|
|
|
98
241
|
const uniqueParams = {};
|
|
99
242
|
const handlerPath = handler.registeredAs[0].path;
|
|
100
243
|
const handlerMethod = hh.method?.toLowerCase() || "get";
|
|
101
|
-
const
|
|
244
|
+
const handlerSummary = hmeta?.label;
|
|
245
|
+
const handlerDescription = hmeta?.swaggerDescription || hmeta?.description;
|
|
102
246
|
const handlerTags = [...controllerTags, ...hmeta?.swaggerTags || []];
|
|
103
247
|
if (!swaggerSpec.paths[handlerPath]) swaggerSpec.paths[handlerPath] = {};
|
|
104
248
|
let responses;
|
|
105
|
-
if (hmeta?.swaggerResponses) for (const [code,
|
|
249
|
+
if (hmeta?.swaggerResponses) for (const [code, responseEntry] of Object.entries(hmeta.swaggerResponses)) {
|
|
106
250
|
const newCode = code === "0" ? getDefaultStatusCode(handlerMethod) : code;
|
|
107
|
-
for (const [contentType, conf] of Object.entries(
|
|
251
|
+
for (const [contentType, conf] of Object.entries(responseEntry.content)) {
|
|
108
252
|
const { response, example } = conf;
|
|
109
|
-
const schema =
|
|
253
|
+
const schema = resolveSwaggerSchemaFromConfig(response);
|
|
110
254
|
if (schema) {
|
|
111
255
|
responses = responses || {};
|
|
112
|
-
|
|
256
|
+
const schemaWithExample = example !== void 0 ? {
|
|
113
257
|
...schema,
|
|
114
|
-
example
|
|
115
|
-
}
|
|
258
|
+
example
|
|
259
|
+
} : schema;
|
|
260
|
+
if (!responses[newCode]) responses[newCode] = {
|
|
261
|
+
description: responseEntry.description || defaultStatusDescription(Number(newCode)),
|
|
262
|
+
content: {}
|
|
263
|
+
};
|
|
264
|
+
responses[newCode].content[contentType] = { schema: schemaWithExample };
|
|
116
265
|
}
|
|
117
266
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
if ([
|
|
122
|
-
"ZodString",
|
|
123
|
-
"ZodNumber",
|
|
124
|
-
"ZodObject",
|
|
125
|
-
"ZodArray",
|
|
126
|
-
"ZodBoolean"
|
|
127
|
-
].includes(parsed.$type)) {
|
|
128
|
-
const schema = getSwaggerSchema(parsed);
|
|
129
|
-
if (schema) {
|
|
267
|
+
if (responseEntry.headers) {
|
|
268
|
+
const resolvedHeaders = resolveResponseHeaders(responseEntry.headers);
|
|
269
|
+
if (resolvedHeaders) {
|
|
130
270
|
responses = responses || {};
|
|
131
|
-
responses[
|
|
271
|
+
if (!responses[newCode]) responses[newCode] = {
|
|
272
|
+
description: defaultStatusDescription(Number(newCode)),
|
|
273
|
+
content: {}
|
|
274
|
+
};
|
|
275
|
+
responses[newCode].headers = resolvedHeaders;
|
|
132
276
|
}
|
|
133
277
|
}
|
|
134
278
|
}
|
|
279
|
+
const defaultCode = getDefaultStatusCode(handlerMethod);
|
|
280
|
+
if (!responses?.[defaultCode] && hmeta?.returnType) {
|
|
281
|
+
const ensured = ensureSchema(hmeta.returnType);
|
|
282
|
+
const schema = toSchemaOrRef(ensured);
|
|
283
|
+
if (schema) {
|
|
284
|
+
responses = responses || {};
|
|
285
|
+
responses[defaultCode] = {
|
|
286
|
+
description: defaultStatusDescription(Number(defaultCode)),
|
|
287
|
+
content: { "*/*": { schema } }
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
}
|
|
135
291
|
let reqBodyRequired = true;
|
|
136
|
-
const
|
|
292
|
+
const bodyContent = {};
|
|
137
293
|
if (hmeta?.swaggerRequestBody) for (const [contentType, type] of Object.entries(hmeta.swaggerRequestBody)) {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
if (type instanceof __moostjs_zod.z.ZodType) zt = type;
|
|
141
|
-
else if (typeof type === "function") zt = (0, __moostjs_zod.getZodType)({ type });
|
|
142
|
-
if (zt) {
|
|
143
|
-
const parsed = myParseZod(zt);
|
|
144
|
-
if ([
|
|
145
|
-
"ZodString",
|
|
146
|
-
"ZodNumber",
|
|
147
|
-
"ZodObject",
|
|
148
|
-
"ZodArray",
|
|
149
|
-
"ZodBoolean"
|
|
150
|
-
].includes(parsed.$type)) schema = getSwaggerSchema(parsed);
|
|
151
|
-
}
|
|
152
|
-
bodyConfig[contentType] = { schema };
|
|
294
|
+
const schema = resolveSwaggerSchemaFromConfig(type);
|
|
295
|
+
if (schema) bodyContent[contentType] = { schema };
|
|
153
296
|
}
|
|
154
297
|
swaggerSpec.paths[handlerPath][handlerMethod] = {
|
|
155
|
-
summary:
|
|
156
|
-
|
|
298
|
+
summary: handlerSummary,
|
|
299
|
+
description: handlerDescription,
|
|
300
|
+
operationId: hmeta?.swaggerOperationId || hmeta?.id || `${handlerMethod.toUpperCase()}_${handlerPath.replaceAll(/\//g, "_").replaceAll(/[{}]/g, "__").replaceAll(/[^\dA-Za-z]/g, "_")}`,
|
|
157
301
|
tags: handlerTags,
|
|
158
302
|
parameters: [],
|
|
159
303
|
responses
|
|
160
304
|
};
|
|
161
305
|
const endpointSpec = swaggerSpec.paths[handlerPath][handlerMethod];
|
|
306
|
+
if (hmeta?.swaggerDeprecated || cmeta?.swaggerDeprecated) endpointSpec.deprecated = true;
|
|
307
|
+
if (hmeta?.swaggerExternalDocs) endpointSpec.externalDocs = hmeta.swaggerExternalDocs;
|
|
308
|
+
const opSecurity = resolveOperationSecurity(cmeta, hmeta, collectedSecuritySchemes);
|
|
309
|
+
if (opSecurity !== void 0) endpointSpec.security = opSecurity;
|
|
310
|
+
if (hmeta?.swaggerLinks?.length) for (const linkConfig of hmeta.swaggerLinks) deferredLinks.push({
|
|
311
|
+
endpointSpec,
|
|
312
|
+
httpMethod: handlerMethod,
|
|
313
|
+
config: linkConfig
|
|
314
|
+
});
|
|
315
|
+
if (hmeta?.swaggerCallbacks?.length) {
|
|
316
|
+
endpointSpec.callbacks = endpointSpec.callbacks || {};
|
|
317
|
+
for (const cb of hmeta.swaggerCallbacks) {
|
|
318
|
+
const method = (cb.method || "post").toLowerCase();
|
|
319
|
+
const contentType = cb.contentType || "application/json";
|
|
320
|
+
const status = String(cb.responseStatus || 200);
|
|
321
|
+
const responseDesc = cb.responseDescription || "OK";
|
|
322
|
+
const pathItem = { responses: { [status]: { description: responseDesc } } };
|
|
323
|
+
if (cb.description) pathItem.description = cb.description;
|
|
324
|
+
if (cb.requestBody) {
|
|
325
|
+
const schema = resolveSwaggerSchemaFromConfig(cb.requestBody);
|
|
326
|
+
if (schema) pathItem.requestBody = { content: { [contentType]: { schema } } };
|
|
327
|
+
}
|
|
328
|
+
endpointSpec.callbacks[cb.name] = { [cb.expression]: { [method]: pathItem } };
|
|
329
|
+
}
|
|
330
|
+
}
|
|
162
331
|
function addParam(param) {
|
|
163
332
|
const key = `${param.in}//${param.name}`;
|
|
164
333
|
if (uniqueParams[key]) {
|
|
@@ -175,298 +344,459 @@ function mapToSwaggerSpec(metadata, options, logger) {
|
|
|
175
344
|
in: param.in,
|
|
176
345
|
description: param.description,
|
|
177
346
|
required: !!param.required,
|
|
178
|
-
schema:
|
|
347
|
+
schema: resolveSwaggerSchemaFromConfig(param.type) || { type: "string" }
|
|
179
348
|
});
|
|
180
349
|
for (const param of hmeta?.swaggerParams || []) addParam({
|
|
181
350
|
name: param.name,
|
|
182
351
|
in: param.in,
|
|
183
352
|
description: param.description,
|
|
184
353
|
required: !!param.required,
|
|
185
|
-
schema:
|
|
354
|
+
schema: resolveSwaggerSchemaFromConfig(param.type) || { type: "string" }
|
|
186
355
|
});
|
|
187
356
|
for (const paramName of handler.registeredAs[0].args) {
|
|
188
357
|
const paramIndex = handler.meta.params.findIndex((param) => param.paramSource === "ROUTE" && param.paramName === paramName);
|
|
189
358
|
const paramMeta = handler.meta.params[paramIndex];
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
if (paramMeta) {
|
|
193
|
-
const zodType = (0, __moostjs_zod.getZodTypeForProp)({
|
|
194
|
-
type: controller.type,
|
|
195
|
-
key: handler.method,
|
|
196
|
-
index: paramIndex
|
|
197
|
-
}, {
|
|
198
|
-
type: paramMeta.type,
|
|
199
|
-
additionalMeta: paramMeta
|
|
200
|
-
}, void 0, logger);
|
|
201
|
-
parsed = myParseZod(zodType);
|
|
202
|
-
schema = getSwaggerSchema(parsed, true);
|
|
203
|
-
}
|
|
359
|
+
const ensured = ensureSchema(paramMeta?.type);
|
|
360
|
+
const schema = toSchemaOrRef(ensured) || { type: "string" };
|
|
204
361
|
addParam({
|
|
205
362
|
name: paramName,
|
|
206
363
|
in: "path",
|
|
207
364
|
description: paramMeta ? paramMeta.description : void 0,
|
|
208
|
-
required: !paramMeta
|
|
209
|
-
schema
|
|
365
|
+
required: !paramMeta?.optional,
|
|
366
|
+
schema
|
|
210
367
|
});
|
|
211
368
|
}
|
|
212
|
-
for (
|
|
213
|
-
const paramMeta = handler.meta.params[i];
|
|
369
|
+
for (const paramMeta of handler.meta.params) {
|
|
214
370
|
if (paramMeta.paramSource && ["QUERY_ITEM", "QUERY"].includes(paramMeta.paramSource)) {
|
|
215
|
-
const
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
371
|
+
const ensured = ensureSchema(paramMeta.type);
|
|
372
|
+
if (paramMeta.paramSource === "QUERY_ITEM") {
|
|
373
|
+
const schema = toSchemaOrRef(ensured);
|
|
374
|
+
const normalized = schema ? normalizeQueryParamSchema(schema) : void 0;
|
|
375
|
+
endpointSpec.parameters.push({
|
|
376
|
+
name: paramMeta.paramName || "",
|
|
377
|
+
in: "query",
|
|
378
|
+
description: paramMeta.description,
|
|
379
|
+
required: !paramMeta.optional,
|
|
380
|
+
schema: normalized || { type: "string" }
|
|
381
|
+
});
|
|
382
|
+
} else if (paramMeta.paramSource === "QUERY") {
|
|
383
|
+
const schema = ensured?.schema;
|
|
384
|
+
if (schema?.type === "object" && schema.properties) {
|
|
385
|
+
const requiredProps = new Set(schema.required || []);
|
|
386
|
+
for (const [key, value] of Object.entries(schema.properties)) {
|
|
387
|
+
const propertySchema = cloneSchema(value);
|
|
388
|
+
const normalizedProperty = normalizeQueryParamSchema(propertySchema);
|
|
389
|
+
if (normalizedProperty) endpointSpec.parameters.push({
|
|
390
|
+
name: key,
|
|
391
|
+
in: "query",
|
|
392
|
+
description: normalizedProperty.description,
|
|
393
|
+
required: !paramMeta.optional && requiredProps.has(key),
|
|
394
|
+
schema: normalizedProperty
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
} else if (ensured) {
|
|
398
|
+
const schema$1 = toSchemaOrRef(ensured);
|
|
399
|
+
const normalized = schema$1 ? normalizeQueryParamSchema(schema$1) : void 0;
|
|
400
|
+
endpointSpec.parameters.push({
|
|
401
|
+
name: paramMeta.paramName || "",
|
|
237
402
|
in: "query",
|
|
238
|
-
description:
|
|
239
|
-
required: !
|
|
240
|
-
schema:
|
|
241
|
-
};
|
|
242
|
-
endpointSpec.parameters.push(swaggerSchema);
|
|
403
|
+
description: paramMeta.description,
|
|
404
|
+
required: !paramMeta.optional,
|
|
405
|
+
schema: normalized || { type: "string" }
|
|
406
|
+
});
|
|
243
407
|
}
|
|
244
408
|
}
|
|
245
409
|
}
|
|
246
410
|
if (paramMeta.paramSource === "BODY") {
|
|
247
|
-
const
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
additionalMeta: paramMeta
|
|
254
|
-
}, void 0, logger);
|
|
255
|
-
const parsed = myParseZod(zodType);
|
|
256
|
-
let contentType = "";
|
|
257
|
-
switch (parsed.$type) {
|
|
258
|
-
case "ZodString":
|
|
259
|
-
case "ZodNumber":
|
|
260
|
-
case "ZodBigInt":
|
|
261
|
-
case "ZodBoolean":
|
|
262
|
-
case "ZodDate":
|
|
263
|
-
case "ZodEnum":
|
|
264
|
-
case "ZodNativeEnum":
|
|
265
|
-
case "ZodLiteral": {
|
|
266
|
-
contentType = "text/plan";
|
|
267
|
-
break;
|
|
268
|
-
}
|
|
269
|
-
default: contentType = "application/json";
|
|
411
|
+
const ensured = ensureSchema(paramMeta.type);
|
|
412
|
+
const schema = toSchemaOrRef(ensured);
|
|
413
|
+
if (schema) {
|
|
414
|
+
const contentType = inferBodyContentType(schema, ensured?.schema);
|
|
415
|
+
if (!bodyContent[contentType]) bodyContent[contentType] = { schema };
|
|
416
|
+
reqBodyRequired = !paramMeta.optional;
|
|
270
417
|
}
|
|
271
|
-
if (!bodyConfig[contentType]) bodyConfig[contentType] = { schema: getSwaggerSchema(parsed) };
|
|
272
|
-
reqBodyRequired = !zodType.isOptional() && !paramMeta.optional;
|
|
273
418
|
}
|
|
274
419
|
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
420
|
+
const bodyEntries = Object.entries(bodyContent).filter((entry) => entry[1] && entry[1].schema !== void 0);
|
|
421
|
+
if (bodyEntries.length > 0) {
|
|
422
|
+
const content = {};
|
|
423
|
+
for (const [contentType, { schema }] of bodyEntries) content[contentType] = { schema };
|
|
424
|
+
endpointSpec.requestBody = {
|
|
425
|
+
content,
|
|
426
|
+
required: reqBodyRequired
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
if (deferredLinks.length > 0) {
|
|
432
|
+
const handlerOpIds = /* @__PURE__ */ new Map();
|
|
433
|
+
for (const controller of metadata) for (const handler of controller.handlers) {
|
|
434
|
+
const hh = handler.handler;
|
|
435
|
+
if (hh.type !== "HTTP" || handler.registeredAs.length === 0) continue;
|
|
436
|
+
const path$1 = handler.registeredAs[0].path;
|
|
437
|
+
const method = hh.method?.toLowerCase() || "get";
|
|
438
|
+
const opId = swaggerSpec.paths[path$1]?.[method]?.operationId;
|
|
439
|
+
if (opId) {
|
|
440
|
+
if (!handlerOpIds.has(controller.type)) handlerOpIds.set(controller.type, /* @__PURE__ */ new Map());
|
|
441
|
+
const methodMap = handlerOpIds.get(controller.type);
|
|
442
|
+
methodMap?.set(handler.method, opId);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
for (const { endpointSpec, httpMethod, config } of deferredLinks) {
|
|
446
|
+
const statusCode = config.statusCode === 0 ? String(getDefaultStatusCode(httpMethod)) : String(config.statusCode);
|
|
447
|
+
const link = {};
|
|
448
|
+
if (config.handler) {
|
|
449
|
+
const [ctrlClass, methodName] = config.handler;
|
|
450
|
+
const resolvedId = handlerOpIds.get(ctrlClass)?.get(methodName);
|
|
451
|
+
if (!resolvedId) continue;
|
|
452
|
+
link.operationId = resolvedId;
|
|
453
|
+
} else if (config.operationId) link.operationId = config.operationId;
|
|
454
|
+
else if (config.operationRef) link.operationRef = config.operationRef;
|
|
455
|
+
if (config.parameters) link.parameters = config.parameters;
|
|
456
|
+
if (config.requestBody) link.requestBody = config.requestBody;
|
|
457
|
+
if (config.description) link.description = config.description;
|
|
458
|
+
if (config.server) link.server = config.server;
|
|
459
|
+
if (!endpointSpec.responses) endpointSpec.responses = {};
|
|
460
|
+
if (!endpointSpec.responses[statusCode]) endpointSpec.responses[statusCode] = {
|
|
461
|
+
description: defaultStatusDescription(Number(statusCode)),
|
|
462
|
+
content: {}
|
|
278
463
|
};
|
|
464
|
+
const responseEntry = endpointSpec.responses[statusCode];
|
|
465
|
+
if (!responseEntry.links) responseEntry.links = {};
|
|
466
|
+
responseEntry.links[config.name] = link;
|
|
279
467
|
}
|
|
280
468
|
}
|
|
469
|
+
const manualTags = new Map((options?.tags || []).map((t) => [t.name, t]));
|
|
470
|
+
const discoveredNames = /* @__PURE__ */ new Set();
|
|
471
|
+
for (const methods of Object.values(swaggerSpec.paths)) for (const endpoint of Object.values(methods)) for (const tag of endpoint.tags) discoveredNames.add(tag);
|
|
472
|
+
for (const tag of manualTags.values()) swaggerSpec.tags.push(tag);
|
|
473
|
+
for (const name of discoveredNames) if (!manualTags.has(name)) swaggerSpec.tags.push({ name });
|
|
474
|
+
const ownedSchemas = {};
|
|
475
|
+
for (const [key, value] of Object.entries(globalSchemas)) ownedSchemas[key] = cloneSchema(value);
|
|
476
|
+
swaggerSpec.components.schemas = ownedSchemas;
|
|
477
|
+
if (Object.keys(collectedSecuritySchemes).length > 0) swaggerSpec.components.securitySchemes = collectedSecuritySchemes;
|
|
478
|
+
if (is31) transformSpecTo31(swaggerSpec);
|
|
281
479
|
return swaggerSpec;
|
|
282
480
|
}
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
const
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
const
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
const { regex } = parsed.$checks;
|
|
300
|
-
if (regex) schema.pattern = regex.source;
|
|
301
|
-
}
|
|
481
|
+
function resolveSwaggerSchemaFromConfig(type) {
|
|
482
|
+
if (type === void 0) return void 0;
|
|
483
|
+
const ensured = ensureSchema(type);
|
|
484
|
+
return toSchemaOrRef(ensured);
|
|
485
|
+
}
|
|
486
|
+
function resolveResponseHeaders(headers) {
|
|
487
|
+
const resolved = {};
|
|
488
|
+
let hasAny = false;
|
|
489
|
+
for (const [name, header] of Object.entries(headers)) {
|
|
490
|
+
const schema = resolveSwaggerSchemaFromConfig(header.type) || { type: "string" };
|
|
491
|
+
const entry = { schema };
|
|
492
|
+
if (header.description) entry.description = header.description;
|
|
493
|
+
if (header.required !== void 0) entry.required = header.required;
|
|
494
|
+
if (header.example !== void 0) entry.example = header.example;
|
|
495
|
+
resolved[name] = entry;
|
|
496
|
+
hasAny = true;
|
|
302
497
|
}
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
498
|
+
return hasAny ? resolved : void 0;
|
|
499
|
+
}
|
|
500
|
+
function toSchemaOrRef(result) {
|
|
501
|
+
if (!result) return void 0;
|
|
502
|
+
if (result.ref) return { $ref: result.ref };
|
|
503
|
+
return cloneSchema(result.schema);
|
|
504
|
+
}
|
|
505
|
+
function inferBodyContentType(schema, resolved) {
|
|
506
|
+
const target = resolved ?? resolveSchemaFromRef(schema);
|
|
507
|
+
const schemaType = target?.type ?? schema.type;
|
|
508
|
+
if (typeof schemaType === "string" && [
|
|
509
|
+
"string",
|
|
510
|
+
"number",
|
|
511
|
+
"integer",
|
|
512
|
+
"boolean"
|
|
513
|
+
].includes(schemaType)) return "text/plain";
|
|
514
|
+
return "application/json";
|
|
515
|
+
}
|
|
516
|
+
const SIMPLE_QUERY_TYPES = new Set([
|
|
517
|
+
"string",
|
|
518
|
+
"number",
|
|
519
|
+
"integer",
|
|
520
|
+
"boolean"
|
|
521
|
+
]);
|
|
522
|
+
function normalizeQueryParamSchema(schema) {
|
|
523
|
+
const target = resolveSchemaFromRef(schema) || schema;
|
|
524
|
+
if (!target) return void 0;
|
|
525
|
+
if (target.type === "array") return isArrayOfSimpleItems(target.items) ? schema : void 0;
|
|
526
|
+
return isSimpleSchema(schema) ? schema : void 0;
|
|
527
|
+
}
|
|
528
|
+
function isArrayOfSimpleItems(items) {
|
|
529
|
+
if (!items) return false;
|
|
530
|
+
if (Array.isArray(items)) {
|
|
531
|
+
if (items.length === 0) return false;
|
|
532
|
+
return items.every((entry) => isSimpleSchema(entry));
|
|
308
533
|
}
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
534
|
+
return isSimpleSchema(items);
|
|
535
|
+
}
|
|
536
|
+
function isSimpleSchema(schema, seen = /* @__PURE__ */ new Set()) {
|
|
537
|
+
if (!schema) return false;
|
|
538
|
+
if (seen.has(schema)) return false;
|
|
539
|
+
seen.add(schema);
|
|
540
|
+
if (schema.$ref) {
|
|
541
|
+
const resolved = resolveSchemaFromRef(schema);
|
|
542
|
+
if (!resolved) return false;
|
|
543
|
+
return isSimpleSchema(resolved, seen);
|
|
314
544
|
}
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
545
|
+
if (typeof schema.type === "string" && SIMPLE_QUERY_TYPES.has(schema.type)) return true;
|
|
546
|
+
if (Array.isArray(schema.enum) && schema.enum.length > 0) return true;
|
|
547
|
+
if (schema.const !== void 0) return true;
|
|
548
|
+
return false;
|
|
549
|
+
}
|
|
550
|
+
function resetSchemaRegistry() {
|
|
551
|
+
schemaRefs = /* @__PURE__ */ new WeakMap();
|
|
552
|
+
nameToType.clear();
|
|
553
|
+
for (const key of Object.keys(globalSchemas)) delete globalSchemas[key];
|
|
554
|
+
}
|
|
555
|
+
function ensureSchema(type) {
|
|
556
|
+
if (type === void 0 || type === null) return void 0;
|
|
557
|
+
const resolution = createSchemaResolution(type);
|
|
558
|
+
if (!resolution) return void 0;
|
|
559
|
+
if (resolution.kind === "inline") return {
|
|
560
|
+
schema: cloneSchema(resolution.schema),
|
|
561
|
+
isComponent: false
|
|
562
|
+
};
|
|
563
|
+
const schemaClone = cloneSchema(resolution.schema);
|
|
564
|
+
const componentName = ensureComponentName(resolution.typeRef, schemaClone, resolution.suggestedName);
|
|
565
|
+
return {
|
|
566
|
+
schema: cloneSchema(globalSchemas[componentName]),
|
|
567
|
+
ref: `#/components/schemas/${componentName}`,
|
|
568
|
+
componentName,
|
|
569
|
+
isComponent: true
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
function createSchemaResolution(type) {
|
|
573
|
+
if (type === void 0 || type === null) return void 0;
|
|
574
|
+
if (isSwaggerSchema(type)) return {
|
|
575
|
+
kind: "inline",
|
|
576
|
+
schema: cloneSchema(type)
|
|
577
|
+
};
|
|
578
|
+
if (Array.isArray(type)) {
|
|
579
|
+
if (type.length === 1) {
|
|
580
|
+
const itemEnsured = ensureSchema(type[0]);
|
|
581
|
+
const itemsSchema = toSchemaOrRef(itemEnsured);
|
|
582
|
+
return {
|
|
583
|
+
kind: "inline",
|
|
584
|
+
schema: {
|
|
585
|
+
type: "array",
|
|
586
|
+
items: itemsSchema
|
|
587
|
+
}
|
|
588
|
+
};
|
|
319
589
|
}
|
|
590
|
+
return {
|
|
591
|
+
kind: "inline",
|
|
592
|
+
schema: { type: "array" }
|
|
593
|
+
};
|
|
320
594
|
}
|
|
321
|
-
if (
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
case "ZodBigInt": {
|
|
333
|
-
schema.type = "integer";
|
|
334
|
-
break;
|
|
335
|
-
}
|
|
336
|
-
case "ZodBoolean": {
|
|
337
|
-
schema.type = "boolean";
|
|
338
|
-
break;
|
|
339
|
-
}
|
|
340
|
-
case "ZodLiteral": {
|
|
341
|
-
asLiteral();
|
|
342
|
-
break;
|
|
343
|
-
}
|
|
344
|
-
case "ZodEnum": {
|
|
345
|
-
asEnum();
|
|
346
|
-
break;
|
|
347
|
-
}
|
|
348
|
-
case "ZodNativeEnum": {
|
|
349
|
-
asNativeEnum();
|
|
350
|
-
break;
|
|
351
|
-
}
|
|
352
|
-
case "ZodDate": {
|
|
353
|
-
schema.type = "string";
|
|
354
|
-
break;
|
|
355
|
-
}
|
|
356
|
-
case "ZodNull": {
|
|
357
|
-
schema.type = "null";
|
|
358
|
-
break;
|
|
359
|
-
}
|
|
360
|
-
default: return void 0;
|
|
595
|
+
if (isLiteralValue(type)) return {
|
|
596
|
+
kind: "inline",
|
|
597
|
+
schema: schemaFromLiteral(type)
|
|
598
|
+
};
|
|
599
|
+
if (isPrimitiveConstructor(type)) return {
|
|
600
|
+
kind: "inline",
|
|
601
|
+
schema: schemaFromPrimitiveCtor(type)
|
|
602
|
+
};
|
|
603
|
+
if (typeof type === "function") {
|
|
604
|
+
const resolution = schemaFromFunction(type);
|
|
605
|
+
if (resolution) return resolution;
|
|
361
606
|
}
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
607
|
+
if (typeof type === "object") {
|
|
608
|
+
const resolution = schemaFromInstance(type);
|
|
609
|
+
if (resolution) return resolution;
|
|
610
|
+
}
|
|
611
|
+
return void 0;
|
|
612
|
+
}
|
|
613
|
+
function schemaFromFunction(fn) {
|
|
614
|
+
const ctor = fn;
|
|
615
|
+
if (typeof ctor.toJsonSchema === "function") {
|
|
616
|
+
const schema = asSwaggerSchema(ctor.toJsonSchema());
|
|
617
|
+
return {
|
|
618
|
+
kind: "component",
|
|
619
|
+
schema,
|
|
620
|
+
typeRef: ctor,
|
|
621
|
+
suggestedName: ctor.name
|
|
622
|
+
};
|
|
623
|
+
}
|
|
624
|
+
if (fn.length === 0) try {
|
|
625
|
+
const result = fn();
|
|
626
|
+
if (result && result !== fn) return createSchemaResolution(result);
|
|
627
|
+
} catch {}
|
|
628
|
+
return void 0;
|
|
629
|
+
}
|
|
630
|
+
function schemaFromInstance(obj) {
|
|
631
|
+
if (isSwaggerSchema(obj)) return {
|
|
632
|
+
kind: "inline",
|
|
633
|
+
schema: cloneSchema(obj)
|
|
634
|
+
};
|
|
635
|
+
const ctor = obj.constructor;
|
|
636
|
+
if (ctor && typeof ctor.toJsonSchema === "function") {
|
|
637
|
+
const schema = asSwaggerSchema(ctor.toJsonSchema());
|
|
638
|
+
return {
|
|
639
|
+
kind: "component",
|
|
640
|
+
schema,
|
|
641
|
+
typeRef: ctor,
|
|
642
|
+
suggestedName: ctor.name
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
if (typeof obj.toJsonSchema === "function") {
|
|
646
|
+
const schema = asSwaggerSchema(obj.toJsonSchema());
|
|
647
|
+
return {
|
|
648
|
+
kind: "component",
|
|
649
|
+
schema,
|
|
650
|
+
typeRef: obj,
|
|
651
|
+
suggestedName: getTypeName(obj)
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
return void 0;
|
|
655
|
+
}
|
|
656
|
+
function asSwaggerSchema(schema) {
|
|
657
|
+
if (!schema || typeof schema !== "object") return {};
|
|
658
|
+
return cloneSchema(schema);
|
|
659
|
+
}
|
|
660
|
+
function ensureComponentName(typeRef, schema, suggestedName) {
|
|
661
|
+
const existing = schemaRefs.get(typeRef);
|
|
662
|
+
if (existing) {
|
|
663
|
+
if (!globalSchemas[existing]) globalSchemas[existing] = cloneSchema(schema);
|
|
664
|
+
return existing;
|
|
665
|
+
}
|
|
666
|
+
const baseName = sanitizeComponentName(suggestedName || schema.title || getTypeName(typeRef) || "Schema");
|
|
667
|
+
let candidate = baseName || "Schema";
|
|
668
|
+
let counter = 1;
|
|
669
|
+
while (nameToType.has(candidate)) candidate = `${baseName}_${counter++}`;
|
|
670
|
+
nameToType.set(candidate, typeRef);
|
|
671
|
+
schemaRefs.set(typeRef, candidate);
|
|
672
|
+
applySwaggerMetadata(typeRef, schema);
|
|
673
|
+
globalSchemas[candidate] = cloneSchema(schema);
|
|
674
|
+
hoistDefs(globalSchemas[candidate]);
|
|
675
|
+
return candidate;
|
|
676
|
+
}
|
|
677
|
+
/**
|
|
678
|
+
* When a schema has `$defs`, hoist each definition into `globalSchemas`
|
|
679
|
+
* (i.e. `#/components/schemas/`) and rewrite all `#/$defs/X` references
|
|
680
|
+
* throughout the schema tree to `#/components/schemas/X`.
|
|
681
|
+
* Removes the `$defs` property from the schema after hoisting.
|
|
682
|
+
*/ function hoistDefs(schema) {
|
|
683
|
+
if (!schema.$defs) return;
|
|
684
|
+
for (const [name, def] of Object.entries(schema.$defs)) if (!globalSchemas[name]) {
|
|
685
|
+
globalSchemas[name] = cloneSchema(def);
|
|
686
|
+
hoistDefs(globalSchemas[name]);
|
|
687
|
+
}
|
|
688
|
+
delete schema.$defs;
|
|
689
|
+
rewriteDefsRefs(schema);
|
|
690
|
+
}
|
|
691
|
+
/** Recursively rewrite `$ref: '#/$defs/X'` → `$ref: '#/components/schemas/X'` */ function rewriteDefsRefs(schema) {
|
|
692
|
+
if (schema.$ref?.startsWith("#/$defs/")) schema.$ref = schema.$ref.replace("#/$defs/", "#/components/schemas/");
|
|
693
|
+
if (schema.discriminator?.mapping) {
|
|
694
|
+
for (const [key, value] of Object.entries(schema.discriminator.mapping)) if (value.startsWith("#/$defs/")) schema.discriminator.mapping[key] = value.replace("#/$defs/", "#/components/schemas/");
|
|
695
|
+
}
|
|
696
|
+
if (schema.items && !Array.isArray(schema.items)) rewriteDefsRefs(schema.items);
|
|
697
|
+
if (schema.properties) for (const prop of Object.values(schema.properties)) rewriteDefsRefs(prop);
|
|
698
|
+
for (const list of [
|
|
699
|
+
schema.allOf,
|
|
700
|
+
schema.anyOf,
|
|
701
|
+
schema.oneOf
|
|
702
|
+
]) if (list) for (const item of list) rewriteDefsRefs(item);
|
|
703
|
+
if (typeof schema.additionalProperties === "object" && schema.additionalProperties) rewriteDefsRefs(schema.additionalProperties);
|
|
704
|
+
if (schema.not) rewriteDefsRefs(schema.not);
|
|
705
|
+
}
|
|
706
|
+
function applySwaggerMetadata(typeRef, schema) {
|
|
707
|
+
try {
|
|
708
|
+
const mate = getSwaggerMate();
|
|
709
|
+
const meta = mate.read(typeRef);
|
|
710
|
+
if (meta) {
|
|
711
|
+
if (meta.swaggerExample !== void 0 && schema.example === void 0) schema.example = meta.swaggerExample;
|
|
712
|
+
const title = meta.label || meta.id;
|
|
713
|
+
if (title && !schema.title) schema.title = title;
|
|
714
|
+
if (meta.swaggerDescription && !schema.description) schema.description = meta.swaggerDescription;
|
|
715
|
+
else if (meta.description && !schema.description) schema.description = meta.description;
|
|
449
716
|
}
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
const checks = parsed.$checks;
|
|
457
|
-
if (parsed.$type === "ZodString") {
|
|
458
|
-
if (typeof checks.min === "number") schema.minLength = checks.min;
|
|
459
|
-
if (typeof checks.max === "number") schema.maxLength = checks.max;
|
|
460
|
-
} else {
|
|
461
|
-
if (typeof checks.min === "number") schema.minimum = checks.min;
|
|
462
|
-
if (typeof checks.max === "number") schema.maximum = checks.max;
|
|
717
|
+
} catch {}
|
|
718
|
+
if (schema.example === void 0) {
|
|
719
|
+
const exampleFn = typeRef.toExampleData;
|
|
720
|
+
if (typeof exampleFn === "function") {
|
|
721
|
+
const example = exampleFn.call(typeRef);
|
|
722
|
+
if (example !== void 0) schema.example = example;
|
|
463
723
|
}
|
|
464
724
|
}
|
|
465
|
-
if (!forParam && zodType.__type_ref) return { $ref: `#/components/schemas/${zodType.__type_ref.name}` };
|
|
466
|
-
return schema;
|
|
467
725
|
}
|
|
468
|
-
function
|
|
469
|
-
|
|
726
|
+
function sanitizeComponentName(name) {
|
|
727
|
+
const sanitized = name.replaceAll(/[^A-Za-z0-9_.-]/g, "_");
|
|
728
|
+
return sanitized || "Schema";
|
|
729
|
+
}
|
|
730
|
+
function getTypeName(typeRef) {
|
|
731
|
+
if (typeof typeRef === "function" && typeRef.name) return typeRef.name;
|
|
732
|
+
const ctor = typeRef.constructor;
|
|
733
|
+
if (ctor && ctor !== Object && ctor.name) return ctor.name;
|
|
734
|
+
return void 0;
|
|
735
|
+
}
|
|
736
|
+
function isSwaggerSchema(candidate) {
|
|
737
|
+
if (!candidate || typeof candidate !== "object") return false;
|
|
738
|
+
const obj = candidate;
|
|
739
|
+
return "$ref" in obj || "type" in obj || "properties" in obj || "items" in obj || "allOf" in obj || "anyOf" in obj || "oneOf" in obj;
|
|
740
|
+
}
|
|
741
|
+
function isLiteralValue(value) {
|
|
742
|
+
const type = typeof value;
|
|
743
|
+
return type === "string" || type === "number" || type === "boolean" || type === "bigint";
|
|
744
|
+
}
|
|
745
|
+
function schemaFromLiteral(value) {
|
|
746
|
+
if (typeof value === "string") {
|
|
747
|
+
if ([
|
|
748
|
+
"string",
|
|
749
|
+
"number",
|
|
750
|
+
"boolean",
|
|
751
|
+
"integer",
|
|
752
|
+
"object",
|
|
753
|
+
"array"
|
|
754
|
+
].includes(value)) return { type: value };
|
|
755
|
+
return {
|
|
756
|
+
const: value,
|
|
757
|
+
type: "string"
|
|
758
|
+
};
|
|
759
|
+
}
|
|
760
|
+
if (typeof value === "number") return {
|
|
761
|
+
const: value,
|
|
762
|
+
type: Number.isInteger(value) ? "integer" : "number"
|
|
763
|
+
};
|
|
764
|
+
if (typeof value === "boolean") return {
|
|
765
|
+
const: value,
|
|
766
|
+
type: "boolean"
|
|
767
|
+
};
|
|
768
|
+
return {
|
|
769
|
+
const: value.toString(),
|
|
770
|
+
type: "integer"
|
|
771
|
+
};
|
|
772
|
+
}
|
|
773
|
+
function isPrimitiveConstructor(value) {
|
|
774
|
+
if (typeof value !== "function") return false;
|
|
775
|
+
return value === String || value === Number || value === Boolean || value === BigInt || value === Date || value === Array || value === Object || value === Symbol;
|
|
776
|
+
}
|
|
777
|
+
function schemaFromPrimitiveCtor(fn) {
|
|
778
|
+
switch (fn) {
|
|
779
|
+
case String: return { type: "string" };
|
|
780
|
+
case Number: return { type: "number" };
|
|
781
|
+
case Boolean: return { type: "boolean" };
|
|
782
|
+
case BigInt: return { type: "integer" };
|
|
783
|
+
case Date: return {
|
|
784
|
+
type: "string",
|
|
785
|
+
format: "date-time"
|
|
786
|
+
};
|
|
787
|
+
case Array: return { type: "array" };
|
|
788
|
+
case Object: return { type: "object" };
|
|
789
|
+
case Symbol: return { type: "string" };
|
|
790
|
+
default: return { type: "object" };
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
function resolveSchemaFromRef(schema) {
|
|
794
|
+
if (!schema.$ref) return schema;
|
|
795
|
+
const refName = schema.$ref.replace("#/components/schemas/", "");
|
|
796
|
+
return globalSchemas[refName];
|
|
797
|
+
}
|
|
798
|
+
function cloneSchema(schema) {
|
|
799
|
+
return JSON.parse(JSON.stringify(schema));
|
|
470
800
|
}
|
|
471
801
|
function getDefaultStatusCode(httpMethod) {
|
|
472
802
|
const defaultStatusCodes = {
|
|
@@ -477,22 +807,106 @@ function getDefaultStatusCode(httpMethod) {
|
|
|
477
807
|
};
|
|
478
808
|
return defaultStatusCodes[httpMethod.toUpperCase()] || 200;
|
|
479
809
|
}
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
810
|
+
const STATUS_DESCRIPTIONS = {
|
|
811
|
+
200: "OK",
|
|
812
|
+
201: "Created",
|
|
813
|
+
202: "Accepted",
|
|
814
|
+
204: "No Content",
|
|
815
|
+
301: "Moved Permanently",
|
|
816
|
+
302: "Found",
|
|
817
|
+
304: "Not Modified",
|
|
818
|
+
400: "Bad Request",
|
|
819
|
+
401: "Unauthorized",
|
|
820
|
+
403: "Forbidden",
|
|
821
|
+
404: "Not Found",
|
|
822
|
+
405: "Method Not Allowed",
|
|
823
|
+
409: "Conflict",
|
|
824
|
+
410: "Gone",
|
|
825
|
+
415: "Unsupported Media Type",
|
|
826
|
+
422: "Unprocessable Entity",
|
|
827
|
+
429: "Too Many Requests",
|
|
828
|
+
500: "Internal Server Error",
|
|
829
|
+
502: "Bad Gateway",
|
|
830
|
+
503: "Service Unavailable"
|
|
831
|
+
};
|
|
832
|
+
function defaultStatusDescription(code) {
|
|
833
|
+
return STATUS_DESCRIPTIONS[code] || "Response";
|
|
834
|
+
}
|
|
835
|
+
function transformSpecTo31(spec) {
|
|
836
|
+
for (const schema of Object.values(spec.components.schemas)) convertSchemaTo31(schema);
|
|
837
|
+
for (const methods of Object.values(spec.paths)) for (const endpoint of Object.values(methods)) {
|
|
838
|
+
for (const param of endpoint.parameters) convertSchemaTo31(param.schema);
|
|
839
|
+
if (endpoint.responses) for (const response of Object.values(endpoint.responses)) {
|
|
840
|
+
for (const media of Object.values(response.content)) convertSchemaTo31(media.schema);
|
|
841
|
+
if (response.headers) for (const header of Object.values(response.headers)) convertSchemaTo31(header.schema);
|
|
842
|
+
}
|
|
843
|
+
if (endpoint.requestBody) for (const media of Object.values(endpoint.requestBody.content)) convertSchemaTo31(media.schema);
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
function convertSchemaTo31(schema) {
|
|
847
|
+
if (schema.nullable) {
|
|
848
|
+
delete schema.nullable;
|
|
849
|
+
if (typeof schema.type === "string") schema.type = [schema.type, "null"];
|
|
850
|
+
else if (Array.isArray(schema.type)) {
|
|
851
|
+
if (!schema.type.includes("null")) schema.type.push("null");
|
|
852
|
+
} else schema.type = "null";
|
|
853
|
+
}
|
|
854
|
+
if (schema.properties) for (const prop of Object.values(schema.properties)) convertSchemaTo31(prop);
|
|
855
|
+
if (schema.items) if (Array.isArray(schema.items)) for (const item of schema.items) convertSchemaTo31(item);
|
|
856
|
+
else convertSchemaTo31(schema.items);
|
|
857
|
+
if (schema.allOf) for (const sub of schema.allOf) convertSchemaTo31(sub);
|
|
858
|
+
if (schema.anyOf) for (const sub of schema.anyOf) convertSchemaTo31(sub);
|
|
859
|
+
if (schema.oneOf) for (const sub of schema.oneOf) convertSchemaTo31(sub);
|
|
860
|
+
if (schema.not) convertSchemaTo31(schema.not);
|
|
861
|
+
if (typeof schema.additionalProperties === "object" && schema.additionalProperties) convertSchemaTo31(schema.additionalProperties);
|
|
862
|
+
}
|
|
863
|
+
function collectSchemesFromTransports(transports, schemes) {
|
|
864
|
+
if (transports.bearer) schemes.bearerAuth = {
|
|
865
|
+
type: "http",
|
|
866
|
+
scheme: "bearer",
|
|
867
|
+
...transports.bearer.format ? { bearerFormat: transports.bearer.format } : {},
|
|
868
|
+
...transports.bearer.description ? { description: transports.bearer.description } : {}
|
|
869
|
+
};
|
|
870
|
+
if (transports.basic) schemes.basicAuth = {
|
|
871
|
+
type: "http",
|
|
872
|
+
scheme: "basic",
|
|
873
|
+
...transports.basic.description ? { description: transports.basic.description } : {}
|
|
874
|
+
};
|
|
875
|
+
if (transports.apiKey) schemes.apiKeyAuth = {
|
|
876
|
+
type: "apiKey",
|
|
877
|
+
name: transports.apiKey.name,
|
|
878
|
+
in: transports.apiKey.in,
|
|
879
|
+
...transports.apiKey.description ? { description: transports.apiKey.description } : {}
|
|
880
|
+
};
|
|
881
|
+
if (transports.cookie) schemes.cookieAuth = {
|
|
882
|
+
type: "apiKey",
|
|
883
|
+
name: transports.cookie.name,
|
|
884
|
+
in: "cookie",
|
|
885
|
+
...transports.cookie.description ? { description: transports.cookie.description } : {}
|
|
886
|
+
};
|
|
887
|
+
}
|
|
888
|
+
function transportsToSecurityRequirement(transports) {
|
|
889
|
+
const requirements = [];
|
|
890
|
+
if (transports.bearer) requirements.push({ bearerAuth: [] });
|
|
891
|
+
if (transports.basic) requirements.push({ basicAuth: [] });
|
|
892
|
+
if (transports.apiKey) requirements.push({ apiKeyAuth: [] });
|
|
893
|
+
if (transports.cookie) requirements.push({ cookieAuth: [] });
|
|
894
|
+
return requirements;
|
|
895
|
+
}
|
|
896
|
+
function resolveOperationSecurity(cmeta, hmeta, schemes) {
|
|
897
|
+
if (hmeta?.swaggerPublic) return [];
|
|
898
|
+
if (hmeta?.swaggerSecurity?.length) return hmeta.swaggerSecurity;
|
|
899
|
+
if (hmeta?.authTransports) {
|
|
900
|
+
collectSchemesFromTransports(hmeta.authTransports, schemes);
|
|
901
|
+
return transportsToSecurityRequirement(hmeta.authTransports);
|
|
902
|
+
}
|
|
903
|
+
if (cmeta?.swaggerPublic) return [];
|
|
904
|
+
if (cmeta?.swaggerSecurity?.length) return cmeta.swaggerSecurity;
|
|
905
|
+
if (cmeta?.authTransports) {
|
|
906
|
+
collectSchemesFromTransports(cmeta.authTransports, schemes);
|
|
907
|
+
return transportsToSecurityRequirement(cmeta.authTransports);
|
|
908
|
+
}
|
|
909
|
+
return void 0;
|
|
496
910
|
}
|
|
497
911
|
|
|
498
912
|
//#endregion
|
|
@@ -522,13 +936,10 @@ function _ts_param(paramIndex, decorator) {
|
|
|
522
936
|
};
|
|
523
937
|
}
|
|
524
938
|
var SwaggerController = class {
|
|
525
|
-
|
|
526
|
-
if (this.opts.cors)
|
|
527
|
-
const { enableCors } = (0, __wooksjs_event_http.useSetHeaders)();
|
|
528
|
-
enableCors(this.opts.cors === true ? void 0 : this.opts.cors);
|
|
529
|
-
}
|
|
939
|
+
processCors() {
|
|
940
|
+
if (this.opts.cors) (0, __wooksjs_event_http.useResponse)().enableCors(this.opts.cors === true ? void 0 : this.opts.cors);
|
|
530
941
|
}
|
|
531
|
-
|
|
942
|
+
serveIndex(url, location, status) {
|
|
532
943
|
this.processCors();
|
|
533
944
|
if (!url.endsWith("index.html") && !url.endsWith("/")) {
|
|
534
945
|
status.value = 302;
|
|
@@ -572,21 +983,30 @@ var SwaggerController = class {
|
|
|
572
983
|
});
|
|
573
984
|
};`;
|
|
574
985
|
}
|
|
575
|
-
async
|
|
576
|
-
this.processCors();
|
|
577
|
-
const logger = (0, moost.useEventLogger)("@moostjs/zod");
|
|
986
|
+
async resolveSpec() {
|
|
578
987
|
if (!this.spec) {
|
|
579
|
-
const
|
|
988
|
+
const ctx = (0, moost.current)();
|
|
989
|
+
const l = (0, moost.useLogger)(ctx);
|
|
990
|
+
const logger = typeof l.topic === "function" ? l.topic("@moostjs/swagger") : l;
|
|
991
|
+
const { instantiate } = (0, moost.useControllerContext)(ctx);
|
|
580
992
|
const moost$1 = await instantiate(moost.Moost);
|
|
581
993
|
this.spec = mapToSwaggerSpec(moost$1.getControllersOverview(), this.opts, logger);
|
|
582
994
|
}
|
|
583
995
|
return this.spec;
|
|
584
996
|
}
|
|
585
|
-
"
|
|
997
|
+
async "spec.json"() {
|
|
998
|
+
this.processCors();
|
|
999
|
+
return this.resolveSpec();
|
|
1000
|
+
}
|
|
1001
|
+
async "spec.yaml"() {
|
|
1002
|
+
this.processCors();
|
|
1003
|
+
return jsonToYaml(await this.resolveSpec());
|
|
1004
|
+
}
|
|
1005
|
+
files(url) {
|
|
586
1006
|
this.processCors();
|
|
587
1007
|
return this.serve(url.split("/").pop());
|
|
588
1008
|
}
|
|
589
|
-
|
|
1009
|
+
serve(path$1) {
|
|
590
1010
|
return (0, __wooksjs_http_static.serveFile)(path$1, {
|
|
591
1011
|
baseDir: this.assetPath,
|
|
592
1012
|
cacheControl: {
|
|
@@ -614,7 +1034,7 @@ _ts_decorate([
|
|
|
614
1034
|
_ts_metadata("design:type", Function),
|
|
615
1035
|
_ts_metadata("design:paramtypes", [
|
|
616
1036
|
String,
|
|
617
|
-
typeof
|
|
1037
|
+
typeof THeaderHook === "undefined" ? Object : THeaderHook,
|
|
618
1038
|
typeof TStatusHook === "undefined" ? Object : TStatusHook
|
|
619
1039
|
]),
|
|
620
1040
|
_ts_metadata("design:returntype", void 0)
|
|
@@ -632,6 +1052,13 @@ _ts_decorate([
|
|
|
632
1052
|
_ts_metadata("design:paramtypes", []),
|
|
633
1053
|
_ts_metadata("design:returntype", Promise)
|
|
634
1054
|
], SwaggerController.prototype, "spec.json", null);
|
|
1055
|
+
_ts_decorate([
|
|
1056
|
+
(0, __moostjs_event_http.Get)(),
|
|
1057
|
+
(0, __moostjs_event_http.SetHeader)("content-type", "text/yaml"),
|
|
1058
|
+
_ts_metadata("design:type", Function),
|
|
1059
|
+
_ts_metadata("design:paramtypes", []),
|
|
1060
|
+
_ts_metadata("design:returntype", Promise)
|
|
1061
|
+
], SwaggerController.prototype, "spec.yaml", null);
|
|
635
1062
|
_ts_decorate([
|
|
636
1063
|
(0, __moostjs_event_http.Get)("swagger-ui-bundle.*(js|js\\.map)"),
|
|
637
1064
|
(0, __moostjs_event_http.Get)("swagger-ui-standalone-preset.*(js|js\\.map)"),
|
|
@@ -644,7 +1071,6 @@ _ts_decorate([
|
|
|
644
1071
|
], SwaggerController.prototype, "files", null);
|
|
645
1072
|
SwaggerController = _ts_decorate([
|
|
646
1073
|
SwaggerExclude(),
|
|
647
|
-
(0, __moostjs_zod.ZodSkip)(),
|
|
648
1074
|
(0, moost.Controller)("api-docs"),
|
|
649
1075
|
_ts_param(0, (0, moost.Const)({ title: "Moost API" })),
|
|
650
1076
|
_ts_metadata("design:type", Function),
|
|
@@ -652,17 +1078,25 @@ SwaggerController = _ts_decorate([
|
|
|
652
1078
|
], SwaggerController);
|
|
653
1079
|
|
|
654
1080
|
//#endregion
|
|
1081
|
+
exports.SwaggerCallback = SwaggerCallback;
|
|
655
1082
|
Object.defineProperty(exports, 'SwaggerController', {
|
|
656
1083
|
enumerable: true,
|
|
657
1084
|
get: function () {
|
|
658
1085
|
return SwaggerController;
|
|
659
1086
|
}
|
|
660
1087
|
});
|
|
1088
|
+
exports.SwaggerDeprecated = SwaggerDeprecated;
|
|
661
1089
|
exports.SwaggerDescription = SwaggerDescription;
|
|
662
1090
|
exports.SwaggerExample = SwaggerExample;
|
|
663
1091
|
exports.SwaggerExclude = SwaggerExclude;
|
|
1092
|
+
exports.SwaggerExternalDocs = SwaggerExternalDocs;
|
|
1093
|
+
exports.SwaggerLink = SwaggerLink;
|
|
1094
|
+
exports.SwaggerOperationId = SwaggerOperationId;
|
|
664
1095
|
exports.SwaggerParam = SwaggerParam;
|
|
1096
|
+
exports.SwaggerPublic = SwaggerPublic;
|
|
665
1097
|
exports.SwaggerRequestBody = SwaggerRequestBody;
|
|
666
1098
|
exports.SwaggerResponse = SwaggerResponse;
|
|
1099
|
+
exports.SwaggerSecurity = SwaggerSecurity;
|
|
1100
|
+
exports.SwaggerSecurityAll = SwaggerSecurityAll;
|
|
667
1101
|
exports.SwaggerTag = SwaggerTag;
|
|
668
1102
|
exports.getSwaggerMate = getSwaggerMate;
|