@znight/http 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +2333 -2
  3. package/dist/adapter-CGSceVEN.d.mts +876 -0
  4. package/dist/compiler.d.mts +156 -0
  5. package/dist/compiler.mjs +2180 -0
  6. package/dist/compiler.mjs.map +1 -0
  7. package/dist/express.d.mts +39 -0
  8. package/dist/express.mjs +135 -0
  9. package/dist/express.mjs.map +1 -0
  10. package/dist/fastify.d.mts +33 -0
  11. package/dist/fastify.mjs +191 -0
  12. package/dist/fastify.mjs.map +1 -0
  13. package/dist/hono.d.mts +49 -0
  14. package/dist/hono.mjs +319 -0
  15. package/dist/hono.mjs.map +1 -0
  16. package/dist/index.d.mts +1254 -0
  17. package/dist/index.mjs +5618 -0
  18. package/dist/index.mjs.map +1 -0
  19. package/dist/node-BUnkdSsa.mjs +183 -0
  20. package/dist/node-BUnkdSsa.mjs.map +1 -0
  21. package/dist/node-io-BY1ZDLRQ.mjs +1566 -0
  22. package/dist/node-io-BY1ZDLRQ.mjs.map +1 -0
  23. package/dist/node.d.mts +30 -0
  24. package/dist/node.mjs +1 -0
  25. package/dist/path-JI87C4k8.d.mts +109 -0
  26. package/dist/path.d.mts +2 -0
  27. package/dist/path.mjs +245 -0
  28. package/dist/path.mjs.map +1 -0
  29. package/dist/size-D8rsQcV0.mjs +184 -0
  30. package/dist/size-D8rsQcV0.mjs.map +1 -0
  31. package/dist/utils-DCbu3a4w.mjs +27 -0
  32. package/dist/utils-DCbu3a4w.mjs.map +1 -0
  33. package/package.json +142 -4
  34. package/src/adapter.ts +240 -0
  35. package/src/adapters/express.ts +201 -0
  36. package/src/adapters/fastify.ts +280 -0
  37. package/src/adapters/hono.ts +488 -0
  38. package/src/adapters/node-io.ts +368 -0
  39. package/src/adapters/node.ts +135 -0
  40. package/src/adapters/shared.ts +508 -0
  41. package/src/body.ts +135 -0
  42. package/src/coerce.ts +195 -0
  43. package/src/compiler/analyze.ts +1513 -0
  44. package/src/compiler/ast.ts +90 -0
  45. package/src/compiler/coercion.ts +267 -0
  46. package/src/compiler/generate.ts +374 -0
  47. package/src/compiler/index.ts +76 -0
  48. package/src/compiler/model.ts +260 -0
  49. package/src/compiler/route-decorators.ts +405 -0
  50. package/src/compiler/validate.ts +288 -0
  51. package/src/context.ts +533 -0
  52. package/src/cookie-settings.ts +68 -0
  53. package/src/cookie-signer.ts +106 -0
  54. package/src/cookies.ts +726 -0
  55. package/src/cors.ts +349 -0
  56. package/src/decisions.ts +331 -0
  57. package/src/decorators.ts +486 -0
  58. package/src/event-channel.ts +197 -0
  59. package/src/exceptions.ts +257 -0
  60. package/src/files.ts +429 -0
  61. package/src/headers.ts +39 -0
  62. package/src/index.ts +185 -0
  63. package/src/inject-sse.ts +309 -0
  64. package/src/interfaces.ts +101 -0
  65. package/src/metadata.ts +162 -0
  66. package/src/mime.ts +157 -0
  67. package/src/multipart.ts +597 -0
  68. package/src/option-error.ts +31 -0
  69. package/src/path.ts +288 -0
  70. package/src/pipeline.ts +918 -0
  71. package/src/plugin-routes.ts +188 -0
  72. package/src/plugin.ts +1432 -0
  73. package/src/proxy.ts +236 -0
  74. package/src/query.ts +30 -0
  75. package/src/response.ts +240 -0
  76. package/src/router.ts +119 -0
  77. package/src/security-headers.ts +161 -0
  78. package/src/serialize.ts +264 -0
  79. package/src/size.ts +66 -0
  80. package/src/sse-parse.ts +194 -0
  81. package/src/sse.ts +650 -0
  82. package/src/standard-schema.ts +81 -0
  83. package/src/status.ts +78 -0
  84. package/src/stream.ts +200 -0
  85. package/src/tokens.ts +21 -0
  86. package/src/uploads.ts +176 -0
  87. package/src/utils.ts +31 -0
  88. package/src/validate.ts +65 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"express.mjs","names":[],"sources":["../src/adapters/express.ts"],"sourcesContent":["/**\n * `@znight/http/express`: serves Znight routes with [Express](https://expressjs.com) 5 (optional\n * peer dependency `express`). Routes are mounted on a dedicated router, after the middleware of the\n * application you pass. `inject()` uses the optional peer `light-my-request`.\n */\nimport type { IncomingMessage, ServerResponse } from 'node:http'\nimport type {\n AdapterCloseOptions,\n AdapterRoute,\n FallbackHandler,\n HttpAdapter,\n HttpServerAddress,\n InjectRequest,\n InjectResponse,\n InjectStreamRequest,\n InjectStreamResponse,\n} from '../adapter.ts'\n\nimport { createServer, type Server } from 'node:http'\nimport express, { type Express, type NextFunction, type Request, type Response, type Router } from 'express'\n\nimport { toRegExpSource } from '../path.ts'\nimport { ConnectionTracker, closeServer, nodeAdapterRequest, writeNodeFailure, writeNodeReply } from './node-io.ts'\nimport { bufferInjectStream, injectNodeStream, serverAddress } from './shared.ts'\n\n/** Options of {@link expressAdapter}. */\nexport interface ExpressAdapterOptions {\n /**\n * An existing application: its middleware (CORS, helmet, sessions...) runs before the Znight\n * routes. Do not add body parsers for Znight routes: Znight reads bodies itself.\n */\n readonly app?: Express\n /**\n * The server to listen with (e.g. `https.createServer({ key, cert })`): the adapter adds its\n * `request` listener. Default: a new `http.createServer()`.\n */\n readonly server?: Server\n}\n\n/** The Express adapter. */\nexport interface ExpressAdapter extends HttpAdapter {\n readonly name: 'express'\n /** The Express application. */\n readonly app: Express\n /** The router holding the Znight routes (mounted on {@link app}). */\n readonly router: Router\n /** The HTTP server, once listening. */\n readonly server: Server | undefined\n}\n\n/**\n * Creates an Express adapter.\n *\n * ```ts\n * import { expressAdapter } from '@znight/http/express'\n *\n * const app = express()\n * app.use(cors())\n * HttpPlugin.configure({ adapter: expressAdapter({ app }) })\n * ```\n */\nexport function expressAdapter(options: ExpressAdapterOptions = {}): ExpressAdapter {\n return new ExpressHttpAdapter(options)\n}\n\ntype Method = 'get' | 'head' | 'post' | 'put' | 'patch' | 'delete' | 'options'\n\nclass ExpressHttpAdapter implements ExpressAdapter {\n readonly name = 'express' as const\n readonly app: Express\n readonly router: Router\n #fallback: FallbackHandler | undefined\n #mounted = false\n #server: Server | undefined\n #tracker: ConnectionTracker | undefined\n #closing: Promise<void> | undefined\n\n readonly #givenServer: Server | undefined\n\n constructor(options: ExpressAdapterOptions) {\n this.#givenServer = options.server\n if (options.app === undefined) {\n this.app = express()\n this.app.disable('x-powered-by')\n } else {\n this.app = options.app\n }\n this.router = express.Router({ caseSensitive: true, strict: true })\n }\n\n get server(): Server | undefined {\n return this.#server\n }\n\n registerRoute(route: AdapterRoute): void {\n const handler = route.handler\n // anchored regular expressions without capture groups: Express decodes nothing, Znight extracts parameters\n this.router[route.method.toLowerCase() as Method](new RegExp(toRegExpSource(route.path)), (request, response) => {\n handler(adapterRequest(request, response)).then(\n reply => writeNodeReply(response, reply),\n () => writeNodeFailure(response),\n )\n })\n }\n\n setFallbackHandler(handler: FallbackHandler): void {\n this.#fallback = handler\n }\n\n #mount(): void {\n if (this.#mounted) return\n this.#mounted = true\n // Express answers unmatched OPTIONS requests itself (200): route them to the Znight fallback\n this.router.options(/^.*$/, (request: Request, response: Response) =>\n this.#dispatchFallback(request, response, undefined),\n )\n this.app.use(this.router)\n this.app.use((request: Request, response: Response) => this.#dispatchFallback(request, response, undefined))\n this.app.use((error: unknown, request: Request, response: Response, _next: NextFunction) =>\n this.#dispatchFallback(request, response, error),\n )\n }\n\n #dispatchFallback(request: Request, response: Response, error: unknown): void {\n if (this.#fallback === undefined) {\n writeNodeFailure(response)\n return\n }\n this.#fallback(adapterRequest(request, response), error).then(\n reply => writeNodeReply(response, reply),\n () => writeNodeFailure(response),\n )\n }\n\n readonly #listener = (request: IncomingMessage, response: ServerResponse): void => {\n this.app(request as Request, response as Response)\n }\n\n async listen(port: number, host: string): Promise<HttpServerAddress> {\n this.#mount()\n const server = this.#givenServer ?? createServer()\n if (!server.listeners('request').includes(this.#listener)) server.on('request', this.#listener)\n this.#server = server\n this.#tracker ??= new ConnectionTracker(server)\n this.#closing = undefined\n await new Promise<void>((resolve, reject) => {\n const onError = (error: Error): void => reject(error)\n server.once('error', onError)\n server.listen(port, host, () => {\n server.off('error', onError)\n resolve()\n })\n })\n const address = server.address()\n if (address === null || typeof address === 'string') return serverAddress(host, port)\n return serverAddress(address.address, address.port, address.family)\n }\n\n close(options: AdapterCloseOptions = {}): Promise<void> {\n const server = this.#server\n const tracker = this.#tracker\n if (server === undefined || tracker === undefined) return Promise.resolve()\n this.#closing = closeServer(server, tracker, this.#closing, options.force === true)\n return this.#closing\n }\n\n inject(request: InjectRequest): Promise<InjectResponse> {\n return bufferInjectStream(stream => this.injectStream(stream), request)\n }\n\n injectStream(request: InjectStreamRequest): Promise<InjectStreamResponse> {\n this.#mount()\n return injectNodeStream('express', this.#injectListener as (req: never, res: never) => void, request)\n }\n\n /**\n * Express replaces the prototype of `req`/`res` with its own, which drops the overrides of the\n * light-my-request fakes (payload capture, body streaming): pin them as own properties first.\n */\n readonly #injectListener = (request: IncomingMessage, response: ServerResponse): void => {\n pinPrototype(request)\n pinPrototype(response)\n this.#listener(request, response)\n }\n}\n\nfunction pinPrototype(target: object): void {\n const prototype: unknown = Object.getPrototypeOf(target)\n if (prototype === null || typeof prototype !== 'object') return\n for (const key of Object.getOwnPropertyNames(prototype)) {\n if (key === 'constructor' || Object.hasOwn(target, key)) continue\n const descriptor = Object.getOwnPropertyDescriptor(prototype, key)\n if (typeof descriptor?.value === 'function') {\n Object.defineProperty(target, key, { value: descriptor.value, writable: true, configurable: true })\n }\n }\n}\n\nfunction adapterRequest(request: Request, response: Response): ReturnType<typeof nodeAdapterRequest> {\n return nodeAdapterRequest(request, response, { url: request.originalUrl ?? request.url, ip: request.ip })\n}\n"],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ import { s as HttpAdapter } from "./adapter-CGSceVEN.mjs";
2
+ import { FastifyInstance, FastifyServerOptions } from "fastify";
3
+ /** Options of {@link fastifyAdapter}. */
4
+ export interface FastifyAdapterOptions {
5
+ /**
6
+ * An existing instance (with your plugins and hooks). Znight registers its routes in an
7
+ * encapsulated child context (its own body handling and 404 handler) and closes the instance on
8
+ * shutdown. Malformed URLs are then answered by Fastify's `frameworkErrors` handler.
9
+ */
10
+ readonly instance?: FastifyInstance;
11
+ /**
12
+ * Options of the instance created when `instance` is absent. Znight sets `exposeHeadRoutes: false`
13
+ * (HEAD is served by Znight), a `maxParamLength` of 8 KiB and a `frameworkErrors` handler.
14
+ */
15
+ readonly options?: FastifyServerOptions;
16
+ }
17
+ /** The Fastify adapter. */
18
+ export interface FastifyAdapter extends HttpAdapter {
19
+ readonly name: "fastify";
20
+ /** The Fastify instance. */
21
+ readonly instance: FastifyInstance;
22
+ }
23
+ /**
24
+ * Creates a Fastify adapter.
25
+ *
26
+ * ```ts
27
+ * import { fastifyAdapter } from '@znight/http/fastify'
28
+ *
29
+ * HttpPlugin.configure({ adapter: fastifyAdapter({ options: { trustProxy: true } }) })
30
+ * ```
31
+ */
32
+ export declare function fastifyAdapter(options?: FastifyAdapterOptions): FastifyAdapter;
33
+ //# sourceMappingURL=fastify.d.mts.map
@@ -0,0 +1,191 @@
1
+ import { N as discardStream, _ as lightStreamResponse, a as nodeBodyStream, c as readNodeBody, d as writeNodeReply, f as bufferInjectStream, g as lightInjectOptions, l as withNativeCookies, o as normalizeNodeHeaders, r as isEncrypted, s as onResponseAbort, t as ConnectionTracker, y as serverAddress } from "./node-io-BY1ZDLRQ.mjs";
2
+ import { toFastifyPath } from "./path.mjs";
3
+ import { Readable } from "node:stream";
4
+ import Fastify from "fastify";
5
+ // src/adapters/fastify.ts
6
+ /**
7
+ * Creates a Fastify adapter.
8
+ *
9
+ * ```ts
10
+ * import { fastifyAdapter } from '@znight/http/fastify'
11
+ *
12
+ * HttpPlugin.configure({ adapter: fastifyAdapter({ options: { trustProxy: true } }) })
13
+ * ```
14
+ */
15
+ function fastifyAdapter(options = {}) {
16
+ return new FastifyHttpAdapter(options);
17
+ }
18
+ const MAX_PARAM_LENGTH = 8192;
19
+ /** `content-type` headers hidden from Fastify (see `#register`), by request. */
20
+ const HIDDEN_CONTENT_TYPES = /* @__PURE__ */ new WeakMap();
21
+ var FastifyHttpAdapter = class {
22
+ name = "fastify";
23
+ instance;
24
+ #routes = [];
25
+ #tracker;
26
+ #fallback;
27
+ #ready;
28
+ #closing;
29
+ constructor(options) {
30
+ if (options.instance === void 0) {
31
+ const serverOptions = options.options ?? {};
32
+ this.instance = Fastify({
33
+ exposeHeadRoutes: false,
34
+ frameworkErrors: (error, request, reply) => {
35
+ this.#reply(reply, this.#dispatchFallback(request, reply, error)).catch(() => void 0);
36
+ },
37
+ ...serverOptions,
38
+ routerOptions: {
39
+ maxParamLength: MAX_PARAM_LENGTH,
40
+ ...serverOptions.routerOptions
41
+ }
42
+ });
43
+ } else this.instance = options.instance;
44
+ this.#tracker = new ConnectionTracker(this.instance.server);
45
+ }
46
+ registerRoute(route) {
47
+ if (this.#ready !== void 0) throw new Error("fastifyAdapter: routes must be registered before listen()/inject()");
48
+ this.#routes.push(route);
49
+ }
50
+ setFallbackHandler(handler) {
51
+ this.#fallback = handler;
52
+ }
53
+ #prepare() {
54
+ this.#ready ??= this.#register();
55
+ return this.#ready;
56
+ }
57
+ async #register() {
58
+ const routes = this.#routes;
59
+ await this.instance.register((child, _options, done) => {
60
+ child.removeAllContentTypeParsers();
61
+ child.addContentTypeParser("*", (_request, _payload, done) => done(null, void 0));
62
+ child.addHook("onRequest", (request, _reply, done) => {
63
+ const contentType = request.headers["content-type"];
64
+ if (contentType !== void 0 && request.mediaType === void 0) {
65
+ HIDDEN_CONTENT_TYPES.set(request.raw, contentType);
66
+ delete request.raw.headers["content-type"];
67
+ }
68
+ done();
69
+ });
70
+ for (const route of routes) {
71
+ const handler = route.handler;
72
+ child.route({
73
+ method: route.method,
74
+ url: toFastifyPath(route.path),
75
+ exposeHeadRoute: false,
76
+ handler: (request, reply) => this.#reply(reply, handler(adapterRequest(request, reply)))
77
+ });
78
+ }
79
+ child.setNotFoundHandler((request, reply) => this.#reply(reply, this.#dispatchFallback(request, reply, void 0)));
80
+ child.setErrorHandler((error, request, reply) => this.#reply(reply, this.#dispatchFallback(request, reply, error)));
81
+ done();
82
+ });
83
+ await this.instance.ready();
84
+ }
85
+ #dispatchFallback(request, reply, error) {
86
+ if (this.#fallback === void 0) return Promise.reject(error ?? /* @__PURE__ */ new Error("No fallback handler"));
87
+ return this.#fallback(adapterRequest(request, void 0, reply), error);
88
+ }
89
+ async #reply(reply, pending) {
90
+ let result;
91
+ try {
92
+ result = await pending;
93
+ } catch {
94
+ result = {
95
+ status: 500,
96
+ headers: { "content-type": "application/json; charset=utf-8" },
97
+ body: new TextEncoder().encode("{\"statusCode\":500,\"error\":\"Internal Server Error\"}")
98
+ };
99
+ }
100
+ if (result.raw === true || reply.sent || reply.raw.destroyed) {
101
+ discardStream(result.body instanceof ReadableStream ? result.body : null);
102
+ return reply;
103
+ }
104
+ if (result.eventStream === true) {
105
+ const native = lowerCaseHeaders(reply.getHeaders());
106
+ const headers = {
107
+ ...native,
108
+ ...withNativeCookies(native["set-cookie"], result.headers)
109
+ };
110
+ reply.hijack();
111
+ writeNodeReply(reply.raw, {
112
+ ...result,
113
+ headers
114
+ });
115
+ return reply;
116
+ }
117
+ reply.code(result.status);
118
+ for (const name of Object.keys(result.headers)) reply.header(name, result.headers[name]);
119
+ const body = result.body;
120
+ if (body === null) return reply.send();
121
+ if (body instanceof Uint8Array) return reply.send(Buffer.from(body.buffer, body.byteOffset, body.byteLength));
122
+ const stream = body instanceof Blob ? body.stream() : body;
123
+ return reply.send(Readable.fromWeb(stream));
124
+ }
125
+ async listen(port, host) {
126
+ await this.#prepare();
127
+ this.#closing = void 0;
128
+ await this.instance.listen({
129
+ port,
130
+ host
131
+ });
132
+ const address = this.instance.server.address();
133
+ if (address === null || typeof address === "string") return serverAddress(host, port);
134
+ return serverAddress(address.address, address.port, address.family);
135
+ }
136
+ close(options = {}) {
137
+ if (this.#closing === void 0) {
138
+ this.#tracker.closeIdle();
139
+ this.#closing = this.instance.close();
140
+ }
141
+ if (options.force === true) {
142
+ this.instance.server.closeAllConnections?.();
143
+ this.#tracker.destroyAll();
144
+ }
145
+ return this.#closing;
146
+ }
147
+ inject(request) {
148
+ return bufferInjectStream((stream) => this.injectStream(stream), request);
149
+ }
150
+ async injectStream(request) {
151
+ await this.#prepare();
152
+ const options = await lightInjectOptions(request);
153
+ const response = await this.instance.inject.call(this.instance, options);
154
+ return lightStreamResponse(response, request.signal);
155
+ }
156
+ };
157
+ function lowerCaseHeaders(headers) {
158
+ const result = {};
159
+ for (const [name, value] of Object.entries(headers)) if (value !== void 0) result[name.toLowerCase()] = typeof value === "object" ? [...value] : String(value);
160
+ return result;
161
+ }
162
+ function adapterRequest(request, reply, response = reply) {
163
+ const hidden = HIDDEN_CONTENT_TYPES.get(request.raw);
164
+ if (hidden !== void 0) {
165
+ HIDDEN_CONTENT_TYPES.delete(request.raw);
166
+ request.raw.headers["content-type"] = hidden;
167
+ }
168
+ let body;
169
+ return {
170
+ method: request.raw.method ?? request.method,
171
+ url: request.raw.url ?? request.url,
172
+ headers: normalizeNodeHeaders(request.headers),
173
+ ip: request.ip,
174
+ remoteAddress: request.raw.socket?.remoteAddress,
175
+ encrypted: isEncrypted(request.raw),
176
+ raw: request,
177
+ rawResponse: reply,
178
+ readBody: (limit) => {
179
+ body ??= readNodeBody(request.raw, limit);
180
+ return body;
181
+ },
182
+ streamBody: (limit) => nodeBodyStream(request.raw, limit),
183
+ rawStatus: () => reply?.statusCode,
184
+ onAbort: (listener) => {
185
+ if (response !== void 0) onResponseAbort(response.raw, listener);
186
+ }
187
+ };
188
+ }
189
+ export { fastifyAdapter };
190
+
191
+ //# sourceMappingURL=fastify.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fastify.mjs","names":[],"sources":["../src/adapters/fastify.ts"],"sourcesContent":["/**\n * `@znight/http/fastify`: serves Znight routes with [Fastify](https://fastify.dev) 5 (optional\n * peer dependency `fastify`). Routing uses Fastify's router; body parsing, validation and\n * serialisation stay Znight's, so responses are identical to the other adapters.\n */\nimport type {\n AdapterCloseOptions,\n AdapterHandler,\n AdapterReply,\n AdapterRequest,\n AdapterRoute,\n FallbackHandler,\n HttpAdapter,\n HttpServerAddress,\n InjectRequest,\n InjectResponse,\n InjectStreamRequest,\n InjectStreamResponse,\n} from '../adapter.ts'\n\nimport { Readable } from 'node:stream'\nimport Fastify, {\n type FastifyInstance,\n type FastifyReply,\n type FastifyRequest,\n type FastifyServerOptions,\n} from 'fastify'\n\nimport { toFastifyPath } from '../path.ts'\nimport { discardStream } from '../stream.ts'\nimport {\n ConnectionTracker,\n isEncrypted,\n nodeBodyStream,\n normalizeNodeHeaders,\n onResponseAbort,\n readNodeBody,\n withNativeCookies,\n writeNodeReply,\n} from './node-io.ts'\nimport {\n bufferInjectStream,\n type LightInjectOptions,\n lightInjectOptions,\n lightStreamResponse,\n serverAddress,\n} from './shared.ts'\n\n/** Options of {@link fastifyAdapter}. */\nexport interface FastifyAdapterOptions {\n /**\n * An existing instance (with your plugins and hooks). Znight registers its routes in an\n * encapsulated child context (its own body handling and 404 handler) and closes the instance on\n * shutdown. Malformed URLs are then answered by Fastify's `frameworkErrors` handler.\n */\n readonly instance?: FastifyInstance\n /**\n * Options of the instance created when `instance` is absent. Znight sets `exposeHeadRoutes: false`\n * (HEAD is served by Znight), a `maxParamLength` of 8 KiB and a `frameworkErrors` handler.\n */\n readonly options?: FastifyServerOptions\n}\n\n/** The Fastify adapter. */\nexport interface FastifyAdapter extends HttpAdapter {\n readonly name: 'fastify'\n /** The Fastify instance. */\n readonly instance: FastifyInstance\n}\n\n/**\n * Creates a Fastify adapter.\n *\n * ```ts\n * import { fastifyAdapter } from '@znight/http/fastify'\n *\n * HttpPlugin.configure({ adapter: fastifyAdapter({ options: { trustProxy: true } }) })\n * ```\n */\nexport function fastifyAdapter(options: FastifyAdapterOptions = {}): FastifyAdapter {\n return new FastifyHttpAdapter(options)\n}\n\nconst MAX_PARAM_LENGTH = 8192\n\n/** `content-type` headers hidden from Fastify (see `#register`), by request. */\nconst HIDDEN_CONTENT_TYPES = new WeakMap<object, string>()\n\nclass FastifyHttpAdapter implements FastifyAdapter {\n readonly name = 'fastify' as const\n readonly instance: FastifyInstance\n readonly #routes: AdapterRoute[] = []\n readonly #tracker: ConnectionTracker\n #fallback: FallbackHandler | undefined\n #ready: Promise<void> | undefined\n #closing: Promise<void> | undefined\n\n constructor(options: FastifyAdapterOptions) {\n if (options.instance === undefined) {\n const serverOptions = options.options ?? {}\n this.instance = Fastify({\n exposeHeadRoutes: false,\n frameworkErrors: (error, request, reply) => {\n this.#reply(reply, this.#dispatchFallback(request, reply, error)).catch(() => undefined)\n },\n ...serverOptions,\n routerOptions: { maxParamLength: MAX_PARAM_LENGTH, ...serverOptions.routerOptions },\n })\n } else {\n this.instance = options.instance\n }\n this.#tracker = new ConnectionTracker(this.instance.server)\n }\n\n registerRoute(route: AdapterRoute): void {\n if (this.#ready !== undefined) throw new Error('fastifyAdapter: routes must be registered before listen()/inject()')\n this.#routes.push(route)\n }\n\n setFallbackHandler(handler: FallbackHandler): void {\n this.#fallback = handler\n }\n\n #prepare(): Promise<void> {\n this.#ready ??= this.#register()\n return this.#ready\n }\n\n async #register(): Promise<void> {\n const routes = this.#routes\n await this.instance.register((child, _options, done) => {\n // Znight reads and parses bodies itself: accept every content type without consuming it\n child.removeAllContentTypeParsers()\n child.addContentTypeParser('*', (_request, _payload, done) => done(null, undefined))\n // Fastify answers 415 to a content type it cannot parse (`foo`, an empty one) before the route\n // runs: hide such a header from Fastify (Znight reads it, and parses the body as bytes)\n child.addHook('onRequest', (request, _reply, done) => {\n const contentType = request.headers['content-type']\n if (contentType !== undefined && request.mediaType === undefined) {\n HIDDEN_CONTENT_TYPES.set(request.raw, contentType)\n delete request.raw.headers['content-type']\n }\n done()\n })\n for (const route of routes) {\n const handler = route.handler\n child.route({\n method: route.method,\n url: toFastifyPath(route.path),\n exposeHeadRoute: false,\n handler: (request, reply) => this.#reply(reply, handler(adapterRequest(request, reply))),\n })\n }\n child.setNotFoundHandler((request, reply) =>\n this.#reply(reply, this.#dispatchFallback(request, reply, undefined)),\n )\n child.setErrorHandler((error, request, reply) =>\n this.#reply(reply, this.#dispatchFallback(request, reply, error)),\n )\n done()\n })\n await this.instance.ready()\n }\n\n #dispatchFallback(request: FastifyRequest, reply: FastifyReply, error: unknown): Promise<AdapterReply> {\n if (this.#fallback === undefined) return Promise.reject(error ?? new Error('No fallback handler'))\n return this.#fallback(adapterRequest(request, undefined, reply), error)\n }\n\n async #reply(reply: FastifyReply, pending: ReturnType<AdapterHandler>): Promise<FastifyReply> {\n let result: AdapterReply\n try {\n result = await pending\n } catch {\n result = {\n status: 500,\n headers: { 'content-type': 'application/json; charset=utf-8' },\n body: new TextEncoder().encode('{\"statusCode\":500,\"error\":\"Internal Server Error\"}'),\n }\n }\n if (result.raw === true || reply.sent || reply.raw.destroyed) {\n discardStream(result.body instanceof ReadableStream ? result.body : null)\n return reply\n }\n if (result.eventStream === true) {\n // no onSend hook (compression, ETag...) may rewrite an event stream: write it on the raw\n // response, with the headers Fastify hooks already set (`onRequest`...) under the plugin's,\n // and their cookies before the plugin's (as reply.header('set-cookie') does on the other path)\n const native = lowerCaseHeaders(reply.getHeaders())\n const headers = { ...native, ...withNativeCookies(native['set-cookie'], result.headers) }\n reply.hijack()\n void writeNodeReply(reply.raw, { ...result, headers })\n return reply\n }\n reply.code(result.status)\n for (const name of Object.keys(result.headers)) reply.header(name, result.headers[name])\n const body = result.body\n if (body === null) return reply.send()\n if (body instanceof Uint8Array) return reply.send(Buffer.from(body.buffer, body.byteOffset, body.byteLength))\n const stream = body instanceof Blob ? body.stream() : body\n return reply.send(Readable.fromWeb(stream as import('node:stream/web').ReadableStream))\n }\n\n async listen(port: number, host: string): Promise<HttpServerAddress> {\n await this.#prepare()\n this.#closing = undefined\n await this.instance.listen({ port, host })\n const address = this.instance.server.address()\n if (address === null || typeof address === 'string') return serverAddress(host, port)\n return serverAddress(address.address, address.port, address.family)\n }\n\n close(options: AdapterCloseOptions = {}): Promise<void> {\n if (this.#closing === undefined) {\n this.#tracker.closeIdle()\n this.#closing = this.instance.close()\n }\n if (options.force === true) {\n this.instance.server.closeAllConnections?.()\n this.#tracker.destroyAll()\n }\n return this.#closing\n }\n\n inject(request: InjectRequest): Promise<InjectResponse> {\n return bufferInjectStream(stream => this.injectStream(stream), request)\n }\n\n async injectStream(request: InjectStreamRequest): Promise<InjectStreamResponse> {\n await this.#prepare()\n const options: LightInjectOptions = await lightInjectOptions(request)\n // Fastify forwards the options to light-my-request (`payloadAsStream`)\n const inject = this.instance.inject as unknown as (options: LightInjectOptions) => Promise<unknown>\n const response = await inject.call(this.instance, options)\n return lightStreamResponse(response as Parameters<typeof lightStreamResponse>[0], request.signal)\n }\n}\n\nfunction lowerCaseHeaders(\n headers: Readonly<Record<string, string | number | readonly string[] | undefined>>,\n): Record<string, string | string[]> {\n const result: Record<string, string | string[]> = {}\n for (const [name, value] of Object.entries(headers)) {\n if (value !== undefined) result[name.toLowerCase()] = typeof value === 'object' ? [...value] : String(value)\n }\n return result\n}\n\nfunction adapterRequest(\n request: FastifyRequest,\n reply: FastifyReply | undefined,\n response: FastifyReply | undefined = reply,\n): AdapterRequest {\n const hidden = HIDDEN_CONTENT_TYPES.get(request.raw)\n if (hidden !== undefined) {\n // Fastify is done with the content type: restore it (for Znight and `@Req()`)\n HIDDEN_CONTENT_TYPES.delete(request.raw)\n request.raw.headers['content-type'] = hidden\n }\n let body: Promise<Uint8Array> | undefined\n return {\n method: request.raw.method ?? request.method,\n url: request.raw.url ?? request.url,\n headers: normalizeNodeHeaders(request.headers),\n ip: request.ip,\n remoteAddress: request.raw.socket?.remoteAddress,\n encrypted: isEncrypted(request.raw),\n raw: request,\n rawResponse: reply,\n readBody: (limit: number): Promise<Uint8Array> => {\n body ??= readNodeBody(request.raw, limit)\n return body\n },\n streamBody: (limit: number): ReadableStream<Uint8Array> => nodeBodyStream(request.raw, limit),\n rawStatus: (): number | undefined => reply?.statusCode,\n onAbort: (listener: () => void): void => {\n if (response !== undefined) onResponseAbort(response.raw, listener)\n },\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,49 @@
1
+ import { s as HttpAdapter } from "./adapter-CGSceVEN.mjs";
2
+ import { Hono } from "hono";
3
+ /** Options of {@link honoAdapter}. */
4
+ export interface HonoAdapterOptions {
5
+ /**
6
+ * An existing application: its middleware (`app.use(...)`) runs before the Znight routes. Znight
7
+ * sets its `notFound` and `onError` handlers.
8
+ */
9
+ readonly app?: Hono;
10
+ /**
11
+ * Options of `Bun.serve()` on Bun (`tls`, `idleTimeout`, `development`, `error`...); Znight sets
12
+ * `port`, `hostname` and `fetch`. Ignored on Node.js.
13
+ */
14
+ readonly serveOptions?: HonoServeOptions;
15
+ }
16
+ /**
17
+ * Options of `Bun.serve()` (see the Bun documentation; Bun types are not required). Defaults set by
18
+ * the adapter: `maxRequestBodySize` unlimited (Znight enforces `bodyLimit` / `@BodyLimit` itself)
19
+ * and Bun's `idleTimeout` (10 s), lifted while a streamed response is sent.
20
+ */
21
+ export interface HonoServeOptions {
22
+ /** Seconds a connection may stay idle (`0`: never closed). Default: 10. */
23
+ readonly idleTimeout?: number;
24
+ /** Request bodies above this size are refused by Bun (413). Default: unlimited. */
25
+ readonly maxRequestBodySize?: number;
26
+ /** TLS options (`{ key, cert }`). */
27
+ readonly tls?: unknown;
28
+ /** Any other `Bun.serve()` option. */
29
+ readonly [option: string]: unknown;
30
+ }
31
+ /** The Hono adapter. */
32
+ export interface HonoAdapter extends HttpAdapter {
33
+ readonly name: "hono";
34
+ /** The Hono application. */
35
+ readonly app: Hono;
36
+ /** Fetch handler of the application (`export default { fetch: adapter.fetch }` on edge runtimes). */
37
+ readonly fetch: (request: Request, env?: unknown) => Response | Promise<Response>;
38
+ }
39
+ /**
40
+ * Creates a Hono adapter.
41
+ *
42
+ * ```ts
43
+ * import { honoAdapter } from '@znight/http/hono'
44
+ *
45
+ * HttpPlugin.configure({ adapter: honoAdapter() })
46
+ * ```
47
+ */
48
+ export declare function honoAdapter(options?: HonoAdapterOptions): HonoAdapter;
49
+ //# sourceMappingURL=hono.d.mts.map
package/dist/hono.mjs ADDED
@@ -0,0 +1,319 @@
1
+ import { N as discardStream, b as webBodyStream, f as bufferInjectStream, n as closeServer, p as injectFetchStream, s as onResponseAbort, t as ConnectionTracker, v as readWebStream, y as serverAddress } from "./node-io-BY1ZDLRQ.mjs";
2
+ import { r as isBun } from "./utils-DCbu3a4w.mjs";
3
+ import { toHonoPath } from "./path.mjs";
4
+ import { Hono } from "hono";
5
+ // src/adapters/hono.ts
6
+ /**
7
+ * Creates a Hono adapter.
8
+ *
9
+ * ```ts
10
+ * import { honoAdapter } from '@znight/http/hono'
11
+ *
12
+ * HttpPlugin.configure({ adapter: honoAdapter() })
13
+ * ```
14
+ */
15
+ function honoAdapter(options = {}) {
16
+ return new HonoHttpAdapter(options);
17
+ }
18
+ /** Default `idleTimeout` of `Bun.serve()`, in seconds. */
19
+ const BUN_IDLE_TIMEOUT = 10;
20
+ /** Response bodies built while serving with `Bun.serve()`, by body (Hono may copy a `Response`, not its body). */
21
+ const BUN_BODIES = /* @__PURE__ */ new WeakMap();
22
+ /** Bindings of injected requests (the remote address the other adapters report for `inject()`). */
23
+ const INJECT_ENV = Object.freeze({ incoming: Object.freeze({ socket: Object.freeze({ remoteAddress: "127.0.0.1" }) }) });
24
+ var HonoHttpAdapter = class {
25
+ name = "hono";
26
+ app;
27
+ #serveOptions;
28
+ #fallback;
29
+ #prepared = false;
30
+ #bun;
31
+ #node;
32
+ #tracker;
33
+ #closing;
34
+ constructor(options) {
35
+ this.app = options.app ?? new Hono();
36
+ this.#serveOptions = options.serveOptions ?? {};
37
+ }
38
+ fetch = (request, env) => {
39
+ this.#prepare();
40
+ return this.app.fetch(request, env);
41
+ };
42
+ registerRoute(route) {
43
+ const handler = route.handler;
44
+ const path = toHonoPath(route.path);
45
+ if (route.method === "HEAD") {
46
+ this.app.get(path, (c, next) => c.req.raw.method === "HEAD" ? this.#serve(c, handler) : next().then(() => c.res));
47
+ return;
48
+ }
49
+ this.app.on(route.method, path, (c) => this.#serve(c, handler));
50
+ }
51
+ setFallbackHandler(handler) {
52
+ this.#fallback = handler;
53
+ }
54
+ #prepare() {
55
+ if (this.#prepared) return;
56
+ this.#prepared = true;
57
+ this.app.notFound((c) => this.#serveFallback(c, void 0));
58
+ this.app.onError((error, c) => this.#serveFallback(c, error));
59
+ }
60
+ async #serve(c, handler) {
61
+ const request = adapterRequest(c);
62
+ const reply = this.#fallback !== void 0 && pathOf(request.url) !== pathOf(pathAndQuery(c.req.raw.url)) ? await this.#fallback(request) : await handler(request);
63
+ return this.#toResponse(c, reply);
64
+ }
65
+ async #serveFallback(c, error) {
66
+ if (this.#fallback === void 0) throw error ?? /* @__PURE__ */ new Error("No fallback handler");
67
+ return this.#toResponse(c, await this.#fallback(adapterRequest(c), error));
68
+ }
69
+ #toResponse(c, reply) {
70
+ const outgoing = nodeOutgoing(c);
71
+ if (outgoing !== void 0) {
72
+ const body = reply.body instanceof ReadableStream ? reply.body : null;
73
+ if (outgoing.destroyed) {
74
+ discardStream(body);
75
+ return toResponse(c, {
76
+ ...reply,
77
+ body: null
78
+ });
79
+ }
80
+ if (body !== null) outgoing.once("close", () => {
81
+ if (!outgoing.writableFinished) discardStream(body);
82
+ });
83
+ if (reply.eventStream === true && typeof outgoing.setTimeout === "function") outgoing.setTimeout(0);
84
+ }
85
+ if (this.#bun === void 0 || !(reply.body instanceof ReadableStream)) return toResponse(c, reply);
86
+ const reader = reply.body.getReader();
87
+ let ended = false;
88
+ let onEnd = () => void 0;
89
+ const end = () => {
90
+ if (ended) return;
91
+ ended = true;
92
+ onEnd();
93
+ };
94
+ const body = new ReadableStream({
95
+ async pull(controller) {
96
+ let result;
97
+ try {
98
+ result = await reader.read();
99
+ } catch (error) {
100
+ end();
101
+ throw error;
102
+ }
103
+ if (result.done) {
104
+ end();
105
+ controller.close();
106
+ return;
107
+ }
108
+ controller.enqueue(result.value);
109
+ },
110
+ cancel(reason) {
111
+ end();
112
+ return reader.cancel(reason);
113
+ }
114
+ }, { highWaterMark: 0 });
115
+ BUN_BODIES.set(body, {
116
+ blob: reply.blob,
117
+ onEnd: (callback) => {
118
+ onEnd = callback;
119
+ }
120
+ });
121
+ return toResponse(c, {
122
+ ...reply,
123
+ body
124
+ });
125
+ }
126
+ /** The fetch handler given to `Bun.serve()`. */
127
+ async #bunFetch(request, server, idleTimeout) {
128
+ const response = await this.app.fetch(request, server);
129
+ const body = response.body;
130
+ const served = body === null ? void 0 : BUN_BODIES.get(body);
131
+ if (body === null || served === void 0) return response;
132
+ BUN_BODIES.delete(body);
133
+ if (served.blob !== void 0 && !body.locked) {
134
+ const native = new Response(served.blob, {
135
+ status: response.status,
136
+ statusText: response.statusText,
137
+ headers: response.headers
138
+ });
139
+ setImmediate(() => discardStream(body));
140
+ return native;
141
+ }
142
+ if (idleTimeout > 0 && typeof server.timeout === "function") {
143
+ server.timeout(request, 0);
144
+ served.onEnd(() => {
145
+ try {
146
+ server.timeout?.(request, idleTimeout);
147
+ } catch {}
148
+ });
149
+ }
150
+ return response;
151
+ }
152
+ async listen(port, host) {
153
+ this.#prepare();
154
+ this.#closing = void 0;
155
+ const bun = globalThis.Bun;
156
+ if (isBun() && bun !== void 0) {
157
+ const options = this.#serveOptions;
158
+ const idleTimeout = typeof options.idleTimeout === "number" ? options.idleTimeout : BUN_IDLE_TIMEOUT;
159
+ const server = bun.serve({
160
+ maxRequestBodySize: Number.MAX_SAFE_INTEGER,
161
+ ...options,
162
+ port,
163
+ hostname: host,
164
+ fetch: (request, server) => this.#bunFetch(request, server, idleTimeout)
165
+ });
166
+ this.#bun = server;
167
+ return serverAddress(server.hostname, server.port);
168
+ }
169
+ let nodeServer;
170
+ try {
171
+ nodeServer = await import("@hono/node-server");
172
+ } catch (error) {
173
+ throw new Error("honoAdapter on Node.js requires the optional peer dependency \"@hono/node-server\" (npm i @hono/node-server)", { cause: error });
174
+ }
175
+ const server = nodeServer.createAdaptorServer({ fetch: this.app.fetch });
176
+ this.#node = server;
177
+ this.#tracker = new ConnectionTracker(server);
178
+ await new Promise((resolve, reject) => {
179
+ const onError = (error) => reject(error);
180
+ server.once("error", onError);
181
+ server.listen(port, host, () => {
182
+ server.off("error", onError);
183
+ resolve();
184
+ });
185
+ });
186
+ const address = server.address();
187
+ if (address === null || typeof address === "string") return serverAddress(host, port);
188
+ return serverAddress(address.address, address.port, address.family);
189
+ }
190
+ close(options = {}) {
191
+ const bun = this.#bun;
192
+ if (bun !== void 0) {
193
+ if (options.force === true) this.#closing = Promise.resolve(bun.stop(true));
194
+ else this.#closing ??= Promise.resolve(bun.stop(false));
195
+ return this.#closing;
196
+ }
197
+ const server = this.#node;
198
+ const tracker = this.#tracker;
199
+ if (server === void 0 || tracker === void 0) return Promise.resolve();
200
+ this.#closing = closeServer(server, tracker, this.#closing, options.force === true);
201
+ return this.#closing;
202
+ }
203
+ inject(request) {
204
+ return bufferInjectStream((stream) => this.injectStream(stream), request);
205
+ }
206
+ injectStream(request) {
207
+ this.#prepare();
208
+ return injectFetchStream((request) => this.app.request(request, void 0, INJECT_ENV), request);
209
+ }
210
+ };
211
+ function adapterRequest(c) {
212
+ const request = c.req.raw;
213
+ const headers = {};
214
+ for (const [name, value] of request.headers) headers[name] = value;
215
+ const incoming = nodeIncoming(c);
216
+ const address = remoteAddress(c);
217
+ let body;
218
+ return {
219
+ method: request.method,
220
+ url: pathAndQuery(typeof incoming?.url === "string" && incoming.url !== "" ? incoming.url : request.url),
221
+ headers,
222
+ ip: address,
223
+ remoteAddress: address,
224
+ encrypted: incoming === void 0 ? request.url.startsWith("https:") : incoming.socket?.encrypted === true,
225
+ raw: request,
226
+ rawResponse: c,
227
+ readBody: (limit) => {
228
+ body ??= readWebStream(() => request.body, limit, headers["content-length"]);
229
+ return body;
230
+ },
231
+ streamBody: (limit) => webBodyStream(() => request.body, limit, headers["content-length"]),
232
+ hasBody: () => incoming === void 0 ? request.body !== null : incoming.httpVersionMajor >= 2 && incoming.stream?.endAfterHeaders === false,
233
+ onAbort: (listener) => {
234
+ const outgoing = nodeOutgoing(c);
235
+ if (outgoing !== void 0) {
236
+ onResponseAbort(outgoing, listener);
237
+ return;
238
+ }
239
+ const signal = request.signal;
240
+ if (signal.aborted) queueMicrotask(listener);
241
+ else signal.addEventListener("abort", listener, { once: true });
242
+ }
243
+ };
244
+ }
245
+ /** The Node.js response of `@hono/node-server` (`c.env.outgoing`), absent on Bun and for `inject()`. */
246
+ function nodeOutgoing(c) {
247
+ const outgoing = c.env?.outgoing;
248
+ return typeof outgoing?.once === "function" ? outgoing : void 0;
249
+ }
250
+ /** The Node.js request of `@hono/node-server` (`c.env.incoming`), absent on Bun and for `inject()`. */
251
+ function nodeIncoming(c) {
252
+ const incoming = c.env?.incoming;
253
+ return typeof incoming?.httpVersionMajor === "number" ? incoming : void 0;
254
+ }
255
+ function pathAndQuery(url) {
256
+ if (url.startsWith("/")) return url;
257
+ const scheme = url.indexOf("://");
258
+ const start = url.indexOf("/", scheme === -1 ? 0 : scheme + 3);
259
+ return start === -1 ? "/" : url.slice(start);
260
+ }
261
+ function pathOf(url) {
262
+ const query = url.indexOf("?");
263
+ return query === -1 ? url : url.slice(0, query);
264
+ }
265
+ function remoteAddress(c) {
266
+ const env = c.env;
267
+ if (env === void 0 || env === null) return void 0;
268
+ if (typeof env.requestIP === "function") return env.requestIP(c.req.raw)?.address ?? void 0;
269
+ return env.incoming?.socket?.remoteAddress;
270
+ }
271
+ function toResponse(c, reply) {
272
+ if (reply.raw === true) {
273
+ discardStream(reply.body instanceof ReadableStream ? reply.body : null);
274
+ return c.res;
275
+ }
276
+ const headers = new Headers();
277
+ try {
278
+ for (const name of Object.keys(reply.headers)) {
279
+ const value = reply.headers[name];
280
+ if (typeof value === "string") headers.set(name, value);
281
+ else if (value !== void 0) for (const item of value) headers.append(name, item);
282
+ }
283
+ } catch {
284
+ discardStream(reply.body instanceof ReadableStream ? reply.body : null);
285
+ return new Response("{\"statusCode\":500,\"error\":\"Internal Server Error\"}", {
286
+ status: 500,
287
+ headers: { "content-type": "application/json; charset=utf-8" }
288
+ });
289
+ }
290
+ keepContextCookies(c, headers);
291
+ return new Response(reply.body, {
292
+ status: reply.status,
293
+ headers
294
+ });
295
+ }
296
+ /**
297
+ * Keeps the cookies Hono middleware set before the Znight route (`setCookie(c, ...)`,
298
+ * `c.header('set-cookie', ..., { append: true })`) before the reply's own, like the other adapters.
299
+ * Hono drops the headers of the context when a handler returns its own `Response` (or, when a
300
+ * middleware read `c.res`, replaces the response's cookies with the context's): the reply's cookies
301
+ * are added to the context too, so that both ways end with every cookie, in order.
302
+ */
303
+ function keepContextCookies(c, headers) {
304
+ let context;
305
+ try {
306
+ context = c.newResponse(null).headers.getSetCookie();
307
+ } catch {
308
+ return;
309
+ }
310
+ if (context.length === 0) return;
311
+ const own = headers.getSetCookie();
312
+ for (const line of own) c.header("set-cookie", line, { append: true });
313
+ headers.delete("set-cookie");
314
+ for (const line of context) headers.append("set-cookie", line);
315
+ for (const line of own) headers.append("set-cookie", line);
316
+ }
317
+ export { honoAdapter };
318
+
319
+ //# sourceMappingURL=hono.mjs.map