@forinda/kickjs-swagger 2.1.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -31,11 +31,15 @@ bootstrap({
31
31
  new SwaggerAdapter({
32
32
  info: { title: 'My API', version: '1.0.0' },
33
33
  bearerAuth: true,
34
+ disableInProd: true, // skip mounting docs when NODE_ENV=production
34
35
  }),
35
36
  ],
36
37
  })
37
38
  ```
38
39
 
40
+ Set `disableInProd: true` to skip mounting docs, the spec, and assets when
41
+ `NODE_ENV === 'production'`.
42
+
39
43
  ### Custom Schema Parser (Joi)
40
44
 
41
45
  ```typescript
package/dist/index.d.mts CHANGED
@@ -114,6 +114,12 @@ interface SwaggerAdapterOptions extends SwaggerOptions {
114
114
  specPath?: string;
115
115
  /** Other adapters to discover (e.g., WsAdapter for WebSocket server URLs) */
116
116
  adapters?: any[];
117
+ /**
118
+ * When true, the adapter is a no-op while `NODE_ENV === 'production'` —
119
+ * docs, spec, and assets are not mounted. Useful for keeping API docs
120
+ * out of production builds without conditionally constructing the adapter.
121
+ */
122
+ disableInProd?: boolean;
117
123
  }
118
124
  /**
119
125
  * Swagger adapter — auto-generates OpenAPI spec from decorators and serves docs.
@@ -139,9 +145,11 @@ interface SwaggerAdapterOptions extends SwaggerOptions {
139
145
  * GET /openapi.json — Raw OpenAPI 3.0.3 spec
140
146
  */
141
147
  declare class SwaggerAdapter implements AppAdapter {
142
- private options;
148
+ private readonly options;
143
149
  name: string;
144
150
  constructor(options?: SwaggerAdapterOptions);
151
+ /** Whether the adapter should skip mounting in the current environment */
152
+ private get disabled();
145
153
  /** Auto-detect server URLs from the running HTTP server and peer adapters */
146
154
  afterStart({
147
155
  server
@@ -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;;;UCjCzB,qBAAA,SAA8B,cAAA;EHH9B;EGKf,QAAA;;EAEA,SAAA;EHLS;EGOT,QAAA;EHDS;EGGT,QAAA;AAAA;;;;AHWF;;;;;;;;AC/BA;;;;;;;;;;AAOA;;cEuCa,cAAA,YAA0B,UAAA;EAAA,QAGjB,OAAA;EAFpB,IAAA;cAEoB,OAAA,GAAS,qBAAA;EFvC7B;EE0CA,UAAA,CAAA;IAAa;EAAA,GAAU,cAAA;EFxCnB;EEmEJ,YAAA,CAAa,eAAA,OAAsB,SAAA;EAInC,WAAA,CAAA;IAAc;EAAA,GAAO,cAAA;AAAA;;;;;;AH1EvB;;;;;;;;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;;;;iBI0BgB,SAAA,CAAU,OAAA,UAAiB,KAAA"}
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs-swagger v2.1.0
2
+ * @forinda/kickjs-swagger v2.2.0
3
3
  *
4
4
  * Copyright (c) Felix Orinda
5
5
  *
@@ -427,8 +427,13 @@ var SwaggerAdapter = class {
427
427
  constructor(options = {}) {
428
428
  this.options = options;
429
429
  }
430
+ /** Whether the adapter should skip mounting in the current environment */
431
+ get disabled() {
432
+ return Boolean(this.options.disableInProd) && process.env.NODE_ENV === "production";
433
+ }
430
434
  /** Auto-detect server URLs from the running HTTP server and peer adapters */
431
435
  afterStart({ server }) {
436
+ if (this.disabled) return;
432
437
  const addr = server?.address?.();
433
438
  if (!addr || typeof addr !== "object") return;
434
439
  const host = addr.address === "::" || addr.address === "0.0.0.0" ? "localhost" : addr.address;
@@ -439,7 +444,7 @@ var SwaggerAdapter = class {
439
444
  const wsAdapter = this.options.adapters?.find((a) => a.name === "WsAdapter" && typeof a.getStats === "function");
440
445
  if (wsAdapter) {
441
446
  const stats = wsAdapter.getStats();
442
- for (const namespace of Object.keys(stats.namespaces || {})) this.options.servers.push({
447
+ for (const namespace of Object.keys(stats.namespaces || {})) this.options.servers?.push({
443
448
  url: `ws://${host}:${addr.port}${namespace}`,
444
449
  description: `WebSocket: ${namespace}`
445
450
  });
@@ -447,9 +452,14 @@ var SwaggerAdapter = class {
447
452
  }
448
453
  /** Collect controller metadata as routes are mounted */
449
454
  onRouteMount(controllerClass, mountPath) {
455
+ if (this.disabled) return;
450
456
  registerControllerForDocs(controllerClass, mountPath);
451
457
  }
452
458
  beforeMount({ app }) {
459
+ if (this.disabled) {
460
+ log.info("Swagger disabled in production (disableInProd=true)");
461
+ return;
462
+ }
453
463
  clearRegisteredRoutes();
454
464
  const docsPath = this.options.docsPath ?? "/docs";
455
465
  const redocPath = this.options.redocPath ?? "/redoc";
@@ -465,13 +475,27 @@ var SwaggerAdapter = class {
465
475
  log.warn("swagger-ui-dist not found — Swagger UI will load from CDN (requires internet).");
466
476
  }
467
477
  docsRouter.use((_req, res, next) => {
478
+ const serverOrigins = /* @__PURE__ */ new Set();
479
+ for (const s of this.options.servers ?? []) try {
480
+ serverOrigins.add(new URL(s.url).origin);
481
+ } catch {}
482
+ const connectSrc = [
483
+ "'self'",
484
+ "http://localhost:*",
485
+ "http://127.0.0.1:*",
486
+ "https://localhost:*",
487
+ "https://127.0.0.1:*",
488
+ "ws://localhost:*",
489
+ "ws://127.0.0.1:*",
490
+ ...serverOrigins
491
+ ].join(" ");
468
492
  res.setHeader("Content-Security-Policy", [
469
493
  "default-src 'self'",
470
494
  "script-src 'self' 'unsafe-inline' https://unpkg.com https://cdn.redoc.ly https://cdn.jsdelivr.net",
471
495
  "style-src 'self' 'unsafe-inline' https://unpkg.com https://fonts.googleapis.com",
472
496
  "font-src 'self' https://fonts.gstatic.com",
473
497
  "img-src 'self' data: https://unpkg.com",
474
- "connect-src 'self'"
498
+ `connect-src ${connectSrc}`
475
499
  ].join("; "));
476
500
  next();
477
501
  });
@@ -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 { Router } from 'express'\nimport express 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\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 options: SwaggerAdapterOptions = {}) {}\n\n /** Auto-detect server URLs from the running HTTP server and peer adapters */\n afterStart({ server }: AdapterContext): 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 }: AdapterContext): 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 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 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 — 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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"}
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":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forinda/kickjs-swagger",
3
- "version": "2.1.0",
3
+ "version": "2.2.0",
4
4
  "description": "OpenAPI spec generation from decorators, Swagger UI and ReDoc serving for KickJS",
5
5
  "keywords": [
6
6
  "kickjs",
@@ -70,7 +70,7 @@
70
70
  "dependencies": {
71
71
  "reflect-metadata": "^0.2.2",
72
72
  "swagger-ui-dist": "^5.32.1",
73
- "@forinda/kickjs": "2.1.0"
73
+ "@forinda/kickjs": "2.2.0"
74
74
  },
75
75
  "peerDependencies": {
76
76
  "express": "^5.1.0",