@zudojs/http 1.3.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +221 -0
- package/dist/httpAdapter/node/httpNode.adapter.d.ts +2 -1
- package/dist/httpAdapter/node/httpNode.adapter.js +17 -2
- package/dist/httpAdapter/node/httpNode.request.js +6 -0
- package/dist/httpAdapter/node/httpNode.type.d.ts +14 -0
- package/dist/httpClient/httpClient.retry.d.ts +17 -12
- package/dist/httpClient/httpClient.retry.js +35 -10
- package/dist/httpClient/httpClient.type.d.ts +14 -0
- package/dist/httpErrors/httpError.base.js +2 -2
- package/dist/httpErrors/httpError.util.d.ts +8 -0
- package/dist/httpErrors/httpError.util.js +12 -0
- package/dist/httpFetchMount/httpFetchMount.core.d.ts +25 -0
- package/dist/httpFetchMount/httpFetchMount.core.js +84 -0
- package/dist/httpFetchMount/httpFetchMount.request.d.ts +21 -0
- package/dist/httpFetchMount/httpFetchMount.request.js +100 -0
- package/dist/httpFetchMount/httpFetchMount.type.d.ts +56 -0
- package/dist/httpFetchMount/httpFetchMount.type.js +5 -0
- package/dist/httpFetchMount/index.d.ts +11 -0
- package/dist/httpFetchMount/index.js +10 -0
- package/dist/httpMiddleware/builtin/rateLimit/httpMiddleware.rateLimit.d.ts +6 -3
- package/dist/httpMiddleware/builtin/rateLimit/httpMiddleware.rateLimit.js +34 -6
- package/dist/httpMiddleware/httpMiddleware.type.d.ts +9 -1
- package/dist/httpMiddleware/pipeline/httpPipeline.execution.js +22 -45
- package/dist/httpMiddleware/pipeline/httpPipeline.guardResponse.d.ts +36 -0
- package/dist/httpMiddleware/pipeline/httpPipeline.guardResponse.js +57 -0
- package/dist/httpMiddleware/pipeline/httpPipeline.helper.d.ts +2 -1
- package/dist/httpMiddleware/pipeline/httpPipeline.helper.js +10 -0
- package/dist/httpMiddleware/pipeline/index.d.ts +1 -0
- package/dist/httpMiddleware/pipeline/index.js +1 -0
- package/dist/httpOpenApi/httpOpenApi.document.d.ts +44 -0
- package/dist/httpOpenApi/httpOpenApi.document.js +61 -0
- package/dist/httpOpenApi/httpOpenApi.mount.d.ts +31 -0
- package/dist/httpOpenApi/httpOpenApi.mount.js +58 -0
- package/dist/httpOpenApi/httpOpenApi.type.d.ts +54 -0
- package/dist/httpOpenApi/httpOpenApi.type.js +5 -0
- package/dist/httpOpenApi/index.d.ts +13 -0
- package/dist/httpOpenApi/index.js +12 -0
- package/dist/httpOpenApi/routeTable/index.d.ts +11 -0
- package/dist/httpOpenApi/routeTable/index.js +11 -0
- package/dist/httpOpenApi/routeTable/routeTable.collect.d.ts +19 -0
- package/dist/httpOpenApi/routeTable/routeTable.collect.js +89 -0
- package/dist/httpOpenApi/routeTable/routeTable.merge.d.ts +15 -0
- package/dist/httpOpenApi/routeTable/routeTable.merge.js +37 -0
- package/dist/httpOpenApi/routeTable/routeTable.template.d.ts +30 -0
- package/dist/httpOpenApi/routeTable/routeTable.template.js +67 -0
- package/dist/httpRequest/httpRequest.context.d.ts +8 -0
- package/dist/httpRequest/httpRequest.context.js +12 -0
- package/dist/httpRequest/index.d.ts +1 -0
- package/dist/httpRequest/index.js +1 -0
- package/dist/httpRequest/requestId/httpRequest.requestId.d.ts +25 -0
- package/dist/httpRequest/requestId/httpRequest.requestId.js +34 -0
- package/dist/httpRequest/requestId/index.d.ts +7 -0
- package/dist/httpRequest/requestId/index.js +7 -0
- package/dist/httpResponse/httpResponse.writer.js +15 -0
- package/dist/httpRouter/core/factory/httpRoute.factory.base.d.ts +6 -3
- package/dist/httpRouter/core/factory/httpRoute.factory.base.js +24 -11
- package/dist/httpRouter/core/group/httpRouterGroup.core.js +13 -0
- package/dist/httpRouter/core/register/httpRouter.register.js +4 -1
- package/dist/httpRouter/core/types/httpRouter.type.d.ts +31 -1
- package/dist/httpRouter/core/util/httpRoute.util.d.ts +8 -0
- package/dist/httpRouter/core/util/httpRoute.util.js +16 -0
- package/dist/httpRouter/dispatch/httpRoute.dispatcher.js +20 -3
- package/dist/httpSecurity/httpSecurity.config.js +4 -1
- package/dist/httpServer/factory/httpServer.factory.d.ts +10 -9
- package/dist/httpServer/factory/httpServer.factory.js +8 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/package.json +11 -8
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Incoming request id handling.
|
|
3
|
+
*
|
|
4
|
+
* @module httpRequest/requestId
|
|
5
|
+
*/
|
|
6
|
+
/** Longest incoming `x-request-id` value reused as `request.id`. */
|
|
7
|
+
export declare const MAX_INCOMING_REQUEST_ID_LENGTH = 128;
|
|
8
|
+
/**
|
|
9
|
+
* Characters an incoming request id may contain: letters, digits and
|
|
10
|
+
* `.`, `_`, `:`, `-`. That covers UUIDs, ULIDs, W3C trace ids and the
|
|
11
|
+
* `service:counter` style many proxies emit, and rules out spaces, quotes,
|
|
12
|
+
* control characters and anything else that could forge a log field.
|
|
13
|
+
*/
|
|
14
|
+
export declare const INCOMING_REQUEST_ID_PATTERN: RegExp;
|
|
15
|
+
/**
|
|
16
|
+
* Returns the incoming request id when it is safe to reuse, otherwise
|
|
17
|
+
* `undefined` (the caller then generates one).
|
|
18
|
+
*
|
|
19
|
+
* The header is client-controlled, so it is only trusted when it is 1 to
|
|
20
|
+
* {@link MAX_INCOMING_REQUEST_ID_LENGTH} characters from
|
|
21
|
+
* {@link INCOMING_REQUEST_ID_PATTERN}. A header sent more than once (joined
|
|
22
|
+
* with `", "` by Node) fails the pattern and is ignored.
|
|
23
|
+
*/
|
|
24
|
+
export declare function resolveIncomingRequestId(value: string | readonly string[] | undefined): string | undefined;
|
|
25
|
+
//# sourceMappingURL=httpRequest.requestId.d.ts.map
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Incoming request id handling.
|
|
3
|
+
*
|
|
4
|
+
* @module httpRequest/requestId
|
|
5
|
+
*/
|
|
6
|
+
/** Longest incoming `x-request-id` value reused as `request.id`. */
|
|
7
|
+
export const MAX_INCOMING_REQUEST_ID_LENGTH = 128;
|
|
8
|
+
/**
|
|
9
|
+
* Characters an incoming request id may contain: letters, digits and
|
|
10
|
+
* `.`, `_`, `:`, `-`. That covers UUIDs, ULIDs, W3C trace ids and the
|
|
11
|
+
* `service:counter` style many proxies emit, and rules out spaces, quotes,
|
|
12
|
+
* control characters and anything else that could forge a log field.
|
|
13
|
+
*/
|
|
14
|
+
export const INCOMING_REQUEST_ID_PATTERN = /^[A-Za-z0-9._:-]+$/;
|
|
15
|
+
/**
|
|
16
|
+
* Returns the incoming request id when it is safe to reuse, otherwise
|
|
17
|
+
* `undefined` (the caller then generates one).
|
|
18
|
+
*
|
|
19
|
+
* The header is client-controlled, so it is only trusted when it is 1 to
|
|
20
|
+
* {@link MAX_INCOMING_REQUEST_ID_LENGTH} characters from
|
|
21
|
+
* {@link INCOMING_REQUEST_ID_PATTERN}. A header sent more than once (joined
|
|
22
|
+
* with `", "` by Node) fails the pattern and is ignored.
|
|
23
|
+
*/
|
|
24
|
+
export function resolveIncomingRequestId(value) {
|
|
25
|
+
if (typeof value !== "string") {
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
const id = value.trim();
|
|
29
|
+
if (id.length === 0 || id.length > MAX_INCOMING_REQUEST_ID_LENGTH) {
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
return INCOMING_REQUEST_ID_PATTERN.test(id) ? id : undefined;
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=httpRequest.requestId.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @zudojs/http/httpRequest/requestId
|
|
3
|
+
*
|
|
4
|
+
* Validation of an incoming `x-request-id` before it becomes `request.id`.
|
|
5
|
+
*/
|
|
6
|
+
export { MAX_INCOMING_REQUEST_ID_LENGTH, INCOMING_REQUEST_ID_PATTERN, resolveIncomingRequestId, } from "./httpRequest.requestId.js";
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @zudojs/http/httpRequest/requestId
|
|
3
|
+
*
|
|
4
|
+
* Validation of an incoming `x-request-id` before it becomes `request.id`.
|
|
5
|
+
*/
|
|
6
|
+
export { MAX_INCOMING_REQUEST_ID_LENGTH, INCOMING_REQUEST_ID_PATTERN, resolveIncomingRequestId, } from "./httpRequest.requestId.js";
|
|
7
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -183,10 +183,21 @@ export function normalizeBody(body) {
|
|
|
183
183
|
/* -------------------------------------------------------------------------- */
|
|
184
184
|
export async function writeReadableStream(stream, writer) {
|
|
185
185
|
const reader = stream.getReader();
|
|
186
|
+
let finished = false;
|
|
186
187
|
try {
|
|
187
188
|
while (true) {
|
|
189
|
+
/*
|
|
190
|
+
* A sink that stopped accepting data (the client disconnected) will
|
|
191
|
+
* never drain. Without this check an unbounded stream — server-sent
|
|
192
|
+
* events, a proxied download — kept being pulled into a dead socket
|
|
193
|
+
* for as long as its source produced.
|
|
194
|
+
*/
|
|
195
|
+
if (writer.writable === false) {
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
188
198
|
const result = await reader.read();
|
|
189
199
|
if (result.done) {
|
|
200
|
+
finished = true;
|
|
190
201
|
break;
|
|
191
202
|
}
|
|
192
203
|
if (result.value) {
|
|
@@ -198,6 +209,10 @@ export async function writeReadableStream(stream, writer) {
|
|
|
198
209
|
}
|
|
199
210
|
}
|
|
200
211
|
finally {
|
|
212
|
+
if (!finished) {
|
|
213
|
+
/* Tell the source to stop producing; its failure is not ours. */
|
|
214
|
+
await reader.cancel().catch(() => undefined);
|
|
215
|
+
}
|
|
201
216
|
reader.releaseLock();
|
|
202
217
|
}
|
|
203
218
|
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Internal normalization, validation, execution, and response utilities for
|
|
5
5
|
* route creation and dispatch.
|
|
6
6
|
*/
|
|
7
|
-
import type { HttpMethod, MatchedRoute, CompiledRoute, HttpRouterContext, HttpRouterRequestContext } from "../types/httpRouter.type.js";
|
|
7
|
+
import type { HttpMethod, MatchedRoute, CompiledRoute, HttpRouterContext, HttpRouterRequestContext, RouterHandlerResult } from "../types/httpRouter.type.js";
|
|
8
8
|
import { type HttpResponseContext as ResponseContext } from "../../../httpResponse/httpResponse.context.js";
|
|
9
9
|
export declare function normalizeMethod(method: string): HttpMethod | "*";
|
|
10
10
|
export declare function normalizeMethods(method: HttpMethod | readonly HttpMethod[] | "*"): readonly (HttpMethod | "*")[];
|
|
@@ -33,9 +33,12 @@ export declare function extractRouteSequence(id: string): number;
|
|
|
33
33
|
*/
|
|
34
34
|
export declare function createFallbackRoute(path: string, method: string): MatchedRoute;
|
|
35
35
|
/**
|
|
36
|
-
* Coerces a handler result into a response context
|
|
36
|
+
* Coerces a handler result into a response context: a response context or
|
|
37
|
+
* web `Response` as built, `undefined`/`null` as `204`, and any other value
|
|
38
|
+
* as a `200` JSON body, the way server handlers treat a plain value. A
|
|
39
|
+
* plain object used to be dropped for an empty `204`.
|
|
37
40
|
*/
|
|
38
|
-
export declare function normalizeResponse(value:
|
|
41
|
+
export declare function normalizeResponse(value: RouterHandlerResult): Promise<ResponseContext>;
|
|
39
42
|
/**
|
|
40
43
|
* Builds the automatic `OPTIONS` response for a matched path.
|
|
41
44
|
*/
|
|
@@ -4,10 +4,13 @@
|
|
|
4
4
|
* Internal normalization, validation, execution, and response utilities for
|
|
5
5
|
* route creation and dispatch.
|
|
6
6
|
*/
|
|
7
|
+
import { isGuardResponse } from "@zudojs/middleware";
|
|
7
8
|
import { HttpRouterError } from "../error/httpRouter.error.js";
|
|
8
9
|
import { formatAllowHeader } from "../../../httpMethods/http.methods.js";
|
|
9
10
|
import { matchCompiledRoute } from "../../matching/httpRoute.matcher.core.js";
|
|
10
11
|
import { HttpResponseContext, } from "../../../httpResponse/httpResponse.context.js";
|
|
12
|
+
import { bufferWebResponse } from "../../../httpResponse/httpResponse.fromWeb.js";
|
|
13
|
+
import { applyGuardResponse } from "../../../httpMiddleware/pipeline/httpPipeline.guardResponse.js";
|
|
11
14
|
/* -------------------------------------------------------------------------- */
|
|
12
15
|
/* Method Helpers */
|
|
13
16
|
/* -------------------------------------------------------------------------- */
|
|
@@ -99,24 +102,27 @@ export function createFallbackRoute(path, method) {
|
|
|
99
102
|
/* Response Helpers */
|
|
100
103
|
/* -------------------------------------------------------------------------- */
|
|
101
104
|
/**
|
|
102
|
-
* Coerces a handler result into a response context
|
|
105
|
+
* Coerces a handler result into a response context: a response context or
|
|
106
|
+
* web `Response` as built, `undefined`/`null` as `204`, and any other value
|
|
107
|
+
* as a `200` JSON body, the way server handlers treat a plain value. A
|
|
108
|
+
* plain object used to be dropped for an empty `204`.
|
|
103
109
|
*/
|
|
104
110
|
export async function normalizeResponse(value) {
|
|
105
111
|
if (value instanceof HttpResponseContext) {
|
|
106
112
|
return value;
|
|
107
113
|
}
|
|
108
114
|
if (typeof Response !== "undefined" && value instanceof Response) {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
});
|
|
115
|
+
/*
|
|
116
|
+
* `Object.fromEntries(headers.entries())` folded every `Set-Cookie` into
|
|
117
|
+
* one comma-joined value, which browsers read as a single malformed
|
|
118
|
+
* cookie. `bufferWebResponse` keeps each cookie separate.
|
|
119
|
+
*/
|
|
120
|
+
return bufferWebResponse(value);
|
|
121
|
+
}
|
|
122
|
+
if (value === undefined || value === null) {
|
|
123
|
+
return new HttpResponseContext({ status: 204 });
|
|
118
124
|
}
|
|
119
|
-
return new HttpResponseContext({ status:
|
|
125
|
+
return new HttpResponseContext({ status: 200 }).json(value);
|
|
120
126
|
}
|
|
121
127
|
/**
|
|
122
128
|
* Builds the automatic `OPTIONS` response for a matched path.
|
|
@@ -219,6 +225,13 @@ export async function executeRoute(route, context) {
|
|
|
219
225
|
if (result instanceof HttpResponseContext) {
|
|
220
226
|
return mergeRouteResponse(ambient, result);
|
|
221
227
|
}
|
|
228
|
+
/*
|
|
229
|
+
* A guard (permissions, tenancy) refusing the request. Before this was
|
|
230
|
+
* honoured the returned object was ignored and the ambient 200 went out.
|
|
231
|
+
*/
|
|
232
|
+
if (isGuardResponse(result)) {
|
|
233
|
+
return applyGuardResponse(ambient, result);
|
|
234
|
+
}
|
|
222
235
|
if (typeof Response !== "undefined" && result instanceof Response) {
|
|
223
236
|
return mergeRouteResponse(ambient, await normalizeResponse(result));
|
|
224
237
|
}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Registers routes against a parent router under a shared path prefix and a
|
|
5
5
|
* shared set of default route options.
|
|
6
6
|
*/
|
|
7
|
+
import { mergeRouteOpenAPI } from "../../../httpOpenApi/routeTable/routeTable.merge.js";
|
|
7
8
|
export class HttpRouterGroup {
|
|
8
9
|
router;
|
|
9
10
|
prefix;
|
|
@@ -51,7 +52,15 @@ export class HttpRouterGroup {
|
|
|
51
52
|
return `${left}/${right}` || "/";
|
|
52
53
|
}
|
|
53
54
|
mergeOptions(options) {
|
|
55
|
+
/*
|
|
56
|
+
* `metadata.openapi` is the same setting as `openapi` (the router stores
|
|
57
|
+
* one as the other). Reading only `openapi` let a group's documentation
|
|
58
|
+
* defaults replace a route's `metadata: { openapi: false }`, publishing
|
|
59
|
+
* a route its author had hidden.
|
|
60
|
+
*/
|
|
61
|
+
const openapi = mergeRouteOpenAPI(openAPIOf(this.defaults), openAPIOf(options));
|
|
54
62
|
return {
|
|
63
|
+
...(openapi === undefined ? {} : { openapi }),
|
|
55
64
|
name: options.name ?? this.defaults.name,
|
|
56
65
|
middleware: [
|
|
57
66
|
...(this.defaults.middleware ?? []),
|
|
@@ -65,4 +74,8 @@ export class HttpRouterGroup {
|
|
|
65
74
|
};
|
|
66
75
|
}
|
|
67
76
|
}
|
|
77
|
+
function openAPIOf(options) {
|
|
78
|
+
return options.openapi ??
|
|
79
|
+
options.metadata?.["openapi"];
|
|
80
|
+
}
|
|
68
81
|
//# sourceMappingURL=httpRouterGroup.core.js.map
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { HttpRouterError, RouteConflictError, } from "../error/httpRouter.error.js";
|
|
5
5
|
import { HttpRouterGroup } from "../group/httpRouterGroup.core.js";
|
|
6
6
|
import { collectAllowedMethods, createFallbackRoute, createOptionsResponse, defaultMethodNotAllowedHandler, defaultNotFoundHandler, executeRoute, extractRouteSequence, isHttpMethod, normalizeMethod, normalizeMethods, normalizeResponse, } from "../factory/httpRoute.factory.base.js";
|
|
7
|
-
import { getRequestMethod, getRequestSignal, getRequestUrl, normalizeMatchPath, normalizePath, normalizeRoutePattern, parseQuery, parseUrl, } from "../util/httpRoute.util.js";
|
|
7
|
+
import { getRequestMethod, getRequestSignal, applyRouteParams, getRequestUrl, normalizeMatchPath, normalizePath, normalizeRoutePattern, parseQuery, parseUrl, } from "../util/httpRoute.util.js";
|
|
8
8
|
import { matchCompiledRoute } from "../../matching/httpRoute.matcher.core.js";
|
|
9
9
|
import { compareSegmentSpecificity, compileRoute, } from "../../pattern/httpRoute.pattern.parse.js";
|
|
10
10
|
import { createRouterMiddlewareContext } from "../../httpRouter.context.js";
|
|
@@ -36,6 +36,7 @@ export class HttpRouter {
|
|
|
36
36
|
middleware: definition.middleware,
|
|
37
37
|
metadata: definition.metadata,
|
|
38
38
|
strictTrailingSlash: definition.strictTrailingSlash,
|
|
39
|
+
openapi: definition.openapi,
|
|
39
40
|
});
|
|
40
41
|
}
|
|
41
42
|
return () => {
|
|
@@ -211,6 +212,7 @@ export class HttpRouter {
|
|
|
211
212
|
signal,
|
|
212
213
|
};
|
|
213
214
|
if (match.matched && match.route) {
|
|
215
|
+
applyRouteParams(request, match.params);
|
|
214
216
|
const response = await executeRoute(match.route, routerContext);
|
|
215
217
|
return {
|
|
216
218
|
response: await normalizeResponse(response),
|
|
@@ -275,6 +277,7 @@ export class HttpRouter {
|
|
|
275
277
|
params: {},
|
|
276
278
|
metadata: Object.freeze({
|
|
277
279
|
...(options.metadata ?? {}),
|
|
280
|
+
...(options.openapi === undefined ? {} : { openapi: options.openapi }),
|
|
278
281
|
}),
|
|
279
282
|
handler,
|
|
280
283
|
middleware: Object.freeze([...(options.middleware ?? [])]),
|
|
@@ -3,11 +3,25 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Core types for routing, route definitions, and router context.
|
|
5
5
|
*/
|
|
6
|
+
import type { RouteOpenAPIMetadata } from "@zudojs/openapi";
|
|
6
7
|
import type { HttpRequestContext as RequestContext } from "../../../httpRequest/httpRequest.context.js";
|
|
7
8
|
import type { HttpResponseContext as ResponseContext } from "../../../httpResponse/httpResponse.context.js";
|
|
8
9
|
import type { HttpMiddleware, HttpMiddlewareContext } from "../../../httpMiddleware/httpMiddleware.type.js";
|
|
9
10
|
export type HttpMethod = "GET" | "HEAD" | "POST" | "PUT" | "PATCH" | "DELETE" | "OPTIONS" | "CONNECT" | "TRACE";
|
|
10
|
-
|
|
11
|
+
/**
|
|
12
|
+
* A plain, JSON-serialisable value a route handler may return: an object,
|
|
13
|
+
* array, string, number, boolean or `null`.
|
|
14
|
+
*/
|
|
15
|
+
export type RouterJsonValue = object | string | number | boolean | null;
|
|
16
|
+
/**
|
|
17
|
+
* What a route handler may return, as server handlers do:
|
|
18
|
+
* - an `HttpResponseContext` or a web `Response`: sent as built;
|
|
19
|
+
* - a plain value: sent as `200` with a JSON body
|
|
20
|
+
* (`return { id }` is `ctx.middleware.response.json({ id })`);
|
|
21
|
+
* - `undefined` / `null`: `204 No Content`.
|
|
22
|
+
*/
|
|
23
|
+
export type RouterHandlerResult = ResponseContext | Response | RouterJsonValue | void;
|
|
24
|
+
export type RouterHandler = (context: HttpRouterContext) => RouterHandlerResult | Promise<RouterHandlerResult>;
|
|
11
25
|
export type RouterHandlerLike = RouterHandler | HttpMiddleware;
|
|
12
26
|
export interface HttpRouterContext {
|
|
13
27
|
readonly request: RequestContext;
|
|
@@ -18,6 +32,14 @@ export interface HttpRouterContext {
|
|
|
18
32
|
readonly middleware: HttpMiddlewareContext;
|
|
19
33
|
readonly signal: AbortSignal;
|
|
20
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* OpenAPI documentation a route carries: summary, tags, operationId,
|
|
37
|
+
* `params` / `query` / `headers` / `body` schemas, responses, security,
|
|
38
|
+
* `deprecated`, `hidden`. It is `@zudojs/openapi`'s `RouteOpenAPIMetadata`,
|
|
39
|
+
* so the schemas it names document the route in `generateOpenAPIDocument`.
|
|
40
|
+
* `false` hides the route from the generated document.
|
|
41
|
+
*/
|
|
42
|
+
export type HttpRouteOpenAPI = RouteOpenAPIMetadata | false;
|
|
21
43
|
export interface RouteDefinition {
|
|
22
44
|
readonly method: HttpMethod | readonly HttpMethod[] | "*";
|
|
23
45
|
readonly path: string;
|
|
@@ -26,12 +48,20 @@ export interface RouteDefinition {
|
|
|
26
48
|
readonly name?: string;
|
|
27
49
|
readonly metadata?: Readonly<Record<string, unknown>>;
|
|
28
50
|
readonly strictTrailingSlash?: boolean;
|
|
51
|
+
/** OpenAPI documentation; stored as `metadata.openapi`. */
|
|
52
|
+
readonly openapi?: HttpRouteOpenAPI;
|
|
29
53
|
}
|
|
30
54
|
export interface RouteOptions {
|
|
31
55
|
readonly name?: string;
|
|
32
56
|
readonly middleware?: readonly HttpMiddleware[];
|
|
33
57
|
readonly metadata?: Readonly<Record<string, unknown>>;
|
|
34
58
|
readonly strictTrailingSlash?: boolean;
|
|
59
|
+
/**
|
|
60
|
+
* OpenAPI documentation; stored as `metadata.openapi`, where
|
|
61
|
+
* `generateOpenAPIDocument` reads it. Takes precedence over a
|
|
62
|
+
* `metadata.openapi` passed alongside it.
|
|
63
|
+
*/
|
|
64
|
+
readonly openapi?: HttpRouteOpenAPI;
|
|
35
65
|
}
|
|
36
66
|
export interface MatchedRoute {
|
|
37
67
|
readonly id: string;
|
|
@@ -8,6 +8,14 @@ import type { HttpRequestContext as RequestContext } from "../../../httpRequest/
|
|
|
8
8
|
export declare function getRequestMethod(request: RequestContext): string;
|
|
9
9
|
export declare function getRequestUrl(request: RequestContext): string;
|
|
10
10
|
export declare function getRequestSignal(request: RequestContext): AbortSignal | undefined;
|
|
11
|
+
/**
|
|
12
|
+
* Copies the matched route's parameters onto the request before any route
|
|
13
|
+
* middleware runs, so `request.getParam("id")` / `request.params` see them
|
|
14
|
+
* in middleware as well as in the handler. They were only on the router
|
|
15
|
+
* context, so a guard reading the request (a permissions
|
|
16
|
+
* `extractResource`, an ownership check) always saw `undefined` and denied.
|
|
17
|
+
*/
|
|
18
|
+
export declare function applyRouteParams(request: RequestContext, params: Readonly<Record<string, string>>): void;
|
|
11
19
|
/**
|
|
12
20
|
* Parses a request-target with the canonical parser shared by the request
|
|
13
21
|
* context and path-scoped middleware.
|
|
@@ -21,6 +21,22 @@ export function getRequestUrl(request) {
|
|
|
21
21
|
export function getRequestSignal(request) {
|
|
22
22
|
return request.signal;
|
|
23
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* Copies the matched route's parameters onto the request before any route
|
|
26
|
+
* middleware runs, so `request.getParam("id")` / `request.params` see them
|
|
27
|
+
* in middleware as well as in the handler. They were only on the router
|
|
28
|
+
* context, so a guard reading the request (a permissions
|
|
29
|
+
* `extractResource`, an ownership check) always saw `undefined` and denied.
|
|
30
|
+
*/
|
|
31
|
+
export function applyRouteParams(request, params) {
|
|
32
|
+
const target = request;
|
|
33
|
+
if (typeof target.setParam !== "function") {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
for (const [name, value] of Object.entries(params)) {
|
|
37
|
+
target.setParam(name, value);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
24
40
|
/**
|
|
25
41
|
* Parses a request-target with the canonical parser shared by the request
|
|
26
42
|
* context and path-scoped middleware.
|
|
@@ -5,10 +5,13 @@
|
|
|
5
5
|
* handler pipeline. Route registration and route matching remain separate
|
|
6
6
|
* concerns.
|
|
7
7
|
*/
|
|
8
|
+
import { isGuardResponse } from "@zudojs/middleware";
|
|
9
|
+
import { applyGuardResponse } from "../../httpMiddleware/pipeline/httpPipeline.guardResponse.js";
|
|
8
10
|
import { HttpResponseContext } from "../../httpResponse/httpResponse.context.js";
|
|
9
11
|
import { createRouterContext } from "../httpRouter.context.js";
|
|
12
|
+
import { normalizeResponse } from "../core/factory/httpRoute.factory.base.js";
|
|
10
13
|
import { RouterMiddlewareState } from "../httpRouter.state.js";
|
|
11
|
-
import { getRequestSignal } from "../core/util/httpRoute.util.js";
|
|
14
|
+
import { applyRouteParams, getRequestSignal, } from "../core/util/httpRoute.util.js";
|
|
12
15
|
/* -------------------------------------------------------------------------- */
|
|
13
16
|
/* Route Dispatcher */
|
|
14
17
|
/* -------------------------------------------------------------------------- */
|
|
@@ -177,6 +180,8 @@ export async function dispatchRoute(dispatcher, request, response) {
|
|
|
177
180
|
/* Context Creation */
|
|
178
181
|
/* -------------------------------------------------------------------------- */
|
|
179
182
|
function createDispatchContext(request, response, match) {
|
|
183
|
+
/* Route middleware reads params from the request, so set them first. */
|
|
184
|
+
applyRouteParams(request, match.params);
|
|
180
185
|
return Object.freeze({
|
|
181
186
|
request,
|
|
182
187
|
response,
|
|
@@ -222,12 +227,20 @@ function normalizeHandler(handler) {
|
|
|
222
227
|
*/
|
|
223
228
|
function toDispatchHandler(handler, context, preserveResponse = false) {
|
|
224
229
|
return async (request, response) => {
|
|
225
|
-
const
|
|
230
|
+
const returned = await handler(createRouterContext({
|
|
226
231
|
request,
|
|
227
232
|
route: context.route,
|
|
228
233
|
params: context.params,
|
|
229
234
|
signal: getRequestSignal(request),
|
|
230
235
|
}));
|
|
236
|
+
/* A web Response or a plain value becomes a response context, exactly
|
|
237
|
+
* as the router treats it (a plain value is a 200 JSON body). */
|
|
238
|
+
const result = preserveResponse ||
|
|
239
|
+
returned === undefined ||
|
|
240
|
+
returned === null ||
|
|
241
|
+
returned instanceof HttpResponseContext
|
|
242
|
+
? returned
|
|
243
|
+
: await normalizeResponse(returned);
|
|
231
244
|
if (!preserveResponse &&
|
|
232
245
|
result instanceof HttpResponseContext &&
|
|
233
246
|
result !== response) {
|
|
@@ -254,7 +267,7 @@ function toDispatchHandler(handler, context, preserveResponse = false) {
|
|
|
254
267
|
*/
|
|
255
268
|
function toRouteMiddleware(middleware, route, state) {
|
|
256
269
|
return async (request, response, next) => {
|
|
257
|
-
await middleware({
|
|
270
|
+
const result = await middleware({
|
|
258
271
|
request,
|
|
259
272
|
response,
|
|
260
273
|
state,
|
|
@@ -264,6 +277,10 @@ function toRouteMiddleware(middleware, route, state) {
|
|
|
264
277
|
await next();
|
|
265
278
|
return response;
|
|
266
279
|
});
|
|
280
|
+
/* A guard's refusal is written onto the dispatch response, not dropped. */
|
|
281
|
+
if (isGuardResponse(result)) {
|
|
282
|
+
applyGuardResponse(response, result);
|
|
283
|
+
}
|
|
267
284
|
};
|
|
268
285
|
}
|
|
269
286
|
function normalizeMiddleware(middleware) {
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
* adapter has its own, separately named timeout options, and those are the
|
|
13
13
|
* ones that take effect.
|
|
14
14
|
*/
|
|
15
|
+
import { INCOMING_REQUEST_ID_PATTERN } from "../httpRequest/requestId/httpRequest.requestId.js";
|
|
15
16
|
/** Default security configuration. */
|
|
16
17
|
export const DEFAULT_SECURITY_CONFIG = Object.freeze({
|
|
17
18
|
maxBodySize: 1_048_576, // 1MB
|
|
@@ -23,7 +24,9 @@ export const DEFAULT_SECURITY_CONFIG = Object.freeze({
|
|
|
23
24
|
requireHost: true,
|
|
24
25
|
trustProxy: false,
|
|
25
26
|
maxRequestIdLength: 128,
|
|
26
|
-
|
|
27
|
+
// Same rule the adapter uses to reuse an incoming x-request-id, so an id
|
|
28
|
+
// it would accept (trace ids like `svc.a:123`) is not refused here first.
|
|
29
|
+
requestIdPattern: INCOMING_REQUEST_ID_PATTERN,
|
|
27
30
|
enableCrlfProtection: true,
|
|
28
31
|
enableSmugglingProtection: true,
|
|
29
32
|
});
|
|
@@ -4,15 +4,16 @@
|
|
|
4
4
|
* @module httpServer/factory
|
|
5
5
|
*/
|
|
6
6
|
import { HttpServer } from "../core/httpServer.core.js";
|
|
7
|
-
import type { HttpServerState, HttpServerAddress } from "../types/httpServer.type.js";
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
7
|
+
import type { HttpServerState, HttpServerAddress, HttpServerOptions } from "../types/httpServer.type.js";
|
|
8
|
+
/**
|
|
9
|
+
* Creates an HTTP server around an adapter.
|
|
10
|
+
*
|
|
11
|
+
* The options are `HttpServerOptions`, so `handler: async (request) => ...`
|
|
12
|
+
* infers `request` as `HttpRequestContext` (it used to be typed `unknown`,
|
|
13
|
+
* which made the quick start fail under `strict` with TS7006), and
|
|
14
|
+
* `errorHandler` receives the thrown error and the request.
|
|
15
|
+
*/
|
|
16
|
+
export declare function createHttpServer(options: HttpServerOptions): HttpServer;
|
|
16
17
|
export declare function startServer(server: HttpServer): Promise<HttpServer>;
|
|
17
18
|
export declare function stopServer(server: HttpServer): Promise<HttpServer>;
|
|
18
19
|
export declare function restartServer(server: HttpServer): Promise<HttpServer>;
|
|
@@ -5,6 +5,14 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { HttpServerLifecycleError } from "@zudojs/errors";
|
|
7
7
|
import { HttpServer } from "../core/httpServer.core.js";
|
|
8
|
+
/**
|
|
9
|
+
* Creates an HTTP server around an adapter.
|
|
10
|
+
*
|
|
11
|
+
* The options are `HttpServerOptions`, so `handler: async (request) => ...`
|
|
12
|
+
* infers `request` as `HttpRequestContext` (it used to be typed `unknown`,
|
|
13
|
+
* which made the quick start fail under `strict` with TS7006), and
|
|
14
|
+
* `errorHandler` receives the thrown error and the request.
|
|
15
|
+
*/
|
|
8
16
|
export function createHttpServer(options) {
|
|
9
17
|
return new HttpServer(options);
|
|
10
18
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -49,6 +49,8 @@ export * from "./httpAgent/index.js";
|
|
|
49
49
|
export * from "./httpProxy/index.js";
|
|
50
50
|
export * from "./httpSecurity/index.js";
|
|
51
51
|
export * from "./httpRouter/index.js";
|
|
52
|
+
export * from "./httpOpenApi/index.js";
|
|
53
|
+
export * from "./httpFetchMount/index.js";
|
|
52
54
|
/**
|
|
53
55
|
* Several sub-modules export the same name. A star export alone would make
|
|
54
56
|
* those names unavailable (TS2308), so ownership is declared explicitly here.
|
package/dist/index.js
CHANGED
|
@@ -49,6 +49,8 @@ export * from "./httpAgent/index.js";
|
|
|
49
49
|
export * from "./httpProxy/index.js";
|
|
50
50
|
export * from "./httpSecurity/index.js";
|
|
51
51
|
export * from "./httpRouter/index.js";
|
|
52
|
+
export * from "./httpOpenApi/index.js";
|
|
53
|
+
export * from "./httpFetchMount/index.js";
|
|
52
54
|
/* -------------------------------------------------------------------------- */
|
|
53
55
|
/* Ambiguous re-exports */
|
|
54
56
|
/* -------------------------------------------------------------------------- */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zudojs/http",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "HTTP primitives, request handling, routing, middleware, and server infrastructure for Zudojs applications.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
@@ -24,17 +24,20 @@
|
|
|
24
24
|
"!dist/.tsbuildinfo"
|
|
25
25
|
],
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@zudojs/crypto": "1.3.
|
|
28
|
-
"@zudojs/errors": "1.
|
|
29
|
-
"@zudojs/logger": "1.
|
|
30
|
-
"@zudojs/
|
|
27
|
+
"@zudojs/crypto": "1.3.1",
|
|
28
|
+
"@zudojs/errors": "1.3.0",
|
|
29
|
+
"@zudojs/logger": "1.4.0",
|
|
30
|
+
"@zudojs/middleware": "1.1.0",
|
|
31
|
+
"@zudojs/openapi": "1.5.0",
|
|
32
|
+
"@zudojs/security": "1.3.0"
|
|
31
33
|
},
|
|
32
34
|
"devDependencies": {
|
|
33
|
-
"@types/node": "^26.
|
|
35
|
+
"@types/node": "^26.6.2",
|
|
36
|
+
"@zudojs/schema": "1.2.0",
|
|
34
37
|
"fluent-ffmpeg": "^2.1.3",
|
|
35
38
|
"sharp": "^0.35.4",
|
|
36
39
|
"typescript": "7.0.2",
|
|
37
|
-
"vitest": "^
|
|
40
|
+
"vitest": "^5.0.1"
|
|
38
41
|
},
|
|
39
42
|
"engines": {
|
|
40
43
|
"node": ">=24.0.0"
|
|
@@ -49,7 +52,7 @@
|
|
|
49
52
|
"middleware",
|
|
50
53
|
"routing"
|
|
51
54
|
],
|
|
52
|
-
"homepage": "https://
|
|
55
|
+
"homepage": "https://zudojs.oyinlola.site/docs/packages-http",
|
|
53
56
|
"bugs": {
|
|
54
57
|
"url": "https://github.com/oyinlola-tech/zudo/issues"
|
|
55
58
|
},
|