@blixis-io/http 0.2.0 → 0.3.1
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 +42 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +85 -13
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
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
|
|
@@ -182,6 +192,12 @@ interface HandlerOptions {
|
|
|
182
192
|
* must pass that signal on to cancellable work (`fetch`, DB queries) to actually stop.
|
|
183
193
|
*/
|
|
184
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";
|
|
185
201
|
}
|
|
186
202
|
export declare class NotAControllerError extends Error {
|
|
187
203
|
readonly name = "NotAControllerError";
|
|
@@ -195,6 +211,8 @@ interface RouteEntry {
|
|
|
195
211
|
paramSources: ReadonlyMap<number, ParamSource>;
|
|
196
212
|
httpCode?: number | undefined;
|
|
197
213
|
responseSchema?: ZodType | undefined;
|
|
214
|
+
/** Per-route `@Returns(..., { validate })` override; `undefined` follows the app-wide `responseValidation`. */
|
|
215
|
+
validateResponse?: boolean | undefined;
|
|
198
216
|
guards: Class<CanActivate>[];
|
|
199
217
|
interceptors: Class<Interceptor>[];
|
|
200
218
|
}
|
|
@@ -244,6 +262,27 @@ export declare class HttpApplication {
|
|
|
244
262
|
}
|
|
245
263
|
export declare function createHttpApplication(rootModule: ModuleRef, options?: HttpApplicationOptions): Promise<HttpApplication>;
|
|
246
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
|
|
247
286
|
//#region src/node-adapter.d.ts
|
|
248
287
|
/**
|
|
249
288
|
* Builds a Web-standard `Request` from a Node `IncomingMessage`, including a body stream and abort signal.
|
|
@@ -304,5 +343,5 @@ export declare class ResponseValidationError extends Error {
|
|
|
304
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. */
|
|
305
344
|
export declare function validateResponse(schema: ZodType | undefined, value: unknown): Promise<unknown>;
|
|
306
345
|
//#endregion
|
|
307
|
-
export type { ApiOperationOptions, CanActivate, ExecutionContext, HandlerOptions, HttpApplicationOptions, HttpMethod, Interceptor, ListenHandle, MountedHandler, ParamResolutionContext, ParamSource, RouteDefinition, RouteFound, RouteLookupResult, RouteMethodNotAllowed, RouteNotFound, ShutdownOptions };
|
|
346
|
+
export type { ApiOperationOptions, CanActivate, ExecutionContext, FetchHandler, HandlerOptions, HttpApplicationOptions, HttpMethod, Interceptor, ListenHandle, MountedHandler, ParamResolutionContext, ParamSource, ReturnsOptions, RouteDefinition, RouteFound, RouteLookupResult, RouteMethodNotAllowed, RouteNotFound, ShutdownOptions };
|
|
308
347
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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":";;;;;UAEiB;EACf;EACA;;EAEA;;;wBAOc,aAAa,SAAS,sBAAsB;wBAM5C,gBAAgB,gBAAgB,+BAA+B;;;;;;;wBAU/D,WAAW,iBAAiB,iBAAiB;wBAa7C,gBAAgB;wBAIhB,iBAAiB,gBAAgB;;;wBCtCjC,WAAW,kBAAc;wBAMzB,oBAAoB;;;UCZnB;EACf,SAAS;EACT,QAAQ,SAAS;;UAGF;EACf,YAAY,SAAS,6BAA6B;;;;;;;;wBAWpC,aAAa,QAAQ,MAAM,iBAAiB,iBAAiB;wBAa7D,eAAe,iBAAiB,MAAM;wBAItC,gBAAgB,gBAAgB,+BAA+B,MAAM;;;UCjCpE;EACf,UAAU,SAAS,kBAAkB,YAAY,QAAQ,YAAY,WAAW,QAAQ;;;;;;;;;wBAY1E,mBAAmB,cAAc,MAAM,iBAAiB,iBAAiB;wBAazE,qBAAqB,iBAAiB,MAAM;wBAI5C,sBAAsB,gBAAgB,+BAA+B,MAAM;;;KC9B/E;EACN;EAAc,SAAS;;EACvB;EAAe,SAAS;;EACxB;EAAe;EAAc,SAAS;;EACtC;EAAiB;;EACjB;;;wBAiBU,KAAK,SAAS,UAAU;;wBAKxB,MAAM,SAAS,UAAU;;wBAKzB,MAAM,cAAc,SAAS,UAAU;;wBAKvC,QAAQ,gBAAgB;;wBAKxB,OAAO;wBAIP,gBAAgB,gBAAgB,+BAA+B,YAAY;;;qBCjD9E;KAED,qBAAqB;;;UCEhB;EACf,QAAQ;EACR;EACA;;
|
|
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
|
}
|
|
@@ -351,10 +359,22 @@ var ResponseValidationError = class extends Error {
|
|
|
351
359
|
this.name = "ResponseValidationError";
|
|
352
360
|
}
|
|
353
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
|
+
}
|
|
354
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. */
|
|
355
375
|
async function validateResponse(schema, value) {
|
|
356
376
|
if (!schema) return value;
|
|
357
|
-
const result = await schema
|
|
377
|
+
const result = await parse(schema, value);
|
|
358
378
|
if (!result.success) throw new ResponseValidationError(result.error.issues);
|
|
359
379
|
return result.data;
|
|
360
380
|
}
|
|
@@ -489,6 +509,7 @@ function buildRouter(controllers) {
|
|
|
489
509
|
paramSources: getParamSources(prototype, route.propertyKey),
|
|
490
510
|
httpCode: getHttpCode(prototype, route.propertyKey),
|
|
491
511
|
responseSchema: getReturnsSchema(prototype, route.propertyKey),
|
|
512
|
+
validateResponse: getReturnsValidate(prototype, route.propertyKey),
|
|
492
513
|
guards: [...classGuards, ...getMethodGuards(prototype, route.propertyKey)],
|
|
493
514
|
interceptors: [...classInterceptors, ...getMethodInterceptors(prototype, route.propertyKey)]
|
|
494
515
|
});
|
|
@@ -560,10 +581,10 @@ function exceptionToResponse(error) {
|
|
|
560
581
|
* entirely — both are deliberate escape hatches from the normal JSON path,
|
|
561
582
|
* not a value the schema was ever meant to describe.
|
|
562
583
|
*/
|
|
563
|
-
async function toResponse(value, httpCode, responseSchema) {
|
|
584
|
+
async function toResponse(value, httpCode, responseSchema, validate) {
|
|
564
585
|
if (value instanceof Response) return value;
|
|
565
586
|
if (value === void 0) return new Response(null, { status: httpCode ?? 204 });
|
|
566
|
-
const validated = await validateResponse(responseSchema, value);
|
|
587
|
+
const validated = validate ? await validateResponse(responseSchema, value) : value;
|
|
567
588
|
return new Response(JSON.stringify(validated), {
|
|
568
589
|
status: httpCode ?? 200,
|
|
569
590
|
headers: { "content-type": "application/json" }
|
|
@@ -593,6 +614,7 @@ function createHandler(controllers, application, options = {}) {
|
|
|
593
614
|
const router = buildRouter(controllers);
|
|
594
615
|
const bodyLimit = options.bodyLimit ?? DEFAULT_BODY_LIMIT;
|
|
595
616
|
const requestTimeout = options.requestTimeout;
|
|
617
|
+
const validateByDefault = options.responseValidation !== "never";
|
|
596
618
|
return async function handle(incoming) {
|
|
597
619
|
const request = requestTimeout === void 0 ? incoming : withTimeout(incoming, requestTimeout);
|
|
598
620
|
const url = new URL(request.url);
|
|
@@ -620,7 +642,7 @@ function createHandler(controllers, application, options = {}) {
|
|
|
620
642
|
return cachedBody.value;
|
|
621
643
|
}
|
|
622
644
|
});
|
|
623
|
-
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);
|
|
624
646
|
};
|
|
625
647
|
const pipeline = route.interceptors.reduceRight((next, interceptorClass) => {
|
|
626
648
|
const interceptor = application.get(interceptorClass);
|
|
@@ -678,13 +700,14 @@ async function sendWebResponse(response, res) {
|
|
|
678
700
|
res.end();
|
|
679
701
|
return;
|
|
680
702
|
}
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
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";
|
|
688
711
|
}
|
|
689
712
|
//#endregion
|
|
690
713
|
//#region src/http-application.ts
|
|
@@ -792,6 +815,55 @@ async function createHttpApplication(rootModule, options = {}) {
|
|
|
792
815
|
return HttpApplication.create(rootModule, options);
|
|
793
816
|
}
|
|
794
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
|
|
795
867
|
//#region src/types.ts
|
|
796
868
|
const HTTP_METHODS = [
|
|
797
869
|
"GET",
|
|
@@ -803,6 +875,6 @@ const HTTP_METHODS = [
|
|
|
803
875
|
"OPTIONS"
|
|
804
876
|
];
|
|
805
877
|
//#endregion
|
|
806
|
-
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, 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 };
|
|
807
879
|
|
|
808
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\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 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 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\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\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(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/** 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\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);\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\";\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 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\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","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.
|
|
3
|
+
"version": "0.3.1",
|
|
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.
|
|
38
|
-
"@blixis-io/di": "0.1.
|
|
37
|
+
"@blixis-io/core": "0.3.0",
|
|
38
|
+
"@blixis-io/di": "0.1.1",
|
|
39
39
|
"zod": "^4.6.5"
|
|
40
40
|
},
|
|
41
41
|
"scripts": {
|