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