@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.
Files changed (68) hide show
  1. package/README.md +221 -0
  2. package/dist/httpAdapter/node/httpNode.adapter.d.ts +2 -1
  3. package/dist/httpAdapter/node/httpNode.adapter.js +17 -2
  4. package/dist/httpAdapter/node/httpNode.request.js +6 -0
  5. package/dist/httpAdapter/node/httpNode.type.d.ts +14 -0
  6. package/dist/httpClient/httpClient.retry.d.ts +17 -12
  7. package/dist/httpClient/httpClient.retry.js +35 -10
  8. package/dist/httpClient/httpClient.type.d.ts +14 -0
  9. package/dist/httpErrors/httpError.base.js +2 -2
  10. package/dist/httpErrors/httpError.util.d.ts +8 -0
  11. package/dist/httpErrors/httpError.util.js +12 -0
  12. package/dist/httpFetchMount/httpFetchMount.core.d.ts +25 -0
  13. package/dist/httpFetchMount/httpFetchMount.core.js +84 -0
  14. package/dist/httpFetchMount/httpFetchMount.request.d.ts +21 -0
  15. package/dist/httpFetchMount/httpFetchMount.request.js +100 -0
  16. package/dist/httpFetchMount/httpFetchMount.type.d.ts +56 -0
  17. package/dist/httpFetchMount/httpFetchMount.type.js +5 -0
  18. package/dist/httpFetchMount/index.d.ts +11 -0
  19. package/dist/httpFetchMount/index.js +10 -0
  20. package/dist/httpMiddleware/builtin/rateLimit/httpMiddleware.rateLimit.d.ts +6 -3
  21. package/dist/httpMiddleware/builtin/rateLimit/httpMiddleware.rateLimit.js +34 -6
  22. package/dist/httpMiddleware/httpMiddleware.type.d.ts +9 -1
  23. package/dist/httpMiddleware/pipeline/httpPipeline.execution.js +22 -45
  24. package/dist/httpMiddleware/pipeline/httpPipeline.guardResponse.d.ts +36 -0
  25. package/dist/httpMiddleware/pipeline/httpPipeline.guardResponse.js +57 -0
  26. package/dist/httpMiddleware/pipeline/httpPipeline.helper.d.ts +2 -1
  27. package/dist/httpMiddleware/pipeline/httpPipeline.helper.js +10 -0
  28. package/dist/httpMiddleware/pipeline/index.d.ts +1 -0
  29. package/dist/httpMiddleware/pipeline/index.js +1 -0
  30. package/dist/httpOpenApi/httpOpenApi.document.d.ts +44 -0
  31. package/dist/httpOpenApi/httpOpenApi.document.js +61 -0
  32. package/dist/httpOpenApi/httpOpenApi.mount.d.ts +31 -0
  33. package/dist/httpOpenApi/httpOpenApi.mount.js +58 -0
  34. package/dist/httpOpenApi/httpOpenApi.type.d.ts +54 -0
  35. package/dist/httpOpenApi/httpOpenApi.type.js +5 -0
  36. package/dist/httpOpenApi/index.d.ts +13 -0
  37. package/dist/httpOpenApi/index.js +12 -0
  38. package/dist/httpOpenApi/routeTable/index.d.ts +11 -0
  39. package/dist/httpOpenApi/routeTable/index.js +11 -0
  40. package/dist/httpOpenApi/routeTable/routeTable.collect.d.ts +19 -0
  41. package/dist/httpOpenApi/routeTable/routeTable.collect.js +89 -0
  42. package/dist/httpOpenApi/routeTable/routeTable.merge.d.ts +15 -0
  43. package/dist/httpOpenApi/routeTable/routeTable.merge.js +37 -0
  44. package/dist/httpOpenApi/routeTable/routeTable.template.d.ts +30 -0
  45. package/dist/httpOpenApi/routeTable/routeTable.template.js +67 -0
  46. package/dist/httpRequest/httpRequest.context.d.ts +8 -0
  47. package/dist/httpRequest/httpRequest.context.js +12 -0
  48. package/dist/httpRequest/index.d.ts +1 -0
  49. package/dist/httpRequest/index.js +1 -0
  50. package/dist/httpRequest/requestId/httpRequest.requestId.d.ts +25 -0
  51. package/dist/httpRequest/requestId/httpRequest.requestId.js +34 -0
  52. package/dist/httpRequest/requestId/index.d.ts +7 -0
  53. package/dist/httpRequest/requestId/index.js +7 -0
  54. package/dist/httpResponse/httpResponse.writer.js +15 -0
  55. package/dist/httpRouter/core/factory/httpRoute.factory.base.d.ts +6 -3
  56. package/dist/httpRouter/core/factory/httpRoute.factory.base.js +24 -11
  57. package/dist/httpRouter/core/group/httpRouterGroup.core.js +13 -0
  58. package/dist/httpRouter/core/register/httpRouter.register.js +4 -1
  59. package/dist/httpRouter/core/types/httpRouter.type.d.ts +31 -1
  60. package/dist/httpRouter/core/util/httpRoute.util.d.ts +8 -0
  61. package/dist/httpRouter/core/util/httpRoute.util.js +16 -0
  62. package/dist/httpRouter/dispatch/httpRoute.dispatcher.js +20 -3
  63. package/dist/httpSecurity/httpSecurity.config.js +4 -1
  64. package/dist/httpServer/factory/httpServer.factory.d.ts +10 -9
  65. package/dist/httpServer/factory/httpServer.factory.js +8 -0
  66. package/dist/index.d.ts +2 -0
  67. package/dist/index.js +2 -0
  68. 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: ResponseContext | Response | void): Promise<ResponseContext>;
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
- const body = value.body === null
110
- ? undefined
111
- : new Uint8Array(await value.arrayBuffer());
112
- return new HttpResponseContext({
113
- status: value.status,
114
- statusText: value.statusText,
115
- headers: Object.fromEntries(value.headers.entries()),
116
- body,
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: 204 });
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
- export type RouterHandler = (context: HttpRouterContext) => ResponseContext | Response | void | Promise<ResponseContext | Response | void>;
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 result = await handler(createRouterContext({
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
- requestIdPattern: /^[a-zA-Z0-9_-]+$/,
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
- export declare function createHttpServer(options: {
9
- readonly name?: string;
10
- readonly adapter: unknown;
11
- readonly handler?: unknown;
12
- readonly errorHandler?: unknown;
13
- readonly gracefulShutdownTimeout?: number;
14
- readonly metadata?: Readonly<Record<string, unknown>>;
15
- }): HttpServer;
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.0",
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.0",
28
- "@zudojs/errors": "1.2.0",
29
- "@zudojs/logger": "1.3.0",
30
- "@zudojs/security": "1.2.0"
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.4.1",
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": "^4.1.11"
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://github.com/oyinlola-tech/zudo#readme",
55
+ "homepage": "https://zudojs.oyinlola.site/docs/packages-http",
53
56
  "bugs": {
54
57
  "url": "https://github.com/oyinlola-tech/zudo/issues"
55
58
  },