@dotpress/adapter-hono 2.0.0-alpha.4 → 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.
@@ -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, void 0, {
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
- "Invalid bodyLimit: expected a non-negative integer (bytes)."
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-4EA3O2OL.js.map
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
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  honoAdapter
3
- } from "./chunk-4EA3O2OL.js";
3
+ } from "./chunk-V62WHZ4B.js";
4
+ import "./chunk-DWK6R675.js";
4
5
  export {
5
6
  honoAdapter
6
7
  };
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
- * the rule of `@hono/aws-lambda`: everything except text, JSON and XML.
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<APIGatewayProxyResult>;
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() expects an app created with honoAdapter() of @dotpress/adapter-hono."
36
+ "createLambdaHandler(): expected an app created with honoAdapter() of @dotpress/adapter-hono."
31
37
  );
32
38
  }
33
39
  return toHandler(native);
@@ -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 {\n handle,\n type APIGatewayProxyResult,\n type LambdaContext,\n type LambdaEvent,\n} from '@hono/aws-lambda'\nimport { Hono } from 'hono'\nimport type { HonoApplication } from './index.js'\n\nexport type { APIGatewayProxyResult, LambdaContext, LambdaEvent }\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 * the rule of `@hono/aws-lambda`: 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<APIGatewayProxyResult>\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 const logger = options.logger ?? createConsoleLogger()\n const toHandler = (app: HonoApplication) =>\n handle(app as unknown as Hono, {\n isContentTypeBinary: options.isContentTypeBinary,\n }) 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() expects 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;AAAA,EACE;AAAA,OAIK;AACP,SAAS,YAAY;AA4BrB,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,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":[]}
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<void>;
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-4EA3O2OL.js";
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<void>\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/**\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 },\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;AAiCpC,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;AAAA,IAC7C;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"]}
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.4",
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.4",
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.4",
56
- "@dotpress/testing": "2.0.0-alpha.4",
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":[]}