@dotpress/adapter-hono 2.0.0-alpha.3 → 2.0.0-alpha.5
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/chunk-DWK6R675.js +16 -0
- package/dist/chunk-DWK6R675.js.map +1 -0
- package/dist/{chunk-4EA3O2OL.js → chunk-V62WHZ4B.js} +18 -5
- package/dist/chunk-V62WHZ4B.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -1
- package/dist/lambda.d.ts +23 -5
- package/dist/lambda.js +10 -4
- package/dist/lambda.js.map +1 -1
- package/dist/node.d.ts +2 -2
- package/dist/node.js +7 -1
- package/dist/node.js.map +1 -1
- package/package.json +4 -4
- package/dist/chunk-4EA3O2OL.js.map +0 -1
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// src/options.ts
|
|
2
|
+
var assertKnownOptions = (label, options, known, prefix = "") => {
|
|
3
|
+
if (typeof options !== "object" || options === null) {
|
|
4
|
+
throw new TypeError(`${label}: options must be an object.`);
|
|
5
|
+
}
|
|
6
|
+
for (const key of Object.keys(options)) {
|
|
7
|
+
if (!known.includes(key)) {
|
|
8
|
+
throw new TypeError(`${label}: unknown option "${prefix}${key}".`);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export {
|
|
14
|
+
assertKnownOptions
|
|
15
|
+
};
|
|
16
|
+
//# sourceMappingURL=chunk-DWK6R675.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/options.ts"],"sourcesContent":["/**\n * Throws a `TypeError` for a key of `options` that is not in `known`: a\n * misspelled option would otherwise be ignored without a word.\n */\nexport const assertKnownOptions = (\n label: string,\n options: object,\n known: readonly string[],\n prefix = ''\n): void => {\n if (typeof options !== 'object' || options === null) {\n throw new TypeError(`${label}: options must be an object.`)\n }\n for (const key of Object.keys(options)) {\n if (!known.includes(key)) {\n throw new TypeError(`${label}: unknown option \"${prefix}${key}\".`)\n }\n }\n}\n"],"mappings":";AAIO,IAAM,qBAAqB,CAChC,OACA,SACA,OACA,SAAS,OACA;AACT,MAAI,OAAO,YAAY,YAAY,YAAY,MAAM;AACnD,UAAM,IAAI,UAAU,GAAG,KAAK,8BAA8B;AAAA,EAC5D;AACA,aAAW,OAAO,OAAO,KAAK,OAAO,GAAG;AACtC,QAAI,CAAC,MAAM,SAAS,GAAG,GAAG;AACxB,YAAM,IAAI,UAAU,GAAG,KAAK,qBAAqB,MAAM,GAAG,GAAG,IAAI;AAAA,IACnE;AAAA,EACF;AACF;","names":[]}
|
|
@@ -1,14 +1,20 @@
|
|
|
1
|
+
import {
|
|
2
|
+
assertKnownOptions
|
|
3
|
+
} from "./chunk-DWK6R675.js";
|
|
4
|
+
|
|
1
5
|
// src/index.ts
|
|
6
|
+
import {
|
|
7
|
+
HttpError
|
|
8
|
+
} from "@dotpress/core";
|
|
2
9
|
import {
|
|
3
10
|
DEFAULT_BODY_LIMIT,
|
|
4
|
-
HttpError,
|
|
5
11
|
createRouteMatcher,
|
|
6
12
|
fromWebRequest,
|
|
7
13
|
parseRequestBody,
|
|
8
14
|
payloadTooLargeError,
|
|
9
15
|
readRequestBody,
|
|
10
16
|
toWebResponse
|
|
11
|
-
} from "@dotpress/core";
|
|
17
|
+
} from "@dotpress/core/adapter";
|
|
12
18
|
import { Hono } from "hono";
|
|
13
19
|
var runtimeIp = (c) => {
|
|
14
20
|
const env = c.env;
|
|
@@ -45,7 +51,7 @@ var fromNativeError = (error) => {
|
|
|
45
51
|
"CLIENT_ERROR",
|
|
46
52
|
"Client Error"
|
|
47
53
|
];
|
|
48
|
-
return new HttpError(status, code, message || defaultMessage,
|
|
54
|
+
return new HttpError(status, code, message || defaultMessage, {
|
|
49
55
|
cause: error
|
|
50
56
|
});
|
|
51
57
|
};
|
|
@@ -130,10 +136,17 @@ var track = (body, controller, streams) => {
|
|
|
130
136
|
});
|
|
131
137
|
};
|
|
132
138
|
function honoAdapter(options = {}) {
|
|
139
|
+
assertKnownOptions("honoAdapter()", options, [
|
|
140
|
+
"app",
|
|
141
|
+
"beforeRoutes",
|
|
142
|
+
"afterRoutes",
|
|
143
|
+
"bodyLimit",
|
|
144
|
+
"getIp"
|
|
145
|
+
]);
|
|
133
146
|
const bodyLimit = options.bodyLimit ?? DEFAULT_BODY_LIMIT;
|
|
134
147
|
if (!Number.isInteger(bodyLimit) || bodyLimit < 0) {
|
|
135
148
|
throw new RangeError(
|
|
136
|
-
"
|
|
149
|
+
"honoAdapter(): bodyLimit must be a non-negative integer (bytes)."
|
|
137
150
|
);
|
|
138
151
|
}
|
|
139
152
|
const application = options.app ?? new Hono();
|
|
@@ -209,4 +222,4 @@ function honoAdapter(options = {}) {
|
|
|
209
222
|
export {
|
|
210
223
|
honoAdapter
|
|
211
224
|
};
|
|
212
|
-
//# sourceMappingURL=chunk-
|
|
225
|
+
//# sourceMappingURL=chunk-V62WHZ4B.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import {\n HttpError,\n type HttpAdapter,\n type NormalizedRequest,\n type NormalizedResponse,\n} from '@dotpress/core'\nimport {\n DEFAULT_BODY_LIMIT,\n createRouteMatcher,\n fromWebRequest,\n parseRequestBody,\n payloadTooLargeError,\n readRequestBody,\n toWebResponse,\n type CompiledRoute,\n type DotpressRuntime,\n type RequestBody,\n type RouteMatch,\n} from '@dotpress/core/adapter'\nimport { Hono, type Context } from 'hono'\nimport { assertKnownOptions } from './options.js'\n\n/**\n * Minimal shape of a Hono application (Hono 4 or 5). The types of Hono 5\n * differ from those of Hono 4, hence the loose signatures.\n */\nexport interface HonoApplication {\n use: (...args: never[]) => unknown\n notFound: (...args: never[]) => unknown\n onError: (...args: never[]) => unknown\n fetch: (\n request: Request,\n env?: never,\n executionContext?: never\n ) => Response | Promise<Response>\n}\n\n/** Members of the Hono application used by the adapter. */\ninterface MountableApp {\n use(\n path: string,\n handler: (c: Context, next: () => Promise<void>) => Promise<Response | void>\n ): unknown\n notFound(handler: (c: Context) => Promise<Response>): unknown\n onError(handler: (error: Error, c: Context) => Promise<Response>): unknown\n fetch(\n request: Request,\n env?: unknown,\n executionContext?: unknown\n ): Response | Promise<Response>\n}\n\nexport interface HonoAdapterOptions<TApp extends HonoApplication = Hono> {\n /** Hono application to mount the routes on. Defaults to `new Hono()`. */\n app?: TApp\n /** Registers native middlewares before the Dotpress routes. */\n beforeRoutes?: (app: TApp) => void | Promise<void>\n /**\n * Registers native middlewares and routes after the Dotpress routes,\n * before the not-found handler.\n */\n afterRoutes?: (app: TApp) => void | Promise<void>\n /**\n * Maximum request body size, in bytes. Defaults to 1 MiB. Routes can\n * override it with their own `bodyLimit`.\n */\n bodyLimit?: number\n /**\n * Client IP address of a request (`ctx.req.ip`). Defaults to the address\n * given by the runtime: the socket on Node.js, the source IP of API\n * Gateway, the last `X-Forwarded-For` entry behind an Application Load\n * Balancer (added by the load balancer). Proxy headers are not read\n * otherwise: behind another proxy (CloudFront…), read them here.\n */\n getIp?: (c: Context) => string | undefined\n}\n\nexport interface HonoAdapter<\n TApp extends HonoApplication = Hono,\n> extends HttpAdapter<TApp> {\n /**\n * Handles a Web request, like `app.native().fetch()`: for runtimes that\n * take a `fetch` handler, and for tests.\n */\n fetch(\n request: Request,\n env?: unknown,\n executionContext?: unknown\n ): Promise<Response>\n /** Closes the responses being streamed (their `req.signal` is aborted). */\n close(): Promise<void>\n}\n\n/** Environment given by `@hono/node-server` and `@hono/aws-lambda`. */\ninterface RuntimeEnv {\n readonly incoming?: { readonly socket?: { readonly remoteAddress?: unknown } }\n readonly requestContext?: {\n readonly identity?: { readonly sourceIp?: unknown }\n readonly http?: { readonly sourceIp?: unknown }\n readonly elb?: unknown\n }\n}\n\n/**\n * Client address given by the runtime: the socket on Node.js, the source\n * IP of API Gateway (REST and HTTP APIs, Function URLs), or the address\n * appended to `X-Forwarded-For` by an Application Load Balancer.\n */\nconst runtimeIp = (c: Context): string | undefined => {\n const env = c.env as RuntimeEnv | undefined\n\n const socket = env?.incoming?.socket?.remoteAddress\n if (typeof socket === 'string') return socket\n\n const context = env?.requestContext\n const sourceIp = context?.identity?.sourceIp ?? context?.http?.sourceIp\n if (typeof sourceIp === 'string') return sourceIp\n\n if (context?.elb !== undefined) {\n const forwarded = c.req.header('x-forwarded-for')?.split(',').at(-1)\n return forwarded?.trim() || undefined\n }\n return undefined\n}\n\n/** Code and default message of client errors. */\nconst CLIENT_ERRORS: Record<number, readonly [string, string]> = {\n 400: ['BAD_REQUEST', 'Bad Request'],\n 401: ['UNAUTHORIZED', 'Unauthorized'],\n 403: ['FORBIDDEN', 'Forbidden'],\n 404: ['NOT_FOUND', 'Not Found'],\n 405: ['METHOD_NOT_ALLOWED', 'Method Not Allowed'],\n 408: ['REQUEST_TIMEOUT', 'Request Timeout'],\n 409: ['CONFLICT', 'Conflict'],\n 413: ['PAYLOAD_TOO_LARGE', 'Payload Too Large'],\n 415: ['UNSUPPORTED_MEDIA_TYPE', 'Unsupported Media Type'],\n 422: ['UNPROCESSABLE_ENTITY', 'Unprocessable Entity'],\n 429: ['TOO_MANY_REQUESTS', 'Too Many Requests'],\n}\n\ninterface HttpExceptionLike {\n readonly status: number\n readonly message: string\n readonly res?: Response\n getResponse(): Response\n}\n\n/** `HTTPException` of Hono 4 or 5, recognized by its shape. */\nconst isHttpException = (error: unknown): error is HttpExceptionLike =>\n error instanceof Error &&\n typeof (error as Partial<HttpExceptionLike>).status === 'number' &&\n typeof (error as Partial<HttpExceptionLike>).getResponse === 'function'\n\n/**\n * Converts the client errors (4xx) raised by native middlewares\n * (`HTTPException`) into Dotpress errors, with the same status. Anything\n * else is an unexpected error.\n */\nconst fromNativeError = (error: unknown): unknown => {\n if (!isHttpException(error)) return error\n\n const { status, message } = error\n if (!Number.isInteger(status) || status < 400 || status > 499) return error\n\n const [code, defaultMessage] = CLIENT_ERRORS[status] ?? [\n 'CLIENT_ERROR',\n 'Client Error',\n ]\n return new HttpError(status, code, message || defaultMessage, {\n cause: error,\n })\n}\n\n/**\n * Adds the headers of the custom response of an `HTTPException`\n * (`WWW-Authenticate`…) to the Dotpress error response.\n */\nconst addNativeHeaders = (\n response: NormalizedResponse,\n error: HttpExceptionLike\n) => {\n if (!error.res) return\n for (const [name, value] of error.res.headers) {\n if (name.startsWith('content-')) continue\n if (name === 'set-cookie') continue\n response.headers[name] = value\n }\n const cookies = error.res.headers.getSetCookie()\n if (cookies.length > 0) {\n const current = response.headers['set-cookie'] ?? []\n response.headers['set-cookie'] = [\n ...(Array.isArray(current) ? current : [current]),\n ...cookies,\n ]\n }\n}\n\nconst encoder = new TextEncoder()\n\n/**\n * Body already read by a native middleware, from the cache of Hono\n * (`c.req.json()`, `c.req.parseBody()`…). Only an `ArrayBuffer` or a\n * `Blob` keeps the exact bytes (`rawBody`).\n */\nconst cachedBody = async (c: Context, limit: number): Promise<RequestBody> => {\n const cache = c.req.bodyCache as Partial<\n Record<'arrayBuffer' | 'blob' | 'text' | 'formData', Promise<unknown>>\n >\n const contentType = c.req.header('content-type')\n\n let bytes: Uint8Array\n let exact = false\n let type = contentType\n\n if (cache.arrayBuffer) {\n bytes = new Uint8Array((await cache.arrayBuffer) as ArrayBuffer)\n exact = true\n } else if (cache.blob) {\n bytes = new Uint8Array(await ((await cache.blob) as Blob).arrayBuffer())\n exact = true\n } else if (cache.text) {\n bytes = encoder.encode((await cache.text) as string)\n } else if (cache.formData) {\n // Serialized again, with a new boundary.\n const response = new Response((await cache.formData) as FormData)\n bytes = new Uint8Array(await response.arrayBuffer())\n type = response.headers.get('content-type') ?? undefined\n } else {\n throw new Error(\n 'The request body was read by a native middleware outside the cache of Hono (c.req.raw): read it with c.req.json(), c.req.text() or c.req.arrayBuffer().'\n )\n }\n\n if (bytes.byteLength > limit) throw payloadTooLargeError()\n const parsed = await parseRequestBody(bytes, type)\n return { ...parsed, rawBody: exact ? bytes : undefined }\n}\n\n// Reading the body through Hono consumes the request.\nconst readBody = (c: Context, limit: number): Promise<RequestBody> =>\n c.req.raw.bodyUsed ? cachedBody(c, limit) : readRequestBody(c.req.raw, limit)\n\n/**\n * Closes the streamed body when the adapter is closed, and aborts the\n * request signal when the client cancels it.\n */\nconst track = (\n body: ReadableStream<Uint8Array>,\n controller: AbortController,\n streams: Set<() => void>\n): ReadableStream<Uint8Array> => {\n const reader = body.getReader()\n let stop: () => void = () => {}\n\n return new ReadableStream<Uint8Array>({\n start() {\n // The cancelled reader ends the stream (see `pull()`).\n stop = () => {\n streams.delete(stop)\n controller.abort()\n reader.cancel().catch(() => {})\n }\n streams.add(stop)\n },\n async pull(stream) {\n try {\n const { done, value } = await reader.read()\n if (done) {\n streams.delete(stop)\n stream.close()\n } else {\n stream.enqueue(value)\n }\n } catch (error) {\n streams.delete(stop)\n stream.error(error)\n }\n },\n cancel(reason) {\n streams.delete(stop)\n controller.abort(reason)\n return reader.cancel(reason)\n },\n })\n}\n\n/**\n * Creates the Hono adapter. The main entry is runtime-agnostic: serve\n * `adapter.fetch` with the runtime, or use `honoAdapter()` of\n * `@dotpress/adapter-hono/node` to listen on Node.js.\n *\n * @example\n * const adapter = honoAdapter()\n * await createApp({ adapter })\n * export default { fetch: adapter.fetch }\n */\nexport function honoAdapter(options?: HonoAdapterOptions<Hono>): HonoAdapter\nexport function honoAdapter<TApp extends HonoApplication>(\n options: HonoAdapterOptions<TApp> & { app: TApp }\n): HonoAdapter<TApp>\nexport function honoAdapter<TApp extends HonoApplication>(\n options: HonoAdapterOptions<TApp> = {}\n): HonoAdapter<TApp> {\n assertKnownOptions('honoAdapter()', options, [\n 'app',\n 'beforeRoutes',\n 'afterRoutes',\n 'bodyLimit',\n 'getIp',\n ] satisfies (keyof HonoAdapterOptions<TApp>)[])\n const bodyLimit = options.bodyLimit ?? DEFAULT_BODY_LIMIT\n\n if (!Number.isInteger(bodyLimit) || bodyLimit < 0) {\n throw new RangeError(\n 'honoAdapter(): bodyLimit must be a non-negative integer (bytes).'\n )\n }\n\n const application = options.app ?? (new Hono() as unknown as TApp)\n const app = application as unknown as MountableApp\n const getIp = options.getIp ?? runtimeIp\n let mounted = false\n /** Closes the responses being streamed, so that `close()` can complete. */\n const streams = new Set<() => void>()\n\n const normalize = (c: Context, signal?: AbortSignal): NormalizedRequest =>\n fromWebRequest(c.req.raw, { ip: getIp(c), signal, raw: c })\n\n const send = (\n c: Context,\n response: NormalizedResponse,\n controller?: AbortController\n ): Promise<Response> => {\n const { body } = response\n if (controller && body instanceof ReadableStream) {\n return toWebResponse(\n { ...response, body: track(body, controller, streams) },\n c.req.method\n )\n }\n return toWebResponse(response, c.req.method)\n }\n\n const handleRoute = async (\n c: Context,\n match: RouteMatch,\n runtime: DotpressRuntime\n ): Promise<Response> => {\n const controller = new AbortController()\n const signal = AbortSignal.any([c.req.raw.signal, controller.signal])\n const request = normalize(c, signal)\n\n try {\n const { body, files, rawBody } = await readBody(\n c,\n match.route.definition.bodyLimit ?? bodyLimit\n )\n request.body = body\n request.files = files\n request.rawBody = rawBody\n } catch (error) {\n return send(c, await runtime.handleError(error, request))\n }\n\n request.params = match.params\n return send(c, await match.route.execute(request), controller)\n }\n\n return {\n name: 'hono',\n\n mount: async (\n routes: readonly CompiledRoute[],\n runtime: DotpressRuntime\n ) => {\n if (mounted) throw new Error('The Hono adapter is already mounted.')\n mounted = true\n\n const matcher = createRouteMatcher(routes)\n\n await options.beforeRoutes?.(application)\n\n app.use('*', async (c, next) => {\n const match = matcher.match(c.req.method, new URL(c.req.url).pathname)\n if (!match) {\n await next()\n return\n }\n return handleRoute(c, match, runtime)\n })\n\n await options.afterRoutes?.(application)\n\n app.notFound(async (c) =>\n send(c, await runtime.handleNotFound(normalize(c)))\n )\n\n app.onError(async (error, c) => {\n const converted = fromNativeError(error)\n const response = await runtime.handleError(converted, normalize(c))\n if (converted !== error) {\n addNativeHeaders(response, error as unknown as HttpExceptionLike)\n }\n return send(c, response)\n })\n },\n\n fetch: async (request, env, executionContext) =>\n app.fetch(request, env, executionContext),\n\n close: async () => {\n for (const stop of [...streams]) stop()\n },\n\n native: () => application,\n }\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ interface HonoApplication {
|
|
|
11
11
|
onError: (...args: never[]) => unknown;
|
|
12
12
|
fetch: (request: Request, env?: never, executionContext?: never) => Response | Promise<Response>;
|
|
13
13
|
}
|
|
14
|
-
interface HonoAdapterOptions<TApp extends HonoApplication> {
|
|
14
|
+
interface HonoAdapterOptions<TApp extends HonoApplication = Hono> {
|
|
15
15
|
/** Hono application to mount the routes on. Defaults to `new Hono()`. */
|
|
16
16
|
app?: TApp;
|
|
17
17
|
/** Registers native middlewares before the Dotpress routes. */
|
package/dist/index.js
CHANGED
package/dist/lambda.d.ts
CHANGED
|
@@ -1,15 +1,33 @@
|
|
|
1
1
|
import { App, Logger } from '@dotpress/core';
|
|
2
|
-
import { LambdaEvent, LambdaContext, APIGatewayProxyResult } from '@hono/aws-lambda';
|
|
3
|
-
export { APIGatewayProxyResult, LambdaContext, LambdaEvent } from '@hono/aws-lambda';
|
|
4
2
|
import { HonoApplication } from './index.js';
|
|
5
3
|
import 'hono';
|
|
6
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Event of API Gateway REST APIs (payload v1), HTTP APIs and Function URLs
|
|
7
|
+
* (payload v2), or Application Load Balancers. Read by the handler only:
|
|
8
|
+
* the types of `@types/aws-lambda` fit.
|
|
9
|
+
*/
|
|
10
|
+
type LambdaEvent = object;
|
|
11
|
+
/** Context of the invocation, given by the Lambda runtime. */
|
|
12
|
+
type LambdaContext = object;
|
|
13
|
+
/** Result of the handler, in the format of the event. */
|
|
14
|
+
interface LambdaResult {
|
|
15
|
+
statusCode: number;
|
|
16
|
+
/** Application Load Balancers only. */
|
|
17
|
+
statusDescription?: string;
|
|
18
|
+
headers?: Record<string, string>;
|
|
19
|
+
multiValueHeaders?: Record<string, string[]>;
|
|
20
|
+
/** Payload v2 only. */
|
|
21
|
+
cookies?: string[];
|
|
22
|
+
body: string;
|
|
23
|
+
isBase64Encoded?: boolean;
|
|
24
|
+
}
|
|
7
25
|
interface LambdaHandlerOptions {
|
|
8
26
|
/** Logs the failures to create the app. Defaults to a console logger. */
|
|
9
27
|
logger?: Logger;
|
|
10
28
|
/**
|
|
11
29
|
* Whether a response content type is binary (sent in base64). Defaults to
|
|
12
|
-
*
|
|
30
|
+
* everything except text, JSON and XML.
|
|
13
31
|
*/
|
|
14
32
|
isContentTypeBinary?: (contentType: string) => boolean;
|
|
15
33
|
}
|
|
@@ -17,7 +35,7 @@ interface LambdaHandlerOptions {
|
|
|
17
35
|
* AWS Lambda handler: API Gateway REST (payload v1) and HTTP APIs (payload
|
|
18
36
|
* v2), Function URLs, Application Load Balancer.
|
|
19
37
|
*/
|
|
20
|
-
type LambdaHandler = (event: LambdaEvent, context?: LambdaContext) => Promise<
|
|
38
|
+
type LambdaHandler = (event: LambdaEvent, context?: LambdaContext) => Promise<LambdaResult>;
|
|
21
39
|
/** App of the factory, created with `honoAdapter()`. */
|
|
22
40
|
type LambdaApp = Pick<App<HonoApplication>, 'native'>;
|
|
23
41
|
/**
|
|
@@ -40,4 +58,4 @@ type LambdaApp = Pick<App<HonoApplication>, 'native'>;
|
|
|
40
58
|
declare const createLambdaHandler: (factory: () => LambdaApp | Promise<LambdaApp>, options?: LambdaHandlerOptions) => LambdaHandler;
|
|
41
59
|
|
|
42
60
|
export { createLambdaHandler };
|
|
43
|
-
export type { LambdaApp, LambdaHandler, LambdaHandlerOptions };
|
|
61
|
+
export type { LambdaApp, LambdaContext, LambdaEvent, LambdaHandler, LambdaHandlerOptions, LambdaResult };
|
package/dist/lambda.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
|
+
import {
|
|
2
|
+
assertKnownOptions
|
|
3
|
+
} from "./chunk-DWK6R675.js";
|
|
4
|
+
|
|
1
5
|
// src/lambda.ts
|
|
2
6
|
import {
|
|
3
7
|
createConsoleLogger,
|
|
4
8
|
internalError,
|
|
5
9
|
toErrorBody
|
|
6
10
|
} from "@dotpress/core";
|
|
7
|
-
import {
|
|
8
|
-
handle
|
|
9
|
-
} from "@hono/aws-lambda";
|
|
11
|
+
import { handle } from "@hono/aws-lambda";
|
|
10
12
|
import { Hono } from "hono";
|
|
11
13
|
var failureApp = () => new Hono().all(
|
|
12
14
|
"*",
|
|
@@ -16,6 +18,10 @@ var failureApp = () => new Hono().all(
|
|
|
16
18
|
})
|
|
17
19
|
);
|
|
18
20
|
var createLambdaHandler = (factory, options = {}) => {
|
|
21
|
+
assertKnownOptions("createLambdaHandler()", options, [
|
|
22
|
+
"logger",
|
|
23
|
+
"isContentTypeBinary"
|
|
24
|
+
]);
|
|
19
25
|
const logger = options.logger ?? createConsoleLogger();
|
|
20
26
|
const toHandler = (app) => handle(app, {
|
|
21
27
|
isContentTypeBinary: options.isContentTypeBinary
|
|
@@ -27,7 +33,7 @@ var createLambdaHandler = (factory, options = {}) => {
|
|
|
27
33
|
const native = app?.native?.();
|
|
28
34
|
if (typeof native?.fetch !== "function") {
|
|
29
35
|
throw new TypeError(
|
|
30
|
-
"createLambdaHandler()
|
|
36
|
+
"createLambdaHandler(): expected an app created with honoAdapter() of @dotpress/adapter-hono."
|
|
31
37
|
);
|
|
32
38
|
}
|
|
33
39
|
return toHandler(native);
|
package/dist/lambda.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/lambda.ts"],"sourcesContent":["import {\n createConsoleLogger,\n internalError,\n toErrorBody,\n type App,\n type Logger,\n} from '@dotpress/core'\nimport {
|
|
1
|
+
{"version":3,"sources":["../src/lambda.ts"],"sourcesContent":["import {\n createConsoleLogger,\n internalError,\n toErrorBody,\n type App,\n type Logger,\n} from '@dotpress/core'\nimport { handle } from '@hono/aws-lambda'\nimport { Hono } from 'hono'\nimport type { HonoApplication } from './index.js'\nimport { assertKnownOptions } from './options.js'\n\n// The Lambda types are the same in @dotpress/adapter-hono/lambda and\n// @dotpress/adapter-fastify/lambda: keep them in sync.\n/**\n * Event of API Gateway REST APIs (payload v1), HTTP APIs and Function URLs\n * (payload v2), or Application Load Balancers. Read by the handler only:\n * the types of `@types/aws-lambda` fit.\n */\nexport type LambdaEvent = object\n\n/** Context of the invocation, given by the Lambda runtime. */\nexport type LambdaContext = object\n\n/** Result of the handler, in the format of the event. */\nexport interface LambdaResult {\n statusCode: number\n /** Application Load Balancers only. */\n statusDescription?: string\n headers?: Record<string, string>\n multiValueHeaders?: Record<string, string[]>\n /** Payload v2 only. */\n cookies?: string[]\n body: string\n isBase64Encoded?: boolean\n}\n\nexport interface LambdaHandlerOptions {\n /** Logs the failures to create the app. Defaults to a console logger. */\n logger?: Logger\n /**\n * Whether a response content type is binary (sent in base64). Defaults to\n * everything except text, JSON and XML.\n */\n isContentTypeBinary?: (contentType: string) => boolean\n}\n\n/**\n * AWS Lambda handler: API Gateway REST (payload v1) and HTTP APIs (payload\n * v2), Function URLs, Application Load Balancer.\n */\nexport type LambdaHandler = (\n event: LambdaEvent,\n context?: LambdaContext\n) => Promise<LambdaResult>\n\n/** App of the factory, created with `honoAdapter()`. */\nexport type LambdaApp = Pick<App<HonoApplication>, 'native'>\n\n/** Answers `500 INTERNAL_ERROR` when the app cannot be created. */\nconst failureApp = () =>\n new Hono().all(\n '*',\n () =>\n new Response(JSON.stringify(toErrorBody(internalError())), {\n status: 500,\n headers: { 'content-type': 'application/json; charset=utf-8' },\n })\n )\n\n/**\n * Creates the AWS Lambda handler of an app (`@hono/aws-lambda`).\n *\n * The app is created by the factory at the first invocation (cold start),\n * then reused by the next invocations of the execution environment.\n * Invocations received meanwhile wait for the same app. When the factory\n * fails, the error is logged, the invocation gets a `500` error, and the\n * next invocation calls the factory again.\n *\n * Responses are sent once complete: streamed responses (`stream()`) are\n * buffered, and Server-Sent Events are not supported.\n *\n * @example\n * export const handler = createLambdaHandler(() =>\n * createApp({ adapter: honoAdapter(), plugins: [authPlugin(auth)] })\n * )\n */\nexport const createLambdaHandler = (\n factory: () => LambdaApp | Promise<LambdaApp>,\n options: LambdaHandlerOptions = {}\n): LambdaHandler => {\n assertKnownOptions('createLambdaHandler()', options, [\n 'logger',\n 'isContentTypeBinary',\n ] satisfies (keyof LambdaHandlerOptions)[])\n const logger = options.logger ?? createConsoleLogger()\n const toHandler = (app: HonoApplication) =>\n handle(app as unknown as Hono, {\n isContentTypeBinary: options.isContentTypeBinary,\n }) as unknown as LambdaHandler\n\n let ready: Promise<LambdaHandler> | undefined\n let failure: LambdaHandler | undefined\n\n const init = async (): Promise<LambdaHandler> => {\n const app = await factory()\n const native: unknown = app?.native?.()\n if (typeof (native as Partial<HonoApplication>)?.fetch !== 'function') {\n throw new TypeError(\n 'createLambdaHandler(): expected an app created with honoAdapter() of @dotpress/adapter-hono.'\n )\n }\n return toHandler(native as HonoApplication)\n }\n\n return async (event, context) => {\n const current = (ready ??= init())\n let handler: LambdaHandler\n try {\n handler = await current\n } catch (error) {\n // Retried at the next invocation.\n if (ready === current) ready = undefined\n logger.error(\n error instanceof Error ? error : { error },\n 'Failed to create the app'\n )\n failure ??= toHandler(failureApp() as unknown as HonoApplication)\n return failure(event, context)\n }\n return handler(event, context)\n }\n}\n"],"mappings":";;;;;AAAA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AACP,SAAS,cAAc;AACvB,SAAS,YAAY;AAoDrB,IAAM,aAAa,MACjB,IAAI,KAAK,EAAE;AAAA,EACT;AAAA,EACA,MACE,IAAI,SAAS,KAAK,UAAU,YAAY,cAAc,CAAC,CAAC,GAAG;AAAA,IACzD,QAAQ;AAAA,IACR,SAAS,EAAE,gBAAgB,kCAAkC;AAAA,EAC/D,CAAC;AACL;AAmBK,IAAM,sBAAsB,CACjC,SACA,UAAgC,CAAC,MACf;AAClB,qBAAmB,yBAAyB,SAAS;AAAA,IACnD;AAAA,IACA;AAAA,EACF,CAA0C;AAC1C,QAAM,SAAS,QAAQ,UAAU,oBAAoB;AACrD,QAAM,YAAY,CAAC,QACjB,OAAO,KAAwB;AAAA,IAC7B,qBAAqB,QAAQ;AAAA,EAC/B,CAAC;AAEH,MAAI;AACJ,MAAI;AAEJ,QAAM,OAAO,YAAoC;AAC/C,UAAM,MAAM,MAAM,QAAQ;AAC1B,UAAM,SAAkB,KAAK,SAAS;AACtC,QAAI,OAAQ,QAAqC,UAAU,YAAY;AACrE,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AACA,WAAO,UAAU,MAAyB;AAAA,EAC5C;AAEA,SAAO,OAAO,OAAO,YAAY;AAC/B,UAAM,UAAW,UAAU,KAAK;AAChC,QAAI;AACJ,QAAI;AACF,gBAAU,MAAM;AAAA,IAClB,SAAS,OAAO;AAEd,UAAI,UAAU,QAAS,SAAQ;AAC/B,aAAO;AAAA,QACL,iBAAiB,QAAQ,QAAQ,EAAE,MAAM;AAAA,QACzC;AAAA,MACF;AACA,kBAAY,UAAU,WAAW,CAA+B;AAChE,aAAO,QAAQ,OAAO,OAAO;AAAA,IAC/B;AACA,WAAO,QAAQ,OAAO,OAAO;AAAA,EAC/B;AACF;","names":[]}
|
package/dist/node.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { Server } from 'node:http';
|
|
2
|
+
import { ListenAddress } from '@dotpress/core';
|
|
2
3
|
import { Hono } from 'hono';
|
|
3
4
|
import { HonoApplication, HonoAdapter, HonoAdapterOptions } from './index.js';
|
|
4
|
-
import '@dotpress/core';
|
|
5
5
|
|
|
6
6
|
interface NodeHonoAdapter<TApp extends HonoApplication = Hono> extends HonoAdapter<TApp> {
|
|
7
7
|
/** Starts an HTTP server (`@hono/node-server`). */
|
|
8
|
-
listen(port: number, host?: string): Promise<
|
|
8
|
+
listen(port: number, host?: string): Promise<ListenAddress>;
|
|
9
9
|
/**
|
|
10
10
|
* Stops the HTTP server: waits for pending requests, and closes the
|
|
11
11
|
* responses being streamed (their `req.signal` is aborted).
|
package/dist/node.js
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
2
|
honoAdapter
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-V62WHZ4B.js";
|
|
4
|
+
import "./chunk-DWK6R675.js";
|
|
4
5
|
|
|
5
6
|
// src/node.ts
|
|
6
7
|
import { createAdaptorServer } from "@hono/node-server";
|
|
8
|
+
var addressOf = (server) => {
|
|
9
|
+
const address = server.address();
|
|
10
|
+
return { port: address.port, host: address.address };
|
|
11
|
+
};
|
|
7
12
|
var trackConnections = (server) => {
|
|
8
13
|
const sockets = /* @__PURE__ */ new Set();
|
|
9
14
|
let closing = false;
|
|
@@ -47,6 +52,7 @@ function honoAdapter2(options = {}) {
|
|
|
47
52
|
});
|
|
48
53
|
server = created;
|
|
49
54
|
closeConnections = trackConnections(created);
|
|
55
|
+
return addressOf(created);
|
|
50
56
|
},
|
|
51
57
|
close: async () => {
|
|
52
58
|
const current = server;
|
package/dist/node.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/node.ts"],"sourcesContent":["import type { IncomingMessage, Server, ServerResponse } from 'node:http'\nimport type { Socket } from 'node:net'\nimport { createAdaptorServer } from '@hono/node-server'\nimport type { Hono } from 'hono'\nimport {\n honoAdapter as createHonoAdapter,\n type HonoAdapter,\n type HonoAdapterOptions,\n type HonoApplication,\n} from './index.js'\n\nexport * from './index.js'\n\nexport interface NodeHonoAdapter<\n TApp extends HonoApplication = Hono,\n> extends HonoAdapter<TApp> {\n /** Starts an HTTP server (`@hono/node-server`). */\n listen(port: number, host?: string): Promise<
|
|
1
|
+
{"version":3,"sources":["../src/node.ts"],"sourcesContent":["import type { IncomingMessage, Server, ServerResponse } from 'node:http'\nimport type { AddressInfo, Socket } from 'node:net'\nimport { createAdaptorServer } from '@hono/node-server'\nimport type { ListenAddress } from '@dotpress/core'\nimport type { Hono } from 'hono'\nimport {\n honoAdapter as createHonoAdapter,\n type HonoAdapter,\n type HonoAdapterOptions,\n type HonoApplication,\n} from './index.js'\n\nexport * from './index.js'\n\nexport interface NodeHonoAdapter<\n TApp extends HonoApplication = Hono,\n> extends HonoAdapter<TApp> {\n /** Starts an HTTP server (`@hono/node-server`). */\n listen(port: number, host?: string): Promise<ListenAddress>\n /**\n * Stops the HTTP server: waits for pending requests, and closes the\n * responses being streamed (their `req.signal` is aborted).\n */\n close(): Promise<void>\n /** HTTP server started by `listen()`, if any. */\n server(): Server | undefined\n}\n\n/** Address of a listening Node.js server. */\nconst addressOf = (server: Server): ListenAddress => {\n const address = server.address() as AddressInfo\n return { port: address.port, host: address.address }\n}\n\n/**\n * Lets `close()` complete as soon as the pending requests are served, and\n * returns the function to call on close. Before Node.js 26, `server.close()`\n * waits for the connections that have sent nothing yet (a browser\n * preconnect…) until `headersTimeout`; and, whatever the version, the\n * connections of the pending requests stay open once served, until\n * `keepAliveTimeout`.\n */\nconst trackConnections = (server: Server) => {\n const sockets = new Set<Socket>()\n let closing = false\n server.on('connection', (socket: Socket) => {\n sockets.add(socket)\n socket.once('close', () => sockets.delete(socket))\n })\n server.on(\n 'request',\n (_request: IncomingMessage, response: ServerResponse) => {\n response.once('finish', () => {\n if (closing) server.closeIdleConnections()\n })\n }\n )\n return () => {\n closing = true\n for (const socket of sockets) {\n if (socket.bytesRead === 0) socket.destroy()\n }\n }\n}\n\n/**\n * Creates the Hono adapter for Node.js: the adapter of\n * `@dotpress/adapter-hono`, with an HTTP server (`@hono/node-server`).\n *\n * @example\n * const app = await createApp({ adapter: honoAdapter() })\n * await app.listen(3000)\n */\nexport function honoAdapter(options?: HonoAdapterOptions<Hono>): NodeHonoAdapter\nexport function honoAdapter<TApp extends HonoApplication>(\n options: HonoAdapterOptions<TApp> & { app: TApp }\n): NodeHonoAdapter<TApp>\nexport function honoAdapter<TApp extends HonoApplication>(\n options: HonoAdapterOptions<TApp> = {}\n): NodeHonoAdapter<TApp> {\n const adapter = createHonoAdapter(\n options as HonoAdapterOptions<TApp> & { app: TApp }\n )\n let server: Server | undefined\n let closeConnections = () => {}\n\n return {\n ...adapter,\n\n listen: async (port: number, host?: string) => {\n if (server) throw new Error('The Hono adapter is already listening.')\n\n const created = createAdaptorServer({ fetch: adapter.fetch }) as Server\n await new Promise<void>((resolve, reject) => {\n created.once('error', reject)\n created.listen(port, host, () => {\n created.off('error', reject)\n resolve()\n })\n })\n server = created\n closeConnections = trackConnections(created)\n return addressOf(created)\n },\n\n close: async () => {\n const current = server\n server = undefined\n if (!current) {\n await adapter.close()\n return\n }\n\n await new Promise<void>((resolve, reject) => {\n // Also closes the idle connections.\n current.close((error) => (error ? reject(error) : resolve()))\n closeConnections()\n // Streams never end by themselves: close them.\n void adapter.close()\n })\n },\n\n server: () => server,\n }\n}\n"],"mappings":";;;;;;AAEA,SAAS,2BAA2B;AA2BpC,IAAM,YAAY,CAAC,WAAkC;AACnD,QAAM,UAAU,OAAO,QAAQ;AAC/B,SAAO,EAAE,MAAM,QAAQ,MAAM,MAAM,QAAQ,QAAQ;AACrD;AAUA,IAAM,mBAAmB,CAAC,WAAmB;AAC3C,QAAM,UAAU,oBAAI,IAAY;AAChC,MAAI,UAAU;AACd,SAAO,GAAG,cAAc,CAAC,WAAmB;AAC1C,YAAQ,IAAI,MAAM;AAClB,WAAO,KAAK,SAAS,MAAM,QAAQ,OAAO,MAAM,CAAC;AAAA,EACnD,CAAC;AACD,SAAO;AAAA,IACL;AAAA,IACA,CAAC,UAA2B,aAA6B;AACvD,eAAS,KAAK,UAAU,MAAM;AAC5B,YAAI,QAAS,QAAO,qBAAqB;AAAA,MAC3C,CAAC;AAAA,IACH;AAAA,EACF;AACA,SAAO,MAAM;AACX,cAAU;AACV,eAAW,UAAU,SAAS;AAC5B,UAAI,OAAO,cAAc,EAAG,QAAO,QAAQ;AAAA,IAC7C;AAAA,EACF;AACF;AAcO,SAASA,aACd,UAAoC,CAAC,GACd;AACvB,QAAM,UAAU;AAAA,IACd;AAAA,EACF;AACA,MAAI;AACJ,MAAI,mBAAmB,MAAM;AAAA,EAAC;AAE9B,SAAO;AAAA,IACL,GAAG;AAAA,IAEH,QAAQ,OAAO,MAAc,SAAkB;AAC7C,UAAI,OAAQ,OAAM,IAAI,MAAM,wCAAwC;AAEpE,YAAM,UAAU,oBAAoB,EAAE,OAAO,QAAQ,MAAM,CAAC;AAC5D,YAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AAC3C,gBAAQ,KAAK,SAAS,MAAM;AAC5B,gBAAQ,OAAO,MAAM,MAAM,MAAM;AAC/B,kBAAQ,IAAI,SAAS,MAAM;AAC3B,kBAAQ;AAAA,QACV,CAAC;AAAA,MACH,CAAC;AACD,eAAS;AACT,yBAAmB,iBAAiB,OAAO;AAC3C,aAAO,UAAU,OAAO;AAAA,IAC1B;AAAA,IAEA,OAAO,YAAY;AACjB,YAAM,UAAU;AAChB,eAAS;AACT,UAAI,CAAC,SAAS;AACZ,cAAM,QAAQ,MAAM;AACpB;AAAA,MACF;AAEA,YAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AAE3C,gBAAQ,MAAM,CAAC,UAAW,QAAQ,OAAO,KAAK,IAAI,QAAQ,CAAE;AAC5D,yBAAiB;AAEjB,aAAK,QAAQ,MAAM;AAAA,MACrB,CAAC;AAAA,IACH;AAAA,IAEA,QAAQ,MAAM;AAAA,EAChB;AACF;","names":["honoAdapter"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dotpress/adapter-hono",
|
|
3
|
-
"version": "2.0.0-alpha.
|
|
3
|
+
"version": "2.0.0-alpha.5",
|
|
4
4
|
"description": "Hono adapter for Dotpress, on Node.js and AWS Lambda",
|
|
5
5
|
"author": "Renaud Guyon",
|
|
6
6
|
"license": "MIT",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"node": ">=22.12"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
41
|
-
"@dotpress/core": "^2.0.0-alpha.
|
|
41
|
+
"@dotpress/core": "^2.0.0-alpha.5",
|
|
42
42
|
"@hono/aws-lambda": "^1.0.0",
|
|
43
43
|
"@hono/node-server": "^2.1.0",
|
|
44
44
|
"hono": "^4.13.0"
|
|
@@ -52,8 +52,8 @@
|
|
|
52
52
|
}
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
|
-
"@dotpress/core": "2.0.0-alpha.
|
|
56
|
-
"@dotpress/testing": "2.0.0-alpha.
|
|
55
|
+
"@dotpress/core": "2.0.0-alpha.5",
|
|
56
|
+
"@dotpress/testing": "2.0.0-alpha.5",
|
|
57
57
|
"@hono/aws-lambda": "^1.0.1",
|
|
58
58
|
"@hono/node-server": "^2.1.6",
|
|
59
59
|
"hono": "^4.13.17",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import {\n DEFAULT_BODY_LIMIT,\n HttpError,\n createRouteMatcher,\n fromWebRequest,\n parseRequestBody,\n payloadTooLargeError,\n readRequestBody,\n toWebResponse,\n type CompiledRoute,\n type DotpressRuntime,\n type HttpAdapter,\n type NormalizedRequest,\n type NormalizedResponse,\n type RequestBody,\n type RouteMatch,\n} from '@dotpress/core'\nimport { Hono, type Context } from 'hono'\n\n/**\n * Minimal shape of a Hono application (Hono 4 or 5). The types of Hono 5\n * differ from those of Hono 4, hence the loose signatures.\n */\nexport interface HonoApplication {\n use: (...args: never[]) => unknown\n notFound: (...args: never[]) => unknown\n onError: (...args: never[]) => unknown\n fetch: (\n request: Request,\n env?: never,\n executionContext?: never\n ) => Response | Promise<Response>\n}\n\n/** Members of the Hono application used by the adapter. */\ninterface MountableApp {\n use(\n path: string,\n handler: (c: Context, next: () => Promise<void>) => Promise<Response | void>\n ): unknown\n notFound(handler: (c: Context) => Promise<Response>): unknown\n onError(handler: (error: Error, c: Context) => Promise<Response>): unknown\n fetch(\n request: Request,\n env?: unknown,\n executionContext?: unknown\n ): Response | Promise<Response>\n}\n\nexport interface HonoAdapterOptions<TApp extends HonoApplication> {\n /** Hono application to mount the routes on. Defaults to `new Hono()`. */\n app?: TApp\n /** Registers native middlewares before the Dotpress routes. */\n beforeRoutes?: (app: TApp) => void | Promise<void>\n /**\n * Registers native middlewares and routes after the Dotpress routes,\n * before the not-found handler.\n */\n afterRoutes?: (app: TApp) => void | Promise<void>\n /**\n * Maximum request body size, in bytes. Defaults to 1 MiB. Routes can\n * override it with their own `bodyLimit`.\n */\n bodyLimit?: number\n /**\n * Client IP address of a request (`ctx.req.ip`). Defaults to the address\n * given by the runtime: the socket on Node.js, the source IP of API\n * Gateway, the last `X-Forwarded-For` entry behind an Application Load\n * Balancer (added by the load balancer). Proxy headers are not read\n * otherwise: behind another proxy (CloudFront…), read them here.\n */\n getIp?: (c: Context) => string | undefined\n}\n\nexport interface HonoAdapter<\n TApp extends HonoApplication = Hono,\n> extends HttpAdapter<TApp> {\n /**\n * Handles a Web request, like `app.native().fetch()`: for runtimes that\n * take a `fetch` handler, and for tests.\n */\n fetch(\n request: Request,\n env?: unknown,\n executionContext?: unknown\n ): Promise<Response>\n /** Closes the responses being streamed (their `req.signal` is aborted). */\n close(): Promise<void>\n}\n\n/** Environment given by `@hono/node-server` and `@hono/aws-lambda`. */\ninterface RuntimeEnv {\n readonly incoming?: { readonly socket?: { readonly remoteAddress?: unknown } }\n readonly requestContext?: {\n readonly identity?: { readonly sourceIp?: unknown }\n readonly http?: { readonly sourceIp?: unknown }\n readonly elb?: unknown\n }\n}\n\n/**\n * Client address given by the runtime: the socket on Node.js, the source\n * IP of API Gateway (REST and HTTP APIs, Function URLs), or the address\n * appended to `X-Forwarded-For` by an Application Load Balancer.\n */\nconst runtimeIp = (c: Context): string | undefined => {\n const env = c.env as RuntimeEnv | undefined\n\n const socket = env?.incoming?.socket?.remoteAddress\n if (typeof socket === 'string') return socket\n\n const context = env?.requestContext\n const sourceIp = context?.identity?.sourceIp ?? context?.http?.sourceIp\n if (typeof sourceIp === 'string') return sourceIp\n\n if (context?.elb !== undefined) {\n const forwarded = c.req.header('x-forwarded-for')?.split(',').at(-1)\n return forwarded?.trim() || undefined\n }\n return undefined\n}\n\n/** Code and default message of client errors. */\nconst CLIENT_ERRORS: Record<number, readonly [string, string]> = {\n 400: ['BAD_REQUEST', 'Bad Request'],\n 401: ['UNAUTHORIZED', 'Unauthorized'],\n 403: ['FORBIDDEN', 'Forbidden'],\n 404: ['NOT_FOUND', 'Not Found'],\n 405: ['METHOD_NOT_ALLOWED', 'Method Not Allowed'],\n 408: ['REQUEST_TIMEOUT', 'Request Timeout'],\n 409: ['CONFLICT', 'Conflict'],\n 413: ['PAYLOAD_TOO_LARGE', 'Payload Too Large'],\n 415: ['UNSUPPORTED_MEDIA_TYPE', 'Unsupported Media Type'],\n 422: ['UNPROCESSABLE_ENTITY', 'Unprocessable Entity'],\n 429: ['TOO_MANY_REQUESTS', 'Too Many Requests'],\n}\n\ninterface HttpExceptionLike {\n readonly status: number\n readonly message: string\n readonly res?: Response\n getResponse(): Response\n}\n\n/** `HTTPException` of Hono 4 or 5, recognized by its shape. */\nconst isHttpException = (error: unknown): error is HttpExceptionLike =>\n error instanceof Error &&\n typeof (error as Partial<HttpExceptionLike>).status === 'number' &&\n typeof (error as Partial<HttpExceptionLike>).getResponse === 'function'\n\n/**\n * Converts the client errors (4xx) raised by native middlewares\n * (`HTTPException`) into Dotpress errors, with the same status. Anything\n * else is an unexpected error.\n */\nconst fromNativeError = (error: unknown): unknown => {\n if (!isHttpException(error)) return error\n\n const { status, message } = error\n if (!Number.isInteger(status) || status < 400 || status > 499) return error\n\n const [code, defaultMessage] = CLIENT_ERRORS[status] ?? [\n 'CLIENT_ERROR',\n 'Client Error',\n ]\n return new HttpError(status, code, message || defaultMessage, undefined, {\n cause: error,\n })\n}\n\n/**\n * Adds the headers of the custom response of an `HTTPException`\n * (`WWW-Authenticate`…) to the Dotpress error response.\n */\nconst addNativeHeaders = (\n response: NormalizedResponse,\n error: HttpExceptionLike\n) => {\n if (!error.res) return\n for (const [name, value] of error.res.headers) {\n if (name.startsWith('content-')) continue\n if (name === 'set-cookie') continue\n response.headers[name] = value\n }\n const cookies = error.res.headers.getSetCookie()\n if (cookies.length > 0) {\n const current = response.headers['set-cookie'] ?? []\n response.headers['set-cookie'] = [\n ...(Array.isArray(current) ? current : [current]),\n ...cookies,\n ]\n }\n}\n\nconst encoder = new TextEncoder()\n\n/**\n * Body already read by a native middleware, from the cache of Hono\n * (`c.req.json()`, `c.req.parseBody()`…). Only an `ArrayBuffer` or a\n * `Blob` keeps the exact bytes (`rawBody`).\n */\nconst cachedBody = async (c: Context, limit: number): Promise<RequestBody> => {\n const cache = c.req.bodyCache as Partial<\n Record<'arrayBuffer' | 'blob' | 'text' | 'formData', Promise<unknown>>\n >\n const contentType = c.req.header('content-type')\n\n let bytes: Uint8Array\n let exact = false\n let type = contentType\n\n if (cache.arrayBuffer) {\n bytes = new Uint8Array((await cache.arrayBuffer) as ArrayBuffer)\n exact = true\n } else if (cache.blob) {\n bytes = new Uint8Array(await ((await cache.blob) as Blob).arrayBuffer())\n exact = true\n } else if (cache.text) {\n bytes = encoder.encode((await cache.text) as string)\n } else if (cache.formData) {\n // Serialized again, with a new boundary.\n const response = new Response((await cache.formData) as FormData)\n bytes = new Uint8Array(await response.arrayBuffer())\n type = response.headers.get('content-type') ?? undefined\n } else {\n throw new Error(\n 'The request body was read by a native middleware outside the cache of Hono (c.req.raw): read it with c.req.json(), c.req.text() or c.req.arrayBuffer().'\n )\n }\n\n if (bytes.byteLength > limit) throw payloadTooLargeError()\n const parsed = await parseRequestBody(bytes, type)\n return { ...parsed, rawBody: exact ? bytes : undefined }\n}\n\n// Reading the body through Hono consumes the request.\nconst readBody = (c: Context, limit: number): Promise<RequestBody> =>\n c.req.raw.bodyUsed ? cachedBody(c, limit) : readRequestBody(c.req.raw, limit)\n\n/**\n * Closes the streamed body when the adapter is closed, and aborts the\n * request signal when the client cancels it.\n */\nconst track = (\n body: ReadableStream<Uint8Array>,\n controller: AbortController,\n streams: Set<() => void>\n): ReadableStream<Uint8Array> => {\n const reader = body.getReader()\n let stop: () => void = () => {}\n\n return new ReadableStream<Uint8Array>({\n start() {\n // The cancelled reader ends the stream (see `pull()`).\n stop = () => {\n streams.delete(stop)\n controller.abort()\n reader.cancel().catch(() => {})\n }\n streams.add(stop)\n },\n async pull(stream) {\n try {\n const { done, value } = await reader.read()\n if (done) {\n streams.delete(stop)\n stream.close()\n } else {\n stream.enqueue(value)\n }\n } catch (error) {\n streams.delete(stop)\n stream.error(error)\n }\n },\n cancel(reason) {\n streams.delete(stop)\n controller.abort(reason)\n return reader.cancel(reason)\n },\n })\n}\n\n/**\n * Creates the Hono adapter. The main entry is runtime-agnostic: serve\n * `adapter.fetch` with the runtime, or use `honoAdapter()` of\n * `@dotpress/adapter-hono/node` to listen on Node.js.\n *\n * @example\n * const adapter = honoAdapter()\n * await createApp({ adapter })\n * export default { fetch: adapter.fetch }\n */\nexport function honoAdapter(options?: HonoAdapterOptions<Hono>): HonoAdapter\nexport function honoAdapter<TApp extends HonoApplication>(\n options: HonoAdapterOptions<TApp> & { app: TApp }\n): HonoAdapter<TApp>\nexport function honoAdapter<TApp extends HonoApplication>(\n options: HonoAdapterOptions<TApp> = {}\n): HonoAdapter<TApp> {\n const bodyLimit = options.bodyLimit ?? DEFAULT_BODY_LIMIT\n\n if (!Number.isInteger(bodyLimit) || bodyLimit < 0) {\n throw new RangeError(\n 'Invalid bodyLimit: expected a non-negative integer (bytes).'\n )\n }\n\n const application = options.app ?? (new Hono() as unknown as TApp)\n const app = application as unknown as MountableApp\n const getIp = options.getIp ?? runtimeIp\n let mounted = false\n /** Closes the responses being streamed, so that `close()` can complete. */\n const streams = new Set<() => void>()\n\n const normalize = (c: Context, signal?: AbortSignal): NormalizedRequest =>\n fromWebRequest(c.req.raw, { ip: getIp(c), signal, raw: c })\n\n const send = (\n c: Context,\n response: NormalizedResponse,\n controller?: AbortController\n ): Promise<Response> => {\n const { body } = response\n if (controller && body instanceof ReadableStream) {\n return toWebResponse(\n { ...response, body: track(body, controller, streams) },\n c.req.method\n )\n }\n return toWebResponse(response, c.req.method)\n }\n\n const handleRoute = async (\n c: Context,\n match: RouteMatch,\n runtime: DotpressRuntime\n ): Promise<Response> => {\n const controller = new AbortController()\n const signal = AbortSignal.any([c.req.raw.signal, controller.signal])\n const request = normalize(c, signal)\n\n try {\n const { body, files, rawBody } = await readBody(\n c,\n match.route.definition.bodyLimit ?? bodyLimit\n )\n request.body = body\n request.files = files\n request.rawBody = rawBody\n } catch (error) {\n return send(c, await runtime.handleError(error, request))\n }\n\n request.params = match.params\n return send(c, await match.route.execute(request), controller)\n }\n\n return {\n name: 'hono',\n\n mount: async (\n routes: readonly CompiledRoute[],\n runtime: DotpressRuntime\n ) => {\n if (mounted) throw new Error('The Hono adapter is already mounted.')\n mounted = true\n\n const matcher = createRouteMatcher(routes)\n\n await options.beforeRoutes?.(application)\n\n app.use('*', async (c, next) => {\n const match = matcher.match(c.req.method, new URL(c.req.url).pathname)\n if (!match) {\n await next()\n return\n }\n return handleRoute(c, match, runtime)\n })\n\n await options.afterRoutes?.(application)\n\n app.notFound(async (c) =>\n send(c, await runtime.handleNotFound(normalize(c)))\n )\n\n app.onError(async (error, c) => {\n const converted = fromNativeError(error)\n const response = await runtime.handleError(converted, normalize(c))\n if (converted !== error) {\n addNativeHeaders(response, error as unknown as HttpExceptionLike)\n }\n return send(c, response)\n })\n },\n\n fetch: async (request, env, executionContext) =>\n app.fetch(request, env, executionContext),\n\n close: async () => {\n for (const stop of [...streams]) stop()\n },\n\n native: () => application,\n }\n}\n"],"mappings":"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","names":[]}
|