@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.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
- function SwaggerRequestBody(opt) {
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
- version: options?.version || "1.0.0"
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 handlerDescription = hmeta?.description;
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, responseConfigs] of Object.entries(hmeta.swaggerResponses)) {
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(responseConfigs)) {
251
+ for (const [contentType, conf] of Object.entries(responseEntry.content)) {
108
252
  const { response, example } = conf;
109
- const schema = getSwaggerSchemaFromSwaggerConfigType(response);
253
+ const schema = resolveSwaggerSchemaFromConfig(response);
110
254
  if (schema) {
111
255
  responses = responses || {};
112
- responses[newCode] = { content: { [contentType]: { schema: {
256
+ const schemaWithExample = example !== void 0 ? {
113
257
  ...schema,
114
- example: example || schema.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
- else if (hmeta?.returnType) {
120
- const parsed = myParseZod((0, __moostjs_zod.getZodType)({ type: hmeta.returnType }));
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[getDefaultStatusCode(handlerMethod)] = { content: { "*/*": { schema } } };
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 bodyConfig = {};
292
+ const bodyContent = {};
137
293
  if (hmeta?.swaggerRequestBody) for (const [contentType, type] of Object.entries(hmeta.swaggerRequestBody)) {
138
- let zt;
139
- let schema;
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: handlerDescription,
156
- operationId: `${handlerMethod.toUpperCase()}_${handlerPath.replace(/\//g, "_").replace(/[{}]/g, "__").replace(/[^\dA-Za-z]/g, "_")}`,
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: getSwaggerSchemaFromSwaggerConfigType(param.type) || { type: "string" }
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: getSwaggerSchemaFromSwaggerConfigType(param.type) || { type: "string" }
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
- let schema;
191
- let parsed;
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.optional && !parsed?.$optional,
209
- schema: schema || { type: "string" }
365
+ required: !paramMeta?.optional,
366
+ schema
210
367
  });
211
368
  }
212
- for (let i = 0; i < handler.meta.params.length; i++) {
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 zodType = (0, __moostjs_zod.getZodTypeForProp)({
216
- type: controller.type,
217
- key: handler.method,
218
- index: i
219
- }, {
220
- type: paramMeta.type,
221
- additionalMeta: paramMeta
222
- }, void 0, logger);
223
- const parsed = myParseZod(zodType);
224
- const schema = getSwaggerSchema(parsed, true);
225
- if (paramMeta.paramSource === "QUERY_ITEM") endpointSpec.parameters.push({
226
- name: paramMeta.paramName || "",
227
- in: "query",
228
- description: paramMeta.description,
229
- required: !paramMeta.optional && !parsed.$optional,
230
- schema: schema || { type: "string" }
231
- });
232
- else if (paramMeta.paramSource === "QUERY" && parsed.$type === "ZodObject") for (const [key, value] of Object.entries(parsed.$inner)) {
233
- const schema$1 = getSwaggerSchema(value, true);
234
- if (schema$1) {
235
- const swaggerSchema = {
236
- name: key,
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: value.description,
239
- required: !parsed.$optional && !value.$optional,
240
- schema: schema$1
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 zodType = (0, __moostjs_zod.getZodTypeForProp)({
248
- type: controller.type,
249
- key: handler.method,
250
- index: i
251
- }, {
252
- type: paramMeta.type,
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
- if (bodyConfig && Object.entries(bodyConfig).some((e) => !!e[1])) swaggerSpec.paths[handlerPath][handlerMethod].requestBody = {
276
- content: bodyConfig,
277
- required: reqBodyRequired
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
- const globalSchemas = {};
284
- function getSwaggerSchema(parsed, forParam) {
285
- const zodType = parsed.$ref;
286
- const meta = zodType.__type_ref ? getSwaggerMate().read(zodType.__type_ref) : void 0;
287
- if (!forParam && zodType.__type_ref && globalSchemas[zodType.__type_ref.name]) return { $ref: `#/components/schemas/${zodType.__type_ref.name}` };
288
- if (forParam && zodType.__type_ref && globalSchemas[zodType.__type_ref.name]) return globalSchemas[zodType.__type_ref.name];
289
- const schema = {};
290
- if (meta) {
291
- if (meta.swaggerExample) schema.example = meta.swaggerExample;
292
- if (meta.label || meta.id) schema.title = meta.label || meta.id;
293
- if (meta.description) schema.description = meta.description;
294
- }
295
- if (!forParam && zodType.__type_ref) globalSchemas[zodType.__type_ref.name] = schema;
296
- function asString() {
297
- schema.type = "string";
298
- if (parsed.$checks) {
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
- function asLiteral() {
304
- if (parsed.$type === "ZodLiteral") {
305
- schema.type = "string";
306
- schema.enum = [parsed.$value];
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
- function asEnum() {
310
- if (parsed.$type === "ZodEnum") {
311
- schema.type = "string";
312
- schema.enum = parsed.$value;
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
- function asNativeEnum() {
316
- if (parsed.$type === "ZodNativeEnum") {
317
- schema.type = "string";
318
- schema.enum = Object.keys(parsed.$value);
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 (forParam) switch (parsed.$type) {
322
- case "ZodAny":
323
- case "ZodUnknown":
324
- case "ZodString": {
325
- asString();
326
- break;
327
- }
328
- case "ZodNumber": {
329
- schema.type = "number";
330
- break;
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
- else switch (parsed.$type) {
363
- case "ZodString": {
364
- asString();
365
- break;
366
- }
367
- case "ZodNumber": {
368
- schema.type = "number";
369
- break;
370
- }
371
- case "ZodBigInt": {
372
- schema.type = "integer";
373
- break;
374
- }
375
- case "ZodBoolean": {
376
- schema.type = "boolean";
377
- break;
378
- }
379
- case "ZodLiteral": {
380
- asLiteral();
381
- break;
382
- }
383
- case "ZodEnum": {
384
- asEnum();
385
- break;
386
- }
387
- case "ZodNativeEnum": {
388
- asNativeEnum();
389
- break;
390
- }
391
- case "ZodDate": {
392
- schema.type = "string";
393
- break;
394
- }
395
- case "ZodNull": {
396
- schema.type = "null";
397
- break;
398
- }
399
- case "ZodFunction":
400
- case "ZodSymbol":
401
- case "ZodUndefined":
402
- case "ZodUnknown":
403
- case "ZodNever":
404
- case "ZodVoid":
405
- case "ZodNaN": return void 0;
406
- case "ZodArray": {
407
- schema.type = "array";
408
- schema.minItems = parsed.$checks?.minLength || void 0;
409
- schema.maxItems = parsed.$checks?.maxLength || void 0;
410
- schema.items = getSwaggerSchema(parsed.$inner);
411
- break;
412
- }
413
- case "ZodTuple": {
414
- schema.type = "array";
415
- schema.items = parsed.$inner.map((t) => getSwaggerSchema(t)).filter((t) => !!t);
416
- break;
417
- }
418
- case "ZodObject": {
419
- schema.type = "object";
420
- schema.properties = {};
421
- schema.required = [];
422
- if (zodType._def.unknownKeys === "passthrough") schema.additionalProperties = {};
423
- for (const [key, val] of Object.entries(parsed.$inner)) {
424
- const prop = getSwaggerSchema(val);
425
- if (prop) {
426
- schema.properties[key] = prop;
427
- if (!val.$optional) schema.required.push(key);
428
- }
429
- }
430
- break;
431
- }
432
- case "ZodPromise":
433
- case "ZodRecord":
434
- case "ZodMap":
435
- case "ZodSet": {
436
- schema.type = "object";
437
- schema.properties = {};
438
- schema.additionalProperties = parsed.$type === "ZodRecord" ? {} : void 0;
439
- break;
440
- }
441
- case "ZodUnion":
442
- case "ZodDiscriminatedUnion": {
443
- schema.oneOf = parsed.$inner.map((t) => getSwaggerSchema(t)).filter((t) => !!t);
444
- break;
445
- }
446
- case "ZodIntersection": {
447
- schema.allOf = parsed.$inner.map((t) => getSwaggerSchema(t)).filter((t) => !!t);
448
- break;
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
- case "ZodLazy": return getSwaggerSchema(parsed.$get());
451
- default: return void 0;
452
- }
453
- if (parsed.$nullable) schema.nullable = parsed.$nullable;
454
- if (parsed.$ref._def.description) schema.description = parsed.$ref._def.description;
455
- if (parsed.$checks) {
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 myParseZod(schema) {
469
- return (0, zod_parser.parseZodType)(schema);
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
- function getSwaggerSchemaFromSwaggerConfigType(type) {
481
- let schema;
482
- let zt;
483
- if (type instanceof __moostjs_zod.z.ZodType) zt = type;
484
- else if (typeof type === "function") zt = (0, __moostjs_zod.getZodType)({ type });
485
- if (zt) {
486
- const parsed = myParseZod(zt);
487
- if ([
488
- "ZodString",
489
- "ZodNumber",
490
- "ZodObject",
491
- "ZodArray",
492
- "ZodBoolean"
493
- ].includes(parsed.$type)) schema = getSwaggerSchema(parsed);
494
- } else if (type.type || type.$ref) schema = type;
495
- return schema;
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
- "processCors"() {
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
- "serveIndex"(url, location, status) {
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 "spec.json"() {
576
- this.processCors();
577
- const logger = (0, moost.useEventLogger)("@moostjs/zod");
986
+ async resolveSpec() {
578
987
  if (!this.spec) {
579
- const { instantiate } = (0, moost.useControllerContext)();
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
- "files"(url) {
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
- "serve"(path$1) {
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 __wooksjs_event_http.THeaderHook === "undefined" ? Object : __wooksjs_event_http.THeaderHook,
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;