@nxgt/openapi-hono 0.1.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.
@@ -0,0 +1,13 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/errors.ts", "../src/routable.ts", "../src/engine.ts", "../src/streams.ts"],
4
+ "sourcesContent": [
5
+ "import type { Context } from 'hono';\n\n/**\n * Where a request value was read: a location Hono's `c.req.valid()` knows, or\n * `body` for a text body, which a handler reads with `c.req.text()`.\n */\nexport type ValidationTarget =\n\t| 'param'\n\t| 'query'\n\t| 'header'\n\t| 'json'\n\t| 'form'\n\t| 'body';\n\nexport interface ValidationIssue {\n\ttarget: ValidationTarget | 'response';\n\t/** Inside the target: `['items', 0, 'name']`, or `[]` for the whole of it. */\n\tpath: (string | number)[];\n\t/**\n\t * Zod's issue code, or one of the engine's own: `invalid_json`,\n\t * `invalid_form`, `invalid_content_type`, `missing_body`,\n\t * `repeated_parameter`, `undeclared_status`.\n\t */\n\tcode: string;\n\tmessage: string;\n}\n\nexport interface ValidationFailure {\n\t/** A request the spec refuses, or, with `validateResponses`, a reply it does not declare. */\n\tkind: 'request' | 'response';\n\toperationId: string;\n\tmethod: string;\n\t/** As the spec writes it: `/employees/{id}`. */\n\tpath: string;\n\t/** The reply's status, for a `response` failure. */\n\tstatus?: number;\n\t/** Every issue, from every target at once. */\n\tissues: ValidationIssue[];\n}\n\n/**\n * Answers a failure. Return a `Response` to send it, throw to hand the\n * failure to `app.onError`, or return nothing for the default answer.\n */\nexport type ValidationErrorHook = (\n\tfailure: ValidationFailure,\n\tc: Context,\n) => Response | undefined | Promise<Response | undefined>;\n\n/**\n * The default answer: 400 for a request, shaped like `ErrorResponse` with the\n * issues added. A reply that fails is a 500 without them, since they name\n * what the reply held (an unrecognized `passwordHash`…): they go to\n * `console.error` instead.\n */\nexport const validationErrorHandler = (\n\tfailure: ValidationFailure,\n\tc: Context,\n): Response => {\n\tconst timestamp = new Date().toISOString();\n\tif (failure.kind === 'response') {\n\t\tconsole.error(\n\t\t\t`${failure.operationId} (${failure.method.toUpperCase()} ${failure.path}) replied ${failure.status} as the spec does not declare:`,\n\t\t\tfailure.issues,\n\t\t);\n\t\treturn c.json(\n\t\t\t{ status: 500, message: 'errors.response-validation-failed', timestamp },\n\t\t\t500,\n\t\t);\n\t}\n\treturn c.json(\n\t\t{\n\t\t\tstatus: 400,\n\t\t\tmessage: 'errors.validation-failed',\n\t\t\ttimestamp,\n\t\t\tissues: failure.issues,\n\t\t},\n\t\t400,\n\t);\n};\n\n/** What the engine reads of a Zod issue. */\nexport interface SchemaIssue {\n\treadonly path: readonly PropertyKey[];\n\treadonly code: string;\n\treadonly message: string;\n}\n\nexport const toIssues = (\n\ttarget: ValidationIssue['target'],\n\tissues: readonly SchemaIssue[],\n): ValidationIssue[] =>\n\tissues.map((issue) => ({\n\t\ttarget,\n\t\tpath: issue.path.filter(\n\t\t\t(key): key is string | number => typeof key !== 'symbol',\n\t\t),\n\t\tcode: issue.code,\n\t\tmessage: issue.message,\n\t}));\n",
6
+ "/**\n * Why `routes` cannot register an operation on Hono, if it cannot: the\n * runtime refuses the route. `@nxgt/openapi-codegen` keeps a deliberate copy\n * in `src/emit/routable.ts`, and warns at generation. Change both together.\n */\nexport function unroutable(operation: {\n\treadonly method: string;\n\treadonly path: string;\n}): string | undefined {\n\tif (operation.method === 'head') {\n\t\treturn `Hono answers HEAD with the GET route of ${operation.path}, so a HEAD handler would never run. Register GET ${operation.path} instead`;\n\t}\n\tconst mixed = operation.path\n\t\t.split('/')\n\t\t.find((segment) => segment.includes('{') && !/^\\{[^{}]+\\}$/.test(segment));\n\tif (mixed !== undefined) {\n\t\treturn `Hono cannot route ${operation.path}: a path parameter must fill its whole segment, and ${mixed} does not. Serve it with app.on() and validate it yourself`;\n\t}\n\treturn undefined;\n}\n",
7
+ "/**\n * The runtime behind `hono.ts`. Each route is registered on a Hono app as\n * `[...middlewares, validator, handler]`: the validator reads the parameters\n * and the body with the validators of `operations.ts`, hands the results\n * to `c.req.valid()`, and answers every issue at once when there is one.\n */\nimport type { Context, Hono, MiddlewareHandler, Next } from 'hono';\nimport {\n\ttype SchemaIssue,\n\ttoIssues,\n\ttype ValidationFailure,\n\ttype ValidationIssue,\n\ttype ValidationTarget,\n\tvalidationErrorHandler,\n} from './errors';\nimport { unroutable } from './routable';\nimport type { Api, ApiOptions, ApiSpec, Method, Routes } from './types';\n\n/** What the engine asks of a validator: Zod's `safeParse`. */\nexport interface Validator {\n\tsafeParse(\n\t\tvalue: unknown,\n\t):\n\t\t| { success: true; data: unknown }\n\t\t| { success: false; error: { issues: readonly SchemaIssue[] } };\n}\n\nexport interface RuntimeMedia {\n\treadonly kind: 'json' | 'form' | 'text' | 'binary' | 'sse' | 'jsonl';\n\treadonly schema?: Validator;\n\t/** `sse`: each event's data, by name: a validator for JSON, `null` for text. */\n\treadonly events?: { readonly [event: string]: Validator | null };\n\t/** `jsonl`: each item. */\n\treadonly item?: Validator;\n}\n\n/** An entry of the `operations` table in `operations.ts`. */\nexport interface RuntimeOperation {\n\treadonly method: Method;\n\treadonly path: string;\n\treadonly honoPath: string;\n\treadonly tags: readonly string[];\n\treadonly parameters: readonly {\n\t\treadonly name: string;\n\t\treadonly in: 'path' | 'query' | 'header';\n\t\treadonly list: boolean;\n\t\treadonly explode: boolean;\n\t}[];\n\treadonly param: Validator;\n\treadonly query: Validator;\n\treadonly header: Validator;\n\treadonly body?: {\n\t\treadonly required: boolean;\n\t\treadonly content: { readonly [mediaType: string]: RuntimeMedia };\n\t};\n\treadonly responses: {\n\t\treadonly [status: number]: { readonly [mediaType: string]: RuntimeMedia };\n\t};\n}\n\n/** The `operations` table, keyed by `operationId`. */\nexport type OperationTable = {\n\treadonly [operationId: string]: RuntimeOperation;\n};\n\nconst METHODS: readonly Method[] = [\n\t'get',\n\t'put',\n\t'post',\n\t'delete',\n\t'options',\n\t'head',\n\t'patch',\n\t'trace',\n\t'query',\n];\n\n/** Stands for the validator in a chain; the engine swaps it out. */\nconst VALIDATE: MiddlewareHandler = async () => {\n\tthrow new Error(\n\t\t'routes.validate marks where a routes chain validates the request; it cannot run anywhere else',\n\t);\n};\n\ntype Settings = ApiOptions & { prefix?: string; tag?: string };\ntype Handler = (c: Context, next: Next) => unknown;\ntype App = Hono<any, any, any>;\n/** What `addValidatedData` takes: whatever the validator returned. */\ntype Validated = Parameters<Context['req']['addValidatedData']>[1];\n\n/** The route a request runs: what `streamEvents()` and `streamLines()` read in its handler. */\nexport interface RunningRoute {\n\treadonly id: string;\n\treadonly operation: RuntimeOperation;\n\treadonly settings: ApiOptions;\n}\n\n/** Set before each handler runs; a request's `Context` is the same object throughout its chain. */\nconst running = new WeakMap<Context, RunningRoute>();\n\nexport const runningRoute = (c: Context): RunningRoute | undefined =>\n\trunning.get(c);\n\n/** Every route the engine put on an app, in order, for the shadowing check. */\nconst onApp = new WeakMap<\n\tobject,\n\t{ method: Method; path: string; label: string }[]\n>();\n\n/**\n * One registry for a spec: `routes(app)` for each app or module, then\n * `assertComplete()`. `hono.ts` calls it with the spec's table.\n */\nexport function createApi<S extends ApiSpec>(\n\toperations: OperationTable,\n\tdefaults: ApiOptions = {},\n): Api<S> {\n\tconst done = new Set<string>();\n\tconst byRoute = new Map<string, string>();\n\tfor (const [id, operation] of Object.entries(operations)) {\n\t\tbyRoute.set(`${operation.method} ${operation.path}`, id);\n\t}\n\tconst label = (id: string, operation: RuntimeOperation): string =>\n\t\t`${id} (${operation.method.toUpperCase()} ${operation.path})`;\n\n\tconst register = (\n\t\tapp: App,\n\t\tid: string,\n\t\tchain: unknown[],\n\t\tsettings: Settings,\n\t): void => {\n\t\tconst operation = operations[id];\n\t\tif (!operation) throw new Error(`${id} is not an operationId of the spec`);\n\t\tconst name = label(id, operation);\n\t\tconst why = unroutable(operation);\n\t\tif (why !== undefined) throw new Error(`${name}: ${why}`);\n\t\tif (done.has(id)) throw new Error(`${name} already has a route`);\n\t\tif (settings.tag !== undefined && !operation.tags.includes(settings.tag)) {\n\t\t\tthrow new Error(`${name} is not tagged ${settings.tag}`);\n\t\t}\n\t\tconst handler = chain.at(-1);\n\t\tconst middlewares = chain.slice(0, -1) as MiddlewareHandler[];\n\t\tif (typeof handler !== 'function' || handler === VALIDATE) {\n\t\t\tthrow new Error(`${name}: the last argument must be the handler`);\n\t\t}\n\t\tconst marker = middlewares.indexOf(VALIDATE);\n\t\tif (marker !== middlewares.lastIndexOf(VALIDATE)) {\n\t\t\tthrow new Error(`${name}: routes.validate appears twice`);\n\t\t}\n\t\tconst path = localPath(operation, settings.prefix, name);\n\t\tconst routes = onApp.get(app) ?? [];\n\t\tfor (const earlier of routes) {\n\t\t\tif (earlier.method === operation.method && shadows(earlier.path, path)) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`${name} would never be reached: ${earlier.label}, registered before it, matches ${path} first. Register ${id} before it`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t\tconst at = marker < 0 ? middlewares.length : marker;\n\t\t// Hono's overloads type a literal chain; this one is built at runtime.\n\t\tconst on = app.on as unknown as (\n\t\t\tmethod: string,\n\t\t\tpath: string,\n\t\t\t...handlers: MiddlewareHandler[]\n\t\t) => unknown;\n\t\ton.call(\n\t\t\tapp,\n\t\t\toperation.method.toUpperCase(),\n\t\t\tpath,\n\t\t\t...middlewares.slice(0, at),\n\t\t\tvalidation(id, operation, settings),\n\t\t\t...middlewares.slice(marker < 0 ? at : at + 1),\n\t\t\treply(id, operation, handler as Handler, settings),\n\t\t);\n\t\troutes.push({ method: operation.method, path, label: name });\n\t\tonApp.set(app, routes);\n\t\tdone.add(id);\n\t};\n\n\tconst make = (app: App, settings: Settings): Record<string, unknown> => {\n\t\tconst routes: Record<string, unknown> = {\n\t\t\tvalidate: VALIDATE,\n\t\t\twith: (options: ApiOptions) => make(app, { ...settings, ...options }),\n\t\t\toperation: (id: string, ...chain: unknown[]) => {\n\t\t\t\tregister(app, id, chain, settings);\n\t\t\t\treturn routes;\n\t\t\t},\n\t\t};\n\t\tfor (const method of METHODS) {\n\t\t\troutes[method] = (path: string, ...chain: unknown[]) => {\n\t\t\t\tconst id = byRoute.get(`${method} ${path}`);\n\t\t\t\tif (id === undefined) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`The spec has no ${method.toUpperCase()} ${path} operation`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tregister(app, id, chain, settings);\n\t\t\t\treturn routes;\n\t\t\t};\n\t\t}\n\t\treturn routes;\n\t};\n\n\t// An operation `routes` cannot register is never missing: the generator\n\t// warned about it, and the app serves it some other way.\n\tconst missing = (tag?: string): string[] =>\n\t\tObject.entries(operations)\n\t\t\t.filter(\n\t\t\t\t([id, operation]) =>\n\t\t\t\t\t!done.has(id) &&\n\t\t\t\t\tunroutable(operation) === undefined &&\n\t\t\t\t\t(tag === undefined || operation.tags.includes(tag)),\n\t\t\t)\n\t\t\t.map(([id]) => id);\n\n\treturn {\n\t\troutes: <Prefix extends string>(app: App, options: Settings = {}) =>\n\t\t\tmake(app, { ...defaults, ...options }) as unknown as Routes<\n\t\t\t\tS,\n\t\t\t\tnever,\n\t\t\t\tPrefix\n\t\t\t>,\n\t\tmissing,\n\t\tassertComplete(tag?: string) {\n\t\t\tconst left = missing(tag);\n\t\t\tif (left.length === 0) return;\n\t\t\tthrow new Error(\n\t\t\t\t`${left.length} operation(s)${tag === undefined ? '' : ` tagged ${tag}`} have no route:\\n${left\n\t\t\t\t\t.map((id) => ` ${label(id, operations[id] as RuntimeOperation)}`)\n\t\t\t\t\t.join('\\n')}`,\n\t\t\t);\n\t\t},\n\t};\n}\n\n/** The operation's Hono path on an app mounted at `prefix`. */\nfunction localPath(\n\toperation: RuntimeOperation,\n\tprefix: string | undefined,\n\tname: string,\n): string {\n\tif (!prefix) return operation.honoPath;\n\tconst mount = prefix.replace(/\\{([^}]+)\\}/g, ':$1').replace(/\\/$/, '');\n\tconst path = operation.honoPath;\n\tif (path !== mount && !path.startsWith(`${mount}/`)) {\n\t\tthrow new Error(\n\t\t\t`${name}: its path ${operation.path} is not under the prefix ${prefix}`,\n\t\t);\n\t}\n\treturn path.slice(mount.length) || '/';\n}\n\n/**\n * Whether a request for `later` would always reach `earlier` first: Hono\n * tries routes in the order they were registered, so `/employees/:id`\n * registered before `/employees/me` answers for it.\n */\nfunction shadows(earlier: string, later: string): boolean {\n\tconst a = earlier.split('/');\n\tconst b = later.split('/');\n\tif (a.length !== b.length) return false;\n\tfor (const [i, segment] of a.entries()) {\n\t\tif (segment !== b[i] && !segment.startsWith(':')) return false;\n\t}\n\treturn true;\n}\n\nfunction validation(\n\tid: string,\n\toperation: RuntimeOperation,\n\tsettings: Settings,\n): MiddlewareHandler {\n\treturn async (c, next) => {\n\t\tconst issues: ValidationIssue[] = [];\n\t\tconst check: Check = (target, validator, value) => {\n\t\t\tconst result = validator.safeParse(value);\n\t\t\tif (!result.success) {\n\t\t\t\tissues.push(...toIssues(target, result.error.issues));\n\t\t\t} else if (target !== 'body') {\n\t\t\t\tc.req.addValidatedData(target, result.data as Validated);\n\t\t\t}\n\t\t};\n\t\tcheck('param', operation.param, c.req.param());\n\t\tcheck('query', operation.query, readQuery(c, operation, issues));\n\t\tcheck('header', operation.header, readHeaders(c, operation));\n\t\tif (operation.body) await readBody(c, operation.body, issues, check);\n\t\tif (issues.length > 0) {\n\t\t\treturn fail(c, settings, {\n\t\t\t\tkind: 'request',\n\t\t\t\toperationId: id,\n\t\t\t\tmethod: operation.method,\n\t\t\t\tpath: operation.path,\n\t\t\t\tissues,\n\t\t\t});\n\t\t}\n\t\tawait next();\n\t};\n}\n\ntype Check = (\n\ttarget: ValidationTarget,\n\tvalidator: Validator,\n\tvalue: unknown,\n) => void;\n\n/**\n * Declared query parameters: a string each, or every value of a list. A key\n * that takes one value and is sent twice is an issue: the handler would see\n * the first value and `c.req.queries()` the other.\n */\nfunction readQuery(\n\tc: Context,\n\toperation: RuntimeOperation,\n\tissues: ValidationIssue[],\n): Record<string, unknown> {\n\tconst query: Record<string, unknown> = {};\n\tfor (const param of operation.parameters) {\n\t\tif (param.in !== 'query') continue;\n\t\tif (param.list && param.explode) {\n\t\t\tconst values = c.req.queries(param.name);\n\t\t\tif (values !== undefined) query[param.name] = values;\n\t\t\tcontinue;\n\t\t}\n\t\tconst sent = c.req.queries(param.name)?.length ?? 0;\n\t\tif (sent > 1) {\n\t\t\tissues.push({\n\t\t\t\ttarget: 'query',\n\t\t\t\tpath: [param.name],\n\t\t\t\tcode: 'repeated_parameter',\n\t\t\t\tmessage: `${param.name} is sent ${sent} times, and takes one value`,\n\t\t\t});\n\t\t}\n\t\tconst value = c.req.query(param.name);\n\t\tif (value !== undefined) {\n\t\t\tquery[param.name] = param.list ? value.split(',') : value;\n\t\t}\n\t}\n\treturn query;\n}\n\n/** Declared headers, keyed lowercased; a list split on commas. */\nfunction readHeaders(\n\tc: Context,\n\toperation: RuntimeOperation,\n): Record<string, unknown> {\n\tconst headers: Record<string, unknown> = {};\n\tfor (const param of operation.parameters) {\n\t\tif (param.in !== 'header') continue;\n\t\tconst value = c.req.header(param.name);\n\t\tif (value === undefined) continue;\n\t\theaders[param.name.toLowerCase()] = param.list\n\t\t\t? value.split(',').map((item) => item.trim())\n\t\t\t: value;\n\t}\n\treturn headers;\n}\n\nconst mediaType = (header: string): string =>\n\t(header.split(';')[0] ?? '').trim().toLowerCase();\n\n/** The declared media type for `type`: itself, else `type/*`, else `*\\/*`. */\nfunction match(\n\tcontent: { readonly [mediaType: string]: RuntimeMedia },\n\ttype: string,\n): RuntimeMedia | undefined {\n\tfor (const [key, media] of Object.entries(content)) {\n\t\tif (key.toLowerCase() === type) return media;\n\t}\n\treturn content[`${type.split('/')[0]}/*`] ?? content['*/*'];\n}\n\nconst targetOf = (kind: RuntimeMedia['kind'] | undefined): ValidationTarget =>\n\tkind === 'json' ? 'json' : kind === 'form' ? 'form' : 'body';\n\nasync function readBody(\n\tc: Context,\n\tbody: NonNullable<RuntimeOperation['body']>,\n\tissues: ValidationIssue[],\n\tcheck: Check,\n): Promise<void> {\n\tconst declared = Object.keys(body.content).join(', ');\n\tconst target = targetOf(Object.values(body.content)[0]?.kind);\n\tconst absent = (at: ValidationTarget): void => {\n\t\tif (body.required) {\n\t\t\tissues.push({\n\t\t\t\ttarget: at,\n\t\t\t\tpath: [],\n\t\t\t\tcode: 'missing_body',\n\t\t\t\tmessage: `A request body is required: ${declared}`,\n\t\t\t});\n\t\t} else if (at === 'json' || at === 'form') {\n\t\t\tc.req.addValidatedData(at, undefined as unknown as Validated);\n\t\t}\n\t};\n\tconst header = c.req.header('content-type');\n\tif (header === undefined) {\n\t\tif ((await read(c, () => c.req.text())) === '') absent(target);\n\t\telse {\n\t\t\tissues.push({\n\t\t\t\ttarget,\n\t\t\t\tpath: [],\n\t\t\t\tcode: 'invalid_content_type',\n\t\t\t\tmessage: `A request body needs a Content-Type: ${declared}`,\n\t\t\t});\n\t\t}\n\t\treturn;\n\t}\n\tconst type = mediaType(header);\n\tconst media = match(body.content, type);\n\tif (!media) {\n\t\tissues.push({\n\t\t\ttarget,\n\t\t\tpath: [],\n\t\t\tcode: 'invalid_content_type',\n\t\t\tmessage: `Content-Type ${type} is not one of ${declared}`,\n\t\t});\n\t\treturn;\n\t}\n\tswitch (media.kind) {\n\t\tcase 'json': {\n\t\t\tconst text = await read(c, () => c.req.text());\n\t\t\tif (text === '') return absent('json');\n\t\t\tlet value: unknown;\n\t\t\ttry {\n\t\t\t\tvalue = JSON.parse(text);\n\t\t\t} catch {\n\t\t\t\tissues.push({\n\t\t\t\t\ttarget: 'json',\n\t\t\t\t\tpath: [],\n\t\t\t\t\tcode: 'invalid_json',\n\t\t\t\t\tmessage: 'The request body is not valid JSON',\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (media.schema) check('json', media.schema, value);\n\t\t\telse c.req.addValidatedData('json', value as Validated);\n\t\t\treturn;\n\t\t}\n\t\tcase 'form': {\n\t\t\t// Read once, so parseBody takes Hono's cached copy of it.\n\t\t\tconst bytes = await read(c, () => c.req.arrayBuffer());\n\t\t\tif (bytes.byteLength === 0) return absent('form');\n\t\t\tlet value: Awaited<ReturnType<Context['req']['parseBody']>>;\n\t\t\ttry {\n\t\t\t\tvalue = await c.req.parseBody({ all: true });\n\t\t\t} catch {\n\t\t\t\tissues.push({\n\t\t\t\t\ttarget: 'form',\n\t\t\t\t\tpath: [],\n\t\t\t\t\tcode: 'invalid_form',\n\t\t\t\t\tmessage: `The request body is not a valid ${type} form`,\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (media.schema) check('form', media.schema, value);\n\t\t\telse c.req.addValidatedData('form', value);\n\t\t\treturn;\n\t\t}\n\t\tcase 'text': {\n\t\t\tconst text = await read(c, () => c.req.text());\n\t\t\tif (text === '') return absent('body');\n\t\t\tif (media.schema) check('body', media.schema, text);\n\t\t\treturn;\n\t\t}\n\t\tdefault:\n\t\t\t// Left unread, for the handler to stream; a length of 0 is no body.\n\t\t\tif (c.req.header('content-length') === '0') absent('body');\n\t\t\treturn;\n\t}\n}\n\n/** Reads the body through Hono's cache, which a read of `c.req.raw` bypasses. */\nasync function read<T>(c: Context, body: () => Promise<T>): Promise<T> {\n\ttry {\n\t\treturn await body();\n\t} catch (error) {\n\t\tif (!c.req.raw.bodyUsed) throw error;\n\t\tthrow new Error(\n\t\t\t'A middleware read the request body through c.req.raw before validation. Read it with c.req.json(), c.req.text() or c.req.arrayBuffer(), which Hono caches for the next reader',\n\t\t\t{ cause: error },\n\t\t);\n\t}\n}\n\nasync function fail(\n\tc: Context,\n\tsettings: Settings,\n\tfailure: ValidationFailure,\n): Promise<Response> {\n\tconst answer = await settings.onValidationError?.(failure, c);\n\treturn answer instanceof Response\n\t\t? answer\n\t\t: validationErrorHandler(failure, c);\n}\n\nfunction reply(\n\tid: string,\n\toperation: RuntimeOperation,\n\thandler: Handler,\n\tsettings: Settings,\n): MiddlewareHandler {\n\treturn async (c, next) => {\n\t\trunning.set(c, { id, operation, settings });\n\t\tconst response = await handler(c, next);\n\t\tif (!settings.validateResponses || !(response instanceof Response)) {\n\t\t\treturn response as Response | undefined;\n\t\t}\n\t\tconst issues = await checkReply(id, operation, response);\n\t\tif (issues.length === 0) return response;\n\t\treturn fail(c, settings, {\n\t\t\tkind: 'response',\n\t\t\toperationId: id,\n\t\t\tmethod: operation.method,\n\t\t\tpath: operation.path,\n\t\t\tstatus: response.status,\n\t\t\tissues,\n\t\t});\n\t};\n}\n\n/** Bodies that may never end: reading one to check it would hang the reply. */\nconst STREAMS = new Set([\n\t'text/event-stream',\n\t'application/x-ndjson',\n\t'application/jsonl',\n\t'application/json-seq',\n]);\n\n/**\n * What `c.json()` and `c.text()` send, `application/json` and `text/plain`,\n * stands for a declared media type of the same kind: `application/problem+json`,\n * `text/csv`.\n */\nfunction sentBy(\n\tcontent: { readonly [mediaType: string]: RuntimeMedia },\n\ttype: string,\n): RuntimeMedia | undefined {\n\tconst kind =\n\t\ttype === 'application/json'\n\t\t\t? 'json'\n\t\t\t: type === 'text/plain'\n\t\t\t\t? 'text'\n\t\t\t\t: undefined;\n\treturn Object.values(content).find((media) => media.kind === kind);\n}\n\nasync function checkReply(\n\tid: string,\n\toperation: RuntimeOperation,\n\tresponse: Response,\n): Promise<ValidationIssue[]> {\n\tconst issue = (code: string, message: string): ValidationIssue[] => [\n\t\t{ target: 'response', path: [], code, message },\n\t];\n\tconst declared = operation.responses[response.status];\n\tif (!declared) {\n\t\treturn issue(\n\t\t\t'undeclared_status',\n\t\t\t`${id} declares no ${response.status} reply`,\n\t\t);\n\t}\n\tconst types = Object.keys(declared);\n\tif (types.length === 0) return [];\n\tconst header = response.headers.get('content-type');\n\tconst type = header === null ? undefined : mediaType(header);\n\tconst media =\n\t\ttype === undefined\n\t\t\t? undefined\n\t\t\t: (match(declared, type) ?? sentBy(declared, type));\n\tif (!media) {\n\t\treturn issue(\n\t\t\t'invalid_content_type',\n\t\t\t`A ${response.status} reply of ${id} is ${types.join(' or ')}, not ${header ?? 'untyped'}`,\n\t\t);\n\t}\n\tif (\n\t\t!media.schema ||\n\t\t(media.kind !== 'json' && media.kind !== 'text') ||\n\t\tSTREAMS.has(type ?? '')\n\t) {\n\t\treturn [];\n\t}\n\tconst text = await response.clone().text();\n\tlet value: unknown = text;\n\tif (media.kind === 'json') {\n\t\ttry {\n\t\t\tvalue = JSON.parse(text);\n\t\t} catch {\n\t\t\treturn issue('invalid_json', 'The reply body is not valid JSON');\n\t\t}\n\t}\n\tconst result = media.schema.safeParse(value);\n\treturn result.success ? [] : toIssues('response', result.error.issues);\n}\n",
8
+ "/**\n * `streamEvents()` and `streamLines()`: a reply sent an item at a time, as\n * the spec's `itemSchema` describes it, from the handler of the operation's\n * route. `hono.ts` binds them to the spec, so each item is typed by its\n * operation; with `validateResponses`, each is checked before it is sent.\n *\n * The body is written here rather than with `hono/streaming`, so that the\n * package still imports `hono` for types only.\n */\nimport type { Context } from 'hono';\nimport type { ContentfulStatusCode } from 'hono/utils/http-status';\nimport { type RunningRoute, runningRoute, type Validator } from './engine';\nimport { toIssues, type ValidationFailure } from './errors';\n\ninterface StreamWriter {\n\t/** Resolves after `ms` milliseconds: a pause between two items. */\n\tsleep(ms: number): Promise<void>;\n\t/** Whether the client went away: stop writing then. */\n\treadonly aborted: boolean;\n\t/** Runs when the client goes away. */\n\tonAbort(listener: () => void | Promise<void>): void;\n}\n\nexport interface EventWriter<Event> extends StreamWriter {\n\t/** Sends an event: its data as JSON when the spec says it is JSON, as text otherwise. */\n\twrite(event: Event): Promise<void>;\n}\n\nexport interface LineWriter<Item> extends StreamWriter {\n\t/** Sends an item, as a line of JSON. */\n\twrite(item: Item): Promise<void>;\n}\n\n/** What any event holds, whatever the spec: `hono.ts` narrows it per operation. */\ninterface OutgoingEvent {\n\treadonly event?: string;\n\treadonly data: unknown;\n\treadonly id?: string;\n\treadonly retry?: number;\n}\n\n/** A JSON text sequence's record separator, written before each record. */\nconst RS = String.fromCharCode(0x1e);\n\n/** The route running `c`, and its 2xx reply of `kind`: its status, media type and validators. */\nfunction streamOf(c: Context, id: string, kind: 'sse' | 'jsonl') {\n\tconst route = runningRoute(c);\n\tif (route?.id !== id) {\n\t\tthrow new Error(\n\t\t\t`${id}: stream its reply from the handler of its own route, registered with routes`,\n\t\t);\n\t}\n\tfor (const [status, content] of Object.entries(route.operation.responses)) {\n\t\tconst code = Number(status);\n\t\tif (code < 200 || code > 299) continue;\n\t\tfor (const [type, media] of Object.entries(content)) {\n\t\t\tif (media.kind === kind) return { route, code, type, media };\n\t\t}\n\t}\n\tthrow new Error(\n\t\t`${id} does not reply with ${kind === 'sse' ? 'server-sent events' : 'JSON lines'}`,\n\t);\n}\n\n/**\n * A streamed reply: `run` writes text to it until it returns, and it ends\n * then. A throw ends it too, and goes to `console.error`, since the status\n * went out with the first item. `aborted` turns true when the client cancels.\n */\nfunction streamed(\n\tc: Context,\n\tstatus: number,\n\theaders: Record<string, string>,\n\trun: (\n\t\tsend: (text: string) => Promise<void>,\n\t\tcontrols: StreamWriter,\n\t) => Promise<void>,\n): Response {\n\tconst { readable, writable } = new TransformStream<Uint8Array, Uint8Array>();\n\tconst writer = writable.getWriter();\n\tconst encoder = new TextEncoder();\n\tconst listeners: (() => void | Promise<void>)[] = [];\n\tlet aborted = false;\n\tconst abort = () => {\n\t\tif (aborted) return;\n\t\taborted = true;\n\t\tfor (const listener of listeners) void listener();\n\t};\n\t// A cancelled body errors the writable side.\n\twriter.closed.catch(abort);\n\tc.req.raw.signal?.addEventListener('abort', abort);\n\tconst controls: StreamWriter = {\n\t\tsleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),\n\t\tget aborted() {\n\t\t\treturn aborted;\n\t\t},\n\t\tonAbort: (listener) => {\n\t\t\tlisteners.push(listener);\n\t\t},\n\t};\n\tconst send = async (text: string) => {\n\t\tif (aborted) return;\n\t\tawait writer.write(encoder.encode(text)).catch(abort);\n\t};\n\tvoid (async () => {\n\t\ttry {\n\t\t\tawait run(send, controls);\n\t\t} catch (error) {\n\t\t\tconsole.error(error);\n\t\t} finally {\n\t\t\tawait writer.close().catch(() => {});\n\t\t}\n\t})();\n\treturn c.body(readable, status as ContentfulStatusCode, headers);\n}\n\n/**\n * With `validateResponses`, refuses an item its validator does not accept,\n * read back from the JSON it is sent as. The status is sent already, so the\n * failure goes to `onValidationError` for its side effects, and the stream\n * ends with an error.\n */\nasync function check(\n\tc: Context,\n\troute: RunningRoute,\n\tstatus: number,\n\tvalidator: Validator | null | undefined,\n\tjson: string,\n\twhat: string,\n): Promise<void> {\n\tif (!route.settings.validateResponses || !validator) return;\n\tconst result = validator.safeParse(JSON.parse(json));\n\tif (result.success) return;\n\tconst { method, path } = route.operation;\n\tconst failure: ValidationFailure = {\n\t\tkind: 'response',\n\t\toperationId: route.id,\n\t\tmethod,\n\t\tpath,\n\t\tstatus,\n\t\tissues: toIssues('response', result.error.issues),\n\t};\n\tawait route.settings.onValidationError?.(failure, c);\n\tthrow new Error(\n\t\t`${route.id} (${method.toUpperCase()} ${path}) streamed ${what} the spec does not declare; the stream ends`,\n\t\t{ cause: failure },\n\t);\n}\n\n/** An event as the stream carries it: its fields, a `data:` per line, then a blank line. */\nfunction frame(event: OutgoingEvent, data: string): string {\n\tfor (const [field, value] of [\n\t\t['event', event.event],\n\t\t['id', event.id],\n\t] as const) {\n\t\tif (value !== undefined && /[\\r\\n]/.test(value)) {\n\t\t\tthrow new Error(`An event's \\`${field}\\` cannot hold a line break`);\n\t\t}\n\t}\n\tconst lines = [\n\t\tevent.event === undefined ? undefined : `event: ${event.event}`,\n\t\t...data.split(/\\r\\n|\\r|\\n/).map((line) => `data: ${line}`),\n\t\tevent.id === undefined ? undefined : `id: ${event.id}`,\n\t\tevent.retry === undefined ? undefined : `retry: ${event.retry}`,\n\t];\n\treturn `${lines.filter((line) => line !== undefined).join('\\n')}\\n\\n`;\n}\n\n/**\n * Replies with server-sent events, from the handler of operation `id`. An\n * event's data is sent as JSON when the spec declares it JSON, and as text\n * otherwise; an event the spec does not declare throws.\n */\nexport function streamEvents<Event>(\n\tc: Context,\n\tid: string,\n\twrite: (stream: EventWriter<Event>) => Promise<void>,\n): Response {\n\tconst { route, code, media } = streamOf(c, id, 'sse');\n\tconst headers = {\n\t\t'content-type': 'text/event-stream',\n\t\t'cache-control': 'no-cache',\n\t};\n\treturn streamed(c, code, headers, (send, controls) =>\n\t\twrite({\n\t\t\t...controls,\n\t\t\tget aborted() {\n\t\t\t\treturn controls.aborted;\n\t\t\t},\n\t\t\tasync write(given) {\n\t\t\t\tconst event = given as OutgoingEvent;\n\t\t\t\tconst name = event.event ?? 'message';\n\t\t\t\tconst validator = media.events?.[name];\n\t\t\t\tif (media.events && validator === undefined) {\n\t\t\t\t\tthrow new Error(`${id} declares no \\`${name}\\` event`);\n\t\t\t\t}\n\t\t\t\tconst data = validator\n\t\t\t\t\t? JSON.stringify(event.data)\n\t\t\t\t\t: String(event.data);\n\t\t\t\tawait check(c, route, code, validator, data, `a \\`${name}\\` event`);\n\t\t\t\tawait send(frame(event, data));\n\t\t\t},\n\t\t}),\n\t);\n}\n\n/**\n * Replies with JSON lines, from the handler of operation `id`: one JSON text\n * a line, as the media type the spec declares, after a record separator for\n * `application/json-seq`.\n */\nexport function streamLines<Item>(\n\tc: Context,\n\tid: string,\n\twrite: (stream: LineWriter<Item>) => Promise<void>,\n): Response {\n\tconst { route, code, type, media } = streamOf(c, id, 'jsonl');\n\tconst prefix = type.toLowerCase().startsWith('application/json-seq')\n\t\t? RS\n\t\t: '';\n\treturn streamed(c, code, { 'content-type': type }, (send, controls) =>\n\t\twrite({\n\t\t\t...controls,\n\t\t\tget aborted() {\n\t\t\t\treturn controls.aborted;\n\t\t\t},\n\t\t\tasync write(item) {\n\t\t\t\tconst json = JSON.stringify(item);\n\t\t\t\tawait check(c, route, code, media.item, json, 'a line');\n\t\t\t\tawait send(`${prefix}${json}\\n`);\n\t\t\t},\n\t\t}),\n\t);\n}\n"
9
+ ],
10
+ "mappings": "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",
11
+ "debugId": "BB964DB307280C7064756E2164756E21",
12
+ "names": []
13
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Why `routes` cannot register an operation on Hono, if it cannot: the
3
+ * runtime refuses the route. `@nxgt/openapi-codegen` keeps a deliberate copy
4
+ * in `src/emit/routable.ts`, and warns at generation. Change both together.
5
+ */
6
+ export declare function unroutable(operation: {
7
+ readonly method: string;
8
+ readonly path: string;
9
+ }): string | undefined;
10
+ //# sourceMappingURL=routable.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routable.d.ts","sourceRoot":"","sources":["../src/routable.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,SAAS,EAAE;IACrC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACtB,GAAG,MAAM,GAAG,SAAS,CAWrB"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * `streamEvents()` and `streamLines()`: a reply sent an item at a time, as
3
+ * the spec's `itemSchema` describes it, from the handler of the operation's
4
+ * route. `hono.ts` binds them to the spec, so each item is typed by its
5
+ * operation; with `validateResponses`, each is checked before it is sent.
6
+ *
7
+ * The body is written here rather than with `hono/streaming`, so that the
8
+ * package still imports `hono` for types only.
9
+ */
10
+ import type { Context } from 'hono';
11
+ interface StreamWriter {
12
+ /** Resolves after `ms` milliseconds: a pause between two items. */
13
+ sleep(ms: number): Promise<void>;
14
+ /** Whether the client went away: stop writing then. */
15
+ readonly aborted: boolean;
16
+ /** Runs when the client goes away. */
17
+ onAbort(listener: () => void | Promise<void>): void;
18
+ }
19
+ export interface EventWriter<Event> extends StreamWriter {
20
+ /** Sends an event: its data as JSON when the spec says it is JSON, as text otherwise. */
21
+ write(event: Event): Promise<void>;
22
+ }
23
+ export interface LineWriter<Item> extends StreamWriter {
24
+ /** Sends an item, as a line of JSON. */
25
+ write(item: Item): Promise<void>;
26
+ }
27
+ /**
28
+ * Replies with server-sent events, from the handler of operation `id`. An
29
+ * event's data is sent as JSON when the spec declares it JSON, and as text
30
+ * otherwise; an event the spec does not declare throws.
31
+ */
32
+ export declare function streamEvents<Event>(c: Context, id: string, write: (stream: EventWriter<Event>) => Promise<void>): Response;
33
+ /**
34
+ * Replies with JSON lines, from the handler of operation `id`: one JSON text
35
+ * a line, as the media type the spec declares, after a record separator for
36
+ * `application/json-seq`.
37
+ */
38
+ export declare function streamLines<Item>(c: Context, id: string, write: (stream: LineWriter<Item>) => Promise<void>): Response;
39
+ export {};
40
+ //# sourceMappingURL=streams.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"streams.d.ts","sourceRoot":"","sources":["../src/streams.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAKpC,UAAU,YAAY;IACrB,mEAAmE;IACnE,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACjC,uDAAuD;IACvD,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,sCAAsC;IACtC,OAAO,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CACpD;AAED,MAAM,WAAW,WAAW,CAAC,KAAK,CAAE,SAAQ,YAAY;IACvD,yFAAyF;IACzF,KAAK,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACnC;AAED,MAAM,WAAW,UAAU,CAAC,IAAI,CAAE,SAAQ,YAAY;IACrD,wCAAwC;IACxC,KAAK,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACjC;AAyID;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,KAAK,EACjC,CAAC,EAAE,OAAO,EACV,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,CAAC,MAAM,EAAE,WAAW,CAAC,KAAK,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,GAClD,QAAQ,CA2BV;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAC/B,CAAC,EAAE,OAAO,EACV,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,CAAC,MAAM,EAAE,UAAU,CAAC,IAAI,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,GAChD,QAAQ,CAkBV"}
@@ -0,0 +1,103 @@
1
+ /**
2
+ * The types of `routes`. Every lookup is an index into the interfaces the
3
+ * generator wrote, never a conditional type, so a route costs TypeScript the
4
+ * same whether the spec has five operations or five hundred.
5
+ */
6
+ import type { Context, Hono, MiddlewareHandler, Next } from 'hono';
7
+ import type { ValidationErrorHook } from './errors';
8
+ export type Method = 'get' | 'put' | 'post' | 'delete' | 'options' | 'head' | 'patch' | 'trace' | 'query';
9
+ type ByMethod = {
10
+ [M in Method]: string;
11
+ };
12
+ /** What `HonoSpec` in `hono.ts` gathers. */
13
+ export interface ApiSpec {
14
+ /** `Operations`, from `types.ts`, keyed by `operationId`. */
15
+ operations: object;
16
+ /** `Replies`, from `hono.ts`, keyed by `operationId`. */
17
+ replies: object;
18
+ /** `OperationsByRoute`: `'put /employees/{id}'` to its `operationId`. */
19
+ routes: object;
20
+ /** `PathsByMethod`. */
21
+ paths: ByMethod;
22
+ /** `OperationsByTag`. */
23
+ tags: object;
24
+ /** `PathsByTag`. */
25
+ tagPaths: object;
26
+ }
27
+ /** The operations a `routes` offers: every one, or one tag's. */
28
+ export interface Scope {
29
+ ids: string;
30
+ paths: ByMethod;
31
+ }
32
+ export type Whole<S extends ApiSpec> = {
33
+ ids: keyof S['operations'] & string;
34
+ paths: S['paths'];
35
+ };
36
+ export type Tagged<S extends ApiSpec, T extends string> = {
37
+ ids: S['tags'][T & keyof S['tags']] & string;
38
+ paths: S['tagPaths'][T & keyof S['tagPaths']] & ByMethod;
39
+ };
40
+ /** Evaluated once per `routes()`, never per route. */
41
+ export type ScopeOf<S extends ApiSpec, Tag extends string> = [Tag] extends [
42
+ never
43
+ ] ? Whole<S> : Tagged<S, Tag>;
44
+ type Entry<S extends ApiSpec, Id extends string> = S['operations'][Id & keyof S['operations']];
45
+ type Reply<S extends ApiSpec, Id extends string> = S['replies'][Id & keyof S['replies']];
46
+ /**
47
+ * The handler of operation `Id`: `c.req.valid()` holds its validated
48
+ * parameters and body, and it returns one of the replies the spec declares.
49
+ */
50
+ export type RouteHandler<S extends ApiSpec, Id extends string> = (c: Context<any, (Entry<S, Id> & {
51
+ honoPath: string;
52
+ })['honoPath'], {
53
+ out: Entry<S, Id> & {};
54
+ }>, next: Next) => Reply<S, Id> | Promise<Reply<S, Id>>;
55
+ /** Middlewares, then the handler. */
56
+ export type Chain<S extends ApiSpec, Id extends string> = [
57
+ ...MiddlewareHandler[],
58
+ RouteHandler<S, Id>
59
+ ];
60
+ export interface ApiOptions {
61
+ /** Answers a request the spec refuses. Default `validationErrorHandler`: a 400 listing the issues. */
62
+ onValidationError?: ValidationErrorHook;
63
+ /**
64
+ * Checks every reply against the spec: its status, its content type and,
65
+ * for JSON and text, its body. A reply that fails goes to
66
+ * `onValidationError`, as a 500 by default. For development and tests: it
67
+ * reads every reply body twice.
68
+ */
69
+ validateResponses?: boolean;
70
+ }
71
+ export interface RoutesOptions<Prefix extends string = '', Tag extends string = never> extends ApiOptions {
72
+ /**
73
+ * Where the app is mounted, as the spec writes it: `/employees`. Routes are
74
+ * registered relative to it, and only paths under it are offered.
75
+ */
76
+ prefix?: Prefix;
77
+ /** Offers only the operations with this tag. */
78
+ tag?: Tag;
79
+ }
80
+ export type Routes<S extends ApiSpec, Sc extends Scope = Whole<S>, Prefix extends string = ''> = {
81
+ readonly [M in Method]: <P extends Sc['paths'][M] & `${Prefix}${string}`>(path: P, ...chain: Chain<S, S['routes'][`${M} ${P}` & keyof S['routes']] & string>) => Routes<S, Sc, Prefix>;
82
+ } & {
83
+ /** Registers an operation by its `operationId`. */
84
+ operation<Id extends Sc['ids']>(id: Id, ...chain: Chain<S, Id>): Routes<S, Sc, Prefix>;
85
+ /**
86
+ * Marks where in a chain the request is validated, when a middleware
87
+ * needs validated input: `routes.put(path, auth, routes.validate, check, handler)`.
88
+ * Without it, validation runs after every middleware.
89
+ */
90
+ readonly validate: MiddlewareHandler;
91
+ /** The same routes, with other options for the routes registered through it. */
92
+ with(options: ApiOptions): Routes<S, Sc, Prefix>;
93
+ };
94
+ export interface Api<S extends ApiSpec> {
95
+ /** Registers routes on `app`, which may be a module's sub-app. */
96
+ routes<Prefix extends string = '', Tag extends keyof S['tags'] & string = never>(app: Hono<any, any, any>, options?: RoutesOptions<Prefix, Tag>): Routes<S, ScopeOf<S, Tag>, Prefix>;
97
+ /** The `operationId`s no route was registered for, of one tag or of all. */
98
+ missing(tag?: keyof S['tags'] & string): string[];
99
+ /** Throws, listing them, when an operation of the tag, or of the spec, has no route. */
100
+ assertComplete(tag?: keyof S['tags'] & string): void;
101
+ }
102
+ export {};
103
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,166 @@
1
+ # Hono runtime
2
+
3
+ `@nxgt/openapi-hono` is what the generated `hono.ts` binds to its spec. It
4
+ imports `hono` for types only, so it loads whether or not `hono` is
5
+ installed; `hono` is its peer because the types need it.
6
+
7
+ | File | Holds |
8
+ | --- | --- |
9
+ | `engine.ts` | `createApi`: registration, request validation, reply checks |
10
+ | `streams.ts` | `streamEvents` and `streamLines`: a reply written an item at a time |
11
+ | `errors.ts` | issues, failures, `validationErrorHandler` |
12
+ | `types.ts` | the types of `routes`: `ApiSpec`, `Routes`, `Scope`, `RouteHandler` |
13
+ | `routable.ts` | `unroutable`: why Hono cannot route an operation; `@nxgt/openapi-codegen` keeps a copy, to warn at generation |
14
+ | `index.ts` | the package's exports, named one by one |
15
+
16
+ ## Registration
17
+
18
+ `createApi(table)` builds an index from `'<method> <path>'` to
19
+ `operationId`, and returns the `Api`. `api.routes(app, options)` returns a
20
+ routes object:
21
+ - a function per HTTP method;
22
+ - `operation`;
23
+ - the `validate` marker;
24
+ - `with`.
25
+
26
+ Each registration:
27
+
28
+ 1. **Finds the operation**, by route or by `operationId`, and throws when
29
+ there is none.
30
+ 2. **Checks the registration**, and throws at startup when it is wrong:
31
+ - the operation must not already have a route;
32
+ - it must carry the `tag`;
33
+ - its path must sit under the `prefix`;
34
+ - the last argument must be the handler;
35
+ - `routes.validate` may appear at most once;
36
+ - the route must not be shadowed.
37
+ 3. **Registers `[...before, validator, ...after, reply]`** with
38
+ `app.on(METHOD, path)`. `before` is every middleware, or those in front
39
+ of `routes.validate`. `path` is the Hono path minus the prefix.
40
+
41
+ **Shadowing.** A route is shadowed when a route registered earlier on the
42
+ same app, for the same method, has the same number of segments and matches
43
+ it segment for segment, where a `:param` matches anything. Hono tries
44
+ routes in registration order, so the later route would never run. The
45
+ engine keeps each app's routes in a `WeakMap` to check this.
46
+
47
+ ## Validation
48
+
49
+ The validator reads each target and runs its validator from the table:
50
+ - **`param`** is `c.req.param()`.
51
+ - **`query`** reads each declared parameter: `c.req.queries()` for an
52
+ exploded list, the one value split on commas for another list,
53
+ `c.req.query()` otherwise.
54
+ - **`header`** reads each declared header, keyed lowercased, and splits a
55
+ list on commas.
56
+ - **The body** is chosen by `Content-Type`: exact, then `type/*`, then
57
+ `*/*`.
58
+ - JSON is `c.req.text()` then `JSON.parse`, so a parse error is an
59
+ `invalid_json` issue rather than an exception.
60
+ - A form is `c.req.parseBody({ all: true })`, then `z<Operation>Form` for
61
+ a flat form, the form's own `z<Name>` otherwise, or nothing when it has
62
+ no schema.
63
+ - Text is validated as read.
64
+ - Binary is not read.
65
+
66
+ Hono caches the body under whichever reader ran first and converts it for
67
+ the others. This is why a middleware that already read it does not starve
68
+ the validator.
69
+
70
+ A target that passes goes to `c.req.addValidatedData(target, data)`, except
71
+ a text body, which Hono's `ValidationTargets` has no key for.
72
+ `c.req.valid()` is then Hono's own mechanism, typed by the route's `Input`.
73
+ Every target is checked before the validator answers, so one failure
74
+ carries every issue.
75
+
76
+ ## Failures
77
+
78
+ `fail()` calls `onValidationError`:
79
+ - a `Response` it returns is sent;
80
+ - whatever it throws reaches `app.onError`;
81
+ - otherwise `validationErrorHandler` answers: 400 for a request, 500 for a
82
+ reply.
83
+
84
+ The default answers directly, rather than throwing an `HTTPException`. An
85
+ app error handler that rebuilds the body from the error's message would
86
+ drop the issues.
87
+
88
+ With `validateResponses`, `reply` checks the handler's `Response`:
89
+ - the status must be declared;
90
+ - the `Content-Type` must be one of its media types, when it declares any;
91
+ - a JSON or text body is validated, on a clone.
92
+
93
+ The table holds exact statuses only: the IR drops `default` and `4XX`. An
94
+ operation declared only through those fails every reply as
95
+ `undeclared_status`, and its `Replies` entry is `Response`.
96
+
97
+ ## Streams
98
+
99
+ `reply` records the running route (`operationId`, table entry, settings) in
100
+ a `WeakMap` keyed by the request's `Context`, the same object through its
101
+ whole chain. `streamEvents(c, id, write)` and `streamLines(c, id, write)`
102
+ read it back:
103
+ - the `id` must be the running route's, so a handler cannot stream another
104
+ operation's reply;
105
+ - the stream is the operation's first 2xx reply of that kind, sent with its
106
+ status.
107
+
108
+ The body is a `TransformStream` written here, not with `hono/streaming`, so
109
+ the package still imports `hono` for types only. Events are framed as
110
+ `EventSource` reads them: `event:`, a `data:` per line, `id:`, `retry:`.
111
+ JSON lines get a record separator first under `application/json-seq`.
112
+
113
+ Each item is encoded, then, with `validateResponses`, parsed back and
114
+ checked by its validator from the table. A failing item is never sent. The
115
+ status is already out, so `onValidationError` is called for its side effects
116
+ only, and the stream ends with an error. A throw inside `write` goes to
117
+ `console.error`, and the stream ends. A cancelled body errors the writable
118
+ side, which sets `aborted` and runs the `onAbort` listeners.
119
+
120
+ The reply checks in `reply` skip a stream's body, since it may never end.
121
+
122
+ ## Types
123
+
124
+ `HonoSpec` gathers `Operations`, `Replies` and the indexes. A route costs
125
+ TypeScript two index lookups and no conditional type:
126
+ - `S['routes'][`${M} ${P}`]` gives the `operationId`;
127
+ - `S['operations'][Id]` and `S['replies'][Id]` give the handler's `Input`
128
+ and its return type.
129
+
130
+ A `Scope` narrows what `routes` offers:
131
+ - `Whole<S>` offers everything;
132
+ - `Tagged<S, Tag>` offers one tag's `operationId`s and paths, read from
133
+ `OperationsByTag` and `PathsByTag`.
134
+
135
+ With `dates: 'date'`, a reply body that holds a date is `Wire<T>`, from
136
+ `types.ts`. Hono types `c.json(x)` as `JSONParsed<typeof x>`, where a
137
+ `Date` is a `string`, so a reply typed with `T` itself would match nothing.
138
+ `Wire<T>` is the only conditional type in a reply, and appears only where a
139
+ date does.
140
+
141
+ `ScopeOf` is the one conditional type of `routes`. It is evaluated once per `routes()`
142
+ call.
143
+
144
+ `routes[method]` takes `...chain: [...MiddlewareHandler[], RouteHandler]`.
145
+ A rest tuple with the handler last is enough for TypeScript to type `c`
146
+ from context, so there are no overloads per middleware count.
147
+
148
+ ## Testing
149
+
150
+ `test/generate.ts` generates `@nxgt/openapi-codegen`'s fixture specs
151
+ (`split`, `query`, `kitchen-sink`, `dates`, `streams`) with `hono: true` into
152
+ `test/generated/`, which git ignores; the `test` and `typecheck` scripts run
153
+ it first. The generator is a `workspace:^` devDependency, read from its
154
+ build. The fixtures' `hono.ts` imports this package by name, which a
155
+ tsconfig `paths` entry points at `src/`, so the specs run the engine they
156
+ import rather than a build of it.
157
+
158
+ | What | Where | Checks |
159
+ | --- | --- | --- |
160
+ | routes | `src/engine.spec.ts` | the fixtures' routes on a real Hono app, through `app.request()`: validation, errors and hooks, QUERY, forms, reply checks, modules, registration mistakes |
161
+ | streams | `src/streams.spec.ts` | the `streams` fixture's events and lines on a real Hono app: framing, text and JSON data, checks with `validateResponses`, misuse, and what the helpers' types refuse |
162
+ | routes typing | `test/types/routes.ts` | what `routes` refuses: an undeclared status, a wrong body, a path without that method, an unknown `operationId`, a tag or a path outside the scope |
163
+ | routes cost | `src/perf.spec.ts` | `tsc --extendedDiagnostics` on 500 generated routes (`test/perf.ts`) stays under an instantiation budget |
164
+
165
+ The cost check is what holds the typing to a budget: a lookup that stops
166
+ being an index shows up there first.