@moostjs/swagger 0.5.33 → 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 +451 -43
- package/dist/index.d.ts +286 -19
- package/dist/index.mjs +445 -45
- package/package.json +32 -30
package/dist/index.cjs
CHANGED
|
@@ -29,34 +29,50 @@ const path = __toESM(require("path"));
|
|
|
29
29
|
const swagger_ui_dist = __toESM(require("swagger-ui-dist"));
|
|
30
30
|
|
|
31
31
|
//#region packages/swagger/src/swagger.mate.ts
|
|
32
|
-
function getSwaggerMate() {
|
|
32
|
+
/** Returns the shared `Mate` instance extended with Swagger/OpenAPI metadata fields. */ function getSwaggerMate() {
|
|
33
33
|
return (0, moost.getMoostMate)();
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
//#endregion
|
|
37
37
|
//#region packages/swagger/src/decorators.ts
|
|
38
|
-
const SwaggerTag = (tag) => getSwaggerMate().decorate("swaggerTags", tag, true);
|
|
39
|
-
const SwaggerExclude = () => getSwaggerMate().decorate("swaggerExclude", true);
|
|
40
|
-
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);
|
|
41
41
|
function SwaggerResponse(code, opts, example) {
|
|
42
42
|
return getSwaggerMate().decorate((meta) => {
|
|
43
43
|
let ex;
|
|
44
44
|
if (example) ex = example;
|
|
45
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
|
+
}
|
|
46
50
|
meta.swaggerResponses = meta.swaggerResponses || {};
|
|
47
51
|
const keyCode = typeof code === "number" ? code : 0;
|
|
48
52
|
const opt = typeof code === "number" ? opts : code;
|
|
49
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;
|
|
50
56
|
const response = ["object", "function"].includes(typeof opt.response) ? opt.response : opt;
|
|
51
|
-
meta.swaggerResponses[keyCode] = meta.swaggerResponses[keyCode] || {};
|
|
52
|
-
meta.swaggerResponses[keyCode][contentType] = {
|
|
57
|
+
meta.swaggerResponses[keyCode] = meta.swaggerResponses[keyCode] || { content: {} };
|
|
58
|
+
meta.swaggerResponses[keyCode].content[contentType] = {
|
|
53
59
|
response,
|
|
54
60
|
example: ex
|
|
55
61
|
};
|
|
62
|
+
if (description) meta.swaggerResponses[keyCode].description = description;
|
|
63
|
+
if (headers) meta.swaggerResponses[keyCode].headers = {
|
|
64
|
+
...meta.swaggerResponses[keyCode].headers,
|
|
65
|
+
...headers
|
|
66
|
+
};
|
|
56
67
|
return meta;
|
|
57
68
|
});
|
|
58
69
|
}
|
|
59
|
-
|
|
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) {
|
|
60
76
|
return getSwaggerMate().decorate((meta) => {
|
|
61
77
|
meta.swaggerRequestBody = meta.swaggerRequestBody || {};
|
|
62
78
|
const contentType = typeof opt.contentType === "string" ? opt.contentType : "application/json";
|
|
@@ -65,12 +81,127 @@ function SwaggerRequestBody(opt) {
|
|
|
65
81
|
return meta;
|
|
66
82
|
});
|
|
67
83
|
}
|
|
68
|
-
function SwaggerParam(opts) {
|
|
84
|
+
/** Defines a parameter (query, path, header, cookie) in the OpenAPI spec. */ function SwaggerParam(opts) {
|
|
69
85
|
return getSwaggerMate().decorate("swaggerParams", opts, true);
|
|
70
86
|
}
|
|
71
|
-
function SwaggerExample(example) {
|
|
87
|
+
/** Attaches an example value to a handler's OpenAPI documentation. */ function SwaggerExample(example) {
|
|
72
88
|
return getSwaggerMate().decorate("swaggerExample", example);
|
|
73
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
|
+
}
|
|
74
205
|
|
|
75
206
|
//#endregion
|
|
76
207
|
//#region packages/swagger/src/mapping.ts
|
|
@@ -79,16 +210,26 @@ let schemaRefs = /* @__PURE__ */ new WeakMap();
|
|
|
79
210
|
const nameToType = /* @__PURE__ */ new Map();
|
|
80
211
|
function mapToSwaggerSpec(metadata, options, logger) {
|
|
81
212
|
resetSchemaRegistry();
|
|
213
|
+
const is31 = options?.openapiVersion === "3.1";
|
|
214
|
+
const collectedSecuritySchemes = { ...options?.securitySchemes };
|
|
82
215
|
const swaggerSpec = {
|
|
83
|
-
openapi: "3.0.0",
|
|
216
|
+
openapi: is31 ? "3.1.0" : "3.0.0",
|
|
84
217
|
info: {
|
|
85
218
|
title: options?.title || "API Documentation",
|
|
86
|
-
|
|
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 } : {}
|
|
87
224
|
},
|
|
88
225
|
paths: {},
|
|
89
226
|
tags: [],
|
|
227
|
+
...options?.servers?.length ? { servers: options.servers } : {},
|
|
228
|
+
...options?.externalDocs ? { externalDocs: options.externalDocs } : {},
|
|
229
|
+
...options?.security ? { security: options.security } : {},
|
|
90
230
|
components: { schemas: globalSchemas }
|
|
91
231
|
};
|
|
232
|
+
const deferredLinks = [];
|
|
92
233
|
for (const controller of metadata) {
|
|
93
234
|
const cmeta = controller.meta;
|
|
94
235
|
if (cmeta?.swaggerExclude) continue;
|
|
@@ -100,13 +241,14 @@ function mapToSwaggerSpec(metadata, options, logger) {
|
|
|
100
241
|
const uniqueParams = {};
|
|
101
242
|
const handlerPath = handler.registeredAs[0].path;
|
|
102
243
|
const handlerMethod = hh.method?.toLowerCase() || "get";
|
|
103
|
-
const
|
|
244
|
+
const handlerSummary = hmeta?.label;
|
|
245
|
+
const handlerDescription = hmeta?.swaggerDescription || hmeta?.description;
|
|
104
246
|
const handlerTags = [...controllerTags, ...hmeta?.swaggerTags || []];
|
|
105
247
|
if (!swaggerSpec.paths[handlerPath]) swaggerSpec.paths[handlerPath] = {};
|
|
106
248
|
let responses;
|
|
107
|
-
if (hmeta?.swaggerResponses) for (const [code,
|
|
249
|
+
if (hmeta?.swaggerResponses) for (const [code, responseEntry] of Object.entries(hmeta.swaggerResponses)) {
|
|
108
250
|
const newCode = code === "0" ? getDefaultStatusCode(handlerMethod) : code;
|
|
109
|
-
for (const [contentType, conf] of Object.entries(
|
|
251
|
+
for (const [contentType, conf] of Object.entries(responseEntry.content)) {
|
|
110
252
|
const { response, example } = conf;
|
|
111
253
|
const schema = resolveSwaggerSchemaFromConfig(response);
|
|
112
254
|
if (schema) {
|
|
@@ -115,16 +257,35 @@ function mapToSwaggerSpec(metadata, options, logger) {
|
|
|
115
257
|
...schema,
|
|
116
258
|
example
|
|
117
259
|
} : schema;
|
|
118
|
-
responses[newCode]
|
|
260
|
+
if (!responses[newCode]) responses[newCode] = {
|
|
261
|
+
description: responseEntry.description || defaultStatusDescription(Number(newCode)),
|
|
262
|
+
content: {}
|
|
263
|
+
};
|
|
264
|
+
responses[newCode].content[contentType] = { schema: schemaWithExample };
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
if (responseEntry.headers) {
|
|
268
|
+
const resolvedHeaders = resolveResponseHeaders(responseEntry.headers);
|
|
269
|
+
if (resolvedHeaders) {
|
|
270
|
+
responses = responses || {};
|
|
271
|
+
if (!responses[newCode]) responses[newCode] = {
|
|
272
|
+
description: defaultStatusDescription(Number(newCode)),
|
|
273
|
+
content: {}
|
|
274
|
+
};
|
|
275
|
+
responses[newCode].headers = resolvedHeaders;
|
|
119
276
|
}
|
|
120
277
|
}
|
|
121
278
|
}
|
|
122
|
-
|
|
279
|
+
const defaultCode = getDefaultStatusCode(handlerMethod);
|
|
280
|
+
if (!responses?.[defaultCode] && hmeta?.returnType) {
|
|
123
281
|
const ensured = ensureSchema(hmeta.returnType);
|
|
124
282
|
const schema = toSchemaOrRef(ensured);
|
|
125
283
|
if (schema) {
|
|
126
284
|
responses = responses || {};
|
|
127
|
-
responses[
|
|
285
|
+
responses[defaultCode] = {
|
|
286
|
+
description: defaultStatusDescription(Number(defaultCode)),
|
|
287
|
+
content: { "*/*": { schema } }
|
|
288
|
+
};
|
|
128
289
|
}
|
|
129
290
|
}
|
|
130
291
|
let reqBodyRequired = true;
|
|
@@ -134,13 +295,39 @@ function mapToSwaggerSpec(metadata, options, logger) {
|
|
|
134
295
|
if (schema) bodyContent[contentType] = { schema };
|
|
135
296
|
}
|
|
136
297
|
swaggerSpec.paths[handlerPath][handlerMethod] = {
|
|
137
|
-
summary:
|
|
138
|
-
|
|
298
|
+
summary: handlerSummary,
|
|
299
|
+
description: handlerDescription,
|
|
300
|
+
operationId: hmeta?.swaggerOperationId || hmeta?.id || `${handlerMethod.toUpperCase()}_${handlerPath.replaceAll(/\//g, "_").replaceAll(/[{}]/g, "__").replaceAll(/[^\dA-Za-z]/g, "_")}`,
|
|
139
301
|
tags: handlerTags,
|
|
140
302
|
parameters: [],
|
|
141
303
|
responses
|
|
142
304
|
};
|
|
143
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
|
+
}
|
|
144
331
|
function addParam(param) {
|
|
145
332
|
const key = `${param.in}//${param.name}`;
|
|
146
333
|
if (uniqueParams[key]) {
|
|
@@ -179,8 +366,7 @@ function mapToSwaggerSpec(metadata, options, logger) {
|
|
|
179
366
|
schema
|
|
180
367
|
});
|
|
181
368
|
}
|
|
182
|
-
for (
|
|
183
|
-
const paramMeta = handler.meta.params[i];
|
|
369
|
+
for (const paramMeta of handler.meta.params) {
|
|
184
370
|
if (paramMeta.paramSource && ["QUERY_ITEM", "QUERY"].includes(paramMeta.paramSource)) {
|
|
185
371
|
const ensured = ensureSchema(paramMeta.type);
|
|
186
372
|
if (paramMeta.paramSource === "QUERY_ITEM") {
|
|
@@ -232,7 +418,7 @@ function mapToSwaggerSpec(metadata, options, logger) {
|
|
|
232
418
|
}
|
|
233
419
|
}
|
|
234
420
|
const bodyEntries = Object.entries(bodyContent).filter((entry) => entry[1] && entry[1].schema !== void 0);
|
|
235
|
-
if (bodyEntries.length) {
|
|
421
|
+
if (bodyEntries.length > 0) {
|
|
236
422
|
const content = {};
|
|
237
423
|
for (const [contentType, { schema }] of bodyEntries) content[contentType] = { schema };
|
|
238
424
|
endpointSpec.requestBody = {
|
|
@@ -242,6 +428,54 @@ function mapToSwaggerSpec(metadata, options, logger) {
|
|
|
242
428
|
}
|
|
243
429
|
}
|
|
244
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: {}
|
|
463
|
+
};
|
|
464
|
+
const responseEntry = endpointSpec.responses[statusCode];
|
|
465
|
+
if (!responseEntry.links) responseEntry.links = {};
|
|
466
|
+
responseEntry.links[config.name] = link;
|
|
467
|
+
}
|
|
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);
|
|
245
479
|
return swaggerSpec;
|
|
246
480
|
}
|
|
247
481
|
function resolveSwaggerSchemaFromConfig(type) {
|
|
@@ -249,6 +483,20 @@ function resolveSwaggerSchemaFromConfig(type) {
|
|
|
249
483
|
const ensured = ensureSchema(type);
|
|
250
484
|
return toSchemaOrRef(ensured);
|
|
251
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;
|
|
497
|
+
}
|
|
498
|
+
return hasAny ? resolved : void 0;
|
|
499
|
+
}
|
|
252
500
|
function toSchemaOrRef(result) {
|
|
253
501
|
if (!result) return void 0;
|
|
254
502
|
if (result.ref) return { $ref: result.ref };
|
|
@@ -257,7 +505,7 @@ function toSchemaOrRef(result) {
|
|
|
257
505
|
function inferBodyContentType(schema, resolved) {
|
|
258
506
|
const target = resolved ?? resolveSchemaFromRef(schema);
|
|
259
507
|
const schemaType = target?.type ?? schema.type;
|
|
260
|
-
if (schemaType && [
|
|
508
|
+
if (typeof schemaType === "string" && [
|
|
261
509
|
"string",
|
|
262
510
|
"number",
|
|
263
511
|
"integer",
|
|
@@ -423,22 +671,60 @@ function ensureComponentName(typeRef, schema, suggestedName) {
|
|
|
423
671
|
schemaRefs.set(typeRef, candidate);
|
|
424
672
|
applySwaggerMetadata(typeRef, schema);
|
|
425
673
|
globalSchemas[candidate] = cloneSchema(schema);
|
|
674
|
+
hoistDefs(globalSchemas[candidate]);
|
|
426
675
|
return candidate;
|
|
427
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
|
+
}
|
|
428
706
|
function applySwaggerMetadata(typeRef, schema) {
|
|
429
707
|
try {
|
|
430
708
|
const mate = getSwaggerMate();
|
|
431
709
|
const meta = mate.read(typeRef);
|
|
432
|
-
if (
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
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;
|
|
716
|
+
}
|
|
438
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;
|
|
723
|
+
}
|
|
724
|
+
}
|
|
439
725
|
}
|
|
440
726
|
function sanitizeComponentName(name) {
|
|
441
|
-
const sanitized = name.
|
|
727
|
+
const sanitized = name.replaceAll(/[^A-Za-z0-9_.-]/g, "_");
|
|
442
728
|
return sanitized || "Schema";
|
|
443
729
|
}
|
|
444
730
|
function getTypeName(typeRef) {
|
|
@@ -521,6 +807,107 @@ function getDefaultStatusCode(httpMethod) {
|
|
|
521
807
|
};
|
|
522
808
|
return defaultStatusCodes[httpMethod.toUpperCase()] || 200;
|
|
523
809
|
}
|
|
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;
|
|
910
|
+
}
|
|
524
911
|
|
|
525
912
|
//#endregion
|
|
526
913
|
//#region packages/swagger/src/swagger.controller.ts
|
|
@@ -549,13 +936,10 @@ function _ts_param(paramIndex, decorator) {
|
|
|
549
936
|
};
|
|
550
937
|
}
|
|
551
938
|
var SwaggerController = class {
|
|
552
|
-
|
|
553
|
-
if (this.opts.cors)
|
|
554
|
-
const { enableCors } = (0, __wooksjs_event_http.useSetHeaders)();
|
|
555
|
-
enableCors(this.opts.cors === true ? void 0 : this.opts.cors);
|
|
556
|
-
}
|
|
939
|
+
processCors() {
|
|
940
|
+
if (this.opts.cors) (0, __wooksjs_event_http.useResponse)().enableCors(this.opts.cors === true ? void 0 : this.opts.cors);
|
|
557
941
|
}
|
|
558
|
-
|
|
942
|
+
serveIndex(url, location, status) {
|
|
559
943
|
this.processCors();
|
|
560
944
|
if (!url.endsWith("index.html") && !url.endsWith("/")) {
|
|
561
945
|
status.value = 302;
|
|
@@ -599,21 +983,30 @@ var SwaggerController = class {
|
|
|
599
983
|
});
|
|
600
984
|
};`;
|
|
601
985
|
}
|
|
602
|
-
async
|
|
603
|
-
this.processCors();
|
|
604
|
-
const logger = (0, moost.useEventLogger)("@moostjs/zod");
|
|
986
|
+
async resolveSpec() {
|
|
605
987
|
if (!this.spec) {
|
|
606
|
-
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);
|
|
607
992
|
const moost$1 = await instantiate(moost.Moost);
|
|
608
993
|
this.spec = mapToSwaggerSpec(moost$1.getControllersOverview(), this.opts, logger);
|
|
609
994
|
}
|
|
610
995
|
return this.spec;
|
|
611
996
|
}
|
|
612
|
-
"
|
|
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) {
|
|
613
1006
|
this.processCors();
|
|
614
1007
|
return this.serve(url.split("/").pop());
|
|
615
1008
|
}
|
|
616
|
-
|
|
1009
|
+
serve(path$1) {
|
|
617
1010
|
return (0, __wooksjs_http_static.serveFile)(path$1, {
|
|
618
1011
|
baseDir: this.assetPath,
|
|
619
1012
|
cacheControl: {
|
|
@@ -641,7 +1034,7 @@ _ts_decorate([
|
|
|
641
1034
|
_ts_metadata("design:type", Function),
|
|
642
1035
|
_ts_metadata("design:paramtypes", [
|
|
643
1036
|
String,
|
|
644
|
-
typeof
|
|
1037
|
+
typeof THeaderHook === "undefined" ? Object : THeaderHook,
|
|
645
1038
|
typeof TStatusHook === "undefined" ? Object : TStatusHook
|
|
646
1039
|
]),
|
|
647
1040
|
_ts_metadata("design:returntype", void 0)
|
|
@@ -659,6 +1052,13 @@ _ts_decorate([
|
|
|
659
1052
|
_ts_metadata("design:paramtypes", []),
|
|
660
1053
|
_ts_metadata("design:returntype", Promise)
|
|
661
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);
|
|
662
1062
|
_ts_decorate([
|
|
663
1063
|
(0, __moostjs_event_http.Get)("swagger-ui-bundle.*(js|js\\.map)"),
|
|
664
1064
|
(0, __moostjs_event_http.Get)("swagger-ui-standalone-preset.*(js|js\\.map)"),
|
|
@@ -678,17 +1078,25 @@ SwaggerController = _ts_decorate([
|
|
|
678
1078
|
], SwaggerController);
|
|
679
1079
|
|
|
680
1080
|
//#endregion
|
|
1081
|
+
exports.SwaggerCallback = SwaggerCallback;
|
|
681
1082
|
Object.defineProperty(exports, 'SwaggerController', {
|
|
682
1083
|
enumerable: true,
|
|
683
1084
|
get: function () {
|
|
684
1085
|
return SwaggerController;
|
|
685
1086
|
}
|
|
686
1087
|
});
|
|
1088
|
+
exports.SwaggerDeprecated = SwaggerDeprecated;
|
|
687
1089
|
exports.SwaggerDescription = SwaggerDescription;
|
|
688
1090
|
exports.SwaggerExample = SwaggerExample;
|
|
689
1091
|
exports.SwaggerExclude = SwaggerExclude;
|
|
1092
|
+
exports.SwaggerExternalDocs = SwaggerExternalDocs;
|
|
1093
|
+
exports.SwaggerLink = SwaggerLink;
|
|
1094
|
+
exports.SwaggerOperationId = SwaggerOperationId;
|
|
690
1095
|
exports.SwaggerParam = SwaggerParam;
|
|
1096
|
+
exports.SwaggerPublic = SwaggerPublic;
|
|
691
1097
|
exports.SwaggerRequestBody = SwaggerRequestBody;
|
|
692
1098
|
exports.SwaggerResponse = SwaggerResponse;
|
|
1099
|
+
exports.SwaggerSecurity = SwaggerSecurity;
|
|
1100
|
+
exports.SwaggerSecurityAll = SwaggerSecurityAll;
|
|
693
1101
|
exports.SwaggerTag = SwaggerTag;
|
|
694
1102
|
exports.getSwaggerMate = getSwaggerMate;
|