@blixis-io/http 0.1.0 → 0.3.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.
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { Class, Token } from "@blixis-io/di";
2
+ import { ZodType } from "zod";
2
3
  import { Application, CreateApplicationOptions, ModuleRef } from "@blixis-io/core";
3
4
  import { IncomingMessage, ServerResponse } from "node:http";
4
- import { ZodType } from "zod";
5
5
  //#region src/decorators/api-metadata.d.ts
6
6
  interface ApiOperationOptions {
7
7
  summary?: string;
@@ -113,7 +113,17 @@ export declare function getHttpCode(target: object, propertyKey: string | symbol
113
113
  * implementation drifted from its own declared contract), not a client
114
114
  * error, so it never reaches the client as anything but a generic 500.
115
115
  */
116
- export declare function Returns(schema: ZodType): MethodDecorator;
116
+ export declare function Returns(schema: ZodType, options?: ReturnsOptions): MethodDecorator;
117
+ interface ReturnsOptions {
118
+ /**
119
+ * Overrides the app-wide `responseValidation` for this route, in either direction. `false` sends the
120
+ * handler's value as-is: no parsing, so none of the schema's coercion, defaults or stripping of unknown
121
+ * keys applies. The schema still describes the route in the OpenAPI document.
122
+ */
123
+ validate?: boolean;
124
+ }
125
+ /** The per-route `validate` override, or `undefined` when the route follows the app-wide setting. */
126
+ export declare function getReturnsValidate(target: object, propertyKey: string | symbol): boolean | undefined;
117
127
  export declare function getReturnsSchema(target: object, propertyKey: string | symbol): ZodType | undefined;
118
128
  //#endregion
119
129
  //#region src/exceptions.d.ts
@@ -145,6 +155,9 @@ export declare class PayloadTooLargeException extends HttpException {
145
155
  export declare class UnsupportedMediaTypeException extends HttpException {
146
156
  constructor(detail?: string);
147
157
  }
158
+ export declare class GatewayTimeoutException extends HttpException {
159
+ constructor(detail?: string);
160
+ }
148
161
  //#endregion
149
162
  //#region src/router.d.ts
150
163
  interface RouteFound<T> {
@@ -173,6 +186,18 @@ export declare class Router<T> {
173
186
  //#region src/handler.d.ts
174
187
  interface HandlerOptions {
175
188
  bodyLimit?: number;
189
+ /**
190
+ * Milliseconds a request may take before the client gets a 504. Off by default. The handler (and
191
+ * anything it awaits) is not killed — it sees the timeout through `request.signal` (`@Req()`), and
192
+ * must pass that signal on to cancellable work (`fetch`, DB queries) to actually stop.
193
+ */
194
+ requestTimeout?: number;
195
+ /**
196
+ * `"always"` (default) validates every `@Returns` route and sends the parsed value, so the schema also
197
+ * strips unknown keys. `"never"` sends handler values as-is, which skips that stripping: an object with
198
+ * extra fields (say a `passwordHash`) is then sent whole. A route's `@Returns(..., { validate })` wins.
199
+ */
200
+ responseValidation?: "always" | "never";
176
201
  }
177
202
  export declare class NotAControllerError extends Error {
178
203
  readonly name = "NotAControllerError";
@@ -186,6 +211,8 @@ interface RouteEntry {
186
211
  paramSources: ReadonlyMap<number, ParamSource>;
187
212
  httpCode?: number | undefined;
188
213
  responseSchema?: ZodType | undefined;
214
+ /** Per-route `@Returns(..., { validate })` override; `undefined` follows the app-wide `responseValidation`. */
215
+ validateResponse?: boolean | undefined;
189
216
  guards: Class<CanActivate>[];
190
217
  interceptors: Class<Interceptor>[];
191
218
  }
@@ -200,7 +227,15 @@ export declare function buildRouter(controllers: readonly Class[]): Router<Route
200
227
  export declare function createHandler(controllers: readonly Class[], application: Application, options?: HandlerOptions): (request: Request) => Promise<Response>;
201
228
  //#endregion
202
229
  //#region src/http-application.d.ts
203
- type HttpApplicationOptions = HandlerOptions & CreateApplicationOptions;
230
+ interface ShutdownOptions {
231
+ /**
232
+ * Milliseconds `close()` lets in-flight requests finish before their sockets are destroyed (their
233
+ * `request.signal` aborts). Default 10 000; `Infinity` waits indefinitely.
234
+ */
235
+ shutdownTimeout?: number;
236
+ }
237
+ type HttpApplicationOptions = HandlerOptions & CreateApplicationOptions & ShutdownOptions;
238
+ type MountedHandler = (request: Request) => Response | Promise<Response>;
204
239
  interface ListenHandle {
205
240
  port: number;
206
241
  }
@@ -213,6 +248,13 @@ export declare class HttpApplication {
213
248
  get<T>(token: Token<T>): T;
214
249
  /** Every controller class in this app's module graph — what `@blixis-io/openapi` walks to build a document that stays in sync with the real running app. */
215
250
  get controllers(): readonly Class[];
251
+ /**
252
+ * Serves an exact path with a plain Web-standard handler, ahead of the controller router — for
253
+ * framework-level endpoints (such as an OpenAPI document) that need the finished app, which a
254
+ * controller can't get at construction time. Mounted routes bypass guards and interceptors, so
255
+ * they are public; mounting the same method and path twice throws.
256
+ */
257
+ mount(method: "GET" | "POST", path: string, handler: MountedHandler): void;
216
258
  /** Runs a request through the handler in-process, without a socket. */
217
259
  handle(request: Request): Promise<Response>;
218
260
  listen(port: number, hostname?: string): Promise<ListenHandle>;
@@ -220,9 +262,34 @@ export declare class HttpApplication {
220
262
  }
221
263
  export declare function createHttpApplication(rootModule: ModuleRef, options?: HttpApplicationOptions): Promise<HttpApplication>;
222
264
  //#endregion
265
+ //#region src/fetch-handler.d.ts
266
+ /** A Web-standard entry for platforms that call `fetch(request)` per request (Vercel, Netlify, Cloudflare Workers, Deno Deploy, ...). */
267
+ interface FetchHandler {
268
+ fetch(request: Request): Promise<Response>;
269
+ /** Runs shutdown hooks. Serverless platforms rarely call this; it exists for tests and long-lived hosts. */
270
+ close(signal?: string): Promise<void>;
271
+ }
272
+ /**
273
+ * Boots the application lazily on the first request and reuses it afterwards, so a cold start pays for
274
+ * module resolution once, not per request. Concurrent first requests share one boot. If boot fails the
275
+ * error is logged, that request gets a generic 500, and the next request tries again instead of caching
276
+ * the failure for the lifetime of the instance.
277
+ *
278
+ * ```ts
279
+ * export default createFetchHandler(AppModule); // Vercel / Workers: `{ fetch }`
280
+ * ```
281
+ *
282
+ * `listen()`, `shutdownTimeout` and `SIGTERM` handling don't apply here: the platform owns the socket.
283
+ */
284
+ export declare function createFetchHandler(rootModule: ModuleRef, options?: HttpApplicationOptions): FetchHandler;
285
+ //#endregion
223
286
  //#region src/node-adapter.d.ts
224
- /** Builds a Web-standard `Request` from a Node `IncomingMessage`, including a body stream and abort signal. */
225
- export declare function toWebRequest(req: IncomingMessage, baseUrl: string): Request;
287
+ /**
288
+ * Builds a Web-standard `Request` from a Node `IncomingMessage`, including a body stream and abort signal.
289
+ * Pass the matching `ServerResponse` so the signal also fires when the client disconnects before the
290
+ * response finished — `req`'s own "aborted" event is deprecated and misses some disconnects.
291
+ */
292
+ export declare function toWebRequest(req: IncomingMessage, baseUrl: string, res?: ServerResponse): Request;
226
293
  /** Writes a Web-standard `Response` back onto a Node `ServerResponse`. */
227
294
  export declare function sendWebResponse(response: Response, res: ServerResponse): Promise<void>;
228
295
  //#endregion
@@ -276,5 +343,5 @@ export declare class ResponseValidationError extends Error {
276
343
  /** Validates a handler's return value against its `@Returns` schema, if any — returns the value (possibly parsed/coerced) unchanged when there's no schema. */
277
344
  export declare function validateResponse(schema: ZodType | undefined, value: unknown): Promise<unknown>;
278
345
  //#endregion
279
- export type { ApiOperationOptions, CanActivate, ExecutionContext, HandlerOptions, HttpApplicationOptions, HttpMethod, Interceptor, ListenHandle, ParamResolutionContext, ParamSource, RouteDefinition, RouteFound, RouteLookupResult, RouteMethodNotAllowed, RouteNotFound };
346
+ export type { ApiOperationOptions, CanActivate, ExecutionContext, FetchHandler, HandlerOptions, HttpApplicationOptions, HttpMethod, Interceptor, ListenHandle, MountedHandler, ParamResolutionContext, ParamSource, ReturnsOptions, RouteDefinition, RouteFound, RouteLookupResult, RouteMethodNotAllowed, RouteNotFound, ShutdownOptions };
280
347
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/decorators/api-metadata.ts","../src/decorators/controller.ts","../src/decorators/guards.ts","../src/decorators/interceptors.ts","../src/decorators/params.ts","../src/types.ts","../src/decorators/routes.ts","../src/exceptions.ts","../src/router.ts","../src/handler.ts","../src/http-application.ts","../src/node-adapter.ts","../src/params.ts","../src/request-context.ts","../src/response.ts"],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/decorators/api-metadata.ts","../src/decorators/controller.ts","../src/decorators/guards.ts","../src/decorators/interceptors.ts","../src/decorators/params.ts","../src/types.ts","../src/decorators/routes.ts","../src/exceptions.ts","../src/router.ts","../src/handler.ts","../src/http-application.ts","../src/fetch-handler.ts","../src/node-adapter.ts","../src/params.ts","../src/request-context.ts","../src/response.ts"],"mappings":"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"}
package/dist/index.js CHANGED
@@ -1,8 +1,10 @@
1
1
  import { Injectable, defineMetadata, getMetadata } from "@blixis-io/di";
2
2
  import { AsyncLocalStorage } from "node:async_hooks";
3
+ import { z } from "zod";
3
4
  import { Module, createApplication } from "@blixis-io/core";
4
5
  import { createServer } from "node:http";
5
6
  import { Readable } from "node:stream";
7
+ import { pipeline } from "node:stream/promises";
6
8
  //#region src/decorators/api-metadata.ts
7
9
  const API_OPERATION = Symbol("blixis:api-operation");
8
10
  const API_TAGS = Symbol("blixis:api-tags");
@@ -160,6 +162,7 @@ function getParamSources(target, propertyKey) {
160
162
  const ROUTES = Symbol("blixis:routes");
161
163
  const HTTP_CODE = Symbol("blixis:http-code");
162
164
  const RETURNS = Symbol("blixis:returns");
165
+ const RETURNS_VALIDATE = Symbol("blixis:returns-validate");
163
166
  function routeDecorator(method, path) {
164
167
  return (target, propertyKey) => {
165
168
  const ctor = target.constructor;
@@ -195,11 +198,16 @@ function getHttpCode(target, propertyKey) {
195
198
  * implementation drifted from its own declared contract), not a client
196
199
  * error, so it never reaches the client as anything but a generic 500.
197
200
  */
198
- function Returns(schema) {
201
+ function Returns(schema, options = {}) {
199
202
  return (target, propertyKey) => {
200
203
  defineMetadata(RETURNS, schema, target, propertyKey);
204
+ if (options.validate !== void 0) defineMetadata(RETURNS_VALIDATE, options.validate, target, propertyKey);
201
205
  };
202
206
  }
207
+ /** The per-route `validate` override, or `undefined` when the route follows the app-wide setting. */
208
+ function getReturnsValidate(target, propertyKey) {
209
+ return getMetadata(RETURNS_VALIDATE, target, propertyKey);
210
+ }
203
211
  function getReturnsSchema(target, propertyKey) {
204
212
  return getMetadata(RETURNS, target, propertyKey);
205
213
  }
@@ -257,6 +265,12 @@ var UnsupportedMediaTypeException = class extends HttpException {
257
265
  this.name = "UnsupportedMediaTypeException";
258
266
  }
259
267
  };
268
+ var GatewayTimeoutException = class extends HttpException {
269
+ constructor(detail = "Gateway Timeout") {
270
+ super(504, detail);
271
+ this.name = "GatewayTimeoutException";
272
+ }
273
+ };
260
274
  //#endregion
261
275
  //#region src/params.ts
262
276
  async function parseWithSchema(schema, value) {
@@ -345,10 +359,22 @@ var ResponseValidationError = class extends Error {
345
359
  this.name = "ResponseValidationError";
346
360
  }
347
361
  };
362
+ /**
363
+ * Sync parsing is 3-4x faster than `safeParseAsync` on large payloads, so try it first and only fall back
364
+ * when the schema contains an async refinement/transform (Zod signals that with `$ZodAsyncError`).
365
+ */
366
+ async function parse(schema, value) {
367
+ try {
368
+ return schema.safeParse(value);
369
+ } catch (error) {
370
+ if (error instanceof z.core.$ZodAsyncError) return schema.safeParseAsync(value);
371
+ throw error;
372
+ }
373
+ }
348
374
  /** Validates a handler's return value against its `@Returns` schema, if any — returns the value (possibly parsed/coerced) unchanged when there's no schema. */
349
375
  async function validateResponse(schema, value) {
350
376
  if (!schema) return value;
351
- const result = await schema.safeParseAsync(value);
377
+ const result = await parse(schema, value);
352
378
  if (!result.success) throw new ResponseValidationError(result.error.issues);
353
379
  return result.data;
354
380
  }
@@ -483,19 +509,36 @@ function buildRouter(controllers) {
483
509
  paramSources: getParamSources(prototype, route.propertyKey),
484
510
  httpCode: getHttpCode(prototype, route.propertyKey),
485
511
  responseSchema: getReturnsSchema(prototype, route.propertyKey),
512
+ validateResponse: getReturnsValidate(prototype, route.propertyKey),
486
513
  guards: [...classGuards, ...getMethodGuards(prototype, route.propertyKey)],
487
514
  interceptors: [...classInterceptors, ...getMethodInterceptors(prototype, route.propertyKey)]
488
515
  });
489
516
  }
490
517
  return router;
491
518
  }
519
+ /** Reads the stream chunk by chunk and cancels it the moment `limit` bytes are exceeded, so a chunked body without a `Content-Length` can't be buffered whole before being rejected. */
520
+ async function readBodyText(body, limit) {
521
+ const reader = body.getReader();
522
+ const decoder = new TextDecoder();
523
+ let received = 0;
524
+ let text = "";
525
+ for (;;) {
526
+ const { done, value } = await reader.read();
527
+ if (done) return text + decoder.decode();
528
+ received += value.byteLength;
529
+ if (received > limit) {
530
+ await reader.cancel();
531
+ throw new PayloadTooLargeException();
532
+ }
533
+ text += decoder.decode(value, { stream: true });
534
+ }
535
+ }
492
536
  async function readJsonBody(request, bodyLimit) {
493
537
  if (request.body === null) return;
494
538
  if (!(request.headers.get("content-type") ?? "").toLowerCase().startsWith("application/json")) throw new UnsupportedMediaTypeException();
495
539
  const contentLength = request.headers.get("content-length");
496
540
  if (contentLength !== null && Number(contentLength) > bodyLimit) throw new PayloadTooLargeException();
497
- const text = await request.text();
498
- if (new TextEncoder().encode(text).length > bodyLimit) throw new PayloadTooLargeException();
541
+ const text = await readBodyText(request.body, bodyLimit);
499
542
  if (text.length === 0) return;
500
543
  try {
501
544
  return JSON.parse(text);
@@ -512,7 +555,8 @@ const STATUS_TITLES = {
512
555
  409: "Conflict",
513
556
  413: "Payload Too Large",
514
557
  415: "Unsupported Media Type",
515
- 500: "Internal Server Error"
558
+ 500: "Internal Server Error",
559
+ 504: "Gateway Timeout"
516
560
  };
517
561
  function problemResponse(status, detail, extra) {
518
562
  const body = {
@@ -537,20 +581,42 @@ function exceptionToResponse(error) {
537
581
  * entirely — both are deliberate escape hatches from the normal JSON path,
538
582
  * not a value the schema was ever meant to describe.
539
583
  */
540
- async function toResponse(value, httpCode, responseSchema) {
584
+ async function toResponse(value, httpCode, responseSchema, validate) {
541
585
  if (value instanceof Response) return value;
542
586
  if (value === void 0) return new Response(null, { status: httpCode ?? 204 });
543
- const validated = await validateResponse(responseSchema, value);
587
+ const validated = validate ? await validateResponse(responseSchema, value) : value;
544
588
  return new Response(JSON.stringify(validated), {
545
589
  status: httpCode ?? 200,
546
590
  headers: { "content-type": "application/json" }
547
591
  });
548
592
  }
593
+ /** Same request, but its `signal` also fires after `ms` — so handlers see the timeout via `request.signal`. */
594
+ function withTimeout(request, ms) {
595
+ const signal = AbortSignal.any([request.signal, AbortSignal.timeout(ms)]);
596
+ return new Request(request, { signal });
597
+ }
598
+ /** Settles with `work`, unless `signal` aborts first: a client disconnect becomes a 499, a timeout a 504. `work` keeps running; its late result/rejection is ignored. */
599
+ function raceAbort(work, signal, clientSignal) {
600
+ const abortResponse = () => clientSignal.aborted ? new Response(null, { status: 499 }) : exceptionToResponse(new GatewayTimeoutException());
601
+ if (signal.aborted) return Promise.resolve(abortResponse());
602
+ return new Promise((resolve, reject) => {
603
+ const onAbort = () => {
604
+ resolve(abortResponse());
605
+ };
606
+ signal.addEventListener("abort", onAbort, { once: true });
607
+ work.then(resolve, reject).finally(() => {
608
+ signal.removeEventListener("abort", onAbort);
609
+ });
610
+ });
611
+ }
549
612
  /** Builds a single `(Request) => Promise<Response>` function serving every controller's routes. */
550
613
  function createHandler(controllers, application, options = {}) {
551
614
  const router = buildRouter(controllers);
552
615
  const bodyLimit = options.bodyLimit ?? DEFAULT_BODY_LIMIT;
553
- return async function handle(request) {
616
+ const requestTimeout = options.requestTimeout;
617
+ const validateByDefault = options.responseValidation !== "never";
618
+ return async function handle(incoming) {
619
+ const request = requestTimeout === void 0 ? incoming : withTimeout(incoming, requestTimeout);
554
620
  const url = new URL(request.url);
555
621
  const match = router.match(request.method, url.pathname);
556
622
  if (match.kind === "not-found") return problemResponse(404, "No route matches this path");
@@ -576,15 +642,16 @@ function createHandler(controllers, application, options = {}) {
576
642
  return cachedBody.value;
577
643
  }
578
644
  });
579
- return toResponse(await application.get(route.controller)[route.propertyKey]?.(...args), route.httpCode, route.responseSchema);
645
+ return toResponse(await application.get(route.controller)[route.propertyKey]?.(...args), route.httpCode, route.responseSchema, route.validateResponse ?? validateByDefault);
580
646
  };
581
- return await route.interceptors.reduceRight((next, interceptorClass) => {
647
+ const pipeline = route.interceptors.reduceRight((next, interceptorClass) => {
582
648
  const interceptor = application.get(interceptorClass);
583
649
  return async () => interceptor.intercept({
584
650
  request,
585
651
  params: match.params
586
652
  }, next);
587
- }, invoke)();
653
+ }, invoke);
654
+ return await (requestTimeout === void 0 ? pipeline() : raceAbort(pipeline(), request.signal, incoming.signal));
588
655
  } catch (error) {
589
656
  return exceptionToResponse(error);
590
657
  }
@@ -593,8 +660,12 @@ function createHandler(controllers, application, options = {}) {
593
660
  }
594
661
  //#endregion
595
662
  //#region src/node-adapter.ts
596
- /** Builds a Web-standard `Request` from a Node `IncomingMessage`, including a body stream and abort signal. */
597
- function toWebRequest(req, baseUrl) {
663
+ /**
664
+ * Builds a Web-standard `Request` from a Node `IncomingMessage`, including a body stream and abort signal.
665
+ * Pass the matching `ServerResponse` so the signal also fires when the client disconnects before the
666
+ * response finished — `req`'s own "aborted" event is deprecated and misses some disconnects.
667
+ */
668
+ function toWebRequest(req, baseUrl, res) {
598
669
  const method = req.method ?? "GET";
599
670
  const url = new URL(req.url ?? "/", baseUrl);
600
671
  const headers = new Headers();
@@ -606,6 +677,9 @@ function toWebRequest(req, baseUrl) {
606
677
  req.once("aborted", () => {
607
678
  controller.abort();
608
679
  });
680
+ res?.once("close", () => {
681
+ if (!res.writableFinished) controller.abort();
682
+ });
609
683
  const hasBody = method !== "GET" && method !== "HEAD" && (req.headers["transfer-encoding"] !== void 0 || req.headers["content-length"] !== void 0 && req.headers["content-length"] !== "0");
610
684
  return new Request(url, {
611
685
  method,
@@ -626,16 +700,18 @@ async function sendWebResponse(response, res) {
626
700
  res.end();
627
701
  return;
628
702
  }
629
- const body = Readable.fromWeb(response.body);
630
- await new Promise((resolve, reject) => {
631
- res.once("finish", resolve);
632
- res.once("error", reject);
633
- body.once("error", reject);
634
- body.pipe(res);
635
- });
703
+ try {
704
+ await pipeline(Readable.fromWeb(response.body), res);
705
+ } catch (error) {
706
+ if (!isPrematureClose(error)) throw error;
707
+ }
708
+ }
709
+ function isPrematureClose(error) {
710
+ return error instanceof Error && "code" in error && error.code === "ERR_STREAM_PREMATURE_CLOSE";
636
711
  }
637
712
  //#endregion
638
713
  //#region src/http-application.ts
714
+ const DEFAULT_SHUTDOWN_TIMEOUT = 1e4;
639
715
  /** Provides `RequestContext` app-wide, without the user needing to import anything — every `createHttpApplication` root gets wrapped with this. */
640
716
  let RequestContextModule = class RequestContextModule {};
641
717
  RequestContextModule = __decorate([Module({
@@ -650,10 +726,13 @@ HttpRootModule = __decorate([Module()], HttpRootModule);
650
726
  var HttpApplication = class HttpApplication {
651
727
  #app;
652
728
  #handle;
729
+ #shutdownTimeout;
730
+ #mounted = /* @__PURE__ */ new Map();
653
731
  #server;
654
- constructor(app, handle) {
732
+ constructor(app, handle, shutdownTimeout) {
655
733
  this.#app = app;
656
734
  this.#handle = handle;
735
+ this.#shutdownTimeout = shutdownTimeout;
657
736
  }
658
737
  static async create(rootModule, options = {}) {
659
738
  const app = await createApplication({
@@ -661,7 +740,7 @@ var HttpApplication = class HttpApplication {
661
740
  imports: [rootModule, RequestContextModule]
662
741
  }, { overrides: options.overrides });
663
742
  const handle = createHandler(app.controllers, app, options);
664
- return new HttpApplication(app, handle);
743
+ return new HttpApplication(app, handle, options.shutdownTimeout ?? DEFAULT_SHUTDOWN_TIMEOUT);
665
744
  }
666
745
  /** Fetches an already-resolved provider directly, bypassing HTTP entirely. */
667
746
  get(token) {
@@ -671,15 +750,30 @@ var HttpApplication = class HttpApplication {
671
750
  get controllers() {
672
751
  return this.#app.controllers;
673
752
  }
753
+ /**
754
+ * Serves an exact path with a plain Web-standard handler, ahead of the controller router — for
755
+ * framework-level endpoints (such as an OpenAPI document) that need the finished app, which a
756
+ * controller can't get at construction time. Mounted routes bypass guards and interceptors, so
757
+ * they are public; mounting the same method and path twice throws.
758
+ */
759
+ mount(method, path, handler) {
760
+ const key = `${method} ${path}`;
761
+ if (this.#mounted.has(key)) throw new Error(`${key} is already mounted`);
762
+ this.#mounted.set(key, handler);
763
+ }
674
764
  /** Runs a request through the handler in-process, without a socket. */
675
- handle(request) {
765
+ async handle(request) {
766
+ if (this.#mounted.size > 0) {
767
+ const mounted = this.#mounted.get(`${request.method} ${new URL(request.url).pathname}`);
768
+ if (mounted) return mounted(request);
769
+ }
676
770
  return this.#handle(request);
677
771
  }
678
772
  listen(port, hostname = "0.0.0.0") {
679
773
  return new Promise((resolve, reject) => {
680
774
  const server = createServer((req, res) => {
681
- const request = toWebRequest(req, `http://${hostname}:${port}`);
682
- this.#handle(request).then((response) => sendWebResponse(response, res)).catch((error) => {
775
+ const request = toWebRequest(req, `http://${hostname}:${port}`, res);
776
+ this.handle(request).then((response) => sendWebResponse(response, res)).catch((error) => {
683
777
  console.error(error);
684
778
  if (!res.headersSent) res.writeHead(500);
685
779
  res.end();
@@ -699,7 +793,11 @@ var HttpApplication = class HttpApplication {
699
793
  const server = this.#server;
700
794
  this.#server = void 0;
701
795
  await new Promise((resolve, reject) => {
796
+ const deadline = Number.isFinite(this.#shutdownTimeout) ? setTimeout(() => {
797
+ server.closeAllConnections();
798
+ }, this.#shutdownTimeout) : void 0;
702
799
  server.close((error) => {
800
+ clearTimeout(deadline);
703
801
  /* v8 ignore next 3 -- @preserve */
704
802
  if (error) {
705
803
  reject(error);
@@ -707,6 +805,7 @@ var HttpApplication = class HttpApplication {
707
805
  }
708
806
  resolve();
709
807
  });
808
+ server.closeIdleConnections();
710
809
  });
711
810
  }
712
811
  await this.#app.close(signal);
@@ -716,6 +815,55 @@ async function createHttpApplication(rootModule, options = {}) {
716
815
  return HttpApplication.create(rootModule, options);
717
816
  }
718
817
  //#endregion
818
+ //#region src/fetch-handler.ts
819
+ /**
820
+ * Boots the application lazily on the first request and reuses it afterwards, so a cold start pays for
821
+ * module resolution once, not per request. Concurrent first requests share one boot. If boot fails the
822
+ * error is logged, that request gets a generic 500, and the next request tries again instead of caching
823
+ * the failure for the lifetime of the instance.
824
+ *
825
+ * ```ts
826
+ * export default createFetchHandler(AppModule); // Vercel / Workers: `{ fetch }`
827
+ * ```
828
+ *
829
+ * `listen()`, `shutdownTimeout` and `SIGTERM` handling don't apply here: the platform owns the socket.
830
+ */
831
+ function createFetchHandler(rootModule, options = {}) {
832
+ let app;
833
+ function boot() {
834
+ app ??= createHttpApplication(rootModule, options).catch((error) => {
835
+ app = void 0;
836
+ throw error;
837
+ });
838
+ return app;
839
+ }
840
+ return {
841
+ async fetch(request) {
842
+ let application;
843
+ try {
844
+ application = await boot();
845
+ } catch (error) {
846
+ console.error(error);
847
+ return new Response(JSON.stringify({
848
+ type: "about:blank",
849
+ title: "Internal Server Error",
850
+ status: 500,
851
+ detail: "An unexpected error occurred"
852
+ }), {
853
+ status: 500,
854
+ headers: { "content-type": "application/problem+json" }
855
+ });
856
+ }
857
+ return application.handle(request);
858
+ },
859
+ async close(signal) {
860
+ const booted = app;
861
+ app = void 0;
862
+ if (booted) await (await booted.catch(() => void 0))?.close(signal);
863
+ }
864
+ };
865
+ }
866
+ //#endregion
719
867
  //#region src/types.ts
720
868
  const HTTP_METHODS = [
721
869
  "GET",
@@ -727,6 +875,6 @@ const HTTP_METHODS = [
727
875
  "OPTIONS"
728
876
  ];
729
877
  //#endregion
730
- export { ApiOperation, ApiTags, BadRequestException, Body, ConflictException, Controller, Delete, ForbiddenException, Get, HTTP_METHODS, Headers$1 as Headers, HttpApplication, HttpCode, HttpException, NotAControllerError, NotFoundException, Param, Patch, PayloadTooLargeException, Post, Put, Query, Req, RequestContext, RequestContextError, ResponseValidationError, Returns, Router, UnauthorizedException, UnsupportedMediaTypeException, UseGuards, UseInterceptors, buildRouter, createHandler, createHttpApplication, getApiOperation, getClassApiTags, getClassGuards, getClassInterceptors, getControllerPrefix, getHttpCode, getMethodApiTags, getMethodGuards, getMethodInterceptors, getParamSources, getReturnsSchema, getRoutes, resolveHandlerArgs, runInRequestContext, sendWebResponse, toWebRequest, validateResponse };
878
+ export { ApiOperation, ApiTags, BadRequestException, Body, ConflictException, Controller, Delete, ForbiddenException, GatewayTimeoutException, Get, HTTP_METHODS, Headers$1 as Headers, HttpApplication, HttpCode, HttpException, NotAControllerError, NotFoundException, Param, Patch, PayloadTooLargeException, Post, Put, Query, Req, RequestContext, RequestContextError, ResponseValidationError, Returns, Router, UnauthorizedException, UnsupportedMediaTypeException, UseGuards, UseInterceptors, buildRouter, createFetchHandler, createHandler, createHttpApplication, getApiOperation, getClassApiTags, getClassGuards, getClassInterceptors, getControllerPrefix, getHttpCode, getMethodApiTags, getMethodGuards, getMethodInterceptors, getParamSources, getReturnsSchema, getReturnsValidate, getRoutes, resolveHandlerArgs, runInRequestContext, sendWebResponse, toWebRequest, validateResponse };
731
879
 
732
880
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["Headers","exhaustive"],"sources":["../src/decorators/api-metadata.ts","../src/decorators/controller.ts","../src/decorators/guards.ts","../src/decorators/interceptors.ts","../src/decorators/params.ts","../src/decorators/routes.ts","../src/exceptions.ts","../src/params.ts","../src/request-context.ts","../src/response.ts","../src/router.ts","../src/handler.ts","../src/node-adapter.ts","../src/http-application.ts","../src/types.ts"],"sourcesContent":["import { defineMetadata, getMetadata } from \"@blixis-io/di\";\n\nexport interface ApiOperationOptions {\n summary?: string;\n description?: string;\n /** Defaults to `${ControllerName}_${methodName}` when omitted — every operation needs a unique one, valid or not. */\n operationId?: string;\n}\n\nconst API_OPERATION = Symbol(\"blixis:api-operation\");\nconst API_TAGS = Symbol(\"blixis:api-tags\");\n\n/** Documents one route for `@blixis-io/openapi` — entirely optional, a route without it still gets a valid (derived) operationId. */\nexport function ApiOperation(options: ApiOperationOptions): MethodDecorator {\n return (target, propertyKey) => {\n defineMetadata(API_OPERATION, options, target, propertyKey);\n };\n}\n\nexport function getApiOperation(target: object, propertyKey: string | symbol): ApiOperationOptions | undefined {\n return getMetadata(API_OPERATION, target, propertyKey);\n}\n\n/**\n * Applies OpenAPI tags to a controller (class position) or a single route\n * (method position) — same dual-shape decorator as `@UseGuards`. Class-level\n * and method-level tags both apply, concatenated, not one replacing the\n * other.\n */\nexport function ApiTags(...tags: string[]): ClassDecorator & MethodDecorator {\n const decorator = (target: object, propertyKey?: string | symbol): void => {\n if (propertyKey === undefined) {\n const existing = getMetadata<string[]>(API_TAGS, target) ?? [];\n defineMetadata(API_TAGS, [...existing, ...tags], target);\n } else {\n const existing = getMetadata<string[]>(API_TAGS, target, propertyKey) ?? [];\n defineMetadata(API_TAGS, [...existing, ...tags], target, propertyKey);\n }\n };\n return decorator;\n}\n\nexport function getClassApiTags(target: object): string[] {\n return getMetadata(API_TAGS, target) ?? [];\n}\n\nexport function getMethodApiTags(target: object, propertyKey: string | symbol): string[] {\n return getMetadata(API_TAGS, target, propertyKey) ?? [];\n}\n","import { defineMetadata, getMetadata } from \"@blixis-io/di\";\n\nconst CONTROLLER_PREFIX = Symbol(\"blixis:controller-prefix\");\n\nfunction stripSlashes(value: string): string {\n return value.replace(/^\\/+/, \"\").replace(/\\/+$/, \"\");\n}\n\nexport function Controller(prefix = \"\"): ClassDecorator {\n return (target) => {\n defineMetadata(CONTROLLER_PREFIX, stripSlashes(prefix), target);\n };\n}\n\nexport function getControllerPrefix(target: object): string | undefined {\n return getMetadata(CONTROLLER_PREFIX, target);\n}\n","import { defineMetadata, getMetadata, type Class } from \"@blixis-io/di\";\n\nexport interface ExecutionContext {\n request: Request;\n params: Readonly<Record<string, string>>;\n}\n\nexport interface CanActivate {\n canActivate(context: ExecutionContext): boolean | Promise<boolean>;\n}\n\nconst GUARDS = Symbol(\"blixis:guards\");\n\n/**\n * Applies guards to a controller (class position) or a single route\n * (method position) — the same decorator function works in both spots,\n * matching the shape TS's legacy decorators give it: a class decorator\n * receives no `propertyKey`, a method decorator does.\n */\nexport function UseGuards(...guards: Class<CanActivate>[]): ClassDecorator & MethodDecorator {\n const decorator = (target: object, propertyKey?: string | symbol): void => {\n if (propertyKey === undefined) {\n const existing = getMetadata<Class<CanActivate>[]>(GUARDS, target) ?? [];\n defineMetadata(GUARDS, [...existing, ...guards], target);\n } else {\n const existing = getMetadata<Class<CanActivate>[]>(GUARDS, target, propertyKey) ?? [];\n defineMetadata(GUARDS, [...existing, ...guards], target, propertyKey);\n }\n };\n return decorator;\n}\n\nexport function getClassGuards(target: object): Class<CanActivate>[] {\n return getMetadata(GUARDS, target) ?? [];\n}\n\nexport function getMethodGuards(target: object, propertyKey: string | symbol): Class<CanActivate>[] {\n return getMetadata(GUARDS, target, propertyKey) ?? [];\n}\n","import { defineMetadata, getMetadata, type Class } from \"@blixis-io/di\";\nimport type { ExecutionContext } from \"./guards.js\";\n\nexport interface Interceptor {\n intercept(context: ExecutionContext, next: () => Promise<Response>): Response | Promise<Response>;\n}\n\nconst INTERCEPTORS = Symbol(\"blixis:interceptors\");\n\n/**\n * Applies interceptors to a controller (class position) or a single route\n * (method position) — same dual-shape decorator as `@UseGuards`. Interceptors\n * wrap param resolution + handler invocation (guards already ran and\n * already decided the request is allowed through); class-level interceptors\n * wrap outermost, method-level innermost, closest to the handler.\n */\nexport function UseInterceptors(...interceptors: Class<Interceptor>[]): ClassDecorator & MethodDecorator {\n const decorator = (target: object, propertyKey?: string | symbol): void => {\n if (propertyKey === undefined) {\n const existing = getMetadata<Class<Interceptor>[]>(INTERCEPTORS, target) ?? [];\n defineMetadata(INTERCEPTORS, [...existing, ...interceptors], target);\n } else {\n const existing = getMetadata<Class<Interceptor>[]>(INTERCEPTORS, target, propertyKey) ?? [];\n defineMetadata(INTERCEPTORS, [...existing, ...interceptors], target, propertyKey);\n }\n };\n return decorator;\n}\n\nexport function getClassInterceptors(target: object): Class<Interceptor>[] {\n return getMetadata(INTERCEPTORS, target) ?? [];\n}\n\nexport function getMethodInterceptors(target: object, propertyKey: string | symbol): Class<Interceptor>[] {\n return getMetadata(INTERCEPTORS, target, propertyKey) ?? [];\n}\n","import { defineMetadata, getMetadata } from \"@blixis-io/di\";\nimport type { ZodType } from \"zod\";\n\nexport type ParamSource =\n | { kind: \"body\"; schema?: ZodType | undefined }\n | { kind: \"query\"; schema?: ZodType | undefined }\n | { kind: \"param\"; name: string; schema?: ZodType | undefined }\n | { kind: \"headers\"; name?: string | undefined }\n | { kind: \"req\" };\n\nconst PARAMS = Symbol(\"blixis:params\");\n\nfunction paramDecorator(source: ParamSource): ParameterDecorator {\n return (target, propertyKey, parameterIndex) => {\n if (propertyKey === undefined) {\n throw new Error(\"@Body/@Query/@Param/@Headers/@Req can only decorate route handler method parameters, not constructor parameters.\");\n }\n const sources =\n getMetadata<Map<number, ParamSource>>(PARAMS, target, propertyKey) ?? new Map<number, ParamSource>();\n sources.set(parameterIndex, source);\n defineMetadata(PARAMS, sources, target, propertyKey);\n };\n}\n\n/** Parses the JSON request body, validating it with `schema` when given. */\nexport function Body(schema?: ZodType): ParameterDecorator {\n return paramDecorator({ kind: \"body\", schema });\n}\n\n/** The parsed query string as an object, validated with `schema` when given. */\nexport function Query(schema?: ZodType): ParameterDecorator {\n return paramDecorator({ kind: \"query\", schema });\n}\n\n/** One route param by name, validated with `schema` when given (otherwise the raw string). */\nexport function Param(name: string, schema?: ZodType): ParameterDecorator {\n return paramDecorator({ kind: \"param\", name, schema });\n}\n\n/** One request header by name, or all headers as an object when `name` is omitted. */\nexport function Headers(name?: string): ParameterDecorator {\n return paramDecorator({ kind: \"headers\", name });\n}\n\n/** The raw Web-standard `Request`. */\nexport function Req(): ParameterDecorator {\n return paramDecorator({ kind: \"req\" });\n}\n\nexport function getParamSources(target: object, propertyKey: string | symbol): Map<number, ParamSource> {\n return getMetadata(PARAMS, target, propertyKey) ?? new Map();\n}\n","import { defineMetadata, getMetadata } from \"@blixis-io/di\";\nimport type { ZodType } from \"zod\";\nimport type { HttpMethod } from \"../types.js\";\n\nexport interface RouteDefinition {\n method: HttpMethod;\n path: string;\n propertyKey: string | symbol;\n}\n\nconst ROUTES = Symbol(\"blixis:routes\");\nconst HTTP_CODE = Symbol(\"blixis:http-code\");\nconst RETURNS = Symbol(\"blixis:returns\");\n\nfunction routeDecorator(method: HttpMethod, path: string): MethodDecorator {\n return (target, propertyKey) => {\n const ctor = target.constructor;\n const routes = getMetadata<RouteDefinition[]>(ROUTES, ctor) ?? [];\n routes.push({ method, path, propertyKey });\n defineMetadata(ROUTES, routes, ctor);\n };\n}\n\nexport const Get = (path = \"\"): MethodDecorator => routeDecorator(\"GET\", path);\nexport const Post = (path = \"\"): MethodDecorator => routeDecorator(\"POST\", path);\nexport const Put = (path = \"\"): MethodDecorator => routeDecorator(\"PUT\", path);\nexport const Patch = (path = \"\"): MethodDecorator => routeDecorator(\"PATCH\", path);\nexport const Delete = (path = \"\"): MethodDecorator => routeDecorator(\"DELETE\", path);\n\nexport function getRoutes(target: object): RouteDefinition[] {\n return getMetadata(ROUTES, target) ?? [];\n}\n\n/** Overrides the default 200 (or 204 for an `undefined` return) status for one route. */\nexport function HttpCode(code: number): MethodDecorator {\n return (target, propertyKey) => {\n defineMetadata(HTTP_CODE, code, target, propertyKey);\n };\n}\n\nexport function getHttpCode(target: object, propertyKey: string | symbol): number | undefined {\n return getMetadata(HTTP_CODE, target, propertyKey);\n}\n\n/**\n * Declares the response body's shape for one route. Validated after the\n * handler returns, before serialization — a mismatch is a server bug (the\n * implementation drifted from its own declared contract), not a client\n * error, so it never reaches the client as anything but a generic 500.\n */\nexport function Returns(schema: ZodType): MethodDecorator {\n return (target, propertyKey) => {\n defineMetadata(RETURNS, schema, target, propertyKey);\n };\n}\n\nexport function getReturnsSchema(target: object, propertyKey: string | symbol): ZodType | undefined {\n return getMetadata(RETURNS, target, propertyKey);\n}\n","/** Thrown from a guard or handler to short-circuit the response with a specific status. */\nexport class HttpException extends Error {\n constructor(\n public readonly status: number,\n public readonly detail: string,\n public readonly extra?: Record<string, unknown>,\n ) {\n super(detail);\n this.name = \"HttpException\";\n }\n}\n\nexport class BadRequestException extends HttpException {\n constructor(detail = \"Bad Request\", extra?: Record<string, unknown>) {\n super(400, detail, extra);\n this.name = \"BadRequestException\";\n }\n}\n\nexport class UnauthorizedException extends HttpException {\n constructor(detail = \"Unauthorized\") {\n super(401, detail);\n this.name = \"UnauthorizedException\";\n }\n}\n\nexport class ForbiddenException extends HttpException {\n constructor(detail = \"Forbidden\") {\n super(403, detail);\n this.name = \"ForbiddenException\";\n }\n}\n\nexport class NotFoundException extends HttpException {\n constructor(detail = \"Not Found\") {\n super(404, detail);\n this.name = \"NotFoundException\";\n }\n}\n\nexport class ConflictException extends HttpException {\n constructor(detail = \"Conflict\") {\n super(409, detail);\n this.name = \"ConflictException\";\n }\n}\n\nexport class PayloadTooLargeException extends HttpException {\n constructor(detail = \"Payload Too Large\") {\n super(413, detail);\n this.name = \"PayloadTooLargeException\";\n }\n}\n\nexport class UnsupportedMediaTypeException extends HttpException {\n constructor(detail = \"Unsupported Media Type\") {\n super(415, detail);\n this.name = \"UnsupportedMediaTypeException\";\n }\n}\n","import type { ZodType } from \"zod\";\nimport type { ParamSource } from \"./decorators/params.js\";\nimport { BadRequestException } from \"./exceptions.js\";\n\nexport interface ParamResolutionContext {\n request: Request;\n routeParams: Readonly<Record<string, string>>;\n /** Reads and JSON-parses the body, memoized per-request by the caller. Rejects with an HttpException (400/413/415) on failure. */\n getBody: () => Promise<unknown>;\n}\n\nasync function parseWithSchema(schema: ZodType, value: unknown): Promise<unknown> {\n const result = await schema.safeParseAsync(value);\n if (!result.success) {\n throw new BadRequestException(\"Validation failed\", { issues: result.error.issues });\n }\n return result.data;\n}\n\nasync function resolveOne(source: ParamSource, context: ParamResolutionContext): Promise<unknown> {\n switch (source.kind) {\n case \"req\":\n return context.request;\n case \"headers\":\n return source.name ? context.request.headers.get(source.name) : Object.fromEntries(context.request.headers);\n case \"param\": {\n const raw = context.routeParams[source.name];\n return source.schema ? parseWithSchema(source.schema, raw) : raw;\n }\n case \"query\": {\n const raw = Object.fromEntries(new URL(context.request.url).searchParams);\n return source.schema ? parseWithSchema(source.schema, raw) : raw;\n }\n case \"body\": {\n const raw = await context.getBody();\n return source.schema ? parseWithSchema(source.schema, raw) : raw;\n }\n /* v8 ignore start -- @preserve: exhaustiveness guard, unreachable through the public API */\n default: {\n const exhaustive: never = source;\n throw new Error(`Unreachable: unknown param source kind ${(exhaustive as ParamSource).kind}`);\n }\n /* v8 ignore stop */\n }\n}\n\n/** Builds the positional argument list for a route handler from its `@Body`/`@Query`/`@Param`/`@Headers`/`@Req` metadata. */\nexport async function resolveHandlerArgs(\n sources: ReadonlyMap<number, ParamSource>,\n context: ParamResolutionContext,\n): Promise<unknown[]> {\n if (sources.size === 0) {\n return [];\n }\n\n const args: unknown[] = Array.from<unknown>({ length: Math.max(...sources.keys()) + 1 });\n\n await Promise.all(\n [...sources.entries()].map(async ([index, source]) => {\n args[index] = await resolveOne(source, context);\n }),\n );\n\n return args;\n}\n","import { Injectable } from \"@blixis-io/di\";\nimport { AsyncLocalStorage } from \"node:async_hooks\";\n\nconst storage = new AsyncLocalStorage<Map<string, unknown>>();\n\n/** Thrown by `RequestContext.set()` when called outside an active request scope. */\nexport class RequestContextError extends Error {\n constructor() {\n super(\"RequestContext.set() can only be called while handling a request.\");\n this.name = \"RequestContextError\";\n }\n}\n\n/**\n * Per-request key/value store. Injectable as a singleton because its\n * methods don't hold state themselves — they read/write whichever\n * `Map` `runInRequestContext` made current for the request now being\n * handled, via `AsyncLocalStorage`. Reading (`get`/`has`) outside a\n * request is a legitimate \"no request\" answer; writing isn't, so\n * `set()` throws instead of silently doing nothing.\n */\n@Injectable()\nexport class RequestContext {\n get<T = unknown>(key: string): T | undefined {\n return storage.getStore()?.get(key) as T | undefined;\n }\n\n has(key: string): boolean {\n return storage.getStore()?.has(key) ?? false;\n }\n\n set(key: string, value: unknown): void {\n const store = storage.getStore();\n if (!store) {\n throw new RequestContextError();\n }\n store.set(key, value);\n }\n}\n\n/** Runs `fn` with a fresh, isolated store current for its whole async call chain — what `createHandler` wraps around each request. */\nexport function runInRequestContext<T>(fn: () => T): T {\n return storage.run(new Map(), fn);\n}\n","import type { ZodType } from \"zod\";\n\ninterface ResponseValidationIssue {\n path: PropertyKey[];\n message: string;\n}\n\n/**\n * Thrown when a handler's return value doesn't match its `@Returns`\n * schema. Deliberately not an `HttpException` — this is a server-side\n * contract bug, not the client's fault, so it falls through to the\n * generic 500 path instead of exposing the mismatch (and the app's\n * internal shape) to the client.\n */\nexport class ResponseValidationError extends Error {\n constructor(public readonly issues: readonly ResponseValidationIssue[]) {\n super(`Response failed its declared schema: ${issues.map((issue) => `${issue.path.join(\".\")}: ${issue.message}`).join(\"; \")}`);\n this.name = \"ResponseValidationError\";\n }\n}\n\n/** Validates a handler's return value against its `@Returns` schema, if any — returns the value (possibly parsed/coerced) unchanged when there's no schema. */\nexport async function validateResponse(schema: ZodType | undefined, value: unknown): Promise<unknown> {\n if (!schema) {\n return value;\n }\n const result = await schema.safeParseAsync(value);\n if (!result.success) {\n throw new ResponseValidationError(result.error.issues);\n }\n return result.data;\n}\n","import type { HttpMethod } from \"./types.js\";\n\nexport interface RouteFound<T> {\n kind: \"found\";\n handler: T;\n params: Record<string, string>;\n}\n\nexport interface RouteNotFound {\n kind: \"not-found\";\n}\n\nexport interface RouteMethodNotAllowed {\n kind: \"method-not-allowed\";\n allowed: HttpMethod[];\n}\n\nexport type RouteLookupResult<T> = RouteFound<T> | RouteNotFound | RouteMethodNotAllowed;\n\nexport class DuplicateRouteError extends Error {\n override readonly name = \"DuplicateRouteError\";\n\n constructor(method: HttpMethod, path: string) {\n super(`Duplicate route: ${method} ${path} is already registered — each method+path pair must be unique.`);\n }\n}\n\ninterface TrieNode<T> {\n staticChildren: Map<string, TrieNode<T>>;\n paramChild?: { name: string; node: TrieNode<T> };\n wildcardChild?: { name: string; node: TrieNode<T> };\n handlers: Map<HttpMethod, T>;\n}\n\nfunction createNode<T>(): TrieNode<T> {\n return { staticChildren: new Map(), handlers: new Map() };\n}\n\nfunction splitPath(path: string): string[] {\n return path.split(\"/\").filter((segment) => segment.length > 0);\n}\n\ninterface WalkResult<T> {\n node: TrieNode<T>;\n params: Record<string, string>;\n}\n\n/**\n * Depth-first with backtracking: a static/param branch that leads to a dead\n * end (no route registered there) must not block a sibling branch — e.g.\n * `/posts/new` (static, POST only) must not swallow `/posts/:id` (GET) when\n * someone requests GET /posts/new.\n */\nfunction walk<T>(\n node: TrieNode<T>,\n segments: readonly string[],\n index: number,\n params: Record<string, string>,\n): WalkResult<T> | undefined {\n if (index === segments.length) {\n return node.handlers.size > 0 ? { node, params } : undefined;\n }\n\n const segment = segments[index] as string;\n\n const staticChild = node.staticChildren.get(segment);\n if (staticChild) {\n const result = walk(staticChild, segments, index + 1, params);\n if (result) {\n return result;\n }\n }\n\n if (node.paramChild) {\n const { name, node: paramNode } = node.paramChild;\n const result = walk(paramNode, segments, index + 1, { ...params, [name]: segment });\n if (result) {\n return result;\n }\n }\n\n if (node.wildcardChild) {\n const { name, node: wildcardNode } = node.wildcardChild;\n if (wildcardNode.handlers.size > 0) {\n return { node: wildcardNode, params: { ...params, [name]: segments.slice(index).join(\"/\") } };\n }\n }\n\n return undefined;\n}\n\n/**\n * A path router keyed on an opaque handler type `T` — this package plugs in\n * controller method references; tests plug in plain strings.\n */\nexport class Router<T> {\n readonly #root = createNode<T>();\n\n add(method: HttpMethod, path: string, handler: T): void {\n const segments = splitPath(path);\n let node = this.#root;\n\n for (const segment of segments) {\n if (segment.startsWith(\":\")) {\n const name = segment.slice(1);\n node.paramChild ??= { name, node: createNode() };\n node = node.paramChild.node;\n } else if (segment === \"*\") {\n node.wildcardChild ??= { name: \"*\", node: createNode() };\n node = node.wildcardChild.node;\n } else {\n let child = node.staticChildren.get(segment);\n if (!child) {\n child = createNode();\n node.staticChildren.set(segment, child);\n }\n node = child;\n }\n }\n\n if (node.handlers.has(method)) {\n throw new DuplicateRouteError(method, path);\n }\n node.handlers.set(method, handler);\n }\n\n match(method: HttpMethod, path: string): RouteLookupResult<T> {\n const segments = splitPath(path);\n const result = walk(this.#root, segments, 0, {});\n\n if (!result) {\n return { kind: \"not-found\" };\n }\n\n const handler = result.node.handlers.get(method);\n if (!handler) {\n return { kind: \"method-not-allowed\", allowed: [...result.node.handlers.keys()] };\n }\n\n return { kind: \"found\", handler, params: result.params };\n }\n}\n","import type { Application } from \"@blixis-io/core\";\nimport type { Class } from \"@blixis-io/di\";\nimport type { ZodType } from \"zod\";\nimport { getControllerPrefix } from \"./decorators/controller.js\";\nimport { getClassGuards, getMethodGuards, type CanActivate } from \"./decorators/guards.js\";\nimport { getClassInterceptors, getMethodInterceptors, type Interceptor } from \"./decorators/interceptors.js\";\nimport { getParamSources, type ParamSource } from \"./decorators/params.js\";\nimport { getHttpCode, getReturnsSchema, getRoutes } from \"./decorators/routes.js\";\nimport {\n BadRequestException,\n ForbiddenException,\n HttpException,\n PayloadTooLargeException,\n UnsupportedMediaTypeException,\n} from \"./exceptions.js\";\nimport { resolveHandlerArgs } from \"./params.js\";\nimport { runInRequestContext } from \"./request-context.js\";\nimport { validateResponse } from \"./response.js\";\nimport { Router } from \"./router.js\";\nimport type { HttpMethod } from \"./types.js\";\n\nconst DEFAULT_BODY_LIMIT = 1024 * 1024; // 1 MiB\n\nexport interface HandlerOptions {\n bodyLimit?: number;\n}\n\nexport class NotAControllerError extends Error {\n override readonly name = \"NotAControllerError\";\n\n constructor(target: { name: string }) {\n super(`${target.name} is not a controller — did you forget @Controller()?`);\n }\n}\n\ninterface RouteEntry {\n controller: Class;\n propertyKey: string | symbol;\n paramSources: ReadonlyMap<number, ParamSource>;\n httpCode?: number | undefined;\n responseSchema?: ZodType | undefined;\n guards: Class<CanActivate>[];\n interceptors: Class<Interceptor>[];\n}\n\n/**\n * Flattens every `@Controller`'s route metadata into one `Router`. Guard\n * classes referenced via `@UseGuards` must also be registered as providers\n * on the owning module — this doesn't instantiate them, it only records\n * which classes `createHandler` will later ask the `Application` for.\n */\nexport function buildRouter(controllers: readonly Class[]): Router<RouteEntry> {\n const router = new Router<RouteEntry>();\n\n for (const controller of controllers) {\n const prefix = getControllerPrefix(controller);\n if (prefix === undefined) {\n throw new NotAControllerError(controller);\n }\n\n const prototype = controller.prototype as object;\n const classGuards = getClassGuards(controller);\n const classInterceptors = getClassInterceptors(controller);\n\n for (const route of getRoutes(controller)) {\n router.add(route.method, `/${prefix}/${route.path}`, {\n controller,\n propertyKey: route.propertyKey,\n paramSources: getParamSources(prototype, route.propertyKey),\n httpCode: getHttpCode(prototype, route.propertyKey),\n responseSchema: getReturnsSchema(prototype, route.propertyKey),\n guards: [...classGuards, ...getMethodGuards(prototype, route.propertyKey)],\n interceptors: [...classInterceptors, ...getMethodInterceptors(prototype, route.propertyKey)],\n });\n }\n }\n\n return router;\n}\n\nasync function readJsonBody(request: Request, bodyLimit: number): Promise<unknown> {\n if (request.body === null) {\n return undefined;\n }\n\n const contentType = request.headers.get(\"content-type\") ?? \"\";\n if (!contentType.toLowerCase().startsWith(\"application/json\")) {\n throw new UnsupportedMediaTypeException();\n }\n\n const contentLength = request.headers.get(\"content-length\");\n if (contentLength !== null && Number(contentLength) > bodyLimit) {\n throw new PayloadTooLargeException();\n }\n\n const text = await request.text();\n if (new TextEncoder().encode(text).length > bodyLimit) {\n throw new PayloadTooLargeException();\n }\n if (text.length === 0) {\n return undefined;\n }\n\n try {\n return JSON.parse(text);\n } catch {\n throw new BadRequestException(\"Invalid JSON body\");\n }\n}\n\nconst STATUS_TITLES: Record<number, string> = {\n 400: \"Bad Request\",\n 401: \"Unauthorized\",\n 403: \"Forbidden\",\n 404: \"Not Found\",\n 405: \"Method Not Allowed\",\n 409: \"Conflict\",\n 413: \"Payload Too Large\",\n 415: \"Unsupported Media Type\",\n 500: \"Internal Server Error\",\n};\n\nfunction problemResponse(status: number, detail: string, extra?: Record<string, unknown>): Response {\n const body = {\n type: \"about:blank\",\n title: STATUS_TITLES[status] ?? \"Error\",\n status,\n detail,\n ...extra,\n };\n return new Response(JSON.stringify(body), { status, headers: { \"content-type\": \"application/problem+json\" } });\n}\n\nfunction exceptionToResponse(error: unknown): Response {\n if (error instanceof HttpException) {\n return problemResponse(error.status, error.detail, error.extra);\n }\n // Internal errors are logged but never surfaced to the client.\n console.error(error);\n return problemResponse(500, \"An unexpected error occurred\");\n}\n\n/**\n * A raw `Response` or an `undefined` (204) return bypasses `responseSchema`\n * entirely — both are deliberate escape hatches from the normal JSON path,\n * not a value the schema was ever meant to describe.\n */\nasync function toResponse(value: unknown, httpCode: number | undefined, responseSchema: ZodType | undefined): Promise<Response> {\n if (value instanceof Response) {\n return value;\n }\n if (value === undefined) {\n return new Response(null, { status: httpCode ?? 204 });\n }\n const validated = await validateResponse(responseSchema, value);\n return new Response(JSON.stringify(validated), {\n status: httpCode ?? 200,\n headers: { \"content-type\": \"application/json\" },\n });\n}\n\n/** Builds a single `(Request) => Promise<Response>` function serving every controller's routes. */\nexport function createHandler(\n controllers: readonly Class[],\n application: Application,\n options: HandlerOptions = {},\n): (request: Request) => Promise<Response> {\n const router = buildRouter(controllers);\n const bodyLimit = options.bodyLimit ?? DEFAULT_BODY_LIMIT;\n\n return async function handle(request: Request): Promise<Response> {\n const url = new URL(request.url);\n const match = router.match(request.method as HttpMethod, url.pathname);\n\n if (match.kind === \"not-found\") {\n return problemResponse(404, \"No route matches this path\");\n }\n if (match.kind === \"method-not-allowed\") {\n const response = problemResponse(405, `Allowed methods: ${match.allowed.join(\", \")}`);\n response.headers.set(\"allow\", match.allowed.join(\", \"));\n return response;\n }\n\n const route = match.handler;\n\n return runInRequestContext(async () => {\n try {\n // Sequential and short-circuiting on purpose: a later guard must not\n // run once an earlier one has already denied the request.\n for (const guardClass of route.guards) {\n const guard = application.get(guardClass);\n const allowed = await guard.canActivate({ request, params: match.params });\n if (!allowed) {\n throw new ForbiddenException();\n }\n }\n\n const invoke = async (): Promise<Response> => {\n let cachedBody: { value: unknown } | undefined;\n const args = await resolveHandlerArgs(route.paramSources, {\n request,\n routeParams: match.params,\n getBody: async () => {\n cachedBody ??= { value: await readJsonBody(request, bodyLimit) };\n return cachedBody.value;\n },\n });\n\n const instance = application.get(route.controller) as Record<PropertyKey, (...args: unknown[]) => unknown>;\n const result: unknown = await instance[route.propertyKey]?.(...args);\n\n return toResponse(result, route.httpCode, route.responseSchema);\n };\n\n // Class-level interceptors wrap outermost, method-level innermost —\n // built right-to-left so the first entry ends up as the outer call.\n const pipeline = route.interceptors.reduceRight<() => Promise<Response>>((next, interceptorClass) => {\n const interceptor = application.get(interceptorClass);\n return async () => interceptor.intercept({ request, params: match.params }, next);\n }, invoke);\n\n return await pipeline();\n } catch (error) {\n return exceptionToResponse(error);\n }\n });\n };\n}\n","import type { IncomingMessage, ServerResponse } from \"node:http\";\nimport { Readable } from \"node:stream\";\n\n/** Builds a Web-standard `Request` from a Node `IncomingMessage`, including a body stream and abort signal. */\nexport function toWebRequest(req: IncomingMessage, baseUrl: string): Request {\n const method = req.method ?? \"GET\";\n const url = new URL(req.url ?? \"/\", baseUrl);\n\n const headers = new Headers();\n for (const [name, value] of Object.entries(req.headers)) {\n if (value === undefined) {\n continue;\n }\n for (const one of Array.isArray(value) ? value : [value]) {\n headers.append(name, one);\n }\n }\n\n const controller = new AbortController();\n req.once(\"aborted\", () => {\n controller.abort();\n });\n\n // GET/HEAD can never carry a body (Fetch spec forbids it on the Request\n // we're about to construct). Otherwise, trust the standard signals for\n // \"the client is actually sending body bytes\" rather than the method —\n // a bodyless POST is common and must not turn into an open, empty stream.\n const hasBody =\n method !== \"GET\" &&\n method !== \"HEAD\" &&\n (req.headers[\"transfer-encoding\"] !== undefined ||\n (req.headers[\"content-length\"] !== undefined && req.headers[\"content-length\"] !== \"0\"));\n\n return new Request(url, {\n method,\n headers,\n signal: controller.signal,\n // `duplex: \"half\"` is required by Node's fetch implementation whenever body is a stream.\n ...(hasBody ? { body: Readable.toWeb(req), duplex: \"half\" as const } : {}),\n });\n}\n\n/** Writes a Web-standard `Response` back onto a Node `ServerResponse`. */\nexport async function sendWebResponse(response: Response, res: ServerResponse): Promise<void> {\n const headers: Record<string, string[]> = {};\n for (const [name, value] of response.headers) {\n (headers[name] ??= []).push(value);\n }\n res.writeHead(response.status, headers);\n\n if (!response.body) {\n res.end();\n return;\n }\n\n const body = Readable.fromWeb(response.body);\n await new Promise<void>((resolve, reject) => {\n res.once(\"finish\", resolve);\n res.once(\"error\", reject);\n body.once(\"error\", reject);\n body.pipe(res);\n });\n}\n","import { createApplication, Module, type Application, type CreateApplicationOptions, type ModuleRef } from \"@blixis-io/core\";\nimport type { Class, Token } from \"@blixis-io/di\";\nimport { createServer, type Server } from \"node:http\";\nimport { createHandler, type HandlerOptions } from \"./handler.js\";\nimport { sendWebResponse, toWebRequest } from \"./node-adapter.js\";\nimport { RequestContext } from \"./request-context.js\";\n\nexport type HttpApplicationOptions = HandlerOptions & CreateApplicationOptions;\n\n/** Provides `RequestContext` app-wide, without the user needing to import anything — every `createHttpApplication` root gets wrapped with this. */\n@Module({ providers: [RequestContext], exports: [RequestContext], global: true })\nclass RequestContextModule {}\n\n/** Empty static anchor for the synthetic root — its own metadata is unused, only `imports` matters. */\n@Module()\nclass HttpRootModule {}\n\nexport interface ListenHandle {\n port: number;\n}\n\n/** The core `Application` plus a request handler, optionally bound to a real Node HTTP socket. */\nexport class HttpApplication {\n readonly #app: Application;\n readonly #handle: (request: Request) => Promise<Response>;\n #server: Server | undefined;\n\n private constructor(app: Application, handle: (request: Request) => Promise<Response>) {\n this.#app = app;\n this.#handle = handle;\n }\n\n static async create(rootModule: ModuleRef, options: HttpApplicationOptions = {}): Promise<HttpApplication> {\n const wrappedRoot = { module: HttpRootModule, imports: [rootModule, RequestContextModule] };\n const app = await createApplication(wrappedRoot, { overrides: options.overrides });\n const handle = createHandler(app.controllers, app, options);\n return new HttpApplication(app, handle);\n }\n\n /** Fetches an already-resolved provider directly, bypassing HTTP entirely. */\n get<T>(token: Token<T>): T {\n return this.#app.get(token);\n }\n\n /** Every controller class in this app's module graph — what `@blixis-io/openapi` walks to build a document that stays in sync with the real running app. */\n get controllers(): readonly Class[] {\n return this.#app.controllers;\n }\n\n /** Runs a request through the handler in-process, without a socket. */\n handle(request: Request): Promise<Response> {\n return this.#handle(request);\n }\n\n listen(port: number, hostname = \"0.0.0.0\"): Promise<ListenHandle> {\n return new Promise((resolve, reject) => {\n const server = createServer((req, res) => {\n const request = toWebRequest(req, `http://${hostname}:${port}`);\n this.#handle(request)\n .then((response) => sendWebResponse(response, res))\n /* v8 ignore start -- @preserve: safety net for a write failure\n (e.g. the client disconnects mid-stream); createHandler's own\n try/catch already turns every request-handling error into a\n Response, so this only fires for socket-level failures, which\n aren't reliably reproducible without a flaky, timing-dependent\n test. */\n .catch((error: unknown) => {\n console.error(error);\n if (!res.headersSent) {\n res.writeHead(500);\n }\n res.end();\n });\n /* v8 ignore stop */\n });\n\n server.once(\"error\", reject);\n server.listen(port, hostname, () => {\n this.#server = server;\n const address = server.address();\n // `address()` only returns a string for a Unix domain socket; `listen()`\n // here always takes a TCP port, so that arm is unreachable in practice.\n /* v8 ignore next -- @preserve */\n const actualPort = typeof address === \"object\" && address !== null ? address.port : port;\n resolve({ port: actualPort });\n });\n });\n }\n\n async close(signal?: string): Promise<void> {\n if (this.#server) {\n const server = this.#server;\n // Cleared before closing, not after: makes close() idempotent (a\n // second call is a no-op instead of ERR_SERVER_NOT_RUNNING) even if\n // the close below is still in flight.\n this.#server = undefined;\n await new Promise<void>((resolve, reject) => {\n server.close((error) => {\n // #server is only ever set right after a successful listen() and\n // cleared right before this close(), so ERR_SERVER_NOT_RUNNING\n // (the one realistic cause) can't occur here.\n /* v8 ignore next 3 -- @preserve */\n if (error) {\n reject(error);\n return;\n }\n resolve();\n });\n });\n }\n await this.#app.close(signal);\n }\n}\n\nexport async function createHttpApplication(\n rootModule: ModuleRef,\n options: HttpApplicationOptions = {},\n): Promise<HttpApplication> {\n return HttpApplication.create(rootModule, options);\n}\n","export const HTTP_METHODS = [\"GET\", \"POST\", \"PUT\", \"PATCH\", \"DELETE\", \"HEAD\", \"OPTIONS\"] as const;\n\nexport type HttpMethod = (typeof HTTP_METHODS)[number];\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["Headers","exhaustive"],"sources":["../src/decorators/api-metadata.ts","../src/decorators/controller.ts","../src/decorators/guards.ts","../src/decorators/interceptors.ts","../src/decorators/params.ts","../src/decorators/routes.ts","../src/exceptions.ts","../src/params.ts","../src/request-context.ts","../src/response.ts","../src/router.ts","../src/handler.ts","../src/node-adapter.ts","../src/http-application.ts","../src/fetch-handler.ts","../src/types.ts"],"sourcesContent":["import { defineMetadata, getMetadata } from \"@blixis-io/di\";\n\nexport interface ApiOperationOptions {\n summary?: string;\n description?: string;\n /** Defaults to `${ControllerName}_${methodName}` when omitted — every operation needs a unique one, valid or not. */\n operationId?: string;\n}\n\nconst API_OPERATION = Symbol(\"blixis:api-operation\");\nconst API_TAGS = Symbol(\"blixis:api-tags\");\n\n/** Documents one route for `@blixis-io/openapi` — entirely optional, a route without it still gets a valid (derived) operationId. */\nexport function ApiOperation(options: ApiOperationOptions): MethodDecorator {\n return (target, propertyKey) => {\n defineMetadata(API_OPERATION, options, target, propertyKey);\n };\n}\n\nexport function getApiOperation(target: object, propertyKey: string | symbol): ApiOperationOptions | undefined {\n return getMetadata(API_OPERATION, target, propertyKey);\n}\n\n/**\n * Applies OpenAPI tags to a controller (class position) or a single route\n * (method position) — same dual-shape decorator as `@UseGuards`. Class-level\n * and method-level tags both apply, concatenated, not one replacing the\n * other.\n */\nexport function ApiTags(...tags: string[]): ClassDecorator & MethodDecorator {\n const decorator = (target: object, propertyKey?: string | symbol): void => {\n if (propertyKey === undefined) {\n const existing = getMetadata<string[]>(API_TAGS, target) ?? [];\n defineMetadata(API_TAGS, [...existing, ...tags], target);\n } else {\n const existing = getMetadata<string[]>(API_TAGS, target, propertyKey) ?? [];\n defineMetadata(API_TAGS, [...existing, ...tags], target, propertyKey);\n }\n };\n return decorator;\n}\n\nexport function getClassApiTags(target: object): string[] {\n return getMetadata(API_TAGS, target) ?? [];\n}\n\nexport function getMethodApiTags(target: object, propertyKey: string | symbol): string[] {\n return getMetadata(API_TAGS, target, propertyKey) ?? [];\n}\n","import { defineMetadata, getMetadata } from \"@blixis-io/di\";\n\nconst CONTROLLER_PREFIX = Symbol(\"blixis:controller-prefix\");\n\nfunction stripSlashes(value: string): string {\n return value.replace(/^\\/+/, \"\").replace(/\\/+$/, \"\");\n}\n\nexport function Controller(prefix = \"\"): ClassDecorator {\n return (target) => {\n defineMetadata(CONTROLLER_PREFIX, stripSlashes(prefix), target);\n };\n}\n\nexport function getControllerPrefix(target: object): string | undefined {\n return getMetadata(CONTROLLER_PREFIX, target);\n}\n","import { defineMetadata, getMetadata, type Class } from \"@blixis-io/di\";\n\nexport interface ExecutionContext {\n request: Request;\n params: Readonly<Record<string, string>>;\n}\n\nexport interface CanActivate {\n canActivate(context: ExecutionContext): boolean | Promise<boolean>;\n}\n\nconst GUARDS = Symbol(\"blixis:guards\");\n\n/**\n * Applies guards to a controller (class position) or a single route\n * (method position) — the same decorator function works in both spots,\n * matching the shape TS's legacy decorators give it: a class decorator\n * receives no `propertyKey`, a method decorator does.\n */\nexport function UseGuards(...guards: Class<CanActivate>[]): ClassDecorator & MethodDecorator {\n const decorator = (target: object, propertyKey?: string | symbol): void => {\n if (propertyKey === undefined) {\n const existing = getMetadata<Class<CanActivate>[]>(GUARDS, target) ?? [];\n defineMetadata(GUARDS, [...existing, ...guards], target);\n } else {\n const existing = getMetadata<Class<CanActivate>[]>(GUARDS, target, propertyKey) ?? [];\n defineMetadata(GUARDS, [...existing, ...guards], target, propertyKey);\n }\n };\n return decorator;\n}\n\nexport function getClassGuards(target: object): Class<CanActivate>[] {\n return getMetadata(GUARDS, target) ?? [];\n}\n\nexport function getMethodGuards(target: object, propertyKey: string | symbol): Class<CanActivate>[] {\n return getMetadata(GUARDS, target, propertyKey) ?? [];\n}\n","import { defineMetadata, getMetadata, type Class } from \"@blixis-io/di\";\nimport type { ExecutionContext } from \"./guards.js\";\n\nexport interface Interceptor {\n intercept(context: ExecutionContext, next: () => Promise<Response>): Response | Promise<Response>;\n}\n\nconst INTERCEPTORS = Symbol(\"blixis:interceptors\");\n\n/**\n * Applies interceptors to a controller (class position) or a single route\n * (method position) — same dual-shape decorator as `@UseGuards`. Interceptors\n * wrap param resolution + handler invocation (guards already ran and\n * already decided the request is allowed through); class-level interceptors\n * wrap outermost, method-level innermost, closest to the handler.\n */\nexport function UseInterceptors(...interceptors: Class<Interceptor>[]): ClassDecorator & MethodDecorator {\n const decorator = (target: object, propertyKey?: string | symbol): void => {\n if (propertyKey === undefined) {\n const existing = getMetadata<Class<Interceptor>[]>(INTERCEPTORS, target) ?? [];\n defineMetadata(INTERCEPTORS, [...existing, ...interceptors], target);\n } else {\n const existing = getMetadata<Class<Interceptor>[]>(INTERCEPTORS, target, propertyKey) ?? [];\n defineMetadata(INTERCEPTORS, [...existing, ...interceptors], target, propertyKey);\n }\n };\n return decorator;\n}\n\nexport function getClassInterceptors(target: object): Class<Interceptor>[] {\n return getMetadata(INTERCEPTORS, target) ?? [];\n}\n\nexport function getMethodInterceptors(target: object, propertyKey: string | symbol): Class<Interceptor>[] {\n return getMetadata(INTERCEPTORS, target, propertyKey) ?? [];\n}\n","import { defineMetadata, getMetadata } from \"@blixis-io/di\";\nimport type { ZodType } from \"zod\";\n\nexport type ParamSource =\n | { kind: \"body\"; schema?: ZodType | undefined }\n | { kind: \"query\"; schema?: ZodType | undefined }\n | { kind: \"param\"; name: string; schema?: ZodType | undefined }\n | { kind: \"headers\"; name?: string | undefined }\n | { kind: \"req\" };\n\nconst PARAMS = Symbol(\"blixis:params\");\n\nfunction paramDecorator(source: ParamSource): ParameterDecorator {\n return (target, propertyKey, parameterIndex) => {\n if (propertyKey === undefined) {\n throw new Error(\"@Body/@Query/@Param/@Headers/@Req can only decorate route handler method parameters, not constructor parameters.\");\n }\n const sources =\n getMetadata<Map<number, ParamSource>>(PARAMS, target, propertyKey) ?? new Map<number, ParamSource>();\n sources.set(parameterIndex, source);\n defineMetadata(PARAMS, sources, target, propertyKey);\n };\n}\n\n/** Parses the JSON request body, validating it with `schema` when given. */\nexport function Body(schema?: ZodType): ParameterDecorator {\n return paramDecorator({ kind: \"body\", schema });\n}\n\n/** The parsed query string as an object, validated with `schema` when given. */\nexport function Query(schema?: ZodType): ParameterDecorator {\n return paramDecorator({ kind: \"query\", schema });\n}\n\n/** One route param by name, validated with `schema` when given (otherwise the raw string). */\nexport function Param(name: string, schema?: ZodType): ParameterDecorator {\n return paramDecorator({ kind: \"param\", name, schema });\n}\n\n/** One request header by name, or all headers as an object when `name` is omitted. */\nexport function Headers(name?: string): ParameterDecorator {\n return paramDecorator({ kind: \"headers\", name });\n}\n\n/** The raw Web-standard `Request`. */\nexport function Req(): ParameterDecorator {\n return paramDecorator({ kind: \"req\" });\n}\n\nexport function getParamSources(target: object, propertyKey: string | symbol): Map<number, ParamSource> {\n return getMetadata(PARAMS, target, propertyKey) ?? new Map();\n}\n","import { defineMetadata, getMetadata } from \"@blixis-io/di\";\nimport type { ZodType } from \"zod\";\nimport type { HttpMethod } from \"../types.js\";\n\nexport interface RouteDefinition {\n method: HttpMethod;\n path: string;\n propertyKey: string | symbol;\n}\n\nconst ROUTES = Symbol(\"blixis:routes\");\nconst HTTP_CODE = Symbol(\"blixis:http-code\");\nconst RETURNS = Symbol(\"blixis:returns\");\nconst RETURNS_VALIDATE = Symbol(\"blixis:returns-validate\");\n\nfunction routeDecorator(method: HttpMethod, path: string): MethodDecorator {\n return (target, propertyKey) => {\n const ctor = target.constructor;\n const routes = getMetadata<RouteDefinition[]>(ROUTES, ctor) ?? [];\n routes.push({ method, path, propertyKey });\n defineMetadata(ROUTES, routes, ctor);\n };\n}\n\nexport const Get = (path = \"\"): MethodDecorator => routeDecorator(\"GET\", path);\nexport const Post = (path = \"\"): MethodDecorator => routeDecorator(\"POST\", path);\nexport const Put = (path = \"\"): MethodDecorator => routeDecorator(\"PUT\", path);\nexport const Patch = (path = \"\"): MethodDecorator => routeDecorator(\"PATCH\", path);\nexport const Delete = (path = \"\"): MethodDecorator => routeDecorator(\"DELETE\", path);\n\nexport function getRoutes(target: object): RouteDefinition[] {\n return getMetadata(ROUTES, target) ?? [];\n}\n\n/** Overrides the default 200 (or 204 for an `undefined` return) status for one route. */\nexport function HttpCode(code: number): MethodDecorator {\n return (target, propertyKey) => {\n defineMetadata(HTTP_CODE, code, target, propertyKey);\n };\n}\n\nexport function getHttpCode(target: object, propertyKey: string | symbol): number | undefined {\n return getMetadata(HTTP_CODE, target, propertyKey);\n}\n\n/**\n * Declares the response body's shape for one route. Validated after the\n * handler returns, before serialization — a mismatch is a server bug (the\n * implementation drifted from its own declared contract), not a client\n * error, so it never reaches the client as anything but a generic 500.\n */\nexport function Returns(schema: ZodType, options: ReturnsOptions = {}): MethodDecorator {\n return (target, propertyKey) => {\n defineMetadata(RETURNS, schema, target, propertyKey);\n if (options.validate !== undefined) {\n defineMetadata(RETURNS_VALIDATE, options.validate, target, propertyKey);\n }\n };\n}\n\nexport interface ReturnsOptions {\n /**\n * Overrides the app-wide `responseValidation` for this route, in either direction. `false` sends the\n * handler's value as-is: no parsing, so none of the schema's coercion, defaults or stripping of unknown\n * keys applies. The schema still describes the route in the OpenAPI document.\n */\n validate?: boolean;\n}\n\n/** The per-route `validate` override, or `undefined` when the route follows the app-wide setting. */\nexport function getReturnsValidate(target: object, propertyKey: string | symbol): boolean | undefined {\n return getMetadata(RETURNS_VALIDATE, target, propertyKey);\n}\n\nexport function getReturnsSchema(target: object, propertyKey: string | symbol): ZodType | undefined {\n return getMetadata(RETURNS, target, propertyKey);\n}\n","/** Thrown from a guard or handler to short-circuit the response with a specific status. */\nexport class HttpException extends Error {\n constructor(\n public readonly status: number,\n public readonly detail: string,\n public readonly extra?: Record<string, unknown>,\n ) {\n super(detail);\n this.name = \"HttpException\";\n }\n}\n\nexport class BadRequestException extends HttpException {\n constructor(detail = \"Bad Request\", extra?: Record<string, unknown>) {\n super(400, detail, extra);\n this.name = \"BadRequestException\";\n }\n}\n\nexport class UnauthorizedException extends HttpException {\n constructor(detail = \"Unauthorized\") {\n super(401, detail);\n this.name = \"UnauthorizedException\";\n }\n}\n\nexport class ForbiddenException extends HttpException {\n constructor(detail = \"Forbidden\") {\n super(403, detail);\n this.name = \"ForbiddenException\";\n }\n}\n\nexport class NotFoundException extends HttpException {\n constructor(detail = \"Not Found\") {\n super(404, detail);\n this.name = \"NotFoundException\";\n }\n}\n\nexport class ConflictException extends HttpException {\n constructor(detail = \"Conflict\") {\n super(409, detail);\n this.name = \"ConflictException\";\n }\n}\n\nexport class PayloadTooLargeException extends HttpException {\n constructor(detail = \"Payload Too Large\") {\n super(413, detail);\n this.name = \"PayloadTooLargeException\";\n }\n}\n\nexport class UnsupportedMediaTypeException extends HttpException {\n constructor(detail = \"Unsupported Media Type\") {\n super(415, detail);\n this.name = \"UnsupportedMediaTypeException\";\n }\n}\n\nexport class GatewayTimeoutException extends HttpException {\n constructor(detail = \"Gateway Timeout\") {\n super(504, detail);\n this.name = \"GatewayTimeoutException\";\n }\n}\n","import type { ZodType } from \"zod\";\nimport type { ParamSource } from \"./decorators/params.js\";\nimport { BadRequestException } from \"./exceptions.js\";\n\nexport interface ParamResolutionContext {\n request: Request;\n routeParams: Readonly<Record<string, string>>;\n /** Reads and JSON-parses the body, memoized per-request by the caller. Rejects with an HttpException (400/413/415) on failure. */\n getBody: () => Promise<unknown>;\n}\n\nasync function parseWithSchema(schema: ZodType, value: unknown): Promise<unknown> {\n const result = await schema.safeParseAsync(value);\n if (!result.success) {\n throw new BadRequestException(\"Validation failed\", { issues: result.error.issues });\n }\n return result.data;\n}\n\nasync function resolveOne(source: ParamSource, context: ParamResolutionContext): Promise<unknown> {\n switch (source.kind) {\n case \"req\":\n return context.request;\n case \"headers\":\n return source.name ? context.request.headers.get(source.name) : Object.fromEntries(context.request.headers);\n case \"param\": {\n const raw = context.routeParams[source.name];\n return source.schema ? parseWithSchema(source.schema, raw) : raw;\n }\n case \"query\": {\n const raw = Object.fromEntries(new URL(context.request.url).searchParams);\n return source.schema ? parseWithSchema(source.schema, raw) : raw;\n }\n case \"body\": {\n const raw = await context.getBody();\n return source.schema ? parseWithSchema(source.schema, raw) : raw;\n }\n /* v8 ignore start -- @preserve: exhaustiveness guard, unreachable through the public API */\n default: {\n const exhaustive: never = source;\n throw new Error(`Unreachable: unknown param source kind ${(exhaustive as ParamSource).kind}`);\n }\n /* v8 ignore stop */\n }\n}\n\n/** Builds the positional argument list for a route handler from its `@Body`/`@Query`/`@Param`/`@Headers`/`@Req` metadata. */\nexport async function resolveHandlerArgs(\n sources: ReadonlyMap<number, ParamSource>,\n context: ParamResolutionContext,\n): Promise<unknown[]> {\n if (sources.size === 0) {\n return [];\n }\n\n const args: unknown[] = Array.from<unknown>({ length: Math.max(...sources.keys()) + 1 });\n\n await Promise.all(\n [...sources.entries()].map(async ([index, source]) => {\n args[index] = await resolveOne(source, context);\n }),\n );\n\n return args;\n}\n","import { Injectable } from \"@blixis-io/di\";\nimport { AsyncLocalStorage } from \"node:async_hooks\";\n\nconst storage = new AsyncLocalStorage<Map<string, unknown>>();\n\n/** Thrown by `RequestContext.set()` when called outside an active request scope. */\nexport class RequestContextError extends Error {\n constructor() {\n super(\"RequestContext.set() can only be called while handling a request.\");\n this.name = \"RequestContextError\";\n }\n}\n\n/**\n * Per-request key/value store. Injectable as a singleton because its\n * methods don't hold state themselves — they read/write whichever\n * `Map` `runInRequestContext` made current for the request now being\n * handled, via `AsyncLocalStorage`. Reading (`get`/`has`) outside a\n * request is a legitimate \"no request\" answer; writing isn't, so\n * `set()` throws instead of silently doing nothing.\n */\n@Injectable()\nexport class RequestContext {\n get<T = unknown>(key: string): T | undefined {\n return storage.getStore()?.get(key) as T | undefined;\n }\n\n has(key: string): boolean {\n return storage.getStore()?.has(key) ?? false;\n }\n\n set(key: string, value: unknown): void {\n const store = storage.getStore();\n if (!store) {\n throw new RequestContextError();\n }\n store.set(key, value);\n }\n}\n\n/** Runs `fn` with a fresh, isolated store current for its whole async call chain — what `createHandler` wraps around each request. */\nexport function runInRequestContext<T>(fn: () => T): T {\n return storage.run(new Map(), fn);\n}\n","import { z, type ZodType } from \"zod\";\n\ninterface ResponseValidationIssue {\n path: PropertyKey[];\n message: string;\n}\n\n/**\n * Thrown when a handler's return value doesn't match its `@Returns`\n * schema. Deliberately not an `HttpException` — this is a server-side\n * contract bug, not the client's fault, so it falls through to the\n * generic 500 path instead of exposing the mismatch (and the app's\n * internal shape) to the client.\n */\nexport class ResponseValidationError extends Error {\n constructor(public readonly issues: readonly ResponseValidationIssue[]) {\n super(`Response failed its declared schema: ${issues.map((issue) => `${issue.path.join(\".\")}: ${issue.message}`).join(\"; \")}`);\n this.name = \"ResponseValidationError\";\n }\n}\n\n/**\n * Sync parsing is 3-4x faster than `safeParseAsync` on large payloads, so try it first and only fall back\n * when the schema contains an async refinement/transform (Zod signals that with `$ZodAsyncError`).\n */\nasync function parse(schema: ZodType, value: unknown) {\n try {\n return schema.safeParse(value);\n } catch (error) {\n if (error instanceof z.core.$ZodAsyncError) {\n return schema.safeParseAsync(value);\n }\n throw error;\n }\n}\n\n/** Validates a handler's return value against its `@Returns` schema, if any — returns the value (possibly parsed/coerced) unchanged when there's no schema. */\nexport async function validateResponse(schema: ZodType | undefined, value: unknown): Promise<unknown> {\n if (!schema) {\n return value;\n }\n const result = await parse(schema, value);\n if (!result.success) {\n throw new ResponseValidationError(result.error.issues);\n }\n return result.data;\n}\n","import type { HttpMethod } from \"./types.js\";\n\nexport interface RouteFound<T> {\n kind: \"found\";\n handler: T;\n params: Record<string, string>;\n}\n\nexport interface RouteNotFound {\n kind: \"not-found\";\n}\n\nexport interface RouteMethodNotAllowed {\n kind: \"method-not-allowed\";\n allowed: HttpMethod[];\n}\n\nexport type RouteLookupResult<T> = RouteFound<T> | RouteNotFound | RouteMethodNotAllowed;\n\nexport class DuplicateRouteError extends Error {\n override readonly name = \"DuplicateRouteError\";\n\n constructor(method: HttpMethod, path: string) {\n super(`Duplicate route: ${method} ${path} is already registered — each method+path pair must be unique.`);\n }\n}\n\ninterface TrieNode<T> {\n staticChildren: Map<string, TrieNode<T>>;\n paramChild?: { name: string; node: TrieNode<T> };\n wildcardChild?: { name: string; node: TrieNode<T> };\n handlers: Map<HttpMethod, T>;\n}\n\nfunction createNode<T>(): TrieNode<T> {\n return { staticChildren: new Map(), handlers: new Map() };\n}\n\nfunction splitPath(path: string): string[] {\n return path.split(\"/\").filter((segment) => segment.length > 0);\n}\n\ninterface WalkResult<T> {\n node: TrieNode<T>;\n params: Record<string, string>;\n}\n\n/**\n * Depth-first with backtracking: a static/param branch that leads to a dead\n * end (no route registered there) must not block a sibling branch — e.g.\n * `/posts/new` (static, POST only) must not swallow `/posts/:id` (GET) when\n * someone requests GET /posts/new.\n */\nfunction walk<T>(\n node: TrieNode<T>,\n segments: readonly string[],\n index: number,\n params: Record<string, string>,\n): WalkResult<T> | undefined {\n if (index === segments.length) {\n return node.handlers.size > 0 ? { node, params } : undefined;\n }\n\n const segment = segments[index] as string;\n\n const staticChild = node.staticChildren.get(segment);\n if (staticChild) {\n const result = walk(staticChild, segments, index + 1, params);\n if (result) {\n return result;\n }\n }\n\n if (node.paramChild) {\n const { name, node: paramNode } = node.paramChild;\n const result = walk(paramNode, segments, index + 1, { ...params, [name]: segment });\n if (result) {\n return result;\n }\n }\n\n if (node.wildcardChild) {\n const { name, node: wildcardNode } = node.wildcardChild;\n if (wildcardNode.handlers.size > 0) {\n return { node: wildcardNode, params: { ...params, [name]: segments.slice(index).join(\"/\") } };\n }\n }\n\n return undefined;\n}\n\n/**\n * A path router keyed on an opaque handler type `T` — this package plugs in\n * controller method references; tests plug in plain strings.\n */\nexport class Router<T> {\n readonly #root = createNode<T>();\n\n add(method: HttpMethod, path: string, handler: T): void {\n const segments = splitPath(path);\n let node = this.#root;\n\n for (const segment of segments) {\n if (segment.startsWith(\":\")) {\n const name = segment.slice(1);\n node.paramChild ??= { name, node: createNode() };\n node = node.paramChild.node;\n } else if (segment === \"*\") {\n node.wildcardChild ??= { name: \"*\", node: createNode() };\n node = node.wildcardChild.node;\n } else {\n let child = node.staticChildren.get(segment);\n if (!child) {\n child = createNode();\n node.staticChildren.set(segment, child);\n }\n node = child;\n }\n }\n\n if (node.handlers.has(method)) {\n throw new DuplicateRouteError(method, path);\n }\n node.handlers.set(method, handler);\n }\n\n match(method: HttpMethod, path: string): RouteLookupResult<T> {\n const segments = splitPath(path);\n const result = walk(this.#root, segments, 0, {});\n\n if (!result) {\n return { kind: \"not-found\" };\n }\n\n const handler = result.node.handlers.get(method);\n if (!handler) {\n return { kind: \"method-not-allowed\", allowed: [...result.node.handlers.keys()] };\n }\n\n return { kind: \"found\", handler, params: result.params };\n }\n}\n","import type { Application } from \"@blixis-io/core\";\nimport type { Class } from \"@blixis-io/di\";\nimport type { ZodType } from \"zod\";\nimport { getControllerPrefix } from \"./decorators/controller.js\";\nimport { getClassGuards, getMethodGuards, type CanActivate } from \"./decorators/guards.js\";\nimport { getClassInterceptors, getMethodInterceptors, type Interceptor } from \"./decorators/interceptors.js\";\nimport { getParamSources, type ParamSource } from \"./decorators/params.js\";\nimport { getHttpCode, getReturnsSchema, getReturnsValidate, getRoutes } from \"./decorators/routes.js\";\nimport {\n BadRequestException,\n ForbiddenException,\n GatewayTimeoutException,\n HttpException,\n PayloadTooLargeException,\n UnsupportedMediaTypeException,\n} from \"./exceptions.js\";\nimport { resolveHandlerArgs } from \"./params.js\";\nimport { runInRequestContext } from \"./request-context.js\";\nimport { validateResponse } from \"./response.js\";\nimport { Router } from \"./router.js\";\nimport type { HttpMethod } from \"./types.js\";\n\nconst DEFAULT_BODY_LIMIT = 1024 * 1024; // 1 MiB\n\nexport interface HandlerOptions {\n bodyLimit?: number;\n /**\n * Milliseconds a request may take before the client gets a 504. Off by default. The handler (and\n * anything it awaits) is not killed — it sees the timeout through `request.signal` (`@Req()`), and\n * must pass that signal on to cancellable work (`fetch`, DB queries) to actually stop.\n */\n requestTimeout?: number;\n /**\n * `\"always\"` (default) validates every `@Returns` route and sends the parsed value, so the schema also\n * strips unknown keys. `\"never\"` sends handler values as-is, which skips that stripping: an object with\n * extra fields (say a `passwordHash`) is then sent whole. A route's `@Returns(..., { validate })` wins.\n */\n responseValidation?: \"always\" | \"never\";\n}\n\nexport class NotAControllerError extends Error {\n override readonly name = \"NotAControllerError\";\n\n constructor(target: { name: string }) {\n super(`${target.name} is not a controller — did you forget @Controller()?`);\n }\n}\n\ninterface RouteEntry {\n controller: Class;\n propertyKey: string | symbol;\n paramSources: ReadonlyMap<number, ParamSource>;\n httpCode?: number | undefined;\n responseSchema?: ZodType | undefined;\n /** Per-route `@Returns(..., { validate })` override; `undefined` follows the app-wide `responseValidation`. */\n validateResponse?: boolean | undefined;\n guards: Class<CanActivate>[];\n interceptors: Class<Interceptor>[];\n}\n\n/**\n * Flattens every `@Controller`'s route metadata into one `Router`. Guard\n * classes referenced via `@UseGuards` must also be registered as providers\n * on the owning module — this doesn't instantiate them, it only records\n * which classes `createHandler` will later ask the `Application` for.\n */\nexport function buildRouter(controllers: readonly Class[]): Router<RouteEntry> {\n const router = new Router<RouteEntry>();\n\n for (const controller of controllers) {\n const prefix = getControllerPrefix(controller);\n if (prefix === undefined) {\n throw new NotAControllerError(controller);\n }\n\n const prototype = controller.prototype as object;\n const classGuards = getClassGuards(controller);\n const classInterceptors = getClassInterceptors(controller);\n\n for (const route of getRoutes(controller)) {\n router.add(route.method, `/${prefix}/${route.path}`, {\n controller,\n propertyKey: route.propertyKey,\n paramSources: getParamSources(prototype, route.propertyKey),\n httpCode: getHttpCode(prototype, route.propertyKey),\n responseSchema: getReturnsSchema(prototype, route.propertyKey),\n validateResponse: getReturnsValidate(prototype, route.propertyKey),\n guards: [...classGuards, ...getMethodGuards(prototype, route.propertyKey)],\n interceptors: [...classInterceptors, ...getMethodInterceptors(prototype, route.propertyKey)],\n });\n }\n }\n\n return router;\n}\n\n/** Reads the stream chunk by chunk and cancels it the moment `limit` bytes are exceeded, so a chunked body without a `Content-Length` can't be buffered whole before being rejected. */\nasync function readBodyText(body: ReadableStream<Uint8Array>, limit: number): Promise<string> {\n const reader = body.getReader();\n const decoder = new TextDecoder();\n let received = 0;\n let text = \"\";\n for (;;) {\n const { done, value } = await reader.read();\n if (done) {\n return text + decoder.decode();\n }\n received += value.byteLength;\n if (received > limit) {\n await reader.cancel();\n throw new PayloadTooLargeException();\n }\n text += decoder.decode(value, { stream: true });\n }\n}\n\nasync function readJsonBody(request: Request, bodyLimit: number): Promise<unknown> {\n if (request.body === null) {\n return undefined;\n }\n\n const contentType = request.headers.get(\"content-type\") ?? \"\";\n if (!contentType.toLowerCase().startsWith(\"application/json\")) {\n throw new UnsupportedMediaTypeException();\n }\n\n const contentLength = request.headers.get(\"content-length\");\n if (contentLength !== null && Number(contentLength) > bodyLimit) {\n throw new PayloadTooLargeException();\n }\n\n const text = await readBodyText(request.body, bodyLimit);\n if (text.length === 0) {\n return undefined;\n }\n\n try {\n return JSON.parse(text);\n } catch {\n throw new BadRequestException(\"Invalid JSON body\");\n }\n}\n\nconst STATUS_TITLES: Record<number, string> = {\n 400: \"Bad Request\",\n 401: \"Unauthorized\",\n 403: \"Forbidden\",\n 404: \"Not Found\",\n 405: \"Method Not Allowed\",\n 409: \"Conflict\",\n 413: \"Payload Too Large\",\n 415: \"Unsupported Media Type\",\n 500: \"Internal Server Error\",\n 504: \"Gateway Timeout\",\n};\n\nfunction problemResponse(status: number, detail: string, extra?: Record<string, unknown>): Response {\n const body = {\n type: \"about:blank\",\n title: STATUS_TITLES[status] ?? \"Error\",\n status,\n detail,\n ...extra,\n };\n return new Response(JSON.stringify(body), { status, headers: { \"content-type\": \"application/problem+json\" } });\n}\n\nfunction exceptionToResponse(error: unknown): Response {\n if (error instanceof HttpException) {\n return problemResponse(error.status, error.detail, error.extra);\n }\n // Internal errors are logged but never surfaced to the client.\n console.error(error);\n return problemResponse(500, \"An unexpected error occurred\");\n}\n\n/**\n * A raw `Response` or an `undefined` (204) return bypasses `responseSchema`\n * entirely — both are deliberate escape hatches from the normal JSON path,\n * not a value the schema was ever meant to describe.\n */\nasync function toResponse(\n value: unknown,\n httpCode: number | undefined,\n responseSchema: ZodType | undefined,\n validate: boolean,\n): Promise<Response> {\n if (value instanceof Response) {\n return value;\n }\n if (value === undefined) {\n return new Response(null, { status: httpCode ?? 204 });\n }\n const validated = validate ? await validateResponse(responseSchema, value) : value;\n return new Response(JSON.stringify(validated), {\n status: httpCode ?? 200,\n headers: { \"content-type\": \"application/json\" },\n });\n}\n\n/** Same request, but its `signal` also fires after `ms` — so handlers see the timeout via `request.signal`. */\nfunction withTimeout(request: Request, ms: number): Request {\n const signal = AbortSignal.any([request.signal, AbortSignal.timeout(ms)]);\n return new Request(request, { signal });\n}\n\n/** Settles with `work`, unless `signal` aborts first: a client disconnect becomes a 499, a timeout a 504. `work` keeps running; its late result/rejection is ignored. */\nfunction raceAbort(work: Promise<Response>, signal: AbortSignal, clientSignal: AbortSignal): Promise<Response> {\n const abortResponse = (): Response =>\n clientSignal.aborted ? new Response(null, { status: 499 }) : exceptionToResponse(new GatewayTimeoutException());\n if (signal.aborted) {\n return Promise.resolve(abortResponse());\n }\n return new Promise<Response>((resolve, reject) => {\n const onAbort = (): void => {\n resolve(abortResponse());\n };\n signal.addEventListener(\"abort\", onAbort, { once: true });\n work.then(resolve, reject).finally(() => {\n signal.removeEventListener(\"abort\", onAbort);\n });\n });\n}\n\n/** Builds a single `(Request) => Promise<Response>` function serving every controller's routes. */\nexport function createHandler(\n controllers: readonly Class[],\n application: Application,\n options: HandlerOptions = {},\n): (request: Request) => Promise<Response> {\n const router = buildRouter(controllers);\n const bodyLimit = options.bodyLimit ?? DEFAULT_BODY_LIMIT;\n\n const requestTimeout = options.requestTimeout;\n const validateByDefault = options.responseValidation !== \"never\";\n\n return async function handle(incoming: Request): Promise<Response> {\n const request = requestTimeout === undefined ? incoming : withTimeout(incoming, requestTimeout);\n const url = new URL(request.url);\n const match = router.match(request.method as HttpMethod, url.pathname);\n\n if (match.kind === \"not-found\") {\n return problemResponse(404, \"No route matches this path\");\n }\n if (match.kind === \"method-not-allowed\") {\n const response = problemResponse(405, `Allowed methods: ${match.allowed.join(\", \")}`);\n response.headers.set(\"allow\", match.allowed.join(\", \"));\n return response;\n }\n\n const route = match.handler;\n\n return runInRequestContext(async () => {\n try {\n // Sequential and short-circuiting on purpose: a later guard must not\n // run once an earlier one has already denied the request.\n for (const guardClass of route.guards) {\n const guard = application.get(guardClass);\n const allowed = await guard.canActivate({ request, params: match.params });\n if (!allowed) {\n throw new ForbiddenException();\n }\n }\n\n const invoke = async (): Promise<Response> => {\n let cachedBody: { value: unknown } | undefined;\n const args = await resolveHandlerArgs(route.paramSources, {\n request,\n routeParams: match.params,\n getBody: async () => {\n cachedBody ??= { value: await readJsonBody(request, bodyLimit) };\n return cachedBody.value;\n },\n });\n\n const instance = application.get(route.controller) as Record<PropertyKey, (...args: unknown[]) => unknown>;\n const result: unknown = await instance[route.propertyKey]?.(...args);\n\n return toResponse(result, route.httpCode, route.responseSchema, route.validateResponse ?? validateByDefault);\n };\n\n // Class-level interceptors wrap outermost, method-level innermost —\n // built right-to-left so the first entry ends up as the outer call.\n const pipeline = route.interceptors.reduceRight<() => Promise<Response>>((next, interceptorClass) => {\n const interceptor = application.get(interceptorClass);\n return async () => interceptor.intercept({ request, params: match.params }, next);\n }, invoke);\n\n return await (requestTimeout === undefined ? pipeline() : raceAbort(pipeline(), request.signal, incoming.signal));\n } catch (error) {\n return exceptionToResponse(error);\n }\n });\n };\n}\n","import type { IncomingMessage, ServerResponse } from \"node:http\";\nimport { Readable } from \"node:stream\";\nimport { pipeline } from \"node:stream/promises\";\n\n/**\n * Builds a Web-standard `Request` from a Node `IncomingMessage`, including a body stream and abort signal.\n * Pass the matching `ServerResponse` so the signal also fires when the client disconnects before the\n * response finished — `req`'s own \"aborted\" event is deprecated and misses some disconnects.\n */\nexport function toWebRequest(req: IncomingMessage, baseUrl: string, res?: ServerResponse): Request {\n const method = req.method ?? \"GET\";\n const url = new URL(req.url ?? \"/\", baseUrl);\n\n const headers = new Headers();\n for (const [name, value] of Object.entries(req.headers)) {\n if (value === undefined) {\n continue;\n }\n for (const one of Array.isArray(value) ? value : [value]) {\n headers.append(name, one);\n }\n }\n\n const controller = new AbortController();\n req.once(\"aborted\", () => {\n controller.abort();\n });\n res?.once(\"close\", () => {\n if (!res.writableFinished) {\n controller.abort();\n }\n });\n\n // GET/HEAD can never carry a body (Fetch spec forbids it on the Request\n // we're about to construct). Otherwise, trust the standard signals for\n // \"the client is actually sending body bytes\" rather than the method —\n // a bodyless POST is common and must not turn into an open, empty stream.\n const hasBody =\n method !== \"GET\" &&\n method !== \"HEAD\" &&\n (req.headers[\"transfer-encoding\"] !== undefined ||\n (req.headers[\"content-length\"] !== undefined && req.headers[\"content-length\"] !== \"0\"));\n\n return new Request(url, {\n method,\n headers,\n signal: controller.signal,\n // `duplex: \"half\"` is required by Node's fetch implementation whenever body is a stream.\n ...(hasBody ? { body: Readable.toWeb(req), duplex: \"half\" as const } : {}),\n });\n}\n\n/** Writes a Web-standard `Response` back onto a Node `ServerResponse`. */\nexport async function sendWebResponse(response: Response, res: ServerResponse): Promise<void> {\n const headers: Record<string, string[]> = {};\n for (const [name, value] of response.headers) {\n (headers[name] ??= []).push(value);\n }\n res.writeHead(response.status, headers);\n\n if (!response.body) {\n res.end();\n return;\n }\n\n // `pipeline` (not `pipe`) so the streams are torn down together: a client that disconnects\n // mid-response destroys the body, which cancels the web stream's source instead of leaving\n // its producer (a DB cursor, a file handle) running. An early disconnect isn't an error.\n try {\n await pipeline(Readable.fromWeb(response.body), res);\n } catch (error) {\n if (!isPrematureClose(error)) {\n throw error;\n }\n }\n}\n\nfunction isPrematureClose(error: unknown): boolean {\n return error instanceof Error && \"code\" in error && error.code === \"ERR_STREAM_PREMATURE_CLOSE\";\n}\n","import { createApplication, Module, type Application, type CreateApplicationOptions, type ModuleRef } from \"@blixis-io/core\";\nimport type { Class, Token } from \"@blixis-io/di\";\nimport { createServer, type Server } from \"node:http\";\nimport { createHandler, type HandlerOptions } from \"./handler.js\";\nimport { sendWebResponse, toWebRequest } from \"./node-adapter.js\";\nimport { RequestContext } from \"./request-context.js\";\n\nconst DEFAULT_SHUTDOWN_TIMEOUT = 10_000;\n\nexport interface ShutdownOptions {\n /**\n * Milliseconds `close()` lets in-flight requests finish before their sockets are destroyed (their\n * `request.signal` aborts). Default 10 000; `Infinity` waits indefinitely.\n */\n shutdownTimeout?: number;\n}\n\nexport type HttpApplicationOptions = HandlerOptions & CreateApplicationOptions & ShutdownOptions;\n\n/** Provides `RequestContext` app-wide, without the user needing to import anything — every `createHttpApplication` root gets wrapped with this. */\n@Module({ providers: [RequestContext], exports: [RequestContext], global: true })\nclass RequestContextModule {}\n\n/** Empty static anchor for the synthetic root — its own metadata is unused, only `imports` matters. */\n@Module()\nclass HttpRootModule {}\n\nexport type MountedHandler = (request: Request) => Response | Promise<Response>;\n\nexport interface ListenHandle {\n port: number;\n}\n\n/** The core `Application` plus a request handler, optionally bound to a real Node HTTP socket. */\nexport class HttpApplication {\n readonly #app: Application;\n readonly #handle: (request: Request) => Promise<Response>;\n readonly #shutdownTimeout: number;\n readonly #mounted = new Map<string, MountedHandler>();\n #server: Server | undefined;\n\n private constructor(app: Application, handle: (request: Request) => Promise<Response>, shutdownTimeout: number) {\n this.#app = app;\n this.#handle = handle;\n this.#shutdownTimeout = shutdownTimeout;\n }\n\n static async create(rootModule: ModuleRef, options: HttpApplicationOptions = {}): Promise<HttpApplication> {\n const wrappedRoot = { module: HttpRootModule, imports: [rootModule, RequestContextModule] };\n const app = await createApplication(wrappedRoot, { overrides: options.overrides });\n const handle = createHandler(app.controllers, app, options);\n return new HttpApplication(app, handle, options.shutdownTimeout ?? DEFAULT_SHUTDOWN_TIMEOUT);\n }\n\n /** Fetches an already-resolved provider directly, bypassing HTTP entirely. */\n get<T>(token: Token<T>): T {\n return this.#app.get(token);\n }\n\n /** Every controller class in this app's module graph — what `@blixis-io/openapi` walks to build a document that stays in sync with the real running app. */\n get controllers(): readonly Class[] {\n return this.#app.controllers;\n }\n\n /**\n * Serves an exact path with a plain Web-standard handler, ahead of the controller router — for\n * framework-level endpoints (such as an OpenAPI document) that need the finished app, which a\n * controller can't get at construction time. Mounted routes bypass guards and interceptors, so\n * they are public; mounting the same method and path twice throws.\n */\n mount(method: \"GET\" | \"POST\", path: string, handler: MountedHandler): void {\n const key = `${method} ${path}`;\n if (this.#mounted.has(key)) {\n throw new Error(`${key} is already mounted`);\n }\n this.#mounted.set(key, handler);\n }\n\n /** Runs a request through the handler in-process, without a socket. */\n async handle(request: Request): Promise<Response> {\n if (this.#mounted.size > 0) {\n const mounted = this.#mounted.get(`${request.method} ${new URL(request.url).pathname}`);\n if (mounted) {\n return mounted(request);\n }\n }\n return this.#handle(request);\n }\n\n listen(port: number, hostname = \"0.0.0.0\"): Promise<ListenHandle> {\n return new Promise((resolve, reject) => {\n const server = createServer((req, res) => {\n const request = toWebRequest(req, `http://${hostname}:${port}`, res);\n this.handle(request)\n .then((response) => sendWebResponse(response, res))\n /* v8 ignore start -- @preserve: safety net for a write failure\n (e.g. the client disconnects mid-stream); createHandler's own\n try/catch already turns every request-handling error into a\n Response, so this only fires for socket-level failures, which\n aren't reliably reproducible without a flaky, timing-dependent\n test. */\n .catch((error: unknown) => {\n console.error(error);\n if (!res.headersSent) {\n res.writeHead(500);\n }\n res.end();\n });\n /* v8 ignore stop */\n });\n\n server.once(\"error\", reject);\n server.listen(port, hostname, () => {\n this.#server = server;\n const address = server.address();\n // `address()` only returns a string for a Unix domain socket; `listen()`\n // here always takes a TCP port, so that arm is unreachable in practice.\n /* v8 ignore next -- @preserve */\n const actualPort = typeof address === \"object\" && address !== null ? address.port : port;\n resolve({ port: actualPort });\n });\n });\n }\n\n async close(signal?: string): Promise<void> {\n if (this.#server) {\n const server = this.#server;\n // Cleared before closing, not after: makes close() idempotent (a\n // second call is a no-op instead of ERR_SERVER_NOT_RUNNING) even if\n // the close below is still in flight.\n this.#server = undefined;\n await new Promise<void>((resolve, reject) => {\n // Stops accepting new connections and waits for in-flight requests;\n // idle keep-alive sockets are dropped now so they can't hold close()\n // open. Anything still running at the deadline is cut off.\n const deadline = Number.isFinite(this.#shutdownTimeout)\n ? setTimeout(() => {\n server.closeAllConnections();\n }, this.#shutdownTimeout)\n : undefined;\n server.close((error) => {\n clearTimeout(deadline);\n // #server is only ever set right after a successful listen() and\n // cleared right before this close(), so ERR_SERVER_NOT_RUNNING\n // (the one realistic cause) can't occur here.\n /* v8 ignore next 3 -- @preserve */\n if (error) {\n reject(error);\n return;\n }\n resolve();\n });\n server.closeIdleConnections();\n });\n }\n await this.#app.close(signal);\n }\n}\n\nexport async function createHttpApplication(\n rootModule: ModuleRef,\n options: HttpApplicationOptions = {},\n): Promise<HttpApplication> {\n return HttpApplication.create(rootModule, options);\n}\n","import type { ModuleRef } from \"@blixis-io/core\";\nimport { createHttpApplication, type HttpApplication, type HttpApplicationOptions } from \"./http-application.js\";\n\n/** A Web-standard entry for platforms that call `fetch(request)` per request (Vercel, Netlify, Cloudflare Workers, Deno Deploy, ...). */\nexport interface FetchHandler {\n fetch(request: Request): Promise<Response>;\n /** Runs shutdown hooks. Serverless platforms rarely call this; it exists for tests and long-lived hosts. */\n close(signal?: string): Promise<void>;\n}\n\n/**\n * Boots the application lazily on the first request and reuses it afterwards, so a cold start pays for\n * module resolution once, not per request. Concurrent first requests share one boot. If boot fails the\n * error is logged, that request gets a generic 500, and the next request tries again instead of caching\n * the failure for the lifetime of the instance.\n *\n * ```ts\n * export default createFetchHandler(AppModule); // Vercel / Workers: `{ fetch }`\n * ```\n *\n * `listen()`, `shutdownTimeout` and `SIGTERM` handling don't apply here: the platform owns the socket.\n */\nexport function createFetchHandler(rootModule: ModuleRef, options: HttpApplicationOptions = {}): FetchHandler {\n let app: Promise<HttpApplication> | undefined;\n\n function boot(): Promise<HttpApplication> {\n app ??= createHttpApplication(rootModule, options).catch((error: unknown) => {\n app = undefined;\n throw error;\n });\n return app;\n }\n\n return {\n async fetch(request) {\n let application: HttpApplication;\n try {\n application = await boot();\n } catch (error) {\n console.error(error);\n return new Response(\n JSON.stringify({ type: \"about:blank\", title: \"Internal Server Error\", status: 500, detail: \"An unexpected error occurred\" }),\n { status: 500, headers: { \"content-type\": \"application/problem+json\" } },\n );\n }\n return application.handle(request);\n },\n\n async close(signal) {\n const booted = app;\n app = undefined;\n if (booted) {\n const application = await booted.catch(() => undefined);\n await application?.close(signal);\n }\n },\n };\n}\n","export const HTTP_METHODS = [\"GET\", \"POST\", \"PUT\", \"PATCH\", \"DELETE\", \"HEAD\", \"OPTIONS\"] as const;\n\nexport type HttpMethod = (typeof HTTP_METHODS)[number];\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blixis-io/http",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "The HTTP layer built on @blixis-io/core — routing, controllers, request validation with Zod, guards, RFC 9457 error responses.",
5
5
  "license": "MIT",
6
6
  "author": "Michael Voeten <michael@voeten.online>",
@@ -34,8 +34,8 @@
34
34
  }
35
35
  },
36
36
  "dependencies": {
37
- "@blixis-io/core": "0.1.0",
38
- "@blixis-io/di": "0.1.0",
37
+ "@blixis-io/core": "0.2.1",
38
+ "@blixis-io/di": "0.1.1",
39
39
  "zod": "^4.6.5"
40
40
  },
41
41
  "scripts": {