@forinda/kickjs-swagger 1.2.11 → 1.2.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -5,6 +5,10 @@ Auto-generated OpenAPI spec from decorators, Swagger UI, and ReDoc for KickJS.
5
5
  ## Install
6
6
 
7
7
  ```bash
8
+ # Using the KickJS CLI (recommended)
9
+ kick add swagger
10
+
11
+ # Manual install
8
12
  pnpm add @forinda/kickjs-swagger @forinda/kickjs-core
9
13
  ```
10
14
 
package/dist/index.js CHANGED
@@ -1,420 +1,9 @@
1
- var __defProp = Object.defineProperty;
2
- var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
-
4
- // src/schema-parser.ts
5
- var zodSchemaParser = {
6
- name: "zod",
7
- supports(schema) {
8
- return schema != null && typeof schema === "object" && typeof schema.safeParse === "function" && typeof schema.toJSONSchema === "function";
9
- },
10
- toJsonSchema(schema) {
11
- const { $schema: _, ...rest } = schema.toJSONSchema();
12
- return rest;
13
- }
14
- };
15
-
16
- // src/decorators.ts
17
- import "reflect-metadata";
18
- var SWAGGER_KEYS = {
19
- OPERATION: /* @__PURE__ */ Symbol("kick:swagger:operation"),
20
- RESPONSES: /* @__PURE__ */ Symbol("kick:swagger:responses"),
21
- TAGS: /* @__PURE__ */ Symbol("kick:swagger:tags"),
22
- BEARER_AUTH: /* @__PURE__ */ Symbol("kick:swagger:bearer"),
23
- EXCLUDE: /* @__PURE__ */ Symbol("kick:swagger:exclude")
24
- };
25
- function ApiOperation(options) {
26
- return (target, propertyKey) => {
27
- Reflect.defineMetadata(SWAGGER_KEYS.OPERATION, options, target.constructor, propertyKey);
28
- };
29
- }
30
- __name(ApiOperation, "ApiOperation");
31
- function ApiResponse(options) {
32
- return (target, propertyKey) => {
33
- const existing = Reflect.getMetadata(SWAGGER_KEYS.RESPONSES, target.constructor, propertyKey) || [];
34
- Reflect.defineMetadata(SWAGGER_KEYS.RESPONSES, [
35
- ...existing,
36
- options
37
- ], target.constructor, propertyKey);
38
- };
39
- }
40
- __name(ApiResponse, "ApiResponse");
41
- function ApiTags(...tags) {
42
- return (target, propertyKey) => {
43
- if (propertyKey) {
44
- Reflect.defineMetadata(SWAGGER_KEYS.TAGS, tags, target.constructor, propertyKey);
45
- } else {
46
- Reflect.defineMetadata(SWAGGER_KEYS.TAGS, tags, target);
47
- }
48
- };
49
- }
50
- __name(ApiTags, "ApiTags");
51
- function ApiBearerAuth(name = "BearerAuth") {
52
- return (target, propertyKey) => {
53
- if (propertyKey) {
54
- Reflect.defineMetadata(SWAGGER_KEYS.BEARER_AUTH, name, target.constructor, propertyKey);
55
- } else {
56
- Reflect.defineMetadata(SWAGGER_KEYS.BEARER_AUTH, name, target);
57
- }
58
- };
59
- }
60
- __name(ApiBearerAuth, "ApiBearerAuth");
61
- function ApiExclude() {
62
- return (target, propertyKey) => {
63
- if (propertyKey) {
64
- Reflect.defineMetadata(SWAGGER_KEYS.EXCLUDE, true, target.constructor, propertyKey);
65
- } else {
66
- Reflect.defineMetadata(SWAGGER_KEYS.EXCLUDE, true, target);
67
- }
68
- };
69
- }
70
- __name(ApiExclude, "ApiExclude");
71
-
72
- // src/openapi-builder.ts
73
- import "reflect-metadata";
74
- import { METADATA, joinPaths } from "@forinda/kickjs-core";
75
- var registeredRoutes = [];
76
- function registerControllerForDocs(controllerClass, mountPath) {
77
- registeredRoutes.push({
78
- controllerClass,
79
- mountPath
80
- });
81
- }
82
- __name(registerControllerForDocs, "registerControllerForDocs");
83
- function clearRegisteredRoutes() {
84
- registeredRoutes.length = 0;
85
- }
86
- __name(clearRegisteredRoutes, "clearRegisteredRoutes");
87
- function buildOpenAPISpec(options = {}) {
88
- const parser = options.schemaParser ?? zodSchemaParser;
89
- const toJsonSchema = /* @__PURE__ */ __name((schema) => {
90
- try {
91
- if (!parser.supports(schema)) return null;
92
- return parser.toJsonSchema(schema);
93
- } catch {
94
- return null;
95
- }
96
- }, "toJsonSchema");
97
- const componentSchemas = {};
98
- let schemaCounter = 0;
99
- const registerSchema = /* @__PURE__ */ __name((jsonSchema, hint) => {
100
- let name = jsonSchema.title || jsonSchema.label || hint || "";
101
- if (!name) {
102
- name = `Schema${++schemaCounter}`;
103
- }
104
- name = name.replace(/[^a-zA-Z0-9]/g, "");
105
- if (!componentSchemas[name]) {
106
- const clean = {
107
- ...jsonSchema
108
- };
109
- delete clean.title;
110
- delete clean.label;
111
- delete clean.$schema;
112
- componentSchemas[name] = clean;
113
- }
114
- return {
115
- $ref: `#/components/schemas/${name}`
116
- };
117
- }, "registerSchema");
118
- const spec = {
119
- openapi: "3.0.3",
120
- info: {
121
- title: options.info?.title || "API",
122
- version: options.info?.version || "1.0.0",
123
- ...options.info?.description ? {
124
- description: options.info.description
125
- } : {}
126
- },
127
- paths: {},
128
- components: {
129
- schemas: {},
130
- securitySchemes: {}
131
- },
132
- tags: []
133
- };
134
- if (options.servers) {
135
- spec.servers = options.servers;
136
- }
137
- const allTags = /* @__PURE__ */ new Set();
138
- const securitySchemes = {};
139
- for (const { controllerClass, mountPath } of registeredRoutes) {
140
- if (Reflect.getMetadata(SWAGGER_KEYS.EXCLUDE, controllerClass)) continue;
141
- const routes = Reflect.getMetadata(METADATA.ROUTES, controllerClass) || [];
142
- const classTags = Reflect.getMetadata(SWAGGER_KEYS.TAGS, controllerClass) || [];
143
- const classAuth = Reflect.getMetadata(SWAGGER_KEYS.BEARER_AUTH, controllerClass);
144
- for (const route of routes) {
145
- if (Reflect.getMetadata(SWAGGER_KEYS.EXCLUDE, controllerClass, route.handlerName)) continue;
146
- const fullPath = joinPaths(mountPath, route.path);
147
- const openApiPath = fullPath.replace(/:([a-zA-Z_]+)/g, "{$1}");
148
- const method = route.method.toLowerCase();
149
- const operation = Reflect.getMetadata(SWAGGER_KEYS.OPERATION, controllerClass, route.handlerName) || {};
150
- const responses = Reflect.getMetadata(SWAGGER_KEYS.RESPONSES, controllerClass, route.handlerName) || [];
151
- const methodTags = Reflect.getMetadata(SWAGGER_KEYS.TAGS, controllerClass, route.handlerName) || [];
152
- const methodAuth = Reflect.getMetadata(SWAGGER_KEYS.BEARER_AUTH, controllerClass, route.handlerName);
153
- const tags = methodTags.length > 0 ? methodTags : classTags;
154
- tags.forEach((t) => allTags.add(t));
155
- const op = {
156
- ...tags.length > 0 ? {
157
- tags
158
- } : {},
159
- ...operation.summary ? {
160
- summary: operation.summary
161
- } : {},
162
- ...operation.description ? {
163
- description: operation.description
164
- } : {},
165
- ...operation.operationId ? {
166
- operationId: operation.operationId
167
- } : {},
168
- ...operation.deprecated ? {
169
- deprecated: true
170
- } : {},
171
- parameters: [],
172
- responses: {}
173
- };
174
- const paramMatches = fullPath.match(/:([a-zA-Z_]+)/g) || [];
175
- for (const match of paramMatches) {
176
- const paramName = match.slice(1);
177
- let schema = {
178
- type: "string"
179
- };
180
- if (route.validation?.params) {
181
- const jsonSchema = toJsonSchema(route.validation.params);
182
- if (jsonSchema?.properties && typeof jsonSchema.properties === "object") {
183
- const props = jsonSchema.properties;
184
- if (props[paramName]) {
185
- schema = props[paramName];
186
- }
187
- }
188
- }
189
- op.parameters.push({
190
- name: paramName,
191
- in: "path",
192
- required: true,
193
- schema
194
- });
195
- }
196
- if (route.validation?.query) {
197
- const jsonSchema = toJsonSchema(route.validation.query);
198
- if (jsonSchema?.properties && typeof jsonSchema.properties === "object") {
199
- const required = Array.isArray(jsonSchema.required) ? jsonSchema.required : [];
200
- for (const [name, propSchema] of Object.entries(jsonSchema.properties)) {
201
- op.parameters.push({
202
- name,
203
- in: "query",
204
- required: required.includes(name),
205
- schema: propSchema
206
- });
207
- }
208
- }
209
- }
210
- const queryParamsConfig = Reflect.getMetadata(METADATA.QUERY_PARAMS, controllerClass, route.handlerName);
211
- if (queryParamsConfig) {
212
- if (queryParamsConfig.filterable?.length) {
213
- op.parameters.push({
214
- name: "filter",
215
- in: "query",
216
- required: false,
217
- description: `Filter fields: ${queryParamsConfig.filterable.join(", ")}. Format: \`field:operator:value\`. Operators: eq, neq, gt, gte, lt, lte, contains, starts, ends, in, between`,
218
- schema: {
219
- type: "array",
220
- items: {
221
- type: "string"
222
- }
223
- },
224
- style: "form",
225
- explode: true
226
- });
227
- }
228
- if (queryParamsConfig.sortable?.length) {
229
- op.parameters.push({
230
- name: "sort",
231
- in: "query",
232
- required: false,
233
- description: `Sort fields: ${queryParamsConfig.sortable.join(", ")}. Format: \`field:asc\` or \`field:desc\``,
234
- schema: {
235
- type: "array",
236
- items: {
237
- type: "string"
238
- }
239
- },
240
- style: "form",
241
- explode: true
242
- });
243
- }
244
- if (queryParamsConfig.searchable?.length) {
245
- op.parameters.push({
246
- name: "q",
247
- in: "query",
248
- required: false,
249
- description: `Search across: ${queryParamsConfig.searchable.join(", ")}`,
250
- schema: {
251
- type: "string"
252
- }
253
- });
254
- }
255
- op.parameters.push({
256
- name: "page",
257
- in: "query",
258
- required: false,
259
- description: "Page number (default: 1)",
260
- schema: {
261
- type: "integer",
262
- minimum: 1,
263
- default: 1
264
- }
265
- }, {
266
- name: "limit",
267
- in: "query",
268
- required: false,
269
- description: "Items per page (default: 20, max: 100)",
270
- schema: {
271
- type: "integer",
272
- minimum: 1,
273
- maximum: 100,
274
- default: 20
275
- }
276
- });
277
- }
278
- if (op.parameters.length === 0) delete op.parameters;
279
- if (route.validation?.body && [
280
- "post",
281
- "put",
282
- "patch"
283
- ].includes(method)) {
284
- const bodySchema = toJsonSchema(route.validation.body);
285
- if (bodySchema) {
286
- const bodyName = route.validation.name || `${route.handlerName}Body`;
287
- const ref = registerSchema(bodySchema, bodyName);
288
- op.requestBody = {
289
- required: true,
290
- content: {
291
- "application/json": {
292
- schema: ref
293
- }
294
- }
295
- };
296
- }
297
- }
298
- const fileUpload = Reflect.getMetadata(METADATA.FILE_UPLOAD, controllerClass, route.handlerName);
299
- if (fileUpload) {
300
- const fieldName = fileUpload.fieldName ?? "file";
301
- const properties = {};
302
- if (fileUpload.mode === "array") {
303
- properties[fieldName] = {
304
- type: "array",
305
- items: {
306
- type: "string",
307
- format: "binary"
308
- }
309
- };
310
- } else if (fileUpload.mode !== "none") {
311
- properties[fieldName] = {
312
- type: "string",
313
- format: "binary"
314
- };
315
- }
316
- op.requestBody = {
317
- required: true,
318
- content: {
319
- "multipart/form-data": {
320
- schema: {
321
- type: "object",
322
- properties
323
- }
324
- }
325
- }
326
- };
327
- }
328
- if (responses.length > 0) {
329
- for (const resp of responses) {
330
- op.responses[String(resp.status)] = {
331
- description: resp.description || "",
332
- ...resp.schema ? (() => {
333
- const converted = typeof resp.schema === "function" || typeof resp.schema === "object" ? toJsonSchema(resp.schema) : null;
334
- const schemaName = resp.name || `${route.handlerName}Response${resp.status}`;
335
- const finalSchema = converted ? registerSchema(converted, schemaName) : typeof resp.schema === "object" ? resp.schema : void 0;
336
- return finalSchema ? {
337
- content: {
338
- "application/json": {
339
- schema: finalSchema
340
- }
341
- }
342
- } : {};
343
- })() : {}
344
- };
345
- }
346
- } else {
347
- const defaultStatus = method === "post" ? "201" : method === "delete" ? "204" : "200";
348
- op.responses[defaultStatus] = {
349
- description: "Successful operation"
350
- };
351
- if (route.validation?.body) {
352
- op.responses["422"] = {
353
- description: "Validation error"
354
- };
355
- }
356
- }
357
- const authName = methodAuth || classAuth;
358
- if (authName) {
359
- op.security = [
360
- {
361
- [authName]: []
362
- }
363
- ];
364
- securitySchemes[authName] = {
365
- type: "http",
366
- scheme: "bearer",
367
- bearerFormat: "JWT"
368
- };
369
- }
370
- if (!spec.paths[openApiPath]) spec.paths[openApiPath] = {};
371
- spec.paths[openApiPath][method] = op;
372
- }
373
- }
374
- spec.tags = Array.from(allTags).map((name) => ({
375
- name
376
- }));
377
- spec.components.securitySchemes = securitySchemes;
378
- if (options.bearerAuth) {
379
- if (!securitySchemes.BearerAuth) {
380
- spec.components.securitySchemes.BearerAuth = {
381
- type: "http",
382
- scheme: "bearer",
383
- bearerFormat: "JWT"
384
- };
385
- }
386
- spec.security = [
387
- {
388
- BearerAuth: []
389
- }
390
- ];
391
- }
392
- spec.components.schemas = componentSchemas;
393
- if (Object.keys(spec.components.schemas).length === 0) delete spec.components.schemas;
394
- if (Object.keys(spec.components.securitySchemes).length === 0) delete spec.components.securitySchemes;
395
- if (Object.keys(spec.components).length === 0) delete spec.components;
396
- return spec;
397
- }
398
- __name(buildOpenAPISpec, "buildOpenAPISpec");
399
-
400
- // src/swagger.adapter.ts
401
- import { Router } from "express";
402
- import { Logger } from "@forinda/kickjs-core";
403
-
404
- // src/ui.ts
405
- function escapeHtml(str) {
406
- return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
407
- }
408
- __name(escapeHtml, "escapeHtml");
409
- function swaggerUIHtml(specUrl, title = "API Docs") {
410
- const safeTitle = escapeHtml(title);
411
- const safeUrl = JSON.stringify(specUrl).replace(/</g, "\\u003c");
412
- return `<!DOCTYPE html>
1
+ var z=Object.defineProperty;var c=(e,t)=>z(e,"name",{value:t,configurable:!0});var v={name:"zod",supports(e){return e!=null&&typeof e=="object"&&typeof e.safeParse=="function"&&typeof e.toJSONSchema=="function"},toJsonSchema(e){let{$schema:t,...o}=e.toJSONSchema();return o}};import"reflect-metadata";var p={OPERATION:Symbol("kick:swagger:operation"),RESPONSES:Symbol("kick:swagger:responses"),TAGS:Symbol("kick:swagger:tags"),BEARER_AUTH:Symbol("kick:swagger:bearer"),EXCLUDE:Symbol("kick:swagger:exclude")};function W(e){return(t,o)=>{Reflect.defineMetadata(p.OPERATION,e,t.constructor,o)}}c(W,"ApiOperation");function X(e){return(t,o)=>{let n=Reflect.getMetadata(p.RESPONSES,t.constructor,o)||[];Reflect.defineMetadata(p.RESPONSES,[...n,e],t.constructor,o)}}c(X,"ApiResponse");function Y(...e){return(t,o)=>{o?Reflect.defineMetadata(p.TAGS,e,t.constructor,o):Reflect.defineMetadata(p.TAGS,e,t)}}c(Y,"ApiTags");function Z(e="BearerAuth"){return(t,o)=>{o?Reflect.defineMetadata(p.BEARER_AUTH,e,t.constructor,o):Reflect.defineMetadata(p.BEARER_AUTH,e,t)}}c(Z,"ApiBearerAuth");function Q(){return(e,t)=>{t?Reflect.defineMetadata(p.EXCLUDE,!0,e.constructor,t):Reflect.defineMetadata(p.EXCLUDE,!0,e)}}c(Q,"ApiExclude");import"reflect-metadata";import{METADATA as x,joinPaths as V}from"@forinda/kickjs-core";var U=[];function j(e,t){U.push({controllerClass:e,mountPath:t})}c(j,"registerControllerForDocs");function M(){U.length=0}c(M,"clearRegisteredRoutes");function N(e={}){let t=e.schemaParser??v,o=c(a=>{try{return t.supports(a)?t.toJsonSchema(a):null}catch{return null}},"toJsonSchema"),n={},S=0,u=c((a,w)=>{let h=a.title||a.label||w||"";if(h||(h=`Schema${++S}`),h=h.replace(/[^a-zA-Z0-9]/g,""),!n[h]){let b={...a};delete b.title,delete b.label,delete b.$schema,n[h]=b}return{$ref:`#/components/schemas/${h}`}},"registerSchema"),r={openapi:"3.0.3",info:{title:e.info?.title||"API",version:e.info?.version||"1.0.0",...e.info?.description?{description:e.info.description}:{}},paths:{},components:{schemas:{},securitySchemes:{}},tags:[]};e.servers&&(r.servers=e.servers);let f=new Set,m={};for(let{controllerClass:a,mountPath:w}of U){if(Reflect.getMetadata(p.EXCLUDE,a))continue;let h=Reflect.getMetadata(x.ROUTES,a)||[],b=Reflect.getMetadata(p.TAGS,a)||[],L=Reflect.getMetadata(p.BEARER_AUTH,a);for(let i of h){if(Reflect.getMetadata(p.EXCLUDE,a,i.handlerName))continue;let D=V(w,i.path),q=D.replace(/:([a-zA-Z_]+)/g,"{$1}"),E=i.method.toLowerCase(),A=Reflect.getMetadata(p.OPERATION,a,i.handlerName)||{},C=Reflect.getMetadata(p.RESPONSES,a,i.handlerName)||[],H=Reflect.getMetadata(p.TAGS,a,i.handlerName)||[],G=Reflect.getMetadata(p.BEARER_AUTH,a,i.handlerName),T=H.length>0?H:b;T.forEach(s=>f.add(s));let l={...T.length>0?{tags:T}:{},...A.summary?{summary:A.summary}:{},...A.description?{description:A.description}:{},...A.operationId?{operationId:A.operationId}:{},...A.deprecated?{deprecated:!0}:{},parameters:[],responses:{}},J=D.match(/:([a-zA-Z_]+)/g)||[];for(let s of J){let d=s.slice(1),g={type:"string"};if(i.validation?.params){let y=o(i.validation.params);if(y?.properties&&typeof y.properties=="object"){let F=y.properties;F[d]&&(g=F[d])}}l.parameters.push({name:d,in:"path",required:!0,schema:g})}if(i.validation?.query){let s=o(i.validation.query);if(s?.properties&&typeof s.properties=="object"){let d=Array.isArray(s.required)?s.required:[];for(let[g,y]of Object.entries(s.properties))l.parameters.push({name:g,in:"query",required:d.includes(g),schema:y})}}let R=Reflect.getMetadata(x.QUERY_PARAMS,a,i.handlerName);if(R&&(R.filterable?.length&&l.parameters.push({name:"filter",in:"query",required:!1,description:`Filter fields: ${R.filterable.join(", ")}. Format: \`field:operator:value\`. Operators: eq, neq, gt, gte, lt, lte, contains, starts, ends, in, between`,schema:{type:"array",items:{type:"string"}},style:"form",explode:!0}),R.sortable?.length&&l.parameters.push({name:"sort",in:"query",required:!1,description:`Sort fields: ${R.sortable.join(", ")}. Format: \`field:asc\` or \`field:desc\``,schema:{type:"array",items:{type:"string"}},style:"form",explode:!0}),R.searchable?.length&&l.parameters.push({name:"q",in:"query",required:!1,description:`Search across: ${R.searchable.join(", ")}`,schema:{type:"string"}}),l.parameters.push({name:"page",in:"query",required:!1,description:"Page number (default: 1)",schema:{type:"integer",minimum:1,default:1}},{name:"limit",in:"query",required:!1,description:"Items per page (default: 20, max: 100)",schema:{type:"integer",minimum:1,maximum:100,default:20}})),l.parameters.length===0&&delete l.parameters,i.validation?.body&&["post","put","patch"].includes(E)){let s=o(i.validation.body);if(s){let d=i.validation.name||`${i.handlerName}Body`,g=u(s,d);l.requestBody={required:!0,content:{"application/json":{schema:g}}}}}let P=Reflect.getMetadata(x.FILE_UPLOAD,a,i.handlerName);if(P){let s=P.fieldName??"file",d={};P.mode==="array"?d[s]={type:"array",items:{type:"string",format:"binary"}}:P.mode!=="none"&&(d[s]={type:"string",format:"binary"}),l.requestBody={required:!0,content:{"multipart/form-data":{schema:{type:"object",properties:d}}}}}if(C.length>0)for(let s of C)l.responses[String(s.status)]={description:s.description||"",...s.schema?(()=>{let d=typeof s.schema=="function"||typeof s.schema=="object"?o(s.schema):null,g=s.name||`${i.handlerName}Response${s.status}`,y=d?u(d,g):typeof s.schema=="object"?s.schema:void 0;return y?{content:{"application/json":{schema:y}}}:{}})():{}};else{let s=E==="post"?"201":E==="delete"?"204":"200";l.responses[s]={description:"Successful operation"},i.validation?.body&&(l.responses[422]={description:"Validation error"})}let O=G||L;O&&(l.security=[{[O]:[]}],m[O]={type:"http",scheme:"bearer",bearerFormat:"JWT"}),r.paths[q]||(r.paths[q]={}),r.paths[q][E]=l}}return r.tags=Array.from(f).map(a=>({name:a})),r.components.securitySchemes=m,e.bearerAuth&&(m.BearerAuth||(r.components.securitySchemes.BearerAuth={type:"http",scheme:"bearer",bearerFormat:"JWT"}),r.security=[{BearerAuth:[]}]),r.components.schemas=n,Object.keys(r.components.schemas).length===0&&delete r.components.schemas,Object.keys(r.components.securitySchemes).length===0&&delete r.components.securitySchemes,Object.keys(r.components).length===0&&delete r.components,r}c(N,"buildOpenAPISpec");import{Router as K}from"express";import{Logger as ee}from"@forinda/kickjs-core";function $(e){return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#39;")}c($,"escapeHtml");function k(e,t="API Docs"){let o=$(t),n=JSON.stringify(e).replace(/</g,"\\u003c");return`<!DOCTYPE html>
413
2
  <html lang="en">
414
3
  <head>
415
4
  <meta charset="UTF-8">
416
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
417
- <title>${safeTitle}</title>
6
+ <title>${o}</title>
418
7
  <link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist@5/swagger-ui.css">
419
8
  </head>
420
9
  <body>
@@ -423,7 +12,7 @@ function swaggerUIHtml(specUrl, title = "API Docs") {
423
12
  <script src="https://unpkg.com/swagger-ui-dist@5/swagger-ui-standalone-preset.js"></script>
424
13
  <script>
425
14
  SwaggerUIBundle({
426
- url: ${safeUrl},
15
+ url: ${n},
427
16
  dom_id: '#swagger-ui',
428
17
  deepLinking: true,
429
18
  presets: [SwaggerUIBundle.presets.apis, SwaggerUIStandalonePreset],
@@ -432,111 +21,15 @@ function swaggerUIHtml(specUrl, title = "API Docs") {
432
21
  });
433
22
  </script>
434
23
  </body>
435
- </html>`;
436
- }
437
- __name(swaggerUIHtml, "swaggerUIHtml");
438
- function redocHtml(specUrl, title = "API Docs") {
439
- const safeTitle = escapeHtml(title);
440
- const safeUrl = escapeHtml(specUrl);
441
- return `<!DOCTYPE html>
24
+ </html>`}c(k,"swaggerUIHtml");function I(e,t="API Docs"){let o=$(t),n=$(e);return`<!DOCTYPE html>
442
25
  <html lang="en">
443
26
  <head>
444
27
  <meta charset="UTF-8">
445
28
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
446
- <title>${safeTitle}</title>
29
+ <title>${o}</title>
447
30
  </head>
448
31
  <body>
449
- <redoc spec-url="${safeUrl}"></redoc>
32
+ <redoc spec-url="${n}"></redoc>
450
33
  <script src="https://cdn.redoc.ly/redoc/latest/bundles/redoc.standalone.js"></script>
451
34
  </body>
452
- </html>`;
453
- }
454
- __name(redocHtml, "redocHtml");
455
-
456
- // src/swagger.adapter.ts
457
- var log = Logger.for("SwaggerAdapter");
458
- var SwaggerAdapter = class {
459
- static {
460
- __name(this, "SwaggerAdapter");
461
- }
462
- options;
463
- name = "SwaggerAdapter";
464
- constructor(options = {}) {
465
- this.options = options;
466
- }
467
- /** Auto-detect server URLs from the running HTTP server and peer adapters */
468
- afterStart(server, _container) {
469
- const addr = server?.address?.();
470
- if (!addr || typeof addr !== "object") return;
471
- const host = addr.address === "::" || addr.address === "0.0.0.0" ? "localhost" : addr.address;
472
- if (!this.options.servers || this.options.servers.length === 0) {
473
- this.options.servers = [
474
- {
475
- url: `http://${host}:${addr.port}`,
476
- description: "HTTP server"
477
- }
478
- ];
479
- }
480
- const wsAdapter = this.options.adapters?.find((a) => a.name === "WsAdapter" && typeof a.getStats === "function");
481
- if (wsAdapter) {
482
- const stats = wsAdapter.getStats();
483
- for (const namespace of Object.keys(stats.namespaces || {})) {
484
- this.options.servers.push({
485
- url: `ws://${host}:${addr.port}${namespace}`,
486
- description: `WebSocket: ${namespace}`
487
- });
488
- }
489
- }
490
- }
491
- /** Collect controller metadata as routes are mounted */
492
- onRouteMount(controllerClass, mountPath) {
493
- registerControllerForDocs(controllerClass, mountPath);
494
- }
495
- beforeMount(app, _container) {
496
- clearRegisteredRoutes();
497
- const docsPath = this.options.docsPath ?? "/docs";
498
- const redocPath = this.options.redocPath ?? "/redoc";
499
- const specPath = this.options.specPath ?? "/openapi.json";
500
- const docsRouter = Router();
501
- docsRouter.use((_req, res, next) => {
502
- res.setHeader("Content-Security-Policy", [
503
- "default-src 'self'",
504
- "script-src 'self' 'unsafe-inline' https://unpkg.com https://cdn.redoc.ly https://cdn.jsdelivr.net",
505
- "style-src 'self' 'unsafe-inline' https://unpkg.com https://fonts.googleapis.com",
506
- "font-src 'self' https://fonts.gstatic.com",
507
- "img-src 'self' data: https://unpkg.com",
508
- "connect-src 'self'"
509
- ].join("; "));
510
- next();
511
- });
512
- docsRouter.get(specPath, (_req, res) => {
513
- const spec = buildOpenAPISpec(this.options);
514
- res.json(spec);
515
- });
516
- docsRouter.get(docsPath, (_req, res) => {
517
- res.type("html").send(swaggerUIHtml(specPath, this.options.info?.title));
518
- });
519
- docsRouter.get(redocPath, (_req, res) => {
520
- res.type("html").send(redocHtml(specPath, this.options.info?.title));
521
- });
522
- app.use(docsRouter);
523
- log.info(`Swagger UI: ${docsPath}`);
524
- log.info(`ReDoc: ${redocPath}`);
525
- log.info(`OpenAPI spec: ${specPath}`);
526
- }
527
- };
528
- export {
529
- ApiBearerAuth,
530
- ApiExclude,
531
- ApiOperation,
532
- ApiResponse,
533
- ApiTags,
534
- SwaggerAdapter,
535
- buildOpenAPISpec,
536
- clearRegisteredRoutes,
537
- redocHtml,
538
- registerControllerForDocs,
539
- swaggerUIHtml,
540
- zodSchemaParser
541
- };
542
- //# sourceMappingURL=index.js.map
35
+ </html>`}c(I,"redocHtml");var _=ee.for("SwaggerAdapter"),B=class{static{c(this,"SwaggerAdapter")}options;name="SwaggerAdapter";constructor(t={}){this.options=t}afterStart(t,o){let n=t?.address?.();if(!n||typeof n!="object")return;let S=n.address==="::"||n.address==="0.0.0.0"?"localhost":n.address;(!this.options.servers||this.options.servers.length===0)&&(this.options.servers=[{url:`http://${S}:${n.port}`,description:"HTTP server"}]);let u=this.options.adapters?.find(r=>r.name==="WsAdapter"&&typeof r.getStats=="function");if(u){let r=u.getStats();for(let f of Object.keys(r.namespaces||{}))this.options.servers.push({url:`ws://${S}:${n.port}${f}`,description:`WebSocket: ${f}`})}}onRouteMount(t,o){j(t,o)}beforeMount(t,o){M();let n=this.options.docsPath??"/docs",S=this.options.redocPath??"/redoc",u=this.options.specPath??"/openapi.json",r=K();r.use((f,m,a)=>{m.setHeader("Content-Security-Policy",["default-src 'self'","script-src 'self' 'unsafe-inline' https://unpkg.com https://cdn.redoc.ly https://cdn.jsdelivr.net","style-src 'self' 'unsafe-inline' https://unpkg.com https://fonts.googleapis.com","font-src 'self' https://fonts.gstatic.com","img-src 'self' data: https://unpkg.com","connect-src 'self'"].join("; ")),a()}),r.get(u,(f,m)=>{let a=N(this.options);m.json(a)}),r.get(n,(f,m)=>{m.type("html").send(k(u,this.options.info?.title))}),r.get(S,(f,m)=>{m.type("html").send(I(u,this.options.info?.title))}),t.use(r),_.info(`Swagger UI: ${n}`),_.info(`ReDoc: ${S}`),_.info(`OpenAPI spec: ${u}`)}};export{Z as ApiBearerAuth,Q as ApiExclude,W as ApiOperation,X as ApiResponse,Y as ApiTags,B as SwaggerAdapter,N as buildOpenAPISpec,M as clearRegisteredRoutes,I as redocHtml,j as registerControllerForDocs,k as swaggerUIHtml,v as zodSchemaParser};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forinda/kickjs-swagger",
3
- "version": "1.2.11",
3
+ "version": "1.2.13",
4
4
  "description": "OpenAPI spec generation from decorators, Swagger UI and ReDoc serving for KickJS",
5
5
  "keywords": [
6
6
  "kickjs",
@@ -40,7 +40,7 @@
40
40
  ],
41
41
  "dependencies": {
42
42
  "reflect-metadata": "^0.2.2",
43
- "@forinda/kickjs-core": "1.2.11"
43
+ "@forinda/kickjs-core": "1.2.13"
44
44
  },
45
45
  "peerDependencies": {
46
46
  "express": "^5.1.0",
package/dist/index.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/schema-parser.ts","../src/decorators.ts","../src/openapi-builder.ts","../src/swagger.adapter.ts","../src/ui.ts"],"sourcesContent":["/**\n * Interface for converting validation library schemas to JSON Schema.\n *\n * KickJS ships with a Zod parser by default. To use a different validation\n * library (Yup, Joi, Valibot, ArkType, etc.), implement this interface and\n * pass it to the SwaggerAdapter.\n *\n * @example\n * ```ts\n * import Joi from 'joi'\n * import joiToJson from 'joi-to-json'\n *\n * const joiParser: SchemaParser = {\n * name: 'joi',\n * supports: (schema) => Joi.isSchema(schema),\n * toJsonSchema: (schema) => joiToJson(schema),\n * }\n *\n * new SwaggerAdapter({ schemaParser: joiParser })\n * ```\n */\nexport interface SchemaParser {\n /** Human-readable name for logging/debugging */\n readonly name: string\n\n /**\n * Return true if this parser can handle the given schema object.\n * Called before `toJsonSchema` to allow graceful fallback.\n */\n supports(schema: unknown): boolean\n\n /**\n * Convert a validation schema to a JSON Schema object.\n * Should return a plain object conforming to JSON Schema draft-07 or later.\n * Must not include the top-level `$schema` key — the builder adds it.\n */\n toJsonSchema(schema: unknown): Record<string, unknown>\n}\n\n/**\n * Default schema parser for Zod v4+.\n * Uses Zod's built-in `.toJSONSchema()` instance method.\n */\nexport const zodSchemaParser: SchemaParser = {\n name: 'zod',\n\n supports(schema: unknown): boolean {\n return (\n schema != null &&\n typeof schema === 'object' &&\n typeof (schema as any).safeParse === 'function' &&\n typeof (schema as any).toJSONSchema === 'function'\n )\n },\n\n toJsonSchema(schema: unknown): Record<string, unknown> {\n const { $schema: _, ...rest } = (schema as any).toJSONSchema() as Record<string, unknown>\n return rest\n },\n}\n","import 'reflect-metadata'\n\nconst SWAGGER_KEYS = {\n OPERATION: Symbol('kick:swagger:operation'),\n RESPONSES: Symbol('kick:swagger:responses'),\n TAGS: Symbol('kick:swagger:tags'),\n BEARER_AUTH: Symbol('kick:swagger:bearer'),\n EXCLUDE: Symbol('kick:swagger:exclude'),\n}\n\nexport { SWAGGER_KEYS }\n\nexport interface ApiOperationOptions {\n summary?: string\n description?: string\n operationId?: string\n deprecated?: boolean\n}\n\nexport interface ApiResponseOptions {\n status: number\n description?: string\n schema?: any\n /** Schema name in components/schemas (e.g., 'UserResponse', 'ErrorBody'). Auto-generated from handler name if omitted. */\n name?: string\n}\n\n/** Attach operation metadata to a route handler */\nexport function ApiOperation(options: ApiOperationOptions): MethodDecorator {\n return (target, propertyKey) => {\n Reflect.defineMetadata(SWAGGER_KEYS.OPERATION, options, target.constructor, propertyKey)\n }\n}\n\n/** Document a response status. Can be stacked multiple times. */\nexport function ApiResponse(options: ApiResponseOptions): MethodDecorator {\n return (target, propertyKey) => {\n const existing: ApiResponseOptions[] =\n Reflect.getMetadata(SWAGGER_KEYS.RESPONSES, target.constructor, propertyKey) || []\n Reflect.defineMetadata(\n SWAGGER_KEYS.RESPONSES,\n [...existing, options],\n target.constructor,\n propertyKey,\n )\n }\n}\n\n/** Apply OpenAPI tags at class or method level */\nexport function ApiTags(...tags: string[]): ClassDecorator & MethodDecorator {\n return (target: any, propertyKey?: string | symbol) => {\n if (propertyKey) {\n Reflect.defineMetadata(SWAGGER_KEYS.TAGS, tags, target.constructor, propertyKey)\n } else {\n Reflect.defineMetadata(SWAGGER_KEYS.TAGS, tags, target)\n }\n }\n}\n\n/** Mark endpoint as requiring Bearer token auth */\nexport function ApiBearerAuth(name = 'BearerAuth'): ClassDecorator & MethodDecorator {\n return (target: any, propertyKey?: string | symbol) => {\n if (propertyKey) {\n Reflect.defineMetadata(SWAGGER_KEYS.BEARER_AUTH, name, target.constructor, propertyKey)\n } else {\n Reflect.defineMetadata(SWAGGER_KEYS.BEARER_AUTH, name, target)\n }\n }\n}\n\n/** Exclude a controller or method from the OpenAPI spec */\nexport function ApiExclude(): ClassDecorator & MethodDecorator {\n return (target: any, propertyKey?: string | symbol) => {\n if (propertyKey) {\n Reflect.defineMetadata(SWAGGER_KEYS.EXCLUDE, true, target.constructor, propertyKey)\n } else {\n Reflect.defineMetadata(SWAGGER_KEYS.EXCLUDE, true, target)\n }\n }\n}\n","import 'reflect-metadata'\nimport { METADATA, joinPaths, type RouteDefinition } from '@forinda/kickjs-core'\nimport { SWAGGER_KEYS, type ApiOperationOptions, type ApiResponseOptions } from './decorators'\nimport { zodSchemaParser, type SchemaParser } from './schema-parser'\n\nexport interface OpenAPIInfo {\n title: string\n version: string\n description?: string\n}\n\nexport interface SwaggerOptions {\n info?: Partial<OpenAPIInfo>\n servers?: { url: string; description?: string }[]\n bearerAuth?: boolean\n /**\n * Pluggable schema parser for converting validation schemas to JSON Schema.\n * Defaults to `zodSchemaParser` which handles Zod v4+ schemas.\n *\n * Override this to use Yup, Joi, Valibot, ArkType, or any other library.\n *\n * @example\n * ```ts\n * new SwaggerAdapter({\n * schemaParser: myYupParser,\n * })\n * ```\n */\n schemaParser?: SchemaParser\n}\n\ninterface RegisteredRoute {\n controllerClass: any\n mountPath: string\n}\n\nconst registeredRoutes: RegisteredRoute[] = []\n\n/** Register a controller for OpenAPI introspection (called by Application during route mounting) */\nexport function registerControllerForDocs(controllerClass: any, mountPath: string): void {\n registeredRoutes.push({ controllerClass, mountPath })\n}\n\n/** Clear all registered routes (for HMR) */\nexport function clearRegisteredRoutes(): void {\n registeredRoutes.length = 0\n}\n\n/** Build a full OpenAPI 3.0.3 spec from registered controllers and their decorators */\nexport function buildOpenAPISpec(options: SwaggerOptions = {}): any {\n const parser = options.schemaParser ?? zodSchemaParser\n\n /** Convert a validation schema to JSON Schema using the configured parser */\n const toJsonSchema = (schema: unknown): Record<string, unknown> | null => {\n try {\n if (!parser.supports(schema)) return null\n return parser.toJsonSchema(schema)\n } catch {\n return null\n }\n }\n\n const componentSchemas: Record<string, any> = {}\n let schemaCounter = 0\n\n /**\n * Register a schema in components.schemas and return a $ref pointer.\n * If the schema has a title/label, use that as the name. Otherwise generate one.\n */\n const registerSchema = (jsonSchema: Record<string, unknown>, hint?: string): any => {\n // Try to extract a name from the schema\n let name = (jsonSchema.title as string) || (jsonSchema.label as string) || hint || ''\n if (!name) {\n name = `Schema${++schemaCounter}`\n }\n // Sanitize name for OpenAPI (remove spaces, special chars)\n name = name.replace(/[^a-zA-Z0-9]/g, '')\n\n // Avoid duplicates — if already registered with same name, reuse\n if (!componentSchemas[name]) {\n const clean = { ...jsonSchema }\n delete clean.title\n delete clean.label\n delete clean.$schema\n componentSchemas[name] = clean\n }\n return { $ref: `#/components/schemas/${name}` }\n }\n\n const spec: any = {\n openapi: '3.0.3',\n info: {\n title: options.info?.title || 'API',\n version: options.info?.version || '1.0.0',\n ...(options.info?.description ? { description: options.info.description } : {}),\n },\n paths: {},\n components: { schemas: {}, securitySchemes: {} },\n tags: [],\n }\n\n if (options.servers) {\n spec.servers = options.servers\n }\n\n const allTags = new Set<string>()\n const securitySchemes: Record<string, any> = {}\n\n for (const { controllerClass, mountPath } of registeredRoutes) {\n // Skip excluded controllers\n if (Reflect.getMetadata(SWAGGER_KEYS.EXCLUDE, controllerClass)) continue\n\n const routes: RouteDefinition[] = Reflect.getMetadata(METADATA.ROUTES, controllerClass) || []\n const classTags: string[] = Reflect.getMetadata(SWAGGER_KEYS.TAGS, controllerClass) || []\n const classAuth: string | undefined = Reflect.getMetadata(\n SWAGGER_KEYS.BEARER_AUTH,\n controllerClass,\n )\n for (const route of routes) {\n // Skip excluded methods\n if (Reflect.getMetadata(SWAGGER_KEYS.EXCLUDE, controllerClass, route.handlerName)) continue\n\n // Build the full path — mountPath is the actual Express mount prefix (from onRouteMount),\n // and route.path is the method-level path. @Controller path is not included here\n // because buildRoutes does not bake it into the router.\n const fullPath = joinPaths(mountPath, route.path)\n\n // Convert Express :param to OpenAPI {param}\n const openApiPath = fullPath.replace(/:([a-zA-Z_]+)/g, '{$1}')\n const method = route.method.toLowerCase()\n\n // Gather metadata\n const operation: ApiOperationOptions =\n Reflect.getMetadata(SWAGGER_KEYS.OPERATION, controllerClass, route.handlerName) || {}\n const responses: ApiResponseOptions[] =\n Reflect.getMetadata(SWAGGER_KEYS.RESPONSES, controllerClass, route.handlerName) || []\n const methodTags: string[] =\n Reflect.getMetadata(SWAGGER_KEYS.TAGS, controllerClass, route.handlerName) || []\n const methodAuth: string | undefined = Reflect.getMetadata(\n SWAGGER_KEYS.BEARER_AUTH,\n controllerClass,\n route.handlerName,\n )\n\n // Tags — method level overrides class level\n const tags = methodTags.length > 0 ? methodTags : classTags\n tags.forEach((t) => allTags.add(t))\n\n // Build operation object\n const op: any = {\n ...(tags.length > 0 ? { tags } : {}),\n ...(operation.summary ? { summary: operation.summary } : {}),\n ...(operation.description ? { description: operation.description } : {}),\n ...(operation.operationId ? { operationId: operation.operationId } : {}),\n ...(operation.deprecated ? { deprecated: true } : {}),\n parameters: [],\n responses: {},\n }\n\n // Path parameters\n const paramMatches = fullPath.match(/:([a-zA-Z_]+)/g) || []\n for (const match of paramMatches) {\n const paramName = match.slice(1)\n let schema: any = { type: 'string' }\n\n // Try to get type from params validation schema\n if (route.validation?.params) {\n const jsonSchema = toJsonSchema(route.validation.params)\n if (jsonSchema?.properties && typeof jsonSchema.properties === 'object') {\n const props = jsonSchema.properties as Record<string, any>\n if (props[paramName]) {\n schema = props[paramName]\n }\n }\n }\n\n op.parameters.push({\n name: paramName,\n in: 'path',\n required: true,\n schema,\n })\n }\n\n // Query parameters\n if (route.validation?.query) {\n const jsonSchema = toJsonSchema(route.validation.query)\n if (jsonSchema?.properties && typeof jsonSchema.properties === 'object') {\n const required = Array.isArray(jsonSchema.required) ? jsonSchema.required : []\n for (const [name, propSchema] of Object.entries(\n jsonSchema.properties as Record<string, any>,\n )) {\n op.parameters.push({\n name,\n in: 'query',\n required: required.includes(name),\n schema: propSchema,\n })\n }\n }\n }\n\n // @ApiQueryParams decorator — document filterable/sortable/searchable fields\n const queryParamsConfig = Reflect.getMetadata(\n METADATA.QUERY_PARAMS,\n controllerClass,\n route.handlerName,\n )\n if (queryParamsConfig) {\n if (queryParamsConfig.filterable?.length) {\n op.parameters.push({\n name: 'filter',\n in: 'query',\n required: false,\n description: `Filter fields: ${queryParamsConfig.filterable.join(', ')}. Format: \\`field:operator:value\\`. Operators: eq, neq, gt, gte, lt, lte, contains, starts, ends, in, between`,\n schema: { type: 'array', items: { type: 'string' } },\n style: 'form',\n explode: true,\n })\n }\n if (queryParamsConfig.sortable?.length) {\n op.parameters.push({\n name: 'sort',\n in: 'query',\n required: false,\n description: `Sort fields: ${queryParamsConfig.sortable.join(', ')}. Format: \\`field:asc\\` or \\`field:desc\\``,\n schema: { type: 'array', items: { type: 'string' } },\n style: 'form',\n explode: true,\n })\n }\n if (queryParamsConfig.searchable?.length) {\n op.parameters.push({\n name: 'q',\n in: 'query',\n required: false,\n description: `Search across: ${queryParamsConfig.searchable.join(', ')}`,\n schema: { type: 'string' },\n })\n }\n op.parameters.push(\n {\n name: 'page',\n in: 'query',\n required: false,\n description: 'Page number (default: 1)',\n schema: { type: 'integer', minimum: 1, default: 1 },\n },\n {\n name: 'limit',\n in: 'query',\n required: false,\n description: 'Items per page (default: 20, max: 100)',\n schema: { type: 'integer', minimum: 1, maximum: 100, default: 20 },\n },\n )\n }\n\n // Remove empty parameters array\n if (op.parameters.length === 0) delete op.parameters\n\n // Request body\n if (route.validation?.body && ['post', 'put', 'patch'].includes(method)) {\n const bodySchema = toJsonSchema(route.validation.body)\n if (bodySchema) {\n const bodyName = route.validation.name || `${route.handlerName}Body`\n const ref = registerSchema(bodySchema, bodyName)\n op.requestBody = {\n required: true,\n content: { 'application/json': { schema: ref } },\n }\n }\n }\n\n // File upload detection\n const fileUpload = Reflect.getMetadata(\n METADATA.FILE_UPLOAD,\n controllerClass,\n route.handlerName,\n )\n if (fileUpload) {\n const fieldName = fileUpload.fieldName ?? 'file'\n const properties: any = {}\n\n if (fileUpload.mode === 'array') {\n properties[fieldName] = {\n type: 'array',\n items: { type: 'string', format: 'binary' },\n }\n } else if (fileUpload.mode !== 'none') {\n properties[fieldName] = {\n type: 'string',\n format: 'binary',\n }\n }\n\n op.requestBody = {\n required: true,\n content: {\n 'multipart/form-data': {\n schema: { type: 'object', properties },\n },\n },\n }\n }\n\n // Responses\n if (responses.length > 0) {\n for (const resp of responses) {\n op.responses[String(resp.status)] = {\n description: resp.description || '',\n ...(resp.schema\n ? (() => {\n const converted =\n typeof resp.schema === 'function' || typeof resp.schema === 'object'\n ? toJsonSchema(resp.schema)\n : null\n const schemaName = resp.name || `${route.handlerName}Response${resp.status}`\n const finalSchema = converted\n ? registerSchema(converted, schemaName)\n : typeof resp.schema === 'object'\n ? resp.schema\n : undefined\n return finalSchema\n ? { content: { 'application/json': { schema: finalSchema } } }\n : {}\n })()\n : {}),\n }\n }\n } else {\n // Auto-generate default responses\n const defaultStatus = method === 'post' ? '201' : method === 'delete' ? '204' : '200'\n op.responses[defaultStatus] = { description: 'Successful operation' }\n\n if (route.validation?.body) {\n op.responses['422'] = { description: 'Validation error' }\n }\n }\n\n // Security\n const authName = methodAuth || classAuth\n if (authName) {\n op.security = [{ [authName]: [] }]\n securitySchemes[authName] = {\n type: 'http',\n scheme: 'bearer',\n bearerFormat: 'JWT',\n }\n }\n\n // Mount\n if (!spec.paths[openApiPath]) spec.paths[openApiPath] = {}\n spec.paths[openApiPath][method] = op\n }\n }\n\n // Finalize\n spec.tags = Array.from(allTags).map((name) => ({ name }))\n spec.components.securitySchemes = securitySchemes\n\n if (options.bearerAuth) {\n if (!securitySchemes.BearerAuth) {\n spec.components.securitySchemes.BearerAuth = {\n type: 'http',\n scheme: 'bearer',\n bearerFormat: 'JWT',\n }\n }\n spec.security = [{ BearerAuth: [] }]\n }\n\n // Merge collected schemas into components\n spec.components.schemas = componentSchemas\n\n // Clean up empty components\n if (Object.keys(spec.components.schemas).length === 0) delete spec.components.schemas\n if (Object.keys(spec.components.securitySchemes).length === 0)\n delete spec.components.securitySchemes\n if (Object.keys(spec.components).length === 0) delete spec.components\n\n return spec\n}\n","import { Router } from 'express'\nimport type { Express } from 'express'\nimport { Logger, type AppAdapter, type Container } from '@forinda/kickjs-core'\nimport {\n buildOpenAPISpec,\n registerControllerForDocs,\n clearRegisteredRoutes,\n type SwaggerOptions,\n} from './openapi-builder'\nimport { swaggerUIHtml, redocHtml } from './ui'\n\nconst log = Logger.for('SwaggerAdapter')\n\nexport interface SwaggerAdapterOptions extends SwaggerOptions {\n /** Path to serve Swagger UI (default: '/docs') */\n docsPath?: string\n /** Path to serve ReDoc (default: '/redoc') */\n redocPath?: string\n /** Path to serve the raw JSON spec (default: '/openapi.json') */\n specPath?: string\n /** Other adapters to discover (e.g., WsAdapter for WebSocket server URLs) */\n adapters?: any[]\n}\n\n/**\n * Swagger adapter — auto-generates OpenAPI spec from decorators and serves docs.\n *\n * @example\n * ```ts\n * bootstrap({\n * modules,\n * adapters: [\n * new SwaggerAdapter({\n * info: { title: 'My API', version: '1.0.0' },\n * }),\n * ],\n * })\n * ```\n *\n * Endpoints:\n * GET /docs — Swagger UI\n * GET /redoc — ReDoc\n * GET /openapi.json — Raw OpenAPI 3.0.3 spec\n */\nexport class SwaggerAdapter implements AppAdapter {\n name = 'SwaggerAdapter'\n\n constructor(private options: SwaggerAdapterOptions = {}) {}\n\n /** Auto-detect server URLs from the running HTTP server and peer adapters */\n afterStart(server: any, _container: Container): void {\n const addr = server?.address?.()\n if (!addr || typeof addr !== 'object') return\n\n const host = addr.address === '::' || addr.address === '0.0.0.0' ? 'localhost' : addr.address\n\n // Auto-add HTTP server URL if none configured\n if (!this.options.servers || this.options.servers.length === 0) {\n this.options.servers = [{ url: `http://${host}:${addr.port}`, description: 'HTTP server' }]\n }\n\n // Auto-add WebSocket server URLs from WsAdapter\n const wsAdapter = this.options.adapters?.find(\n (a) => a.name === 'WsAdapter' && typeof a.getStats === 'function',\n )\n if (wsAdapter) {\n const stats = wsAdapter.getStats()\n for (const namespace of Object.keys(stats.namespaces || {})) {\n this.options.servers!.push({\n url: `ws://${host}:${addr.port}${namespace}`,\n description: `WebSocket: ${namespace}`,\n })\n }\n }\n }\n\n /** Collect controller metadata as routes are mounted */\n onRouteMount(controllerClass: any, mountPath: string): void {\n registerControllerForDocs(controllerClass, mountPath)\n }\n\n beforeMount(app: Express, _container: Container): void {\n // Clear previous registrations (supports HMR rebuild)\n clearRegisteredRoutes()\n const docsPath = this.options.docsPath ?? '/docs'\n const redocPath = this.options.redocPath ?? '/redoc'\n const specPath = this.options.specPath ?? '/openapi.json'\n\n // Use a sub-router with relaxed CSP so CDN scripts load\n const docsRouter = Router()\n\n docsRouter.use((_req, res, next) => {\n res.setHeader(\n 'Content-Security-Policy',\n [\n \"default-src 'self'\",\n \"script-src 'self' 'unsafe-inline' https://unpkg.com https://cdn.redoc.ly https://cdn.jsdelivr.net\",\n \"style-src 'self' 'unsafe-inline' https://unpkg.com https://fonts.googleapis.com\",\n \"font-src 'self' https://fonts.gstatic.com\",\n \"img-src 'self' data: https://unpkg.com\",\n \"connect-src 'self'\",\n ].join('; '),\n )\n next()\n })\n\n // Spec endpoint (JSON)\n docsRouter.get(specPath, (_req, res) => {\n const spec = buildOpenAPISpec(this.options)\n res.json(spec)\n })\n\n // Swagger UI\n docsRouter.get(docsPath, (_req, res) => {\n res.type('html').send(swaggerUIHtml(specPath, this.options.info?.title))\n })\n\n // ReDoc\n docsRouter.get(redocPath, (_req, res) => {\n res.type('html').send(redocHtml(specPath, this.options.info?.title))\n })\n\n app.use(docsRouter)\n\n log.info(`Swagger UI: ${docsPath}`)\n log.info(`ReDoc: ${redocPath}`)\n log.info(`OpenAPI spec: ${specPath}`)\n }\n}\n\n// Re-export for use by Application when mounting module routes\nexport { registerControllerForDocs, clearRegisteredRoutes }\n","/** Escape a string for safe HTML attribute/content interpolation */\nfunction escapeHtml(str: string): string {\n return str\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&#39;')\n}\n\n/** Generate Swagger UI HTML that loads from CDN */\nexport function swaggerUIHtml(specUrl: string, title = 'API Docs'): string {\n const safeTitle = escapeHtml(title)\n const safeUrl = JSON.stringify(specUrl).replace(/</g, '\\\\u003c')\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>${safeTitle}</title>\n <link rel=\"stylesheet\" href=\"https://unpkg.com/swagger-ui-dist@5/swagger-ui.css\">\n</head>\n<body>\n <div id=\"swagger-ui\"></div>\n <script src=\"https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js\"></script>\n <script src=\"https://unpkg.com/swagger-ui-dist@5/swagger-ui-standalone-preset.js\"></script>\n <script>\n SwaggerUIBundle({\n url: ${safeUrl},\n dom_id: '#swagger-ui',\n deepLinking: true,\n presets: [SwaggerUIBundle.presets.apis, SwaggerUIStandalonePreset],\n plugins: [SwaggerUIBundle.plugins.DownloadUrl],\n layout: 'StandaloneLayout',\n });\n </script>\n</body>\n</html>`\n}\n\n/** Generate ReDoc HTML that loads from CDN */\nexport function redocHtml(specUrl: string, title = 'API Docs'): string {\n const safeTitle = escapeHtml(title)\n const safeUrl = escapeHtml(specUrl)\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>${safeTitle}</title>\n</head>\n<body>\n <redoc spec-url=\"${safeUrl}\"></redoc>\n <script src=\"https://cdn.redoc.ly/redoc/latest/bundles/redoc.standalone.js\"></script>\n</body>\n</html>`\n}\n"],"mappings":"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","names":["zodSchemaParser","name","supports","schema","safeParse","toJSONSchema","toJsonSchema","$schema","_","rest","SWAGGER_KEYS","OPERATION","Symbol","RESPONSES","TAGS","BEARER_AUTH","EXCLUDE","ApiOperation","options","target","propertyKey","Reflect","defineMetadata","SWAGGER_KEYS","OPERATION","ApiResponse","existing","getMetadata","RESPONSES","ApiTags","tags","TAGS","ApiBearerAuth","name","BEARER_AUTH","ApiExclude","EXCLUDE","METADATA","joinPaths","registeredRoutes","registerControllerForDocs","controllerClass","mountPath","push","clearRegisteredRoutes","length","buildOpenAPISpec","options","parser","schemaParser","zodSchemaParser","toJsonSchema","schema","supports","componentSchemas","schemaCounter","registerSchema","jsonSchema","hint","name","title","label","replace","clean","$schema","$ref","spec","openapi","info","version","description","paths","components","schemas","securitySchemes","tags","servers","allTags","Set","Reflect","getMetadata","SWAGGER_KEYS","EXCLUDE","routes","METADATA","ROUTES","classTags","TAGS","classAuth","BEARER_AUTH","route","handlerName","fullPath","joinPaths","path","openApiPath","method","toLowerCase","operation","OPERATION","responses","RESPONSES","methodTags","methodAuth","forEach","t","add","op","summary","operationId","deprecated","parameters","paramMatches","match","paramName","slice","type","validation","params","properties","props","in","required","query","Array","isArray","propSchema","Object","entries","includes","queryParamsConfig","QUERY_PARAMS","filterable","join","items","style","explode","sortable","searchable","minimum","default","maximum","body","bodySchema","bodyName","ref","requestBody","content","fileUpload","FILE_UPLOAD","fieldName","mode","format","resp","String","status","converted","schemaName","finalSchema","undefined","defaultStatus","authName","security","scheme","bearerFormat","from","map","bearerAuth","BearerAuth","keys","Router","Logger","escapeHtml","str","replace","swaggerUIHtml","specUrl","title","safeTitle","safeUrl","JSON","stringify","redocHtml","log","Logger","for","SwaggerAdapter","name","options","afterStart","server","_container","addr","address","host","servers","length","url","port","description","wsAdapter","adapters","find","a","getStats","stats","namespace","Object","keys","namespaces","push","onRouteMount","controllerClass","mountPath","registerControllerForDocs","beforeMount","app","clearRegisteredRoutes","docsPath","redocPath","specPath","docsRouter","Router","use","_req","res","next","setHeader","join","get","spec","buildOpenAPISpec","json","type","send","swaggerUIHtml","info","title","redocHtml"]}