{"version":3,"sources":["../../../src/server/openapi.ts"],"sourcesContent":["import { convert } from '@openapi-contrib/json-schema-to-openapi-schema'\nimport type { OpenAPIV3_1 } from 'openapi-types'\nimport { capitalize, type JsonSchema } from 'valleyed'\n\nimport { Instance } from '../instance'\nimport type { ServerConfig } from './pipes'\nimport { Router } from './routes'\nimport type { Route } from './types'\n\ndeclare module 'openapi-types' {\n\tnamespace OpenAPIV3 {\n\t\tinterface Document {\n\t\t\t'x-tagGroups': { name: string; tags: string[] }[]\n\t\t}\n\t\tinterface TagObject {\n\t\t\t'x-displayName': string\n\t\t}\n\t}\n}\n\nexport type OpenApiSchemaDef = {\n\trequest: Partial<Record<'body' | 'query' | 'params' | 'headers' | 'response', JsonSchema>>\n\tresponse: Partial<Record<'response' | 'responseHeaders', { status: number; schema: JsonSchema; contentType: string }[]>>\n}\n\nexport class OpenApi {\n\t#registeredTags: Record<string, boolean> = {}\n\t#registeredTagGroups: Record<string, { name: string; tags: string[] }> = {}\n\t#baseOpenapiDoc: OpenAPIV3_1.Document\n\n\tconstructor(private config: ServerConfig) {\n\t\tconst instance = Instance.get()\n\t\tconst { app } = instance.settings\n\t\tthis.#baseOpenapiDoc = {\n\t\t\topenapi: '3.0.0',\n\t\t\tinfo: {\n\t\t\t\ttitle: `${app.name} ${instance.id}`,\n\t\t\t\tversion: config.openapi.docsVersion ?? '',\n\t\t\t},\n\t\t\tservers: config.openapi.docsBaseUrl?.map((url) => ({ url })),\n\t\t\tpaths: {},\n\t\t\tcomponents: {\n\t\t\t\tschemas: {},\n\t\t\t\tsecuritySchemes: {\n\t\t\t\t\tAuthorization: {\n\t\t\t\t\t\ttype: 'apiKey',\n\t\t\t\t\t\tname: 'authorization',\n\t\t\t\t\t\tin: 'header',\n\t\t\t\t\t},\n\t\t\t\t\tRefreshToken: {\n\t\t\t\t\t\ttype: 'apiKey',\n\t\t\t\t\t\tname: 'x-refresh-token',\n\t\t\t\t\t\tin: 'header',\n\t\t\t\t\t},\n\t\t\t\t\tApiKey: {\n\t\t\t\t\t\ttype: 'apiKey',\n\t\t\t\t\t\tname: 'x-api-key',\n\t\t\t\t\t\tin: 'header',\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\ttags: [],\n\t\t\t'x-tagGroups': [],\n\t\t}\n\t}\n\n\tcleanPath(path: string) {\n\t\tlet cleaned = path.replace(/(\\/\\s*)+/g, '/')\n\t\tif (!cleaned.startsWith('/')) cleaned = `/${cleaned}`\n\t\tif (cleaned !== '/' && cleaned.endsWith('/')) cleaned = cleaned.slice(0, -1)\n\t\treturn cleaned\n\t}\n\n\tasync register(route: Route<any>, def: OpenApiSchemaDef) {\n\t\tif (route.hide) return\n\n\t\tconst tag = this.#buildTag(route.groups ?? [])\n\n\t\tconst cleanPath = this.cleanPath(route.path)\n\t\tconst operationId = `(${route.method.toUpperCase()}) ${cleanPath}`\n\t\tawait this.#addRouteToOpenApiDoc(cleanPath, route.method.toLowerCase(), def, {\n\t\t\toperationId,\n\t\t\tsummary: route.title ?? cleanPath,\n\t\t\tdescription: route.descriptions?.join('\\n\\n'),\n\t\t\ttags: tag ? [tag] : undefined,\n\t\t\tsecurity: route.security,\n\t\t})\n\t}\n\n\tasync #addRouteToOpenApiDoc(path: string, method: string, def: OpenApiSchemaDef, methodObj: OpenAPIV3_1.OperationObject) {\n\t\tif (def.response.response?.length) {\n\t\t\tmethodObj.responses ??= {}\n\t\t\tfor (const resp of def.response.response) {\n\t\t\t\tmethodObj.responses[resp.status] ??= { description: '', content: {} }\n\t\t\t\tconst res = methodObj.responses[resp.status] as OpenAPIV3_1.ResponseObject\n\t\t\t\tres.content![resp.contentType] = { schema: await convert(this.#visit(resp.schema)) }\n\t\t\t}\n\t\t}\n\n\t\tif (def.response.responseHeaders?.length) {\n\t\t\tmethodObj.responses ??= {}\n\t\t\tfor (const resp of def.response.responseHeaders) {\n\t\t\t\tmethodObj.responses[resp.status] ??= { description: '', content: {} }\n\t\t\t\tmethodObj.responses[resp.status] as OpenAPIV3_1.ResponseObject\n\t\t\t\tconst res = methodObj.responses[resp.status] as OpenAPIV3_1.ResponseObject\n\t\t\t\tres.headers = { schema: (await convert(this.#visit(resp.schema))) as any }\n\t\t\t}\n\t\t}\n\n\t\tif (def.request.body)\n\t\t\tmethodObj.requestBody = {\n\t\t\t\trequired: true,\n\t\t\t\tcontent: {\n\t\t\t\t\t'application/json': { schema: await convert(this.#visit(def.request.body)) },\n\t\t\t\t},\n\t\t\t}\n\n\t\tconst parameters: OpenAPIV3_1.ParameterObject[] = []\n\n\t\tconst addParams = async (location: 'query' | 'path' | 'header', schema: JsonSchema | undefined) => {\n\t\t\tif (!schema) return\n\t\t\tconst flat = this.#flattenForParameters(schema)\n\t\t\tfor (const schema of flat) {\n\t\t\t\tif (!schema.properties) continue\n\t\t\t\tfor (const [name, value] of Object.entries(schema.properties))\n\t\t\t\t\tparameters.push({\n\t\t\t\t\t\tname,\n\t\t\t\t\t\tin: location,\n\t\t\t\t\t\tschema: await convert(this.#visit(value)),\n\t\t\t\t\t\trequired: (schema.required || []).includes(name),\n\t\t\t\t\t})\n\t\t\t}\n\t\t}\n\n\t\tawait Promise.all([\n\t\t\taddParams('query', def.request.query),\n\t\t\taddParams('path', def.request.params),\n\t\t\taddParams('header', def.request.headers),\n\t\t])\n\t\tif (parameters.length) methodObj.parameters = parameters\n\n\t\tconst base = this.#baseOpenapiDoc\n\t\tif (!base.paths) base.paths = {}\n\t\tif (!base.paths[path]) base.paths[path] = {}\n\t\tbase.paths[path]![method] = methodObj\n\t}\n\n\trouter() {\n\t\tconst jsonPath = '/openapi.json'\n\t\tconst router = new Router({ path: this.config.openapi.docsPath ?? '/', hide: true })\n\t\trouter.get('/index.html')((req) => req.res({ body: this.#html(`.${jsonPath}`), contentType: 'text/html' }))\n\t\trouter.get(jsonPath)((req) => req.res({ body: this.#baseOpenapiDoc }))\n\t\treturn router\n\t}\n\n\t#flattenForParameters(node: JsonSchema): JsonSchema[] {\n\t\tconst { allOf, oneOf, anyOf, ...schema } = node\n\t\tif (allOf) return allOf.flatMap((n) => this.#flattenForParameters(n))\n\t\treturn [schema]\n\t}\n\n\t#visit(node: JsonSchema) {\n\t\tif (!node || typeof node !== 'object') return node\n\t\tif (typeof node.$refId === 'string') {\n\t\t\tconst { $refId: id, ...rest } = node\n\t\t\tconst res = this.#visit(rest)\n\t\t\tif (this.#baseOpenapiDoc.components?.schemas) {\n\t\t\t\tthis.#baseOpenapiDoc.components.schemas[id] = res\n\t\t\t\treturn { $ref: `#/components/schemas/${id}` }\n\t\t\t} else return res\n\t\t}\n\n\t\tif (Array.isArray(node)) return node.map((n) => this.#visit(n)) as any\n\t\treturn Object.fromEntries(Object.entries(node).map(([key, value]) => [key, this.#visit(value as any)]))\n\t}\n\n\t#buildTag(groups: NonNullable<Route<any>['groups']>) {\n\t\tif (!groups.length) return undefined\n\t\tconst parsed = groups.map((g) => (typeof g === 'string' ? { name: g } : g))\n\t\tconst name = parsed.map((g) => g.name).join(' > ')\n\t\tconst displayName = parsed.at(-1)?.name ?? ''\n\t\tconst description = parsed\n\t\t\t.map((g) => g.description?.trim() ?? '')\n\t\t\t.filter(Boolean)\n\t\t\t.join('\\n\\n\\n\\n')\n\n\t\tif (!this.#registeredTags[name]) {\n\t\t\tthis.#registeredTags[name] = true\n\t\t\tthis.#baseOpenapiDoc.tags!.push({ name, 'x-displayName': displayName, description })\n\n\t\t\tconst tagGroups = parsed.slice(0, -1)\n\t\t\tconst groupName = tagGroups.map((g) => g.name).join(' > ') || 'default'\n\t\t\tif (!this.#registeredTagGroups[groupName]) {\n\t\t\t\tconst group = { name: groupName, tags: [] }\n\t\t\t\tthis.#baseOpenapiDoc['x-tagGroups'].push(group)\n\t\t\t\tthis.#registeredTagGroups[groupName] = group\n\t\t\t}\n\t\t\tthis.#registeredTagGroups[groupName].tags = [...new Set([...this.#registeredTagGroups[groupName].tags, name])]\n\t\t}\n\n\t\treturn name\n\t}\n\n\t#html(jsonPath: string) {\n\t\tconst instance = Instance.get()\n\t\tconst title = capitalize(`${instance.settings.app.name} ${instance.id}`)\n\t\treturn `\n<!doctype html>\n<html>\n  <head>\n    <title>${title}</title>\n    <meta charset=\"utf-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n\t<style>\n      .darklight-reference {\n        display: none;\n      }\n    </style>\n  </head>\n  <body>\n    <script id=\"api-reference\" data-url=\"${jsonPath}\"></script>\n    <script>\n      const configuration = { theme: 'purple' };\n      document.getElementById('api-reference').dataset.configuration = JSON.stringify(configuration);\n    </script>\n    <script src=\"https://cdn.jsdelivr.net/npm/@scalar/api-reference@1.38.0\"></script>\n  </body>\n</html>\n`\n\t}\n}\n"],"mappings":"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","names":["schema"]}