@forinda/kickjs-swagger 0.4.3 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +6 -0
- package/dist/index.js +107 -4
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -53,6 +53,8 @@ interface ApiResponseOptions {
|
|
|
53
53
|
status: number;
|
|
54
54
|
description?: string;
|
|
55
55
|
schema?: any;
|
|
56
|
+
/** Schema name in components/schemas (e.g., 'UserResponse', 'ErrorBody'). Auto-generated from handler name if omitted. */
|
|
57
|
+
name?: string;
|
|
56
58
|
}
|
|
57
59
|
/** Attach operation metadata to a route handler */
|
|
58
60
|
declare function ApiOperation(options: ApiOperationOptions): MethodDecorator;
|
|
@@ -106,6 +108,8 @@ interface SwaggerAdapterOptions extends SwaggerOptions {
|
|
|
106
108
|
redocPath?: string;
|
|
107
109
|
/** Path to serve the raw JSON spec (default: '/openapi.json') */
|
|
108
110
|
specPath?: string;
|
|
111
|
+
/** Other adapters to discover (e.g., WsAdapter for WebSocket server URLs) */
|
|
112
|
+
adapters?: any[];
|
|
109
113
|
}
|
|
110
114
|
/**
|
|
111
115
|
* Swagger adapter — auto-generates OpenAPI spec from decorators and serves docs.
|
|
@@ -131,6 +135,8 @@ declare class SwaggerAdapter implements AppAdapter {
|
|
|
131
135
|
private options;
|
|
132
136
|
name: string;
|
|
133
137
|
constructor(options?: SwaggerAdapterOptions);
|
|
138
|
+
/** Auto-detect server URLs from the running HTTP server and peer adapters */
|
|
139
|
+
afterStart(server: any, _container: Container): void;
|
|
134
140
|
/** Collect controller metadata as routes are mounted */
|
|
135
141
|
onRouteMount(controllerClass: any, mountPath: string): void;
|
|
136
142
|
beforeMount(app: Express, _container: Container): void;
|
package/dist/index.js
CHANGED
|
@@ -210,6 +210,74 @@ function buildOpenAPISpec(options = {}) {
|
|
|
210
210
|
}
|
|
211
211
|
}
|
|
212
212
|
}
|
|
213
|
+
const queryParamsConfig = Reflect.getMetadata(METADATA.QUERY_PARAMS, controllerClass, route.handlerName);
|
|
214
|
+
if (queryParamsConfig) {
|
|
215
|
+
if (queryParamsConfig.filterable?.length) {
|
|
216
|
+
op.parameters.push({
|
|
217
|
+
name: "filter",
|
|
218
|
+
in: "query",
|
|
219
|
+
required: false,
|
|
220
|
+
description: `Filter fields: ${queryParamsConfig.filterable.join(", ")}. Format: \`field:operator:value\`. Operators: eq, neq, gt, gte, lt, lte, contains, starts, ends, in, between`,
|
|
221
|
+
schema: {
|
|
222
|
+
type: "array",
|
|
223
|
+
items: {
|
|
224
|
+
type: "string"
|
|
225
|
+
}
|
|
226
|
+
},
|
|
227
|
+
style: "form",
|
|
228
|
+
explode: true
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
if (queryParamsConfig.sortable?.length) {
|
|
232
|
+
op.parameters.push({
|
|
233
|
+
name: "sort",
|
|
234
|
+
in: "query",
|
|
235
|
+
required: false,
|
|
236
|
+
description: `Sort fields: ${queryParamsConfig.sortable.join(", ")}. Format: \`field:asc\` or \`field:desc\``,
|
|
237
|
+
schema: {
|
|
238
|
+
type: "array",
|
|
239
|
+
items: {
|
|
240
|
+
type: "string"
|
|
241
|
+
}
|
|
242
|
+
},
|
|
243
|
+
style: "form",
|
|
244
|
+
explode: true
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
if (queryParamsConfig.searchable?.length) {
|
|
248
|
+
op.parameters.push({
|
|
249
|
+
name: "q",
|
|
250
|
+
in: "query",
|
|
251
|
+
required: false,
|
|
252
|
+
description: `Search across: ${queryParamsConfig.searchable.join(", ")}`,
|
|
253
|
+
schema: {
|
|
254
|
+
type: "string"
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
op.parameters.push({
|
|
259
|
+
name: "page",
|
|
260
|
+
in: "query",
|
|
261
|
+
required: false,
|
|
262
|
+
description: "Page number (default: 1)",
|
|
263
|
+
schema: {
|
|
264
|
+
type: "integer",
|
|
265
|
+
minimum: 1,
|
|
266
|
+
default: 1
|
|
267
|
+
}
|
|
268
|
+
}, {
|
|
269
|
+
name: "limit",
|
|
270
|
+
in: "query",
|
|
271
|
+
required: false,
|
|
272
|
+
description: "Items per page (default: 20, max: 100)",
|
|
273
|
+
schema: {
|
|
274
|
+
type: "integer",
|
|
275
|
+
minimum: 1,
|
|
276
|
+
maximum: 100,
|
|
277
|
+
default: 20
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
}
|
|
213
281
|
if (op.parameters.length === 0) delete op.parameters;
|
|
214
282
|
if (route.validation?.body && [
|
|
215
283
|
"post",
|
|
@@ -218,7 +286,8 @@ function buildOpenAPISpec(options = {}) {
|
|
|
218
286
|
].includes(method)) {
|
|
219
287
|
const bodySchema = toJsonSchema(route.validation.body);
|
|
220
288
|
if (bodySchema) {
|
|
221
|
-
const
|
|
289
|
+
const bodyName = route.validation.name || `${route.handlerName}Body`;
|
|
290
|
+
const ref = registerSchema(bodySchema, bodyName);
|
|
222
291
|
op.requestBody = {
|
|
223
292
|
required: true,
|
|
224
293
|
content: {
|
|
@@ -231,9 +300,18 @@ function buildOpenAPISpec(options = {}) {
|
|
|
231
300
|
}
|
|
232
301
|
const fileUpload = Reflect.getMetadata(METADATA.FILE_UPLOAD, controllerClass, route.handlerName);
|
|
233
302
|
if (fileUpload) {
|
|
303
|
+
const fieldName = fileUpload.fieldName ?? "file";
|
|
234
304
|
const properties = {};
|
|
235
|
-
if (fileUpload.
|
|
236
|
-
properties[
|
|
305
|
+
if (fileUpload.mode === "array") {
|
|
306
|
+
properties[fieldName] = {
|
|
307
|
+
type: "array",
|
|
308
|
+
items: {
|
|
309
|
+
type: "string",
|
|
310
|
+
format: "binary"
|
|
311
|
+
}
|
|
312
|
+
};
|
|
313
|
+
} else if (fileUpload.mode !== "none") {
|
|
314
|
+
properties[fieldName] = {
|
|
237
315
|
type: "string",
|
|
238
316
|
format: "binary"
|
|
239
317
|
};
|
|
@@ -256,7 +334,8 @@ function buildOpenAPISpec(options = {}) {
|
|
|
256
334
|
description: resp.description || "",
|
|
257
335
|
...resp.schema ? (() => {
|
|
258
336
|
const converted = typeof resp.schema === "function" || typeof resp.schema === "object" ? toJsonSchema(resp.schema) : null;
|
|
259
|
-
const
|
|
337
|
+
const schemaName = resp.name || `${route.handlerName}Response${resp.status}`;
|
|
338
|
+
const finalSchema = converted ? registerSchema(converted, schemaName) : typeof resp.schema === "object" ? resp.schema : void 0;
|
|
260
339
|
return finalSchema ? {
|
|
261
340
|
content: {
|
|
262
341
|
"application/json": {
|
|
@@ -388,6 +467,30 @@ var SwaggerAdapter = class {
|
|
|
388
467
|
constructor(options = {}) {
|
|
389
468
|
this.options = options;
|
|
390
469
|
}
|
|
470
|
+
/** Auto-detect server URLs from the running HTTP server and peer adapters */
|
|
471
|
+
afterStart(server, _container) {
|
|
472
|
+
const addr = server?.address?.();
|
|
473
|
+
if (!addr || typeof addr !== "object") return;
|
|
474
|
+
const host = addr.address === "::" || addr.address === "0.0.0.0" ? "localhost" : addr.address;
|
|
475
|
+
if (!this.options.servers || this.options.servers.length === 0) {
|
|
476
|
+
this.options.servers = [
|
|
477
|
+
{
|
|
478
|
+
url: `http://${host}:${addr.port}`,
|
|
479
|
+
description: "HTTP server"
|
|
480
|
+
}
|
|
481
|
+
];
|
|
482
|
+
}
|
|
483
|
+
const wsAdapter = this.options.adapters?.find((a) => a.name === "WsAdapter" && typeof a.getStats === "function");
|
|
484
|
+
if (wsAdapter) {
|
|
485
|
+
const stats = wsAdapter.getStats();
|
|
486
|
+
for (const namespace of Object.keys(stats.namespaces || {})) {
|
|
487
|
+
this.options.servers.push({
|
|
488
|
+
url: `ws://${host}:${addr.port}${namespace}`,
|
|
489
|
+
description: `WebSocket: ${namespace}`
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
}
|
|
391
494
|
/** Collect controller metadata as routes are mounted */
|
|
392
495
|
onRouteMount(controllerClass, mountPath) {
|
|
393
496
|
registerControllerForDocs(controllerClass, mountPath);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/schema-parser.ts","../src/decorators.ts","../src/openapi-builder.ts","../src/swagger.adapter.ts","../src/ui.ts"],"sourcesContent":["/**\n * Interface for converting validation library schemas to JSON Schema.\n *\n * KickJS ships with a Zod parser by default. To use a different validation\n * library (Yup, Joi, Valibot, ArkType, etc.), implement this interface and\n * pass it to the SwaggerAdapter.\n *\n * @example\n * ```ts\n * import Joi from 'joi'\n * import joiToJson from 'joi-to-json'\n *\n * const joiParser: SchemaParser = {\n * name: 'joi',\n * supports: (schema) => Joi.isSchema(schema),\n * toJsonSchema: (schema) => joiToJson(schema),\n * }\n *\n * new SwaggerAdapter({ schemaParser: joiParser })\n * ```\n */\nexport interface SchemaParser {\n /** Human-readable name for logging/debugging */\n readonly name: string\n\n /**\n * Return true if this parser can handle the given schema object.\n * Called before `toJsonSchema` to allow graceful fallback.\n */\n supports(schema: unknown): boolean\n\n /**\n * Convert a validation schema to a JSON Schema object.\n * Should return a plain object conforming to JSON Schema draft-07 or later.\n * Must not include the top-level `$schema` key — the builder adds it.\n */\n toJsonSchema(schema: unknown): Record<string, unknown>\n}\n\n/**\n * Default schema parser for Zod v4+.\n * Uses Zod's built-in `.toJSONSchema()` instance method.\n */\nexport const zodSchemaParser: SchemaParser = {\n name: 'zod',\n\n supports(schema: unknown): boolean {\n return (\n schema != null &&\n typeof schema === 'object' &&\n typeof (schema as any).safeParse === 'function' &&\n typeof (schema as any).toJSONSchema === 'function'\n )\n },\n\n toJsonSchema(schema: unknown): Record<string, unknown> {\n const { $schema: _, ...rest } = (schema as any).toJSONSchema() as Record<string, unknown>\n return rest\n },\n}\n","import 'reflect-metadata'\n\nconst SWAGGER_KEYS = {\n OPERATION: Symbol('kick:swagger:operation'),\n RESPONSES: Symbol('kick:swagger:responses'),\n TAGS: Symbol('kick:swagger:tags'),\n BEARER_AUTH: Symbol('kick:swagger:bearer'),\n EXCLUDE: Symbol('kick:swagger:exclude'),\n}\n\nexport { SWAGGER_KEYS }\n\nexport interface ApiOperationOptions {\n summary?: string\n description?: string\n operationId?: string\n deprecated?: boolean\n}\n\nexport interface ApiResponseOptions {\n status: number\n description?: string\n schema?: any\n}\n\n/** Attach operation metadata to a route handler */\nexport function ApiOperation(options: ApiOperationOptions): MethodDecorator {\n return (target, propertyKey) => {\n Reflect.defineMetadata(SWAGGER_KEYS.OPERATION, options, target.constructor, propertyKey)\n }\n}\n\n/** Document a response status. Can be stacked multiple times. */\nexport function ApiResponse(options: ApiResponseOptions): MethodDecorator {\n return (target, propertyKey) => {\n const existing: ApiResponseOptions[] =\n Reflect.getMetadata(SWAGGER_KEYS.RESPONSES, target.constructor, propertyKey) || []\n Reflect.defineMetadata(\n SWAGGER_KEYS.RESPONSES,\n [...existing, options],\n target.constructor,\n propertyKey,\n )\n }\n}\n\n/** Apply OpenAPI tags at class or method level */\nexport function ApiTags(...tags: string[]): ClassDecorator & MethodDecorator {\n return (target: any, propertyKey?: string | symbol) => {\n if (propertyKey) {\n Reflect.defineMetadata(SWAGGER_KEYS.TAGS, tags, target.constructor, propertyKey)\n } else {\n Reflect.defineMetadata(SWAGGER_KEYS.TAGS, tags, target)\n }\n }\n}\n\n/** Mark endpoint as requiring Bearer token auth */\nexport function ApiBearerAuth(name = 'BearerAuth'): ClassDecorator & MethodDecorator {\n return (target: any, propertyKey?: string | symbol) => {\n if (propertyKey) {\n Reflect.defineMetadata(SWAGGER_KEYS.BEARER_AUTH, name, target.constructor, propertyKey)\n } else {\n Reflect.defineMetadata(SWAGGER_KEYS.BEARER_AUTH, name, target)\n }\n }\n}\n\n/** Exclude a controller or method from the OpenAPI spec */\nexport function ApiExclude(): ClassDecorator & MethodDecorator {\n return (target: any, propertyKey?: string | symbol) => {\n if (propertyKey) {\n Reflect.defineMetadata(SWAGGER_KEYS.EXCLUDE, true, target.constructor, propertyKey)\n } else {\n Reflect.defineMetadata(SWAGGER_KEYS.EXCLUDE, true, target)\n }\n }\n}\n","import 'reflect-metadata'\nimport { METADATA, type RouteDefinition } from '@forinda/kickjs-core'\nimport { SWAGGER_KEYS, type ApiOperationOptions, type ApiResponseOptions } from './decorators'\nimport { zodSchemaParser, type SchemaParser } from './schema-parser'\n\nexport interface OpenAPIInfo {\n title: string\n version: string\n description?: string\n}\n\nexport interface SwaggerOptions {\n info?: Partial<OpenAPIInfo>\n servers?: { url: string; description?: string }[]\n bearerAuth?: boolean\n /**\n * Pluggable schema parser for converting validation schemas to JSON Schema.\n * Defaults to `zodSchemaParser` which handles Zod v4+ schemas.\n *\n * Override this to use Yup, Joi, Valibot, ArkType, or any other library.\n *\n * @example\n * ```ts\n * new SwaggerAdapter({\n * schemaParser: myYupParser,\n * })\n * ```\n */\n schemaParser?: SchemaParser\n}\n\ninterface RegisteredRoute {\n controllerClass: any\n mountPath: string\n}\n\nconst registeredRoutes: RegisteredRoute[] = []\n\n/** Register a controller for OpenAPI introspection (called by Application during route mounting) */\nexport function registerControllerForDocs(controllerClass: any, mountPath: string): void {\n registeredRoutes.push({ controllerClass, mountPath })\n}\n\n/** Clear all registered routes (for HMR) */\nexport function clearRegisteredRoutes(): void {\n registeredRoutes.length = 0\n}\n\n/** Build a full OpenAPI 3.0.3 spec from registered controllers and their decorators */\nexport function buildOpenAPISpec(options: SwaggerOptions = {}): any {\n const parser = options.schemaParser ?? zodSchemaParser\n\n /** Convert a validation schema to JSON Schema using the configured parser */\n const toJsonSchema = (schema: unknown): Record<string, unknown> | null => {\n try {\n if (!parser.supports(schema)) return null\n return parser.toJsonSchema(schema)\n } catch {\n return null\n }\n }\n\n const componentSchemas: Record<string, any> = {}\n let schemaCounter = 0\n\n /**\n * Register a schema in components.schemas and return a $ref pointer.\n * If the schema has a title/label, use that as the name. Otherwise generate one.\n */\n const registerSchema = (jsonSchema: Record<string, unknown>, hint?: string): any => {\n // Try to extract a name from the schema\n let name = (jsonSchema.title as string) || (jsonSchema.label as string) || hint || ''\n if (!name) {\n name = `Schema${++schemaCounter}`\n }\n // Sanitize name for OpenAPI (remove spaces, special chars)\n name = name.replace(/[^a-zA-Z0-9]/g, '')\n\n // Avoid duplicates — if already registered with same name, reuse\n if (!componentSchemas[name]) {\n const clean = { ...jsonSchema }\n delete clean.title\n delete clean.label\n delete clean.$schema\n componentSchemas[name] = clean\n }\n return { $ref: `#/components/schemas/${name}` }\n }\n\n const spec: any = {\n openapi: '3.0.3',\n info: {\n title: options.info?.title || 'API',\n version: options.info?.version || '1.0.0',\n ...(options.info?.description ? { description: options.info.description } : {}),\n },\n paths: {},\n components: { schemas: {}, securitySchemes: {} },\n tags: [],\n }\n\n if (options.servers) {\n spec.servers = options.servers\n }\n\n const allTags = new Set<string>()\n const securitySchemes: Record<string, any> = {}\n\n for (const { controllerClass, mountPath } of registeredRoutes) {\n // Skip excluded controllers\n if (Reflect.getMetadata(SWAGGER_KEYS.EXCLUDE, controllerClass)) continue\n\n const routes: RouteDefinition[] = Reflect.getMetadata(METADATA.ROUTES, controllerClass) || []\n const classTags: string[] = Reflect.getMetadata(SWAGGER_KEYS.TAGS, controllerClass) || []\n const classAuth: string | undefined = Reflect.getMetadata(\n SWAGGER_KEYS.BEARER_AUTH,\n controllerClass,\n )\n const controllerPath = Reflect.getMetadata(METADATA.CONTROLLER_PATH, controllerClass) || '/'\n\n for (const route of routes) {\n // Skip excluded methods\n if (Reflect.getMetadata(SWAGGER_KEYS.EXCLUDE, controllerClass, route.handlerName)) continue\n\n // Build the full path\n let routePath = route.path === '/' ? '' : route.path\n let fullPath = mountPath + (controllerPath === '/' ? '' : controllerPath) + routePath\n if (!fullPath) fullPath = '/'\n\n // Convert Express :param to OpenAPI {param}\n const openApiPath = fullPath.replace(/:([a-zA-Z_]+)/g, '{$1}')\n const method = route.method.toLowerCase()\n\n // Gather metadata\n const operation: ApiOperationOptions =\n Reflect.getMetadata(SWAGGER_KEYS.OPERATION, controllerClass, route.handlerName) || {}\n const responses: ApiResponseOptions[] =\n Reflect.getMetadata(SWAGGER_KEYS.RESPONSES, controllerClass, route.handlerName) || []\n const methodTags: string[] =\n Reflect.getMetadata(SWAGGER_KEYS.TAGS, controllerClass, route.handlerName) || []\n const methodAuth: string | undefined = Reflect.getMetadata(\n SWAGGER_KEYS.BEARER_AUTH,\n controllerClass,\n route.handlerName,\n )\n\n // Tags — method level overrides class level\n const tags = methodTags.length > 0 ? methodTags : classTags\n tags.forEach((t) => allTags.add(t))\n\n // Build operation object\n const op: any = {\n ...(tags.length > 0 ? { tags } : {}),\n ...(operation.summary ? { summary: operation.summary } : {}),\n ...(operation.description ? { description: operation.description } : {}),\n ...(operation.operationId ? { operationId: operation.operationId } : {}),\n ...(operation.deprecated ? { deprecated: true } : {}),\n parameters: [],\n responses: {},\n }\n\n // Path parameters\n const paramMatches = fullPath.match(/:([a-zA-Z_]+)/g) || []\n for (const match of paramMatches) {\n const paramName = match.slice(1)\n let schema: any = { type: 'string' }\n\n // Try to get type from params validation schema\n if (route.validation?.params) {\n const jsonSchema = toJsonSchema(route.validation.params)\n if (jsonSchema?.properties && typeof jsonSchema.properties === 'object') {\n const props = jsonSchema.properties as Record<string, any>\n if (props[paramName]) {\n schema = props[paramName]\n }\n }\n }\n\n op.parameters.push({\n name: paramName,\n in: 'path',\n required: true,\n schema,\n })\n }\n\n // Query parameters\n if (route.validation?.query) {\n const jsonSchema = toJsonSchema(route.validation.query)\n if (jsonSchema?.properties && typeof jsonSchema.properties === 'object') {\n const required = Array.isArray(jsonSchema.required) ? jsonSchema.required : []\n for (const [name, propSchema] of Object.entries(\n jsonSchema.properties as Record<string, any>,\n )) {\n op.parameters.push({\n name,\n in: 'query',\n required: required.includes(name),\n schema: propSchema,\n })\n }\n }\n }\n\n // 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 ref = registerSchema(bodySchema, `${route.handlerName}Body`)\n op.requestBody = {\n required: true,\n content: { 'application/json': { schema: ref } },\n }\n }\n }\n\n // File upload detection\n const fileUpload = Reflect.getMetadata(\n METADATA.FILE_UPLOAD,\n controllerClass,\n route.handlerName,\n )\n if (fileUpload) {\n const properties: any = {}\n if (fileUpload.fieldName) {\n properties[fileUpload.fieldName] = {\n type: 'string',\n format: 'binary',\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 finalSchema = converted\n ? registerSchema(converted, `${route.handlerName}Response${resp.status}`)\n : typeof resp.schema === 'object'\n ? resp.schema\n : undefined\n return finalSchema\n ? { content: { 'application/json': { schema: finalSchema } } }\n : {}\n })()\n : {}),\n }\n }\n } else {\n // Auto-generate default responses\n const defaultStatus = method === 'post' ? '201' : method === 'delete' ? '204' : '200'\n op.responses[defaultStatus] = { description: 'Successful operation' }\n\n if (route.validation?.body) {\n op.responses['422'] = { description: 'Validation error' }\n }\n }\n\n // Security\n const authName = methodAuth || classAuth\n if (authName) {\n op.security = [{ [authName]: [] }]\n securitySchemes[authName] = {\n type: 'http',\n scheme: 'bearer',\n bearerFormat: 'JWT',\n }\n }\n\n // Mount\n if (!spec.paths[openApiPath]) spec.paths[openApiPath] = {}\n spec.paths[openApiPath][method] = op\n }\n }\n\n // Finalize\n spec.tags = Array.from(allTags).map((name) => ({ name }))\n spec.components.securitySchemes = securitySchemes\n\n if (options.bearerAuth) {\n if (!securitySchemes.BearerAuth) {\n spec.components.securitySchemes.BearerAuth = {\n type: 'http',\n scheme: 'bearer',\n bearerFormat: 'JWT',\n }\n }\n spec.security = [{ BearerAuth: [] }]\n }\n\n // Merge collected schemas into components\n spec.components.schemas = componentSchemas\n\n // Clean up empty components\n if (Object.keys(spec.components.schemas).length === 0) delete spec.components.schemas\n if (Object.keys(spec.components.securitySchemes).length === 0)\n delete spec.components.securitySchemes\n if (Object.keys(spec.components).length === 0) delete spec.components\n\n return spec\n}\n","import { Router } from 'express'\nimport type { Express } from 'express'\nimport { Logger, type AppAdapter, type Container } from '@forinda/kickjs-core'\nimport {\n buildOpenAPISpec,\n registerControllerForDocs,\n clearRegisteredRoutes,\n type SwaggerOptions,\n} from './openapi-builder'\nimport { swaggerUIHtml, redocHtml } from './ui'\n\nconst log = Logger.for('SwaggerAdapter')\n\nexport interface SwaggerAdapterOptions extends SwaggerOptions {\n /** Path to serve Swagger UI (default: '/docs') */\n docsPath?: string\n /** Path to serve ReDoc (default: '/redoc') */\n redocPath?: string\n /** Path to serve the raw JSON spec (default: '/openapi.json') */\n specPath?: string\n}\n\n/**\n * Swagger adapter — auto-generates OpenAPI spec from decorators and serves docs.\n *\n * @example\n * ```ts\n * bootstrap({\n * modules,\n * adapters: [\n * new SwaggerAdapter({\n * info: { title: 'My API', version: '1.0.0' },\n * }),\n * ],\n * })\n * ```\n *\n * Endpoints:\n * GET /docs — Swagger UI\n * GET /redoc — ReDoc\n * GET /openapi.json — Raw OpenAPI 3.0.3 spec\n */\nexport class SwaggerAdapter implements AppAdapter {\n name = 'SwaggerAdapter'\n\n constructor(private options: SwaggerAdapterOptions = {}) {}\n\n /** Collect controller metadata as routes are mounted */\n onRouteMount(controllerClass: any, mountPath: string): void {\n registerControllerForDocs(controllerClass, mountPath)\n }\n\n beforeMount(app: Express, _container: Container): void {\n // Clear previous registrations (supports HMR rebuild)\n clearRegisteredRoutes()\n const docsPath = this.options.docsPath ?? '/docs'\n const redocPath = this.options.redocPath ?? '/redoc'\n const specPath = this.options.specPath ?? '/openapi.json'\n\n // Use a sub-router with relaxed CSP so CDN scripts load\n const docsRouter = Router()\n\n docsRouter.use((_req, res, next) => {\n res.setHeader(\n 'Content-Security-Policy',\n [\n \"default-src 'self'\",\n \"script-src 'self' 'unsafe-inline' https://unpkg.com https://cdn.redoc.ly https://cdn.jsdelivr.net\",\n \"style-src 'self' 'unsafe-inline' https://unpkg.com https://fonts.googleapis.com\",\n \"font-src 'self' https://fonts.gstatic.com\",\n \"img-src 'self' data: https://unpkg.com\",\n \"connect-src 'self'\",\n ].join('; '),\n )\n next()\n })\n\n // Spec endpoint (JSON)\n docsRouter.get(specPath, (_req, res) => {\n const spec = buildOpenAPISpec(this.options)\n res.json(spec)\n })\n\n // Swagger UI\n docsRouter.get(docsPath, (_req, res) => {\n res.type('html').send(swaggerUIHtml(specPath, this.options.info?.title))\n })\n\n // ReDoc\n docsRouter.get(redocPath, (_req, res) => {\n res.type('html').send(redocHtml(specPath, this.options.info?.title))\n })\n\n app.use(docsRouter)\n\n log.info(`Swagger UI: ${docsPath}`)\n log.info(`ReDoc: ${redocPath}`)\n log.info(`OpenAPI spec: ${specPath}`)\n }\n}\n\n// Re-export for use by Application when mounting module routes\nexport { registerControllerForDocs, clearRegisteredRoutes }\n","/** Escape a string for safe HTML attribute/content interpolation */\nfunction escapeHtml(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''')\n}\n\n/** Generate Swagger UI HTML that loads from CDN */\nexport function swaggerUIHtml(specUrl: string, title = 'API Docs'): string {\n const safeTitle = escapeHtml(title)\n const safeUrl = JSON.stringify(specUrl).replace(/</g, '\\\\u003c')\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>${safeTitle}</title>\n <link rel=\"stylesheet\" href=\"https://unpkg.com/swagger-ui-dist@5/swagger-ui.css\">\n</head>\n<body>\n <div id=\"swagger-ui\"></div>\n <script src=\"https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js\"></script>\n <script src=\"https://unpkg.com/swagger-ui-dist@5/swagger-ui-standalone-preset.js\"></script>\n <script>\n SwaggerUIBundle({\n url: ${safeUrl},\n dom_id: '#swagger-ui',\n deepLinking: true,\n presets: [SwaggerUIBundle.presets.apis, SwaggerUIStandalonePreset],\n plugins: [SwaggerUIBundle.plugins.DownloadUrl],\n layout: 'StandaloneLayout',\n });\n </script>\n</body>\n</html>`\n}\n\n/** Generate ReDoc HTML that loads from CDN */\nexport function redocHtml(specUrl: string, title = 'API Docs'): string {\n const safeTitle = escapeHtml(title)\n const safeUrl = escapeHtml(specUrl)\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>${safeTitle}</title>\n</head>\n<body>\n <redoc spec-url=\"${safeUrl}\"></redoc>\n <script src=\"https://cdn.redoc.ly/redoc/latest/bundles/redoc.standalone.js\"></script>\n</body>\n</html>`\n}\n"],"mappings":"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","names":["zodSchemaParser","name","supports","schema","safeParse","toJSONSchema","toJsonSchema","$schema","_","rest","SWAGGER_KEYS","OPERATION","Symbol","RESPONSES","TAGS","BEARER_AUTH","EXCLUDE","ApiOperation","options","target","propertyKey","Reflect","defineMetadata","SWAGGER_KEYS","OPERATION","ApiResponse","existing","getMetadata","RESPONSES","ApiTags","tags","TAGS","ApiBearerAuth","name","BEARER_AUTH","ApiExclude","EXCLUDE","METADATA","registeredRoutes","registerControllerForDocs","controllerClass","mountPath","push","clearRegisteredRoutes","length","buildOpenAPISpec","options","parser","schemaParser","zodSchemaParser","toJsonSchema","schema","supports","componentSchemas","schemaCounter","registerSchema","jsonSchema","hint","name","title","label","replace","clean","$schema","$ref","spec","openapi","info","version","description","paths","components","schemas","securitySchemes","tags","servers","allTags","Set","Reflect","getMetadata","SWAGGER_KEYS","EXCLUDE","routes","METADATA","ROUTES","classTags","TAGS","classAuth","BEARER_AUTH","controllerPath","CONTROLLER_PATH","route","handlerName","routePath","path","fullPath","openApiPath","method","toLowerCase","operation","OPERATION","responses","RESPONSES","methodTags","methodAuth","forEach","t","add","op","summary","operationId","deprecated","parameters","paramMatches","match","paramName","slice","type","validation","params","properties","props","in","required","query","Array","isArray","propSchema","Object","entries","includes","body","bodySchema","ref","requestBody","content","fileUpload","FILE_UPLOAD","fieldName","format","resp","String","status","converted","finalSchema","undefined","defaultStatus","authName","security","scheme","bearerFormat","from","map","bearerAuth","BearerAuth","keys","Router","Logger","escapeHtml","str","replace","swaggerUIHtml","specUrl","title","safeTitle","safeUrl","JSON","stringify","redocHtml","log","Logger","for","SwaggerAdapter","name","options","onRouteMount","controllerClass","mountPath","registerControllerForDocs","beforeMount","app","_container","clearRegisteredRoutes","docsPath","redocPath","specPath","docsRouter","Router","use","_req","res","next","setHeader","join","get","spec","buildOpenAPISpec","json","type","send","swaggerUIHtml","info","title","redocHtml"]}
|
|
1
|
+
{"version":3,"sources":["../src/schema-parser.ts","../src/decorators.ts","../src/openapi-builder.ts","../src/swagger.adapter.ts","../src/ui.ts"],"sourcesContent":["/**\n * Interface for converting validation library schemas to JSON Schema.\n *\n * KickJS ships with a Zod parser by default. To use a different validation\n * library (Yup, Joi, Valibot, ArkType, etc.), implement this interface and\n * pass it to the SwaggerAdapter.\n *\n * @example\n * ```ts\n * import Joi from 'joi'\n * import joiToJson from 'joi-to-json'\n *\n * const joiParser: SchemaParser = {\n * name: 'joi',\n * supports: (schema) => Joi.isSchema(schema),\n * toJsonSchema: (schema) => joiToJson(schema),\n * }\n *\n * new SwaggerAdapter({ schemaParser: joiParser })\n * ```\n */\nexport interface SchemaParser {\n /** Human-readable name for logging/debugging */\n readonly name: string\n\n /**\n * Return true if this parser can handle the given schema object.\n * Called before `toJsonSchema` to allow graceful fallback.\n */\n supports(schema: unknown): boolean\n\n /**\n * Convert a validation schema to a JSON Schema object.\n * Should return a plain object conforming to JSON Schema draft-07 or later.\n * Must not include the top-level `$schema` key — the builder adds it.\n */\n toJsonSchema(schema: unknown): Record<string, unknown>\n}\n\n/**\n * Default schema parser for Zod v4+.\n * Uses Zod's built-in `.toJSONSchema()` instance method.\n */\nexport const zodSchemaParser: SchemaParser = {\n name: 'zod',\n\n supports(schema: unknown): boolean {\n return (\n schema != null &&\n typeof schema === 'object' &&\n typeof (schema as any).safeParse === 'function' &&\n typeof (schema as any).toJSONSchema === 'function'\n )\n },\n\n toJsonSchema(schema: unknown): Record<string, unknown> {\n const { $schema: _, ...rest } = (schema as any).toJSONSchema() as Record<string, unknown>\n return rest\n },\n}\n","import 'reflect-metadata'\n\nconst SWAGGER_KEYS = {\n OPERATION: Symbol('kick:swagger:operation'),\n RESPONSES: Symbol('kick:swagger:responses'),\n TAGS: Symbol('kick:swagger:tags'),\n BEARER_AUTH: Symbol('kick:swagger:bearer'),\n EXCLUDE: Symbol('kick:swagger:exclude'),\n}\n\nexport { SWAGGER_KEYS }\n\nexport interface ApiOperationOptions {\n summary?: string\n description?: string\n operationId?: string\n deprecated?: boolean\n}\n\nexport interface ApiResponseOptions {\n status: number\n description?: string\n schema?: any\n /** Schema name in components/schemas (e.g., 'UserResponse', 'ErrorBody'). Auto-generated from handler name if omitted. */\n name?: string\n}\n\n/** Attach operation metadata to a route handler */\nexport function ApiOperation(options: ApiOperationOptions): MethodDecorator {\n return (target, propertyKey) => {\n Reflect.defineMetadata(SWAGGER_KEYS.OPERATION, options, target.constructor, propertyKey)\n }\n}\n\n/** Document a response status. Can be stacked multiple times. */\nexport function ApiResponse(options: ApiResponseOptions): MethodDecorator {\n return (target, propertyKey) => {\n const existing: ApiResponseOptions[] =\n Reflect.getMetadata(SWAGGER_KEYS.RESPONSES, target.constructor, propertyKey) || []\n Reflect.defineMetadata(\n SWAGGER_KEYS.RESPONSES,\n [...existing, options],\n target.constructor,\n propertyKey,\n )\n }\n}\n\n/** Apply OpenAPI tags at class or method level */\nexport function ApiTags(...tags: string[]): ClassDecorator & MethodDecorator {\n return (target: any, propertyKey?: string | symbol) => {\n if (propertyKey) {\n Reflect.defineMetadata(SWAGGER_KEYS.TAGS, tags, target.constructor, propertyKey)\n } else {\n Reflect.defineMetadata(SWAGGER_KEYS.TAGS, tags, target)\n }\n }\n}\n\n/** Mark endpoint as requiring Bearer token auth */\nexport function ApiBearerAuth(name = 'BearerAuth'): ClassDecorator & MethodDecorator {\n return (target: any, propertyKey?: string | symbol) => {\n if (propertyKey) {\n Reflect.defineMetadata(SWAGGER_KEYS.BEARER_AUTH, name, target.constructor, propertyKey)\n } else {\n Reflect.defineMetadata(SWAGGER_KEYS.BEARER_AUTH, name, target)\n }\n }\n}\n\n/** Exclude a controller or method from the OpenAPI spec */\nexport function ApiExclude(): ClassDecorator & MethodDecorator {\n return (target: any, propertyKey?: string | symbol) => {\n if (propertyKey) {\n Reflect.defineMetadata(SWAGGER_KEYS.EXCLUDE, true, target.constructor, propertyKey)\n } else {\n Reflect.defineMetadata(SWAGGER_KEYS.EXCLUDE, true, target)\n }\n }\n}\n","import 'reflect-metadata'\nimport { METADATA, type RouteDefinition } from '@forinda/kickjs-core'\nimport { SWAGGER_KEYS, type ApiOperationOptions, type ApiResponseOptions } from './decorators'\nimport { zodSchemaParser, type SchemaParser } from './schema-parser'\n\nexport interface OpenAPIInfo {\n title: string\n version: string\n description?: string\n}\n\nexport interface SwaggerOptions {\n info?: Partial<OpenAPIInfo>\n servers?: { url: string; description?: string }[]\n bearerAuth?: boolean\n /**\n * Pluggable schema parser for converting validation schemas to JSON Schema.\n * Defaults to `zodSchemaParser` which handles Zod v4+ schemas.\n *\n * Override this to use Yup, Joi, Valibot, ArkType, or any other library.\n *\n * @example\n * ```ts\n * new SwaggerAdapter({\n * schemaParser: myYupParser,\n * })\n * ```\n */\n schemaParser?: SchemaParser\n}\n\ninterface RegisteredRoute {\n controllerClass: any\n mountPath: string\n}\n\nconst registeredRoutes: RegisteredRoute[] = []\n\n/** Register a controller for OpenAPI introspection (called by Application during route mounting) */\nexport function registerControllerForDocs(controllerClass: any, mountPath: string): void {\n registeredRoutes.push({ controllerClass, mountPath })\n}\n\n/** Clear all registered routes (for HMR) */\nexport function clearRegisteredRoutes(): void {\n registeredRoutes.length = 0\n}\n\n/** Build a full OpenAPI 3.0.3 spec from registered controllers and their decorators */\nexport function buildOpenAPISpec(options: SwaggerOptions = {}): any {\n const parser = options.schemaParser ?? zodSchemaParser\n\n /** Convert a validation schema to JSON Schema using the configured parser */\n const toJsonSchema = (schema: unknown): Record<string, unknown> | null => {\n try {\n if (!parser.supports(schema)) return null\n return parser.toJsonSchema(schema)\n } catch {\n return null\n }\n }\n\n const componentSchemas: Record<string, any> = {}\n let schemaCounter = 0\n\n /**\n * Register a schema in components.schemas and return a $ref pointer.\n * If the schema has a title/label, use that as the name. Otherwise generate one.\n */\n const registerSchema = (jsonSchema: Record<string, unknown>, hint?: string): any => {\n // Try to extract a name from the schema\n let name = (jsonSchema.title as string) || (jsonSchema.label as string) || hint || ''\n if (!name) {\n name = `Schema${++schemaCounter}`\n }\n // Sanitize name for OpenAPI (remove spaces, special chars)\n name = name.replace(/[^a-zA-Z0-9]/g, '')\n\n // Avoid duplicates — if already registered with same name, reuse\n if (!componentSchemas[name]) {\n const clean = { ...jsonSchema }\n delete clean.title\n delete clean.label\n delete clean.$schema\n componentSchemas[name] = clean\n }\n return { $ref: `#/components/schemas/${name}` }\n }\n\n const spec: any = {\n openapi: '3.0.3',\n info: {\n title: options.info?.title || 'API',\n version: options.info?.version || '1.0.0',\n ...(options.info?.description ? { description: options.info.description } : {}),\n },\n paths: {},\n components: { schemas: {}, securitySchemes: {} },\n tags: [],\n }\n\n if (options.servers) {\n spec.servers = options.servers\n }\n\n const allTags = new Set<string>()\n const securitySchemes: Record<string, any> = {}\n\n for (const { controllerClass, mountPath } of registeredRoutes) {\n // Skip excluded controllers\n if (Reflect.getMetadata(SWAGGER_KEYS.EXCLUDE, controllerClass)) continue\n\n const routes: RouteDefinition[] = Reflect.getMetadata(METADATA.ROUTES, controllerClass) || []\n const classTags: string[] = Reflect.getMetadata(SWAGGER_KEYS.TAGS, controllerClass) || []\n const classAuth: string | undefined = Reflect.getMetadata(\n SWAGGER_KEYS.BEARER_AUTH,\n controllerClass,\n )\n const controllerPath = Reflect.getMetadata(METADATA.CONTROLLER_PATH, controllerClass) || '/'\n\n for (const route of routes) {\n // Skip excluded methods\n if (Reflect.getMetadata(SWAGGER_KEYS.EXCLUDE, controllerClass, route.handlerName)) continue\n\n // Build the full path\n let routePath = route.path === '/' ? '' : route.path\n let fullPath = mountPath + (controllerPath === '/' ? '' : controllerPath) + routePath\n if (!fullPath) fullPath = '/'\n\n // Convert Express :param to OpenAPI {param}\n const openApiPath = fullPath.replace(/:([a-zA-Z_]+)/g, '{$1}')\n const method = route.method.toLowerCase()\n\n // Gather metadata\n const operation: ApiOperationOptions =\n Reflect.getMetadata(SWAGGER_KEYS.OPERATION, controllerClass, route.handlerName) || {}\n const responses: ApiResponseOptions[] =\n Reflect.getMetadata(SWAGGER_KEYS.RESPONSES, controllerClass, route.handlerName) || []\n const methodTags: string[] =\n Reflect.getMetadata(SWAGGER_KEYS.TAGS, controllerClass, route.handlerName) || []\n const methodAuth: string | undefined = Reflect.getMetadata(\n SWAGGER_KEYS.BEARER_AUTH,\n controllerClass,\n route.handlerName,\n )\n\n // Tags — method level overrides class level\n const tags = methodTags.length > 0 ? methodTags : classTags\n tags.forEach((t) => allTags.add(t))\n\n // Build operation object\n const op: any = {\n ...(tags.length > 0 ? { tags } : {}),\n ...(operation.summary ? { summary: operation.summary } : {}),\n ...(operation.description ? { description: operation.description } : {}),\n ...(operation.operationId ? { operationId: operation.operationId } : {}),\n ...(operation.deprecated ? { deprecated: true } : {}),\n parameters: [],\n responses: {},\n }\n\n // Path parameters\n const paramMatches = fullPath.match(/:([a-zA-Z_]+)/g) || []\n for (const match of paramMatches) {\n const paramName = match.slice(1)\n let schema: any = { type: 'string' }\n\n // Try to get type from params validation schema\n if (route.validation?.params) {\n const jsonSchema = toJsonSchema(route.validation.params)\n if (jsonSchema?.properties && typeof jsonSchema.properties === 'object') {\n const props = jsonSchema.properties as Record<string, any>\n if (props[paramName]) {\n schema = props[paramName]\n }\n }\n }\n\n op.parameters.push({\n name: paramName,\n in: 'path',\n required: true,\n schema,\n })\n }\n\n // Query parameters\n if (route.validation?.query) {\n const jsonSchema = toJsonSchema(route.validation.query)\n if (jsonSchema?.properties && typeof jsonSchema.properties === 'object') {\n const required = Array.isArray(jsonSchema.required) ? jsonSchema.required : []\n for (const [name, propSchema] of Object.entries(\n jsonSchema.properties as Record<string, any>,\n )) {\n op.parameters.push({\n name,\n in: 'query',\n required: required.includes(name),\n schema: propSchema,\n })\n }\n }\n }\n\n // @ApiQueryParams decorator — document filterable/sortable/searchable fields\n const queryParamsConfig = Reflect.getMetadata(\n METADATA.QUERY_PARAMS,\n controllerClass,\n route.handlerName,\n )\n if (queryParamsConfig) {\n if (queryParamsConfig.filterable?.length) {\n op.parameters.push({\n name: 'filter',\n in: 'query',\n required: false,\n description: `Filter fields: ${queryParamsConfig.filterable.join(', ')}. Format: \\`field:operator:value\\`. Operators: eq, neq, gt, gte, lt, lte, contains, starts, ends, in, between`,\n schema: { type: 'array', items: { type: 'string' } },\n style: 'form',\n explode: true,\n })\n }\n if (queryParamsConfig.sortable?.length) {\n op.parameters.push({\n name: 'sort',\n in: 'query',\n required: false,\n description: `Sort fields: ${queryParamsConfig.sortable.join(', ')}. Format: \\`field:asc\\` or \\`field:desc\\``,\n schema: { type: 'array', items: { type: 'string' } },\n style: 'form',\n explode: true,\n })\n }\n if (queryParamsConfig.searchable?.length) {\n op.parameters.push({\n name: 'q',\n in: 'query',\n required: false,\n description: `Search across: ${queryParamsConfig.searchable.join(', ')}`,\n schema: { type: 'string' },\n })\n }\n op.parameters.push(\n {\n name: 'page',\n in: 'query',\n required: false,\n description: 'Page number (default: 1)',\n schema: { type: 'integer', minimum: 1, default: 1 },\n },\n {\n name: 'limit',\n in: 'query',\n required: false,\n description: 'Items per page (default: 20, max: 100)',\n schema: { type: 'integer', minimum: 1, maximum: 100, default: 20 },\n },\n )\n }\n\n // Remove empty parameters array\n if (op.parameters.length === 0) delete op.parameters\n\n // Request body\n if (route.validation?.body && ['post', 'put', 'patch'].includes(method)) {\n const bodySchema = toJsonSchema(route.validation.body)\n if (bodySchema) {\n const bodyName = route.validation.name || `${route.handlerName}Body`\n const ref = registerSchema(bodySchema, bodyName)\n op.requestBody = {\n required: true,\n content: { 'application/json': { schema: ref } },\n }\n }\n }\n\n // File upload detection\n const fileUpload = Reflect.getMetadata(\n METADATA.FILE_UPLOAD,\n controllerClass,\n route.handlerName,\n )\n if (fileUpload) {\n const fieldName = fileUpload.fieldName ?? 'file'\n const properties: any = {}\n\n if (fileUpload.mode === 'array') {\n properties[fieldName] = {\n type: 'array',\n items: { type: 'string', format: 'binary' },\n }\n } else if (fileUpload.mode !== 'none') {\n properties[fieldName] = {\n type: 'string',\n format: 'binary',\n }\n }\n\n op.requestBody = {\n required: true,\n content: {\n 'multipart/form-data': {\n schema: { type: 'object', properties },\n },\n },\n }\n }\n\n // Responses\n if (responses.length > 0) {\n for (const resp of responses) {\n op.responses[String(resp.status)] = {\n description: resp.description || '',\n ...(resp.schema\n ? (() => {\n const converted =\n typeof resp.schema === 'function' || typeof resp.schema === 'object'\n ? toJsonSchema(resp.schema)\n : null\n const schemaName = resp.name || `${route.handlerName}Response${resp.status}`\n const finalSchema = converted\n ? registerSchema(converted, schemaName)\n : typeof resp.schema === 'object'\n ? resp.schema\n : undefined\n return finalSchema\n ? { content: { 'application/json': { schema: finalSchema } } }\n : {}\n })()\n : {}),\n }\n }\n } else {\n // Auto-generate default responses\n const defaultStatus = method === 'post' ? '201' : method === 'delete' ? '204' : '200'\n op.responses[defaultStatus] = { description: 'Successful operation' }\n\n if (route.validation?.body) {\n op.responses['422'] = { description: 'Validation error' }\n }\n }\n\n // Security\n const authName = methodAuth || classAuth\n if (authName) {\n op.security = [{ [authName]: [] }]\n securitySchemes[authName] = {\n type: 'http',\n scheme: 'bearer',\n bearerFormat: 'JWT',\n }\n }\n\n // Mount\n if (!spec.paths[openApiPath]) spec.paths[openApiPath] = {}\n spec.paths[openApiPath][method] = op\n }\n }\n\n // Finalize\n spec.tags = Array.from(allTags).map((name) => ({ name }))\n spec.components.securitySchemes = securitySchemes\n\n if (options.bearerAuth) {\n if (!securitySchemes.BearerAuth) {\n spec.components.securitySchemes.BearerAuth = {\n type: 'http',\n scheme: 'bearer',\n bearerFormat: 'JWT',\n }\n }\n spec.security = [{ BearerAuth: [] }]\n }\n\n // Merge collected schemas into components\n spec.components.schemas = componentSchemas\n\n // Clean up empty components\n if (Object.keys(spec.components.schemas).length === 0) delete spec.components.schemas\n if (Object.keys(spec.components.securitySchemes).length === 0)\n delete spec.components.securitySchemes\n if (Object.keys(spec.components).length === 0) delete spec.components\n\n return spec\n}\n","import { Router } from 'express'\nimport type { Express } from 'express'\nimport { Logger, type AppAdapter, type Container } from '@forinda/kickjs-core'\nimport {\n buildOpenAPISpec,\n registerControllerForDocs,\n clearRegisteredRoutes,\n type SwaggerOptions,\n} from './openapi-builder'\nimport { swaggerUIHtml, redocHtml } from './ui'\n\nconst log = Logger.for('SwaggerAdapter')\n\nexport interface SwaggerAdapterOptions extends SwaggerOptions {\n /** Path to serve Swagger UI (default: '/docs') */\n docsPath?: string\n /** Path to serve ReDoc (default: '/redoc') */\n redocPath?: string\n /** Path to serve the raw JSON spec (default: '/openapi.json') */\n specPath?: string\n /** Other adapters to discover (e.g., WsAdapter for WebSocket server URLs) */\n adapters?: any[]\n}\n\n/**\n * Swagger adapter — auto-generates OpenAPI spec from decorators and serves docs.\n *\n * @example\n * ```ts\n * bootstrap({\n * modules,\n * adapters: [\n * new SwaggerAdapter({\n * info: { title: 'My API', version: '1.0.0' },\n * }),\n * ],\n * })\n * ```\n *\n * Endpoints:\n * GET /docs — Swagger UI\n * GET /redoc — ReDoc\n * GET /openapi.json — Raw OpenAPI 3.0.3 spec\n */\nexport class SwaggerAdapter implements AppAdapter {\n name = 'SwaggerAdapter'\n\n constructor(private options: SwaggerAdapterOptions = {}) {}\n\n /** Auto-detect server URLs from the running HTTP server and peer adapters */\n afterStart(server: any, _container: Container): void {\n const addr = server?.address?.()\n if (!addr || typeof addr !== 'object') return\n\n const host = addr.address === '::' || addr.address === '0.0.0.0' ? 'localhost' : addr.address\n\n // Auto-add HTTP server URL if none configured\n if (!this.options.servers || this.options.servers.length === 0) {\n this.options.servers = [{ url: `http://${host}:${addr.port}`, description: 'HTTP server' }]\n }\n\n // Auto-add WebSocket server URLs from WsAdapter\n const wsAdapter = this.options.adapters?.find(\n (a) => a.name === 'WsAdapter' && typeof a.getStats === 'function',\n )\n if (wsAdapter) {\n const stats = wsAdapter.getStats()\n for (const namespace of Object.keys(stats.namespaces || {})) {\n this.options.servers!.push({\n url: `ws://${host}:${addr.port}${namespace}`,\n description: `WebSocket: ${namespace}`,\n })\n }\n }\n }\n\n /** Collect controller metadata as routes are mounted */\n onRouteMount(controllerClass: any, mountPath: string): void {\n registerControllerForDocs(controllerClass, mountPath)\n }\n\n beforeMount(app: Express, _container: Container): void {\n // Clear previous registrations (supports HMR rebuild)\n clearRegisteredRoutes()\n const docsPath = this.options.docsPath ?? '/docs'\n const redocPath = this.options.redocPath ?? '/redoc'\n const specPath = this.options.specPath ?? '/openapi.json'\n\n // Use a sub-router with relaxed CSP so CDN scripts load\n const docsRouter = Router()\n\n docsRouter.use((_req, res, next) => {\n res.setHeader(\n 'Content-Security-Policy',\n [\n \"default-src 'self'\",\n \"script-src 'self' 'unsafe-inline' https://unpkg.com https://cdn.redoc.ly https://cdn.jsdelivr.net\",\n \"style-src 'self' 'unsafe-inline' https://unpkg.com https://fonts.googleapis.com\",\n \"font-src 'self' https://fonts.gstatic.com\",\n \"img-src 'self' data: https://unpkg.com\",\n \"connect-src 'self'\",\n ].join('; '),\n )\n next()\n })\n\n // Spec endpoint (JSON)\n docsRouter.get(specPath, (_req, res) => {\n const spec = buildOpenAPISpec(this.options)\n res.json(spec)\n })\n\n // Swagger UI\n docsRouter.get(docsPath, (_req, res) => {\n res.type('html').send(swaggerUIHtml(specPath, this.options.info?.title))\n })\n\n // ReDoc\n docsRouter.get(redocPath, (_req, res) => {\n res.type('html').send(redocHtml(specPath, this.options.info?.title))\n })\n\n app.use(docsRouter)\n\n log.info(`Swagger UI: ${docsPath}`)\n log.info(`ReDoc: ${redocPath}`)\n log.info(`OpenAPI spec: ${specPath}`)\n }\n}\n\n// Re-export for use by Application when mounting module routes\nexport { registerControllerForDocs, clearRegisteredRoutes }\n","/** Escape a string for safe HTML attribute/content interpolation */\nfunction escapeHtml(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''')\n}\n\n/** Generate Swagger UI HTML that loads from CDN */\nexport function swaggerUIHtml(specUrl: string, title = 'API Docs'): string {\n const safeTitle = escapeHtml(title)\n const safeUrl = JSON.stringify(specUrl).replace(/</g, '\\\\u003c')\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>${safeTitle}</title>\n <link rel=\"stylesheet\" href=\"https://unpkg.com/swagger-ui-dist@5/swagger-ui.css\">\n</head>\n<body>\n <div id=\"swagger-ui\"></div>\n <script src=\"https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js\"></script>\n <script src=\"https://unpkg.com/swagger-ui-dist@5/swagger-ui-standalone-preset.js\"></script>\n <script>\n SwaggerUIBundle({\n url: ${safeUrl},\n dom_id: '#swagger-ui',\n deepLinking: true,\n presets: [SwaggerUIBundle.presets.apis, SwaggerUIStandalonePreset],\n plugins: [SwaggerUIBundle.plugins.DownloadUrl],\n layout: 'StandaloneLayout',\n });\n </script>\n</body>\n</html>`\n}\n\n/** Generate ReDoc HTML that loads from CDN */\nexport function redocHtml(specUrl: string, title = 'API Docs'): string {\n const safeTitle = escapeHtml(title)\n const safeUrl = escapeHtml(specUrl)\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>${safeTitle}</title>\n</head>\n<body>\n <redoc spec-url=\"${safeUrl}\"></redoc>\n <script src=\"https://cdn.redoc.ly/redoc/latest/bundles/redoc.standalone.js\"></script>\n</body>\n</html>`\n}\n"],"mappings":"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","names":["zodSchemaParser","name","supports","schema","safeParse","toJSONSchema","toJsonSchema","$schema","_","rest","SWAGGER_KEYS","OPERATION","Symbol","RESPONSES","TAGS","BEARER_AUTH","EXCLUDE","ApiOperation","options","target","propertyKey","Reflect","defineMetadata","SWAGGER_KEYS","OPERATION","ApiResponse","existing","getMetadata","RESPONSES","ApiTags","tags","TAGS","ApiBearerAuth","name","BEARER_AUTH","ApiExclude","EXCLUDE","METADATA","registeredRoutes","registerControllerForDocs","controllerClass","mountPath","push","clearRegisteredRoutes","length","buildOpenAPISpec","options","parser","schemaParser","zodSchemaParser","toJsonSchema","schema","supports","componentSchemas","schemaCounter","registerSchema","jsonSchema","hint","name","title","label","replace","clean","$schema","$ref","spec","openapi","info","version","description","paths","components","schemas","securitySchemes","tags","servers","allTags","Set","Reflect","getMetadata","SWAGGER_KEYS","EXCLUDE","routes","METADATA","ROUTES","classTags","TAGS","classAuth","BEARER_AUTH","controllerPath","CONTROLLER_PATH","route","handlerName","routePath","path","fullPath","openApiPath","method","toLowerCase","operation","OPERATION","responses","RESPONSES","methodTags","methodAuth","forEach","t","add","op","summary","operationId","deprecated","parameters","paramMatches","match","paramName","slice","type","validation","params","properties","props","in","required","query","Array","isArray","propSchema","Object","entries","includes","queryParamsConfig","QUERY_PARAMS","filterable","join","items","style","explode","sortable","searchable","minimum","default","maximum","body","bodySchema","bodyName","ref","requestBody","content","fileUpload","FILE_UPLOAD","fieldName","mode","format","resp","String","status","converted","schemaName","finalSchema","undefined","defaultStatus","authName","security","scheme","bearerFormat","from","map","bearerAuth","BearerAuth","keys","Router","Logger","escapeHtml","str","replace","swaggerUIHtml","specUrl","title","safeTitle","safeUrl","JSON","stringify","redocHtml","log","Logger","for","SwaggerAdapter","name","options","afterStart","server","_container","addr","address","host","servers","length","url","port","description","wsAdapter","adapters","find","a","getStats","stats","namespace","Object","keys","namespaces","push","onRouteMount","controllerClass","mountPath","registerControllerForDocs","beforeMount","app","clearRegisteredRoutes","docsPath","redocPath","specPath","docsRouter","Router","use","_req","res","next","setHeader","join","get","spec","buildOpenAPISpec","json","type","send","swaggerUIHtml","info","title","redocHtml"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forinda/kickjs-swagger",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.2",
|
|
4
4
|
"description": "OpenAPI spec generation from decorators, Swagger UI and ReDoc serving for KickJS",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"kickjs",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
],
|
|
41
41
|
"dependencies": {
|
|
42
42
|
"reflect-metadata": "^0.2.2",
|
|
43
|
-
"@forinda/kickjs-core": "0.
|
|
43
|
+
"@forinda/kickjs-core": "0.5.2"
|
|
44
44
|
},
|
|
45
45
|
"peerDependencies": {
|
|
46
46
|
"express": "^5.1.0",
|