{
  "$schema": "https://hyperjs.ai/schema/registry-item.json",
  "name": "openapi",
  "version": "0.1.0",
  "description": "OpenAPI 3.1 serializer + Swagger UI for Hyper. Pluggable SchemaConverter.",
  "readme": "# @hyper/openapi\n\nOpenAPI 3.1 serializer + Swagger UI for Hyper. Pluggable `SchemaConverter`.\n\n## Install\n\nComponents are installed as source into your repo, not pulled from npm. Pair `openapi` with a converter for your validation library:\n\n```bash\nbunx hyper add openapi openapi-zod\n# or: bunx hyper add openapi openapi-arktype\n# or: bunx hyper add openapi openapi-valibot\n```\n\nSee [hyperjs.ai](https://hyperjs.ai) for the full registry.\n\n## Usage\n\n```ts\nimport { Hyper } from \"@hyper/core\"\nimport { openapiPlugin } from \"@hyper/openapi\"\nimport { zodConverter } from \"@hyper/openapi-zod\"\n\nexport default new Hyper()\n  .use(openapiPlugin({ converter: zodConverter }))\n  .listen(3000)\n```\n\n## Docs\n\nSee the [main README](../../README.md) and [docs/](../../docs) for guides and integration recipes.\n\n## License\n\nMIT\n",
  "registryDeps": [
    "core"
  ],
  "peerDeps": {},
  "optionalPeerDeps": {},
  "files": [
    {
      "path": "openapi/converter.ts",
      "contents": "/**\n * SchemaConverter — the pluggable boundary.\n *\n * A converter inspects a Standard Schema value and returns an OpenAPI\n * JSON Schema fragment. The base @hyper/openapi package ships a\n * `fallbackConverter` that just emits `type: object`. Integrations like\n * `@hyper/openapi-zod` / `-valibot` / `-arktype` extend this.\n */\n\nimport type { StandardSchemaV1 } from \"@hyper/core\"\n\nexport type JsonSchema = Record<string, unknown>\n\nexport interface SchemaConverter {\n  readonly name: string\n  readonly canHandle: (s: unknown) => boolean\n  readonly toJsonSchema: (s: unknown) => JsonSchema\n}\n\nexport const fallbackConverter: SchemaConverter = {\n  name: \"fallback\",\n  canHandle: () => true,\n  toJsonSchema: () => ({ type: \"object\" }),\n}\n\nexport function firstConverter(\n  converters: readonly SchemaConverter[],\n  schema: unknown,\n): SchemaConverter {\n  for (const c of converters) if (c.canHandle(schema)) return c\n  return fallbackConverter\n}\n\n/**\n * Detect a Standard Schema value — gives us a baseline fall-through.\n */\nexport function isStandardSchema(x: unknown): x is StandardSchemaV1 {\n  return Boolean(x && typeof x === \"object\" && \"~standard\" in (x as Record<string, unknown>))\n}\n",
      "sha256": "698bac9a0aeafc78a6e658f58fab4f100f4d1e90ee76304330048e61a3f09e02"
    },
    {
      "path": "openapi/generate.ts",
      "contents": "/**\n * Convert a Hyper app's route list into an OpenAPI 3.1 document,\n * threading schemas through the registered `SchemaConverter`s.\n */\n\nimport type { HyperApp, Route, RouteExample } from \"@hyper/core\"\nimport { type JsonSchema, type SchemaConverter, firstConverter } from \"./converter.ts\"\n\nexport interface GenerateConfig {\n  readonly title?: string\n  readonly version?: string\n  readonly description?: string\n  readonly servers?: readonly { url: string; description?: string }[]\n  readonly converters?: readonly SchemaConverter[]\n}\n\nexport interface OpenAPIDoc {\n  readonly openapi: \"3.1.0\"\n  readonly info: { title: string; version: string; description?: string }\n  readonly servers?: readonly { url: string; description?: string }[]\n  readonly paths: Record<string, Record<string, OpenAPIOperation>>\n  readonly components?: { schemas?: Record<string, JsonSchema> }\n}\n\ninterface OpenAPIOperation {\n  readonly operationId?: string\n  readonly summary?: string\n  readonly tags?: readonly string[]\n  readonly deprecated?: boolean\n  readonly parameters?: readonly OpenAPIParam[]\n  readonly requestBody?: {\n    readonly content: Record<string, { schema: JsonSchema; examples?: Record<string, unknown> }>\n  }\n  readonly responses: Record<\n    string,\n    { description: string; content?: Record<string, { schema?: JsonSchema; example?: unknown }> }\n  >\n  readonly \"x-sunset\"?: string\n  readonly \"x-version\"?: string\n}\n\ninterface OpenAPIParam {\n  readonly name: string\n  readonly in: \"path\" | \"query\" | \"header\"\n  readonly required: boolean\n  readonly schema?: JsonSchema\n}\n\nconst PATH_PARAM = /:([A-Za-z0-9_]+)/g\n\nexport function generate(app: HyperApp, cfg: GenerateConfig = {}): OpenAPIDoc {\n  const converters = cfg.converters ?? []\n  const paths: Record<string, Record<string, OpenAPIOperation>> = {}\n  for (const r of app.routeList) {\n    if (r.meta.internal) continue\n    const p = toOpenApiPath(r.path)\n    const operation = buildOperation(r, converters)\n    if (!paths[p]) paths[p] = {}\n    paths[p][r.method.toLowerCase()] = operation\n  }\n  return {\n    openapi: \"3.1.0\",\n    info: {\n      title: cfg.title ?? \"Hyper API\",\n      version: cfg.version ?? \"0.0.0\",\n      ...(cfg.description !== undefined && { description: cfg.description }),\n    },\n    ...(cfg.servers && { servers: cfg.servers }),\n    paths,\n  }\n}\n\nfunction toOpenApiPath(path: string): string {\n  return path.replace(PATH_PARAM, \"{$1}\")\n}\n\nfunction buildOperation(r: Route, converters: readonly SchemaConverter[]): OpenAPIOperation {\n  const parameters: OpenAPIParam[] = []\n  for (const match of r.path.matchAll(PATH_PARAM)) {\n    parameters.push({ name: match[1]!, in: \"path\", required: true })\n  }\n  if (r.query) {\n    const conv = firstConverter(converters, r.query)\n    const js = conv.toJsonSchema(r.query)\n    if (js.type === \"object\" && typeof js.properties === \"object\" && js.properties) {\n      const required = new Set<string>((js.required as string[]) ?? [])\n      for (const [name, sub] of Object.entries(js.properties as Record<string, JsonSchema>)) {\n        parameters.push({\n          name,\n          in: \"query\",\n          required: required.has(name),\n          schema: sub,\n        })\n      }\n    }\n  }\n\n  let requestBody: OpenAPIOperation[\"requestBody\"]\n  if (r.body) {\n    const conv = firstConverter(converters, r.body)\n    const schema = conv.toJsonSchema(r.body)\n    const examples = buildBodyExamples(r.meta.examples as readonly RouteExample[] | undefined)\n    requestBody = {\n      content: {\n        \"application/json\": {\n          schema,\n          ...(examples && { examples }),\n        },\n      },\n    }\n  }\n\n  const responseExamples = buildResponseExamples(\n    r.meta.examples as readonly RouteExample[] | undefined,\n  )\n  const responses: OpenAPIOperation[\"responses\"] = {\n    \"200\": {\n      description: \"success\",\n      ...(responseExamples && {\n        content: { \"application/json\": { example: responseExamples } },\n      }),\n    },\n  }\n\n  if (r.throws) {\n    for (const [status, schema] of Object.entries(r.throws)) {\n      const conv = firstConverter(converters, schema)\n      responses[status] = {\n        description: \"declared error\",\n        content: { \"application/json\": { schema: conv.toJsonSchema(schema) } },\n      }\n    }\n  }\n\n  const meta = r.meta\n  const deprecated = !!meta.deprecated\n  const sunset =\n    typeof meta.deprecated === \"object\" && meta.deprecated?.sunset\n      ? meta.deprecated.sunset\n      : undefined\n  return {\n    ...(meta.name !== undefined && { operationId: meta.name }),\n    ...(meta.tags !== undefined && { tags: meta.tags }),\n    ...(deprecated && { deprecated: true }),\n    ...(parameters.length > 0 && { parameters }),\n    ...(requestBody && { requestBody }),\n    responses,\n    ...(sunset && { \"x-sunset\": sunset }),\n    ...(meta.version !== undefined && { \"x-version\": meta.version }),\n  }\n}\n\nfunction buildBodyExamples(\n  examples: readonly RouteExample[] | undefined,\n): Record<string, { value: unknown }> | undefined {\n  if (!examples) return undefined\n  const out: Record<string, { value: unknown }> = {}\n  for (const ex of examples) {\n    if (ex.input?.body !== undefined) out[ex.name] = { value: ex.input.body }\n  }\n  return Object.keys(out).length > 0 ? out : undefined\n}\n\nfunction buildResponseExamples(examples: readonly RouteExample[] | undefined): unknown | undefined {\n  if (!examples) return undefined\n  const ex = examples.find((e) => e.output?.body !== undefined)\n  return ex?.output?.body\n}\n",
      "sha256": "3d1a43081b5a814d0e99130b3d31e2bfb1e0c706cdf66d256923cd97a145bb89"
    },
    {
      "path": "openapi/index.ts",
      "contents": "/**\n * @hyper/openapi — OpenAPI 3.1 serializer + Swagger UI for Hyper apps.\n *\n *   import { openapiHandlers } from \"@hyper/openapi\"\n *   const oa = openapiHandlers(app, { title: \"My API\", converters: [...] })\n *   // Then mount oa.spec at /openapi.json and oa.docs at /docs.\n *\n * SchemaConverter is pluggable — see @hyper/openapi-zod / -valibot / -arktype.\n */\n\nexport { fallbackConverter, firstConverter, isStandardSchema } from \"./converter.ts\"\nexport type { JsonSchema, SchemaConverter } from \"./converter.ts\"\nexport { generate } from \"./generate.ts\"\nexport type { GenerateConfig, OpenAPIDoc } from \"./generate.ts\"\nexport { openapiHandlers, openapiPlugin } from \"./plugin.ts\"\nexport type { OpenApiPluginConfig } from \"./plugin.ts\"\nexport { swaggerHtml } from \"./swagger.ts\"\nexport type { SwaggerHtmlOptions } from \"./swagger.ts\"\n",
      "sha256": "044532288c945e2b9c9e6cbcdf74561007540706ceae7afebdc5d421fe5536af"
    },
    {
      "path": "openapi/plugin.ts",
      "contents": "/**\n * openapiPlugin — exposes /openapi.json and /docs.\n *\n * Plugins don't add routes directly; the consumer mounts our two handlers\n * explicitly (`openapiHandlers(...)`) and the plugin wires default-on\n * cache headers for the spec URL.\n */\n\nimport type { HyperApp, HyperPlugin, InvokeInput, Route } from \"@hyper/core\"\nimport type { SchemaConverter } from \"./converter.ts\"\nimport { type GenerateConfig, type OpenAPIDoc, generate } from \"./generate.ts\"\nimport { type SwaggerHtmlOptions, swaggerHtml } from \"./swagger.ts\"\n\nexport interface OpenApiPluginConfig extends GenerateConfig, SwaggerHtmlOptions {}\n\nexport function openapiPlugin(config: OpenApiPluginConfig = {}): HyperPlugin {\n  return {\n    name: \"@hyper/openapi\",\n    build() {\n      // Reserved for future dynamic-route registration.\n    },\n  }\n}\n\n/** Standalone handler pair users mount on their app. */\nexport function openapiHandlers(\n  app: HyperApp,\n  config: OpenApiPluginConfig = {},\n): {\n  spec: (req: Request) => Response\n  docs: (req: Request) => Response\n  doc: OpenAPIDoc\n} {\n  const doc = generate(app, config)\n  const docJson = JSON.stringify(doc)\n  const html = swaggerHtml({\n    ...(config.specUrl !== undefined && { specUrl: config.specUrl }),\n    ...(config.title !== undefined && { title: config.title }),\n  })\n  return {\n    doc,\n    spec: () =>\n      new Response(docJson, {\n        headers: {\n          \"content-type\": \"application/json; charset=utf-8\",\n          \"cache-control\": \"public, max-age=60\",\n        },\n      }),\n    docs: () =>\n      new Response(html, {\n        headers: {\n          \"content-type\": \"text/html; charset=utf-8\",\n          \"cache-control\": \"public, max-age=60\",\n        },\n      }),\n  }\n}\n\n// Unused but exported for TypeScript — keeps the type dep alive.\nexport type _InvokeInput = InvokeInput\nexport type _Route = Route\nexport type _SchemaConverter = SchemaConverter\n",
      "sha256": "b0c3ea65ec3b71cb72b681d53845368653f63b8fcf83aef83eea43b3b805cae4"
    },
    {
      "path": "openapi/swagger.ts",
      "contents": "/**\n * Swagger UI handler — self-contained HTML + redirect to /openapi.json.\n * CDN-loaded; no bundling required.\n */\n\nexport interface SwaggerHtmlOptions {\n  readonly specUrl?: string\n  readonly title?: string\n}\n\nexport function swaggerHtml(opts: SwaggerHtmlOptions = {}): string {\n  const spec = opts.specUrl ?? \"/openapi.json\"\n  const title = opts.title ?? \"API Docs\"\n  return `<!doctype html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\">\n  <title>${escapeHtml(title)}</title>\n  <link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui.css\">\n  <style>body { margin: 0 }</style>\n</head>\n<body>\n  <div id=\"ui\"></div>\n  <script src=\"https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui-bundle.js\"></script>\n  <script>\n    window.ui = SwaggerUIBundle({\n      url: ${JSON.stringify(spec)},\n      dom_id: \"#ui\",\n      deepLinking: true,\n      docExpansion: \"list\",\n    })\n  </script>\n</body>\n</html>`\n}\n\nfunction escapeHtml(s: string): string {\n  return s.replace(\n    /[&<>\"']/g,\n    (c) =>\n      ({\n        \"&\": \"&amp;\",\n        \"<\": \"&lt;\",\n        \">\": \"&gt;\",\n        '\"': \"&quot;\",\n        \"'\": \"&#39;\",\n      })[c] as string,\n  )\n}\n",
      "sha256": "50a2379f4f0dc2cb8749bc252d353b395daf2aad9723ed68acfc8925e500d2c8"
    }
  ],
  "subpaths": {}
}