@forinda/kickjs-swagger 2.3.2 → 2.3.3

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.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/schema-parser.ts","../src/decorators.ts","../src/openapi-builder.ts","../src/swagger.adapter.ts","../src/ui.ts"],"mappings":";;;;;;;AAqBA;;;;;;;;;;;;AAsBA;;;;;;UAtBiB,YAAA;;WAEN,IAAA;ECXyB;;;;EDiBlC,QAAA,CAAS,MAAA;ECdT;;;;AAIF;EDiBE,YAAA,CAAa,MAAA,YAAkB,MAAA;AAAA;;;;;cAOpB,eAAA,EAAiB,YAAA;;;UC/Bb,mBAAA;EACf,OAAA;EACA,WAAA;EACA,WAAA;EACA,UAAA;AAAA;AAAA,UAGe,kBAAA;EACf,MAAA;EACA,WAAA;EACA,MAAA;EDqB4B;ECnB5B,IAAA;AAAA;;iBAIc,YAAA,CAAa,OAAA,EAAS,mBAAA,GAAsB,eAAA;AAhB5D;AAAA,iBAuBgB,WAAA,CAAY,OAAA,EAAS,kBAAA,GAAqB,eAAA;;iBAY1C,OAAA,CAAA,GAAW,IAAA,aAAiB,cAAA,GAAiB,eAAA;;iBAW7C,aAAA,CAAc,IAAA,YAAsB,cAAA,GAAiB,eAAA;;iBAWrD,UAAA,CAAA,GAAc,cAAA,GAAiB,eAAA;;;UCxD9B,WAAA;EACf,KAAA;EACA,OAAA;EACA,WAAA;AAAA;AAAA,UAGe,cAAA;EACf,IAAA,GAAO,OAAA,CAAQ,WAAA;EACf,OAAA;IAAY,GAAA;IAAa,WAAA;EAAA;EACzB,UAAA;EFcqC;;AAOvC;;;;;;;;AC/BA;;;ECwBE,YAAA,GAAe,YAAA;AAAA;;iBAWD,yBAAA,CAA0B,eAAA,OAAsB,SAAA;;iBAKhD,qBAAA,CAAA;;iBAKA,gBAAA,CAAiB,OAAA,GAAS,cAAA;;;UClCzB,qBAAA,SAA8B,cAAA;EHF9B;EGIf,QAAA;;EAEA,SAAA;EHJS;EGMT,QAAA;EHAS;EGET,QAAA;EHKa;;;;AAOf;EGNE,aAAA;AAAA;;;;;;AFzBF;;;;;;;;;;AAOA;;;;;;;;cE4Ca,cAAA,YAA0B,UAAA;EAAA,iBAGR,OAAA;EAF7B,IAAA;cAE6B,OAAA,GAAS,qBAAA;;cAG1B,QAAA,CAAA;EFzCwB;EE8CpC,UAAA,CAAA;IAAa;EAAA,GAAU,cAAA;EF9CmC;EE0E1D,YAAA,CAAa,eAAA,OAAsB,SAAA;EAKnC,WAAA,CAAA;IAAc;EAAA,GAAO,cAAA;AAAA;;;;;;AHtFvB;;;;;;;;iBIAgB,aAAA,CAAc,OAAA,UAAiB,KAAA,WAAoB,UAAA;;;;AJsBnE;;;;iBI0BgB,SAAA,CAAU,OAAA,UAAiB,KAAA"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/schema-parser.ts","../src/decorators.ts","../src/openapi-builder.ts","../src/swagger.adapter.ts","../src/ui.ts"],"mappings":";;;;;;;AAqBA;;;;;;;;;;;;AAsBA;;;;;;UAtBiB,YAAA;;WAEN,IAAA;ECXyB;;;;EDiBlC,QAAA,CAAS,MAAA;ECdT;;;;AAIF;EDiBE,YAAA,CAAa,MAAA,YAAkB,MAAA;AAAA;;;;;cAOpB,eAAA,EAAiB,YAAA;;;UC/Bb,mBAAA;EACf,OAAA;EACA,WAAA;EACA,WAAA;EACA,UAAA;AAAA;AAAA,UAGe,kBAAA;EACf,MAAA;EACA,WAAA;EACA,MAAA;EDqB4B;ECnB5B,IAAA;AAAA;;iBAIc,YAAA,CAAa,OAAA,EAAS,mBAAA,GAAsB,eAAA;AAhB5D;AAAA,iBAuBgB,WAAA,CAAY,OAAA,EAAS,kBAAA,GAAqB,eAAA;;iBAY1C,OAAA,CAAA,GAAW,IAAA,aAAiB,cAAA,GAAiB,eAAA;;iBAW7C,aAAA,CAAc,IAAA,YAAsB,cAAA,GAAiB,eAAA;;iBAWrD,UAAA,CAAA,GAAc,cAAA,GAAiB,eAAA;;;UCxD9B,WAAA;EACf,KAAA;EACA,OAAA;EACA,WAAA;AAAA;AAAA,UAGe,cAAA;EACf,IAAA,GAAO,OAAA,CAAQ,WAAA;EACf,OAAA;IAAY,GAAA;IAAa,WAAA;EAAA;EACzB,UAAA;EFcqC;;AAOvC;;;;;;;;AC/BA;;;ECwBE,YAAA,GAAe,YAAA;AAAA;;iBAWD,yBAAA,CAA0B,eAAA,OAAsB,SAAA;;iBAKhD,qBAAA,CAAA;;iBAKA,gBAAA,CAAiB,OAAA,GAAS,cAAA;;;UClCzB,qBAAA,SAA8B,cAAA;EHF9B;EGIf,QAAA;;EAEA,SAAA;EHJS;EGMT,QAAA;EHAS;EGET,QAAA;EHKa;;;;AAOf;EGNE,aAAA;AAAA;;;;;;AFzBF;;;;;;;;;;AAOA;;;;;;;;cE4Ca,cAAA,YAA0B,UAAA;EAAA,iBAGR,OAAA;EAF7B,IAAA;cAE6B,OAAA,GAAS,qBAAA;;cAG1B,QAAA,CAAA;EFzCwB;EE8CpC,UAAA,CAAA;IAAa;EAAA,GAAU,cAAA;EF9CmC;EE0E1D,YAAA,CAAa,eAAA,OAAsB,SAAA;EAKnC,WAAA,CAAA;IAAc;EAAA,GAAO,cAAA;AAAA;;;;;;AHtFvB;;;;;;;;iBIAgB,aAAA,CAAc,OAAA,UAAiB,KAAA,WAAoB,UAAA;;;;AJsBnE;;;;iBIyCgB,SAAA,CAAU,OAAA,UAAiB,KAAA"}
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs-swagger v2.3.2
2
+ * @forinda/kickjs-swagger v2.3.3
3
3
  *
4
4
  * Copyright (c) Felix Orinda
5
5
  *
@@ -128,7 +128,19 @@ function buildOpenAPISpec(options = {}) {
128
128
  },
129
129
  tags: []
130
130
  };
131
- if (options.servers) spec.servers = options.servers;
131
+ if (options.servers) {
132
+ const validServers = options.servers.filter((s) => {
133
+ if (!s?.url || typeof s.url !== "string") return false;
134
+ if (s.url.startsWith("/")) return true;
135
+ try {
136
+ new URL(s.url);
137
+ return true;
138
+ } catch {
139
+ return false;
140
+ }
141
+ });
142
+ if (validServers.length > 0) spec.servers = validServers;
143
+ }
132
144
  const allTags = /* @__PURE__ */ new Set();
133
145
  const securitySchemes = {};
134
146
  for (const { controllerClass, mountPath } of registeredRoutes) {
@@ -356,14 +368,23 @@ function swaggerUIHtml(specUrl, title = "API Docs", assetsPath) {
356
368
  <script src="${assetsPath ? `${assetsPath}/swagger-ui-bundle.js` : "https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js"}"><\/script>
357
369
  <script src="${assetsPath ? `${assetsPath}/swagger-ui-standalone-preset.js` : "https://unpkg.com/swagger-ui-dist@5/swagger-ui-standalone-preset.js"}"><\/script>
358
370
  <script>
359
- SwaggerUIBundle({
360
- url: ${safeUrl},
361
- dom_id: '#swagger-ui',
362
- deepLinking: true,
363
- presets: [SwaggerUIBundle.presets.apis, SwaggerUIStandalonePreset],
364
- plugins: [SwaggerUIBundle.plugins.DownloadUrl],
365
- layout: 'StandaloneLayout',
366
- });
371
+ (function () {
372
+ var rawUrl = ${safeUrl};
373
+ var specUrl;
374
+ try {
375
+ specUrl = new URL(rawUrl, window.location.origin).href;
376
+ } catch (_e) {
377
+ specUrl = rawUrl;
378
+ }
379
+ SwaggerUIBundle({
380
+ url: specUrl,
381
+ dom_id: '#swagger-ui',
382
+ deepLinking: true,
383
+ presets: [SwaggerUIBundle.presets.apis, SwaggerUIStandalonePreset],
384
+ plugins: [SwaggerUIBundle.plugins.DownloadUrl],
385
+ layout: 'StandaloneLayout',
386
+ });
387
+ })();
367
388
  <\/script>
368
389
  </body>
369
390
  </html>`;
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../src/schema-parser.ts","../src/decorators.ts","../src/openapi-builder.ts","../src/ui.ts","../src/swagger.adapter.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 { setMethodMeta, setClassMeta, pushMethodMeta } from '@forinda/kickjs'\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 setMethodMeta(SWAGGER_KEYS.OPERATION, options, target.constructor, propertyKey as string)\n }\n}\n\n/** Document a response status. Can be stacked multiple times. */\nexport function ApiResponse(options: ApiResponseOptions): MethodDecorator {\n return (target, propertyKey) => {\n pushMethodMeta<ApiResponseOptions>(\n SWAGGER_KEYS.RESPONSES,\n target.constructor,\n propertyKey as string,\n options,\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 setMethodMeta(SWAGGER_KEYS.TAGS, tags, target.constructor, propertyKey as string)\n } else {\n setClassMeta(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 setMethodMeta(SWAGGER_KEYS.BEARER_AUTH, name, target.constructor, propertyKey as string)\n } else {\n setClassMeta(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 setMethodMeta(SWAGGER_KEYS.EXCLUDE, true, target.constructor, propertyKey as string)\n } else {\n setClassMeta(SWAGGER_KEYS.EXCLUDE, true, target)\n }\n }\n}\n","import {\n METADATA,\n joinPaths,\n type RouteDefinition,\n getClassMeta,\n getClassMetaOrUndefined,\n getMethodMeta,\n getMethodMetaOrUndefined,\n hasClassMeta,\n} from '@forinda/kickjs'\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 (hasClassMeta(SWAGGER_KEYS.EXCLUDE, controllerClass)) continue\n\n const routes: RouteDefinition[] = getClassMeta<RouteDefinition[]>(\n METADATA.ROUTES,\n controllerClass,\n [],\n )\n const classTags: string[] = getClassMeta<string[]>(SWAGGER_KEYS.TAGS, controllerClass, [])\n const classAuth: string | undefined = getClassMetaOrUndefined<string>(\n SWAGGER_KEYS.BEARER_AUTH,\n controllerClass,\n )\n for (const route of routes) {\n // Skip excluded methods\n if (getMethodMetaOrUndefined(SWAGGER_KEYS.EXCLUDE, controllerClass, route.handlerName))\n 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 = getMethodMeta<ApiOperationOptions>(\n SWAGGER_KEYS.OPERATION,\n controllerClass,\n route.handlerName,\n {} as ApiOperationOptions,\n )\n const responses: ApiResponseOptions[] = getMethodMeta<ApiResponseOptions[]>(\n SWAGGER_KEYS.RESPONSES,\n controllerClass,\n route.handlerName,\n [],\n )\n const methodTags: string[] = getMethodMeta<string[]>(\n SWAGGER_KEYS.TAGS,\n controllerClass,\n route.handlerName,\n [],\n )\n const methodAuth: string | undefined = getMethodMetaOrUndefined<string>(\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 = getMethodMetaOrUndefined<any>(\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 = getMethodMetaOrUndefined<any>(\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","/** 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/**\n * Generate Swagger UI HTML using local assets from swagger-ui-dist.\n *\n * Assets are served from `/_swagger-assets/` by the adapter's Express\n * static middleware. Falls back to CDN if the local path is not provided.\n * This ensures Swagger UI works fully offline in development.\n *\n * @param specUrl - Path to the OpenAPI JSON spec (e.g., '/openapi.json')\n * @param title - Page title\n * @param assetsPath - Base path for local swagger-ui-dist assets (e.g., '/_swagger-assets')\n */\nexport function swaggerUIHtml(specUrl: string, title = 'API Docs', assetsPath?: string): string {\n const safeTitle = escapeHtml(title)\n const safeUrl = JSON.stringify(specUrl).replace(/</g, '\\\\u003c')\n\n // Use local assets if available, CDN as fallback\n const cssHref = assetsPath\n ? `${assetsPath}/swagger-ui.css`\n : 'https://unpkg.com/swagger-ui-dist@5/swagger-ui.css'\n const bundleSrc = assetsPath\n ? `${assetsPath}/swagger-ui-bundle.js`\n : 'https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js'\n const presetSrc = assetsPath\n ? `${assetsPath}/swagger-ui-standalone-preset.js`\n : 'https://unpkg.com/swagger-ui-dist@5/swagger-ui-standalone-preset.js'\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=\"${cssHref}\">\n</head>\n<body>\n <div id=\"swagger-ui\"></div>\n <script src=\"${bundleSrc}\"></script>\n <script src=\"${presetSrc}\"></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/**\n * Generate ReDoc HTML.\n *\n * ReDoc doesn't publish a standalone npm package suitable for local serving,\n * so it still loads from CDN. If offline support for ReDoc is needed,\n * vendor the standalone bundle into the package's public/ directory.\n */\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","import { dirname } from 'node:path'\nimport { createRequire } from 'node:module'\nimport express, { Router } from 'express'\nimport { Logger, type AppAdapter, type AdapterContext } from '@forinda/kickjs'\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\n/**\n * Resolve the absolute path to swagger-ui-dist's static assets.\n * Uses createRequire to find it relative to this package (works with pnpm).\n */\nfunction getSwaggerUiDistPath(): string {\n const require = createRequire(import.meta.url)\n return dirname(require.resolve('swagger-ui-dist/package.json'))\n}\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 * When true, the adapter is a no-op while `NODE_ENV === 'production'` —\n * docs, spec, and assets are not mounted. Useful for keeping API docs\n * out of production builds without conditionally constructing the adapter.\n */\n disableInProd?: boolean\n}\n\n/**\n * Swagger adapter — auto-generates OpenAPI spec from decorators and serves docs.\n *\n * Assets are served locally from `swagger-ui-dist` (npm dependency) —\n * no CDN required, works fully offline.\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 (local assets, no CDN)\n * GET /redoc — ReDoc (CDN — no local package available)\n * GET /openapi.json — Raw OpenAPI 3.0.3 spec\n */\nexport class SwaggerAdapter implements AppAdapter {\n name = 'SwaggerAdapter'\n\n constructor(private readonly options: SwaggerAdapterOptions = {}) {}\n\n /** Whether the adapter should skip mounting in the current environment */\n private get disabled(): boolean {\n return Boolean(this.options.disableInProd) && process.env.NODE_ENV === 'production'\n }\n\n /** Auto-detect server URLs from the running HTTP server and peer adapters */\n afterStart({ server }: AdapterContext): void {\n if (this.disabled) return\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 if (this.disabled) return\n registerControllerForDocs(controllerClass, mountPath)\n }\n\n beforeMount({ app }: AdapterContext): void {\n if (this.disabled) {\n log.info('Swagger disabled in production (disableInProd=true)')\n return\n }\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 let uiDistAvailable = false\n\n const docsRouter = Router()\n\n // ── Serve swagger-ui-dist static assets locally ──────────────────\n // This makes Swagger UI work offline — no CDN needed.\n // Assets served at /_swagger-assets/ (CSS, JS, fonts, etc.)\n const swaggerAssetsPath = '/_swagger-assets'\n try {\n const swaggerDistDir = getSwaggerUiDistPath()\n docsRouter.use(swaggerAssetsPath, express.static(swaggerDistDir))\n uiDistAvailable = true\n } catch {\n log.warn('swagger-ui-dist not found — Swagger UI will load from CDN (requires internet).')\n }\n\n // Relax CSP for Swagger UI in both local and CDN modes (inline script is used in both)\n docsRouter.use((_req, res, next) => {\n // Build connect-src dynamically so \"Try it out\" can call any configured server URL.\n // Includes dev-friendly localhost/127.0.0.1 origins so docs served from one host\n // can call an API spec'd at the other (a common cross-origin gotcha).\n const serverOrigins = new Set<string>()\n for (const s of this.options.servers ?? []) {\n try {\n serverOrigins.add(new URL(s.url).origin)\n } catch {\n // ignore relative or malformed URLs\n }\n }\n const connectSrc = [\n \"'self'\",\n 'http://localhost:*',\n 'http://127.0.0.1:*',\n 'https://localhost:*',\n 'https://127.0.0.1:*',\n 'ws://localhost:*',\n 'ws://127.0.0.1:*',\n ...serverOrigins,\n ].join(' ')\n\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 ${connectSrc}`,\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 — uses local assets if available, CDN fallback\n docsRouter.get(docsPath, (_req, res) => {\n res\n .type('html')\n .send(\n swaggerUIHtml(\n specPath,\n this.options.info?.title,\n uiDistAvailable ? swaggerAssetsPath : undefined,\n ),\n )\n })\n\n // ReDoc — still CDN-based (no npm package for standalone bundle)\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"],"mappings":";;;;;;;;;;;;;;;;;;;AA2CA,MAAa,kBAAgC;CAC3C,MAAM;CAEN,SAAS,QAA0B;AACjC,SACE,UAAU,QACV,OAAO,WAAW,YAClB,OAAQ,OAAe,cAAc,cACrC,OAAQ,OAAe,iBAAiB;;CAI5C,aAAa,QAA0C;EACrD,MAAM,EAAE,SAAS,GAAG,GAAG,SAAU,OAAe,cAAc;AAC9D,SAAO;;CAEV;;;ACzDD,MAAM,eAAe;CACnB,WAAW,OAAO,yBAAyB;CAC3C,WAAW,OAAO,yBAAyB;CAC3C,MAAM,OAAO,oBAAoB;CACjC,aAAa,OAAO,sBAAsB;CAC1C,SAAS,OAAO,uBAAuB;CACxC;;AAoBD,SAAgB,aAAa,SAA+C;AAC1E,SAAQ,QAAQ,gBAAgB;AAC9B,gBAAc,aAAa,WAAW,SAAS,OAAO,aAAa,YAAsB;;;;AAK7F,SAAgB,YAAY,SAA8C;AACxE,SAAQ,QAAQ,gBAAgB;AAC9B,iBACE,aAAa,WACb,OAAO,aACP,aACA,QACD;;;;AAKL,SAAgB,QAAQ,GAAG,MAAkD;AAC3E,SAAQ,QAAa,gBAAkC;AACrD,MAAI,YACF,eAAc,aAAa,MAAM,MAAM,OAAO,aAAa,YAAsB;MAEjF,cAAa,aAAa,MAAM,MAAM,OAAO;;;;AAMnD,SAAgB,cAAc,OAAO,cAAgD;AACnF,SAAQ,QAAa,gBAAkC;AACrD,MAAI,YACF,eAAc,aAAa,aAAa,MAAM,OAAO,aAAa,YAAsB;MAExF,cAAa,aAAa,aAAa,MAAM,OAAO;;;;AAM1D,SAAgB,aAA+C;AAC7D,SAAQ,QAAa,gBAAkC;AACrD,MAAI,YACF,eAAc,aAAa,SAAS,MAAM,OAAO,aAAa,YAAsB;MAEpF,cAAa,aAAa,SAAS,MAAM,OAAO;;;;;AC9BtD,MAAM,mBAAsC,EAAE;;AAG9C,SAAgB,0BAA0B,iBAAsB,WAAyB;AACvF,kBAAiB,KAAK;EAAE;EAAiB;EAAW,CAAC;;;AAIvD,SAAgB,wBAA8B;AAC5C,kBAAiB,SAAS;;;AAI5B,SAAgB,iBAAiB,UAA0B,EAAE,EAAO;CAClE,MAAM,SAAS,QAAQ,gBAAgB;;CAGvC,MAAM,gBAAgB,WAAoD;AACxE,MAAI;AACF,OAAI,CAAC,OAAO,SAAS,OAAO,CAAE,QAAO;AACrC,UAAO,OAAO,aAAa,OAAO;UAC5B;AACN,UAAO;;;CAIX,MAAM,mBAAwC,EAAE;CAChD,IAAI,gBAAgB;;;;;CAMpB,MAAM,kBAAkB,YAAqC,SAAuB;EAElF,IAAI,OAAQ,WAAW,SAAqB,WAAW,SAAoB,QAAQ;AACnF,MAAI,CAAC,KACH,QAAO,SAAS,EAAE;AAGpB,SAAO,KAAK,QAAQ,iBAAiB,GAAG;AAGxC,MAAI,CAAC,iBAAiB,OAAO;GAC3B,MAAM,QAAQ,EAAE,GAAG,YAAY;AAC/B,UAAO,MAAM;AACb,UAAO,MAAM;AACb,UAAO,MAAM;AACb,oBAAiB,QAAQ;;AAE3B,SAAO,EAAE,MAAM,wBAAwB,QAAQ;;CAGjD,MAAM,OAAY;EAChB,SAAS;EACT,MAAM;GACJ,OAAO,QAAQ,MAAM,SAAS;GAC9B,SAAS,QAAQ,MAAM,WAAW;GAClC,GAAI,QAAQ,MAAM,cAAc,EAAE,aAAa,QAAQ,KAAK,aAAa,GAAG,EAAE;GAC/E;EACD,OAAO,EAAE;EACT,YAAY;GAAE,SAAS,EAAE;GAAE,iBAAiB,EAAE;GAAE;EAChD,MAAM,EAAE;EACT;AAED,KAAI,QAAQ,QACV,MAAK,UAAU,QAAQ;CAGzB,MAAM,0BAAU,IAAI,KAAa;CACjC,MAAM,kBAAuC,EAAE;AAE/C,MAAK,MAAM,EAAE,iBAAiB,eAAe,kBAAkB;AAE7D,MAAI,aAAa,aAAa,SAAS,gBAAgB,CAAE;EAEzD,MAAM,SAA4B,aAChC,SAAS,QACT,iBACA,EAAE,CACH;EACD,MAAM,YAAsB,aAAuB,aAAa,MAAM,iBAAiB,EAAE,CAAC;EAC1F,MAAM,YAAgC,wBACpC,aAAa,aACb,gBACD;AACD,OAAK,MAAM,SAAS,QAAQ;AAE1B,OAAI,yBAAyB,aAAa,SAAS,iBAAiB,MAAM,YAAY,CACpF;GAKF,MAAM,WAAW,UAAU,WAAW,MAAM,KAAK;GAGjD,MAAM,cAAc,SAAS,QAAQ,kBAAkB,OAAO;GAC9D,MAAM,SAAS,MAAM,OAAO,aAAa;GAGzC,MAAM,YAAiC,cACrC,aAAa,WACb,iBACA,MAAM,aACN,EAAE,CACH;GACD,MAAM,YAAkC,cACtC,aAAa,WACb,iBACA,MAAM,aACN,EAAE,CACH;GACD,MAAM,aAAuB,cAC3B,aAAa,MACb,iBACA,MAAM,aACN,EAAE,CACH;GACD,MAAM,aAAiC,yBACrC,aAAa,aACb,iBACA,MAAM,YACP;GAGD,MAAM,OAAO,WAAW,SAAS,IAAI,aAAa;AAClD,QAAK,SAAS,MAAM,QAAQ,IAAI,EAAE,CAAC;GAGnC,MAAM,KAAU;IACd,GAAI,KAAK,SAAS,IAAI,EAAE,MAAM,GAAG,EAAE;IACnC,GAAI,UAAU,UAAU,EAAE,SAAS,UAAU,SAAS,GAAG,EAAE;IAC3D,GAAI,UAAU,cAAc,EAAE,aAAa,UAAU,aAAa,GAAG,EAAE;IACvE,GAAI,UAAU,cAAc,EAAE,aAAa,UAAU,aAAa,GAAG,EAAE;IACvE,GAAI,UAAU,aAAa,EAAE,YAAY,MAAM,GAAG,EAAE;IACpD,YAAY,EAAE;IACd,WAAW,EAAE;IACd;GAGD,MAAM,eAAe,SAAS,MAAM,iBAAiB,IAAI,EAAE;AAC3D,QAAK,MAAM,SAAS,cAAc;IAChC,MAAM,YAAY,MAAM,MAAM,EAAE;IAChC,IAAI,SAAc,EAAE,MAAM,UAAU;AAGpC,QAAI,MAAM,YAAY,QAAQ;KAC5B,MAAM,aAAa,aAAa,MAAM,WAAW,OAAO;AACxD,SAAI,YAAY,cAAc,OAAO,WAAW,eAAe,UAAU;MACvE,MAAM,QAAQ,WAAW;AACzB,UAAI,MAAM,WACR,UAAS,MAAM;;;AAKrB,OAAG,WAAW,KAAK;KACjB,MAAM;KACN,IAAI;KACJ,UAAU;KACV;KACD,CAAC;;AAIJ,OAAI,MAAM,YAAY,OAAO;IAC3B,MAAM,aAAa,aAAa,MAAM,WAAW,MAAM;AACvD,QAAI,YAAY,cAAc,OAAO,WAAW,eAAe,UAAU;KACvE,MAAM,WAAW,MAAM,QAAQ,WAAW,SAAS,GAAG,WAAW,WAAW,EAAE;AAC9E,UAAK,MAAM,CAAC,MAAM,eAAe,OAAO,QACtC,WAAW,WACZ,CACC,IAAG,WAAW,KAAK;MACjB;MACA,IAAI;MACJ,UAAU,SAAS,SAAS,KAAK;MACjC,QAAQ;MACT,CAAC;;;GAMR,MAAM,oBAAoB,yBACxB,SAAS,cACT,iBACA,MAAM,YACP;AACD,OAAI,mBAAmB;AACrB,QAAI,kBAAkB,YAAY,OAChC,IAAG,WAAW,KAAK;KACjB,MAAM;KACN,IAAI;KACJ,UAAU;KACV,aAAa,kBAAkB,kBAAkB,WAAW,KAAK,KAAK,CAAC;KACvE,QAAQ;MAAE,MAAM;MAAS,OAAO,EAAE,MAAM,UAAU;MAAE;KACpD,OAAO;KACP,SAAS;KACV,CAAC;AAEJ,QAAI,kBAAkB,UAAU,OAC9B,IAAG,WAAW,KAAK;KACjB,MAAM;KACN,IAAI;KACJ,UAAU;KACV,aAAa,gBAAgB,kBAAkB,SAAS,KAAK,KAAK,CAAC;KACnE,QAAQ;MAAE,MAAM;MAAS,OAAO,EAAE,MAAM,UAAU;MAAE;KACpD,OAAO;KACP,SAAS;KACV,CAAC;AAEJ,QAAI,kBAAkB,YAAY,OAChC,IAAG,WAAW,KAAK;KACjB,MAAM;KACN,IAAI;KACJ,UAAU;KACV,aAAa,kBAAkB,kBAAkB,WAAW,KAAK,KAAK;KACtE,QAAQ,EAAE,MAAM,UAAU;KAC3B,CAAC;AAEJ,OAAG,WAAW,KACZ;KACE,MAAM;KACN,IAAI;KACJ,UAAU;KACV,aAAa;KACb,QAAQ;MAAE,MAAM;MAAW,SAAS;MAAG,SAAS;MAAG;KACpD,EACD;KACE,MAAM;KACN,IAAI;KACJ,UAAU;KACV,aAAa;KACb,QAAQ;MAAE,MAAM;MAAW,SAAS;MAAG,SAAS;MAAK,SAAS;MAAI;KACnE,CACF;;AAIH,OAAI,GAAG,WAAW,WAAW,EAAG,QAAO,GAAG;AAG1C,OAAI,MAAM,YAAY,QAAQ;IAAC;IAAQ;IAAO;IAAQ,CAAC,SAAS,OAAO,EAAE;IACvE,MAAM,aAAa,aAAa,MAAM,WAAW,KAAK;AACtD,QAAI,YAAY;KAEd,MAAM,MAAM,eAAe,YADV,MAAM,WAAW,QAAQ,GAAG,MAAM,YAAY,MACf;AAChD,QAAG,cAAc;MACf,UAAU;MACV,SAAS,EAAE,oBAAoB,EAAE,QAAQ,KAAK,EAAE;MACjD;;;GAKL,MAAM,aAAa,yBACjB,SAAS,aACT,iBACA,MAAM,YACP;AACD,OAAI,YAAY;IACd,MAAM,YAAY,WAAW,aAAa;IAC1C,MAAM,aAAkB,EAAE;AAE1B,QAAI,WAAW,SAAS,QACtB,YAAW,aAAa;KACtB,MAAM;KACN,OAAO;MAAE,MAAM;MAAU,QAAQ;MAAU;KAC5C;aACQ,WAAW,SAAS,OAC7B,YAAW,aAAa;KACtB,MAAM;KACN,QAAQ;KACT;AAGH,OAAG,cAAc;KACf,UAAU;KACV,SAAS,EACP,uBAAuB,EACrB,QAAQ;MAAE,MAAM;MAAU;MAAY,EACvC,EACF;KACF;;AAIH,OAAI,UAAU,SAAS,EACrB,MAAK,MAAM,QAAQ,UACjB,IAAG,UAAU,OAAO,KAAK,OAAO,IAAI;IAClC,aAAa,KAAK,eAAe;IACjC,GAAI,KAAK,gBACE;KACL,MAAM,YACJ,OAAO,KAAK,WAAW,cAAc,OAAO,KAAK,WAAW,WACxD,aAAa,KAAK,OAAO,GACzB;KACN,MAAM,aAAa,KAAK,QAAQ,GAAG,MAAM,YAAY,UAAU,KAAK;KACpE,MAAM,cAAc,YAChB,eAAe,WAAW,WAAW,GACrC,OAAO,KAAK,WAAW,WACrB,KAAK,SACL,KAAA;AACN,YAAO,cACH,EAAE,SAAS,EAAE,oBAAoB,EAAE,QAAQ,aAAa,EAAE,EAAE,GAC5D,EAAE;QACJ,GACJ,EAAE;IACP;QAEE;IAEL,MAAM,gBAAgB,WAAW,SAAS,QAAQ,WAAW,WAAW,QAAQ;AAChF,OAAG,UAAU,iBAAiB,EAAE,aAAa,wBAAwB;AAErE,QAAI,MAAM,YAAY,KACpB,IAAG,UAAU,SAAS,EAAE,aAAa,oBAAoB;;GAK7D,MAAM,WAAW,cAAc;AAC/B,OAAI,UAAU;AACZ,OAAG,WAAW,CAAC,GAAG,WAAW,EAAE,EAAE,CAAC;AAClC,oBAAgB,YAAY;KAC1B,MAAM;KACN,QAAQ;KACR,cAAc;KACf;;AAIH,OAAI,CAAC,KAAK,MAAM,aAAc,MAAK,MAAM,eAAe,EAAE;AAC1D,QAAK,MAAM,aAAa,UAAU;;;AAKtC,MAAK,OAAO,MAAM,KAAK,QAAQ,CAAC,KAAK,UAAU,EAAE,MAAM,EAAE;AACzD,MAAK,WAAW,kBAAkB;AAElC,KAAI,QAAQ,YAAY;AACtB,MAAI,CAAC,gBAAgB,WACnB,MAAK,WAAW,gBAAgB,aAAa;GAC3C,MAAM;GACN,QAAQ;GACR,cAAc;GACf;AAEH,OAAK,WAAW,CAAC,EAAE,YAAY,EAAE,EAAE,CAAC;;AAItC,MAAK,WAAW,UAAU;AAG1B,KAAI,OAAO,KAAK,KAAK,WAAW,QAAQ,CAAC,WAAW,EAAG,QAAO,KAAK,WAAW;AAC9E,KAAI,OAAO,KAAK,KAAK,WAAW,gBAAgB,CAAC,WAAW,EAC1D,QAAO,KAAK,WAAW;AACzB,KAAI,OAAO,KAAK,KAAK,WAAW,CAAC,WAAW,EAAG,QAAO,KAAK;AAE3D,QAAO;;;;;ACrZT,SAAS,WAAW,KAAqB;AACvC,QAAO,IACJ,QAAQ,MAAM,QAAQ,CACtB,QAAQ,MAAM,OAAO,CACrB,QAAQ,MAAM,OAAO,CACrB,QAAQ,MAAM,SAAS,CACvB,QAAQ,MAAM,QAAQ;;;;;;;;;;;;;AAc3B,SAAgB,cAAc,SAAiB,QAAQ,YAAY,YAA6B;CAC9F,MAAM,YAAY,WAAW,MAAM;CACnC,MAAM,UAAU,KAAK,UAAU,QAAQ,CAAC,QAAQ,MAAM,UAAU;AAahE,QAAO;;;;;WAKE,UAAU;iCAfH,aACZ,GAAG,WAAW,mBACd,qDAcmC;;;;iBAbrB,aACd,GAAG,WAAW,yBACd,2DAeqB;iBAdP,aACd,GAAG,WAAW,oCACd,sEAaqB;;;aAGd,QAAQ;;;;;;;;;;;;;;;;;;AAmBrB,SAAgB,UAAU,SAAiB,QAAQ,YAAoB;AAIrE,QAAO;;;;;WAHW,WAAW,MAAM,CAQhB;;;qBAPH,WAAW,QAAQ,CAUR;;;;;;;ACrE7B,MAAM,MAAM,OAAO,IAAI,iBAAiB;;;;;AAMxC,SAAS,uBAA+B;AAEtC,QAAO,QADS,cAAc,OAAO,KAAK,IAAI,CACvB,QAAQ,+BAA+B,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;AA2CjE,IAAa,iBAAb,MAAkD;CAChD,OAAO;CAEP,YAAY,UAAkD,EAAE,EAAE;AAArC,OAAA,UAAA;;;CAG7B,IAAY,WAAoB;AAC9B,SAAO,QAAQ,KAAK,QAAQ,cAAc,IAAI,QAAQ,IAAI,aAAa;;;CAIzE,WAAW,EAAE,UAAgC;AAC3C,MAAI,KAAK,SAAU;EACnB,MAAM,OAAO,QAAQ,WAAW;AAChC,MAAI,CAAC,QAAQ,OAAO,SAAS,SAAU;EAEvC,MAAM,OAAO,KAAK,YAAY,QAAQ,KAAK,YAAY,YAAY,cAAc,KAAK;AAGtF,MAAI,CAAC,KAAK,QAAQ,WAAW,KAAK,QAAQ,QAAQ,WAAW,EAC3D,MAAK,QAAQ,UAAU,CAAC;GAAE,KAAK,UAAU,KAAK,GAAG,KAAK;GAAQ,aAAa;GAAe,CAAC;EAI7F,MAAM,YAAY,KAAK,QAAQ,UAAU,MACtC,MAAM,EAAE,SAAS,eAAe,OAAO,EAAE,aAAa,WACxD;AACD,MAAI,WAAW;GACb,MAAM,QAAQ,UAAU,UAAU;AAClC,QAAK,MAAM,aAAa,OAAO,KAAK,MAAM,cAAc,EAAE,CAAC,CACzD,MAAK,QAAQ,SAAS,KAAK;IACzB,KAAK,QAAQ,KAAK,GAAG,KAAK,OAAO;IACjC,aAAa,cAAc;IAC5B,CAAC;;;;CAMR,aAAa,iBAAsB,WAAyB;AAC1D,MAAI,KAAK,SAAU;AACnB,4BAA0B,iBAAiB,UAAU;;CAGvD,YAAY,EAAE,OAA6B;AACzC,MAAI,KAAK,UAAU;AACjB,OAAI,KAAK,sDAAsD;AAC/D;;AAGF,yBAAuB;EACvB,MAAM,WAAW,KAAK,QAAQ,YAAY;EAC1C,MAAM,YAAY,KAAK,QAAQ,aAAa;EAC5C,MAAM,WAAW,KAAK,QAAQ,YAAY;EAC1C,IAAI,kBAAkB;EAEtB,MAAM,aAAa,QAAQ;EAK3B,MAAM,oBAAoB;AAC1B,MAAI;GACF,MAAM,iBAAiB,sBAAsB;AAC7C,cAAW,IAAI,mBAAmB,QAAQ,OAAO,eAAe,CAAC;AACjE,qBAAkB;UACZ;AACN,OAAI,KAAK,iFAAiF;;AAI5F,aAAW,KAAK,MAAM,KAAK,SAAS;GAIlC,MAAM,gCAAgB,IAAI,KAAa;AACvC,QAAK,MAAM,KAAK,KAAK,QAAQ,WAAW,EAAE,CACxC,KAAI;AACF,kBAAc,IAAI,IAAI,IAAI,EAAE,IAAI,CAAC,OAAO;WAClC;GAIV,MAAM,aAAa;IACjB;IACA;IACA;IACA;IACA;IACA;IACA;IACA,GAAG;IACJ,CAAC,KAAK,IAAI;AAEX,OAAI,UACF,2BACA;IACE;IACA;IACA;IACA;IACA;IACA,eAAe;IAChB,CAAC,KAAK,KAAK,CACb;AACD,SAAM;IACN;AAGF,aAAW,IAAI,WAAW,MAAM,QAAQ;GACtC,MAAM,OAAO,iBAAiB,KAAK,QAAQ;AAC3C,OAAI,KAAK,KAAK;IACd;AAGF,aAAW,IAAI,WAAW,MAAM,QAAQ;AACtC,OACG,KAAK,OAAO,CACZ,KACC,cACE,UACA,KAAK,QAAQ,MAAM,OACnB,kBAAkB,oBAAoB,KAAA,EACvC,CACF;IACH;AAGF,aAAW,IAAI,YAAY,MAAM,QAAQ;AACvC,OAAI,KAAK,OAAO,CAAC,KAAK,UAAU,UAAU,KAAK,QAAQ,MAAM,MAAM,CAAC;IACpE;AAEF,MAAI,IAAI,WAAW;AAEnB,MAAI,KAAK,gBAAgB,WAAW;AACpC,MAAI,KAAK,gBAAgB,YAAY;AACrC,MAAI,KAAK,iBAAiB,WAAW"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/schema-parser.ts","../src/decorators.ts","../src/openapi-builder.ts","../src/ui.ts","../src/swagger.adapter.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 { setMethodMeta, setClassMeta, pushMethodMeta } from '@forinda/kickjs'\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 setMethodMeta(SWAGGER_KEYS.OPERATION, options, target.constructor, propertyKey as string)\n }\n}\n\n/** Document a response status. Can be stacked multiple times. */\nexport function ApiResponse(options: ApiResponseOptions): MethodDecorator {\n return (target, propertyKey) => {\n pushMethodMeta<ApiResponseOptions>(\n SWAGGER_KEYS.RESPONSES,\n target.constructor,\n propertyKey as string,\n options,\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 setMethodMeta(SWAGGER_KEYS.TAGS, tags, target.constructor, propertyKey as string)\n } else {\n setClassMeta(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 setMethodMeta(SWAGGER_KEYS.BEARER_AUTH, name, target.constructor, propertyKey as string)\n } else {\n setClassMeta(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 setMethodMeta(SWAGGER_KEYS.EXCLUDE, true, target.constructor, propertyKey as string)\n } else {\n setClassMeta(SWAGGER_KEYS.EXCLUDE, true, target)\n }\n }\n}\n","import {\n METADATA,\n joinPaths,\n type RouteDefinition,\n getClassMeta,\n getClassMetaOrUndefined,\n getMethodMeta,\n getMethodMetaOrUndefined,\n hasClassMeta,\n} from '@forinda/kickjs'\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 // Drop entries whose URL can't be parsed by the browser's URL\n // constructor. Swagger UI runs `new URL(server.url)` on the client\n // and crashes with `Failed to construct 'URL': Invalid URL` if any\n // entry is malformed — which can happen on Windows dev when an\n // adapter hook populates servers with a path that was never meant\n // to be a URL. Relative URLs (e.g. '/') are allowed through.\n const validServers = options.servers.filter((s) => {\n if (!s?.url || typeof s.url !== 'string') return false\n if (s.url.startsWith('/')) return true\n try {\n new URL(s.url)\n return true\n } catch {\n return false\n }\n })\n if (validServers.length > 0) {\n spec.servers = validServers\n }\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 (hasClassMeta(SWAGGER_KEYS.EXCLUDE, controllerClass)) continue\n\n const routes: RouteDefinition[] = getClassMeta<RouteDefinition[]>(\n METADATA.ROUTES,\n controllerClass,\n [],\n )\n const classTags: string[] = getClassMeta<string[]>(SWAGGER_KEYS.TAGS, controllerClass, [])\n const classAuth: string | undefined = getClassMetaOrUndefined<string>(\n SWAGGER_KEYS.BEARER_AUTH,\n controllerClass,\n )\n for (const route of routes) {\n // Skip excluded methods\n if (getMethodMetaOrUndefined(SWAGGER_KEYS.EXCLUDE, controllerClass, route.handlerName))\n 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 = getMethodMeta<ApiOperationOptions>(\n SWAGGER_KEYS.OPERATION,\n controllerClass,\n route.handlerName,\n {} as ApiOperationOptions,\n )\n const responses: ApiResponseOptions[] = getMethodMeta<ApiResponseOptions[]>(\n SWAGGER_KEYS.RESPONSES,\n controllerClass,\n route.handlerName,\n [],\n )\n const methodTags: string[] = getMethodMeta<string[]>(\n SWAGGER_KEYS.TAGS,\n controllerClass,\n route.handlerName,\n [],\n )\n const methodAuth: string | undefined = getMethodMetaOrUndefined<string>(\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 = getMethodMetaOrUndefined<any>(\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 = getMethodMetaOrUndefined<any>(\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","/** 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/**\n * Generate Swagger UI HTML using local assets from swagger-ui-dist.\n *\n * Assets are served from `/_swagger-assets/` by the adapter's Express\n * static middleware. Falls back to CDN if the local path is not provided.\n * This ensures Swagger UI works fully offline in development.\n *\n * @param specUrl - Path to the OpenAPI JSON spec (e.g., '/openapi.json')\n * @param title - Page title\n * @param assetsPath - Base path for local swagger-ui-dist assets (e.g., '/_swagger-assets')\n */\nexport function swaggerUIHtml(specUrl: string, title = 'API Docs', assetsPath?: string): string {\n const safeTitle = escapeHtml(title)\n // JSON-stringify for safe inlining into the `<script>` block. The inline\n // script below resolves this to an absolute URL against\n // `window.location.origin` before passing it to SwaggerUIBundle —\n // some swagger-ui-dist builds call `new URL(url)` without a base and\n // crash with `Failed to construct 'URL': Invalid URL` when the value\n // is a bare path like `/openapi.json`.\n const safeUrl = JSON.stringify(specUrl).replace(/</g, '\\\\u003c')\n\n // Use local assets if available, CDN as fallback\n const cssHref = assetsPath\n ? `${assetsPath}/swagger-ui.css`\n : 'https://unpkg.com/swagger-ui-dist@5/swagger-ui.css'\n const bundleSrc = assetsPath\n ? `${assetsPath}/swagger-ui-bundle.js`\n : 'https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js'\n const presetSrc = assetsPath\n ? `${assetsPath}/swagger-ui-standalone-preset.js`\n : 'https://unpkg.com/swagger-ui-dist@5/swagger-ui-standalone-preset.js'\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=\"${cssHref}\">\n</head>\n<body>\n <div id=\"swagger-ui\"></div>\n <script src=\"${bundleSrc}\"></script>\n <script src=\"${presetSrc}\"></script>\n <script>\n (function () {\n var rawUrl = ${safeUrl};\n var specUrl;\n try {\n specUrl = new URL(rawUrl, window.location.origin).href;\n } catch (_e) {\n specUrl = rawUrl;\n }\n SwaggerUIBundle({\n url: specUrl,\n dom_id: '#swagger-ui',\n deepLinking: true,\n presets: [SwaggerUIBundle.presets.apis, SwaggerUIStandalonePreset],\n plugins: [SwaggerUIBundle.plugins.DownloadUrl],\n layout: 'StandaloneLayout',\n });\n })();\n </script>\n</body>\n</html>`\n}\n\n/**\n * Generate ReDoc HTML.\n *\n * ReDoc doesn't publish a standalone npm package suitable for local serving,\n * so it still loads from CDN. If offline support for ReDoc is needed,\n * vendor the standalone bundle into the package's public/ directory.\n */\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","import { dirname } from 'node:path'\nimport { createRequire } from 'node:module'\nimport express, { Router } from 'express'\nimport { Logger, type AppAdapter, type AdapterContext } from '@forinda/kickjs'\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\n/**\n * Resolve the absolute path to swagger-ui-dist's static assets.\n * Uses createRequire to find it relative to this package (works with pnpm).\n */\nfunction getSwaggerUiDistPath(): string {\n const require = createRequire(import.meta.url)\n return dirname(require.resolve('swagger-ui-dist/package.json'))\n}\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 * When true, the adapter is a no-op while `NODE_ENV === 'production'` —\n * docs, spec, and assets are not mounted. Useful for keeping API docs\n * out of production builds without conditionally constructing the adapter.\n */\n disableInProd?: boolean\n}\n\n/**\n * Swagger adapter — auto-generates OpenAPI spec from decorators and serves docs.\n *\n * Assets are served locally from `swagger-ui-dist` (npm dependency) —\n * no CDN required, works fully offline.\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 (local assets, no CDN)\n * GET /redoc — ReDoc (CDN — no local package available)\n * GET /openapi.json — Raw OpenAPI 3.0.3 spec\n */\nexport class SwaggerAdapter implements AppAdapter {\n name = 'SwaggerAdapter'\n\n constructor(private readonly options: SwaggerAdapterOptions = {}) {}\n\n /** Whether the adapter should skip mounting in the current environment */\n private get disabled(): boolean {\n return Boolean(this.options.disableInProd) && process.env.NODE_ENV === 'production'\n }\n\n /** Auto-detect server URLs from the running HTTP server and peer adapters */\n afterStart({ server }: AdapterContext): void {\n if (this.disabled) return\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 if (this.disabled) return\n registerControllerForDocs(controllerClass, mountPath)\n }\n\n beforeMount({ app }: AdapterContext): void {\n if (this.disabled) {\n log.info('Swagger disabled in production (disableInProd=true)')\n return\n }\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 let uiDistAvailable = false\n\n const docsRouter = Router()\n\n // ── Serve swagger-ui-dist static assets locally ──────────────────\n // This makes Swagger UI work offline — no CDN needed.\n // Assets served at /_swagger-assets/ (CSS, JS, fonts, etc.)\n const swaggerAssetsPath = '/_swagger-assets'\n try {\n const swaggerDistDir = getSwaggerUiDistPath()\n docsRouter.use(swaggerAssetsPath, express.static(swaggerDistDir))\n uiDistAvailable = true\n } catch {\n log.warn('swagger-ui-dist not found — Swagger UI will load from CDN (requires internet).')\n }\n\n // Relax CSP for Swagger UI in both local and CDN modes (inline script is used in both)\n docsRouter.use((_req, res, next) => {\n // Build connect-src dynamically so \"Try it out\" can call any configured server URL.\n // Includes dev-friendly localhost/127.0.0.1 origins so docs served from one host\n // can call an API spec'd at the other (a common cross-origin gotcha).\n const serverOrigins = new Set<string>()\n for (const s of this.options.servers ?? []) {\n try {\n serverOrigins.add(new URL(s.url).origin)\n } catch {\n // ignore relative or malformed URLs\n }\n }\n const connectSrc = [\n \"'self'\",\n 'http://localhost:*',\n 'http://127.0.0.1:*',\n 'https://localhost:*',\n 'https://127.0.0.1:*',\n 'ws://localhost:*',\n 'ws://127.0.0.1:*',\n ...serverOrigins,\n ].join(' ')\n\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 ${connectSrc}`,\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 — uses local assets if available, CDN fallback\n docsRouter.get(docsPath, (_req, res) => {\n res\n .type('html')\n .send(\n swaggerUIHtml(\n specPath,\n this.options.info?.title,\n uiDistAvailable ? swaggerAssetsPath : undefined,\n ),\n )\n })\n\n // ReDoc — still CDN-based (no npm package for standalone bundle)\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"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forinda/kickjs-swagger",
3
- "version": "2.3.2",
3
+ "version": "2.3.3",
4
4
  "description": "OpenAPI spec generation from decorators, Swagger UI and ReDoc serving for KickJS",
5
5
  "keywords": [
6
6
  "kickjs",
@@ -73,7 +73,7 @@
73
73
  "dependencies": {
74
74
  "reflect-metadata": "^0.2.2",
75
75
  "swagger-ui-dist": "^5.32.1",
76
- "@forinda/kickjs": "2.3.2"
76
+ "@forinda/kickjs": "2.3.3"
77
77
  },
78
78
  "peerDependencies": {
79
79
  "express": "^5.1.0",