@zudojs/http 1.2.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 (127) hide show
  1. package/README.md +221 -0
  2. package/dist/httpAdapter/http.adapters.d.ts +24 -3
  3. package/dist/httpAdapter/http.adapters.js +20 -11
  4. package/dist/httpAdapter/node/httpNode.adapter.d.ts +2 -1
  5. package/dist/httpAdapter/node/httpNode.adapter.js +17 -2
  6. package/dist/httpAdapter/node/httpNode.request.js +7 -1
  7. package/dist/httpAdapter/node/httpNode.type.d.ts +14 -0
  8. package/dist/httpAgent/http.agent.d.ts +22 -3
  9. package/dist/httpAgent/http.agent.js +52 -14
  10. package/dist/httpCacheControl/httpCacheControl.freshness.d.ts +7 -1
  11. package/dist/httpCacheControl/httpCacheControl.freshness.js +30 -3
  12. package/dist/httpClient/httpClient.retry.d.ts +17 -12
  13. package/dist/httpClient/httpClient.retry.js +35 -10
  14. package/dist/httpClient/httpClient.type.d.ts +14 -0
  15. package/dist/httpErrors/httpError.base.js +2 -2
  16. package/dist/httpErrors/httpError.util.d.ts +8 -0
  17. package/dist/httpErrors/httpError.util.js +12 -0
  18. package/dist/httpFetchMount/httpFetchMount.core.d.ts +25 -0
  19. package/dist/httpFetchMount/httpFetchMount.core.js +84 -0
  20. package/dist/httpFetchMount/httpFetchMount.request.d.ts +21 -0
  21. package/dist/httpFetchMount/httpFetchMount.request.js +100 -0
  22. package/dist/httpFetchMount/httpFetchMount.type.d.ts +56 -0
  23. package/dist/httpFetchMount/httpFetchMount.type.js +5 -0
  24. package/dist/httpFetchMount/index.d.ts +11 -0
  25. package/dist/httpFetchMount/index.js +10 -0
  26. package/dist/httpKeepAlive/httpKeepAlive.core.js +14 -2
  27. package/dist/httpMiddleware/builtin/logging/httpMiddleware.logging.d.ts +18 -0
  28. package/dist/httpMiddleware/builtin/logging/httpMiddleware.logging.js +17 -1
  29. package/dist/httpMiddleware/builtin/rateLimit/httpMiddleware.rateLimit.d.ts +6 -3
  30. package/dist/httpMiddleware/builtin/rateLimit/httpMiddleware.rateLimit.js +34 -6
  31. package/dist/httpMiddleware/builtin/security/httpMiddleware.security.d.ts +11 -0
  32. package/dist/httpMiddleware/builtin/security/httpMiddleware.security.js +25 -12
  33. package/dist/httpMiddleware/httpMiddleware.type.d.ts +9 -1
  34. package/dist/httpMiddleware/pipeline/httpPipeline.execution.js +22 -45
  35. package/dist/httpMiddleware/pipeline/httpPipeline.guardResponse.d.ts +36 -0
  36. package/dist/httpMiddleware/pipeline/httpPipeline.guardResponse.js +57 -0
  37. package/dist/httpMiddleware/pipeline/httpPipeline.helper.d.ts +2 -1
  38. package/dist/httpMiddleware/pipeline/httpPipeline.helper.js +10 -0
  39. package/dist/httpMiddleware/pipeline/index.d.ts +1 -0
  40. package/dist/httpMiddleware/pipeline/index.js +1 -0
  41. package/dist/httpNegotiation/httpNegotiation.core.d.ts +13 -0
  42. package/dist/httpNegotiation/httpNegotiation.core.js +60 -8
  43. package/dist/httpOpenApi/httpOpenApi.document.d.ts +44 -0
  44. package/dist/httpOpenApi/httpOpenApi.document.js +61 -0
  45. package/dist/httpOpenApi/httpOpenApi.mount.d.ts +31 -0
  46. package/dist/httpOpenApi/httpOpenApi.mount.js +58 -0
  47. package/dist/httpOpenApi/httpOpenApi.type.d.ts +54 -0
  48. package/dist/httpOpenApi/httpOpenApi.type.js +5 -0
  49. package/dist/httpOpenApi/index.d.ts +13 -0
  50. package/dist/httpOpenApi/index.js +12 -0
  51. package/dist/httpOpenApi/routeTable/index.d.ts +11 -0
  52. package/dist/httpOpenApi/routeTable/index.js +11 -0
  53. package/dist/httpOpenApi/routeTable/routeTable.collect.d.ts +19 -0
  54. package/dist/httpOpenApi/routeTable/routeTable.collect.js +89 -0
  55. package/dist/httpOpenApi/routeTable/routeTable.merge.d.ts +15 -0
  56. package/dist/httpOpenApi/routeTable/routeTable.merge.js +37 -0
  57. package/dist/httpOpenApi/routeTable/routeTable.template.d.ts +30 -0
  58. package/dist/httpOpenApi/routeTable/routeTable.template.js +67 -0
  59. package/dist/httpProxy/http.proxy.d.ts +9 -0
  60. package/dist/httpProxy/http.proxy.js +35 -3
  61. package/dist/httpQuery/index.d.ts +11 -2
  62. package/dist/httpQuery/index.js +11 -2
  63. package/dist/httpQuery/queryParse/index.d.ts +10 -0
  64. package/dist/httpQuery/queryParse/index.js +10 -0
  65. package/dist/httpQuery/queryParse/queryParse.flat.d.ts +13 -0
  66. package/dist/httpQuery/queryParse/queryParse.flat.js +44 -0
  67. package/dist/httpQuery/queryParse/queryParse.nested.d.ts +25 -0
  68. package/dist/httpQuery/queryParse/queryParse.nested.js +112 -0
  69. package/dist/httpQuery/queryParse/queryParse.tokenizer.d.ts +37 -0
  70. package/dist/httpQuery/queryParse/queryParse.tokenizer.js +95 -0
  71. package/dist/httpQuery/queryRequest/index.d.ts +9 -0
  72. package/dist/httpQuery/queryRequest/index.js +9 -0
  73. package/dist/httpQuery/queryRequest/query.request.d.ts +43 -0
  74. package/dist/httpQuery/queryRequest/query.request.js +96 -0
  75. package/dist/httpQuery/querySerialize/index.d.ts +10 -0
  76. package/dist/httpQuery/querySerialize/index.js +10 -0
  77. package/dist/httpQuery/querySerialize/query.util.d.ts +21 -0
  78. package/dist/httpQuery/querySerialize/query.util.js +67 -0
  79. package/dist/httpQuery/querySerialize/querySerialize.core.d.ts +12 -0
  80. package/dist/httpQuery/querySerialize/querySerialize.core.js +97 -0
  81. package/dist/httpQuery/queryTypes/index.d.ts +11 -0
  82. package/dist/httpQuery/queryTypes/index.js +9 -0
  83. package/dist/httpQuery/queryTypes/query.container.d.ts +16 -0
  84. package/dist/httpQuery/queryTypes/query.container.js +51 -0
  85. package/dist/httpQuery/queryTypes/query.limit.d.ts +25 -0
  86. package/dist/httpQuery/queryTypes/query.limit.js +32 -0
  87. package/dist/httpQuery/queryTypes/query.type.d.ts +62 -0
  88. package/dist/httpQuery/queryTypes/query.type.js +2 -0
  89. package/dist/httpRedirect/http.redirect.d.ts +6 -0
  90. package/dist/httpRedirect/http.redirect.js +53 -2
  91. package/dist/httpRequest/http.request.d.ts +61 -2
  92. package/dist/httpRequest/http.request.js +86 -35
  93. package/dist/httpRequest/httpRequest.context.d.ts +8 -0
  94. package/dist/httpRequest/httpRequest.context.js +23 -18
  95. package/dist/httpRequest/index.d.ts +1 -0
  96. package/dist/httpRequest/index.js +1 -0
  97. package/dist/httpRequest/requestId/httpRequest.requestId.d.ts +25 -0
  98. package/dist/httpRequest/requestId/httpRequest.requestId.js +34 -0
  99. package/dist/httpRequest/requestId/index.d.ts +7 -0
  100. package/dist/httpRequest/requestId/index.js +7 -0
  101. package/dist/httpRequest/target/httpRequest.target.d.ts +2 -2
  102. package/dist/httpRequest/target/httpRequest.target.js +23 -5
  103. package/dist/httpResponse/httpResponse.writer.js +15 -0
  104. package/dist/httpRouter/core/factory/httpRoute.factory.base.d.ts +24 -4
  105. package/dist/httpRouter/core/factory/httpRoute.factory.base.js +73 -17
  106. package/dist/httpRouter/core/factory/httpRoute.factory.js +3 -3
  107. package/dist/httpRouter/core/group/httpRouterGroup.core.js +13 -0
  108. package/dist/httpRouter/core/register/httpRouter.register.js +19 -21
  109. package/dist/httpRouter/core/types/httpRouter.type.d.ts +37 -1
  110. package/dist/httpRouter/core/util/httpRoute.util.d.ts +55 -0
  111. package/dist/httpRouter/core/util/httpRoute.util.js +101 -4
  112. package/dist/httpRouter/dispatch/httpRoute.dispatcher.d.ts +11 -0
  113. package/dist/httpRouter/dispatch/httpRoute.dispatcher.js +25 -6
  114. package/dist/httpRouter/matching/httpRoute.matcher.core.js +51 -6
  115. package/dist/httpRouter/matching/httpRoute.matcher.d.ts +0 -1
  116. package/dist/httpRouter/matching/httpRoute.matcher.js +64 -26
  117. package/dist/httpRouter/pattern/httpRoute.pattern.parse.d.ts +16 -0
  118. package/dist/httpRouter/pattern/httpRoute.pattern.parse.js +40 -11
  119. package/dist/httpRouter/pattern/index.d.ts +1 -1
  120. package/dist/httpRouter/pattern/index.js +1 -1
  121. package/dist/httpSecurity/httpSecurity.config.js +4 -1
  122. package/dist/httpSecurity/httpSecurity.validator.js +16 -7
  123. package/dist/httpServer/factory/httpServer.factory.d.ts +10 -9
  124. package/dist/httpServer/factory/httpServer.factory.js +8 -0
  125. package/dist/index.d.ts +2 -0
  126. package/dist/index.js +2 -0
  127. package/package.json +11 -8
@@ -0,0 +1,61 @@
1
+ /**
2
+ * OpenAPI document generation from a router's registered routes.
3
+ */
4
+ import { createOpenAPIDocumentFromRoutes, createOpenAPIManagerFromRoutes, routeDescriptorToRouteInfo, } from "@zudojs/openapi";
5
+ import { collectOpenAPIRoutes } from "./routeTable/routeTable.collect.js";
6
+ /**
7
+ * Generates an OpenAPI document from the routes a router has registered.
8
+ *
9
+ * Nothing is added by hand: every route's method and path come from the
10
+ * router, and its summary, tags, schemas and responses from the `openapi`
11
+ * option it was registered with. Call it again after adding routes and the
12
+ * new routes are in the result.
13
+ *
14
+ * ```ts
15
+ * const router = createRouter();
16
+ * router.get("/users/:id", getUser, {
17
+ * openapi: { summary: "Get a user", tags: ["users"],
18
+ * responses: { "200": { schema: userSchema } } },
19
+ * });
20
+ * const document = generateOpenAPIDocument(router, {
21
+ * info: { title: "Users API", version: "1.0.0" },
22
+ * exclude: ["/health"],
23
+ * });
24
+ * ```
25
+ *
26
+ * @throws {OpenAPIRouteError} When a documented route cannot be expressed.
27
+ * @throws {OpenAPIValidationError} When `validate` is set and the document
28
+ * is invalid.
29
+ */
30
+ export function generateOpenAPIDocument(router, options) {
31
+ return createOpenAPIDocumentFromRoutes(collectOpenAPIRoutes(router, options), options);
32
+ }
33
+ function fingerprint(router) {
34
+ return router
35
+ .compiled()
36
+ .map((route) => route.definition.id)
37
+ .sort()
38
+ .join(",");
39
+ }
40
+ /**
41
+ * Creates an `OpenAPIManager` kept in step with `router`: each access checks
42
+ * the route table and re-reads it only when a route was added or removed,
43
+ * so serving the document stays cheap while never going stale.
44
+ */
45
+ export function createRouterOpenAPI(router, options) {
46
+ const manager = createOpenAPIManagerFromRoutes([], options);
47
+ let seen;
48
+ const refresh = () => {
49
+ const current = fingerprint(router);
50
+ if (current !== seen) {
51
+ manager.setRoutes(collectOpenAPIRoutes(router, options).map(routeDescriptorToRouteInfo));
52
+ seen = current;
53
+ }
54
+ return manager;
55
+ };
56
+ return Object.freeze({
57
+ manager: refresh,
58
+ document: () => refresh().getDocument(options.validate ?? false),
59
+ });
60
+ }
61
+ //# sourceMappingURL=httpOpenApi.document.js.map
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Serving a router's own OpenAPI document and documentation page.
3
+ */
4
+ import type { OpenAPIDocument } from "@zudojs/openapi";
5
+ import type { HttpRouter } from "../httpRouter/core/register/httpRouter.register.js";
6
+ import type { HttpOpenAPIMountOptions } from "./httpOpenApi.type.js";
7
+ /** What `mountOpenAPI` registered. */
8
+ export interface HttpOpenAPIMount {
9
+ /** The current document, built from the router's routes. */
10
+ document(): OpenAPIDocument;
11
+ /** Removes every route `mountOpenAPI` registered. */
12
+ unmount(): void;
13
+ }
14
+ /**
15
+ * Serves the router's OpenAPI document at `path` (default `/openapi.json`),
16
+ * optionally as YAML at `yamlPath`, and a Swagger UI page (or ReDoc, with
17
+ * `ui: { renderer: "redoc" }`) at `docsPath` (default `/docs`).
18
+ *
19
+ * The document is generated from the routes registered on `router`, so a
20
+ * route added after mounting appears on the next request. The routes this
21
+ * registers are hidden from the document themselves.
22
+ *
23
+ * ```ts
24
+ * mountOpenAPI(router, {
25
+ * info: { title: "Orders API", version: "1.0.0" },
26
+ * exclude: ["/health", "/internal/*"],
27
+ * });
28
+ * ```
29
+ */
30
+ export declare function mountOpenAPI(router: HttpRouter, options: HttpOpenAPIMountOptions): HttpOpenAPIMount;
31
+ //# sourceMappingURL=httpOpenApi.mount.d.ts.map
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Serving a router's own OpenAPI document and documentation page.
3
+ */
4
+ import { createResponseContext } from "../httpResponse/httpResponse.context.js";
5
+ import { createRouterOpenAPI } from "./httpOpenApi.document.js";
6
+ /**
7
+ * Serves the router's OpenAPI document at `path` (default `/openapi.json`),
8
+ * optionally as YAML at `yamlPath`, and a Swagger UI page (or ReDoc, with
9
+ * `ui: { renderer: "redoc" }`) at `docsPath` (default `/docs`).
10
+ *
11
+ * The document is generated from the routes registered on `router`, so a
12
+ * route added after mounting appears on the next request. The routes this
13
+ * registers are hidden from the document themselves.
14
+ *
15
+ * ```ts
16
+ * mountOpenAPI(router, {
17
+ * info: { title: "Orders API", version: "1.0.0" },
18
+ * exclude: ["/health", "/internal/*"],
19
+ * });
20
+ * ```
21
+ */
22
+ export function mountOpenAPI(router, options) {
23
+ const source = createRouterOpenAPI(router, options);
24
+ const jsonPath = options.path ?? "/openapi.json";
25
+ const cacheControl = options.cacheControl ?? "no-cache";
26
+ const routeOptions = {
27
+ openapi: false,
28
+ ...(options.middleware ? { middleware: options.middleware } : {}),
29
+ };
30
+ const serve = (format) => () => {
31
+ const response = source.manager().toResponse({
32
+ format,
33
+ validate: options.validate ?? false,
34
+ cacheControl,
35
+ });
36
+ return createResponseContext({ ...response, headers: { ...response.headers } });
37
+ };
38
+ const removers = [router.get(jsonPath, serve("json"), routeOptions)];
39
+ if (typeof options.yamlPath === "string") {
40
+ removers.push(router.get(options.yamlPath, serve("yaml"), routeOptions));
41
+ }
42
+ const docsPath = options.docsPath ?? "/docs";
43
+ if (docsPath !== false) {
44
+ const ui = { ...options.ui, specUrl: options.ui?.specUrl ?? jsonPath };
45
+ removers.push(router.get(docsPath, () => {
46
+ const response = source.manager().toUIResponse(ui);
47
+ return createResponseContext({ ...response, headers: { ...response.headers } });
48
+ }, routeOptions));
49
+ }
50
+ return Object.freeze({
51
+ document: source.document,
52
+ unmount: () => {
53
+ for (const remove of removers)
54
+ remove();
55
+ },
56
+ });
57
+ }
58
+ //# sourceMappingURL=httpOpenApi.mount.js.map
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Types for generating and serving OpenAPI documents from a router.
3
+ */
4
+ import type { OpenAPIDocumentFromRoutesOptions, OpenAPIUIOptions } from "@zudojs/openapi";
5
+ import type { CompiledRoute, MatchedRoute } from "../httpRouter/core/types/httpRouter.type.js";
6
+ import type { HttpMiddleware } from "../httpMiddleware/httpMiddleware.type.js";
7
+ import type { HttpOpenAPIWildcardMode } from "./routeTable/routeTable.template.js";
8
+ /** Anything exposing the router's compiled route table (`HttpRouter`). */
9
+ export interface HttpOpenAPIRouteSource {
10
+ compiled(): readonly CompiledRoute[];
11
+ }
12
+ /** A path matcher: exact path, `prefix/*` prefix, a RegExp, or a predicate. */
13
+ export type HttpOpenAPIRouteFilter = readonly (string | RegExp)[] | ((route: MatchedRoute) => boolean);
14
+ /** Which registered routes reach the document. */
15
+ export interface HttpOpenAPIRouteSelection {
16
+ /**
17
+ * Routes to leave out, matched against the registered pattern
18
+ * (`/health`, `/internal/*`, `/^\/admin/`) or by predicate. Routes whose
19
+ * `openapi` is `false` or `{ hidden: true }` are always left out.
20
+ */
21
+ readonly exclude?: HttpOpenAPIRouteFilter;
22
+ /**
23
+ * `"include"` (default) documents every route, with whatever the route
24
+ * itself declares; `"exclude"` documents only routes that declare
25
+ * `openapi` metadata.
26
+ */
27
+ readonly undocumented?: "include" | "exclude";
28
+ /** How `*rest` segments are documented. Default: `"parameter"`. */
29
+ readonly wildcards?: HttpOpenAPIWildcardMode;
30
+ /** Receives routes skipped because another route owns the same operation. */
31
+ readonly onRouteWarning?: (message: string) => void;
32
+ }
33
+ /** Options for `generateOpenAPIDocument`. */
34
+ export interface HttpOpenAPIOptions extends OpenAPIDocumentFromRoutesOptions, HttpOpenAPIRouteSelection {
35
+ }
36
+ /** Options for `mountOpenAPI`. */
37
+ export interface HttpOpenAPIMountOptions extends HttpOpenAPIOptions {
38
+ /** Where the JSON document is served. Default: `/openapi.json`. */
39
+ readonly path?: string;
40
+ /** Where a YAML copy is served, or `false` (default) for none. */
41
+ readonly yamlPath?: string | false;
42
+ /** Where the documentation page is served, or `false`. Default: `/docs`. */
43
+ readonly docsPath?: string | false;
44
+ /**
45
+ * Documentation page options (`renderer: "redoc"`, `logo`, …). `specUrl`
46
+ * defaults to `path`; set it when the router is served under a prefix.
47
+ */
48
+ readonly ui?: Partial<OpenAPIUIOptions>;
49
+ /** `cache-control` for the document responses. Default: `no-cache`. */
50
+ readonly cacheControl?: string;
51
+ /** Middleware run before the documentation routes (e.g. auth). */
52
+ readonly middleware?: readonly HttpMiddleware[];
53
+ }
54
+ //# sourceMappingURL=httpOpenApi.type.d.ts.map
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Types for generating and serving OpenAPI documents from a router.
3
+ */
4
+ export {};
5
+ //# sourceMappingURL=httpOpenApi.type.js.map
@@ -0,0 +1,13 @@
1
+ /**
2
+ * @zudojs/http/httpOpenApi
3
+ *
4
+ * OpenAPI generated from the application: documents built from the routes a
5
+ * router has actually registered (`generateOpenAPIDocument`), kept in step
6
+ * with it (`createRouterOpenAPI`), and served with a documentation page
7
+ * (`mountOpenAPI`). Route-level documentation is the `openapi` route option.
8
+ */
9
+ export { generateOpenAPIDocument, createRouterOpenAPI, type HttpRouterOpenAPI, } from "./httpOpenApi.document.js";
10
+ export { mountOpenAPI, type HttpOpenAPIMount } from "./httpOpenApi.mount.js";
11
+ export type { HttpOpenAPIOptions, HttpOpenAPIMountOptions, HttpOpenAPIRouteSelection, HttpOpenAPIRouteFilter, HttpOpenAPIRouteSource, } from "./httpOpenApi.type.js";
12
+ export * from "./routeTable/index.js";
13
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,12 @@
1
+ /**
2
+ * @zudojs/http/httpOpenApi
3
+ *
4
+ * OpenAPI generated from the application: documents built from the routes a
5
+ * router has actually registered (`generateOpenAPIDocument`), kept in step
6
+ * with it (`createRouterOpenAPI`), and served with a documentation page
7
+ * (`mountOpenAPI`). Route-level documentation is the `openapi` route option.
8
+ */
9
+ export { generateOpenAPIDocument, createRouterOpenAPI, } from "./httpOpenApi.document.js";
10
+ export { mountOpenAPI } from "./httpOpenApi.mount.js";
11
+ export * from "./routeTable/index.js";
12
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,11 @@
1
+ /**
2
+ * @zudojs/http/httpOpenApi/routeTable
3
+ *
4
+ * Reads a router's compiled route table as OpenAPI route descriptors:
5
+ * pattern → path template translation, route selection, and merging of
6
+ * group-level documentation into route-level documentation.
7
+ */
8
+ export { collectOpenAPIRoutes } from "./routeTable.collect.js";
9
+ export { routePathVariants, wildcardParameterName, type HttpOpenAPIPathVariant, type HttpOpenAPIWildcardMode, } from "./routeTable.template.js";
10
+ export { mergeRouteOpenAPI } from "./routeTable.merge.js";
11
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,11 @@
1
+ /**
2
+ * @zudojs/http/httpOpenApi/routeTable
3
+ *
4
+ * Reads a router's compiled route table as OpenAPI route descriptors:
5
+ * pattern → path template translation, route selection, and merging of
6
+ * group-level documentation into route-level documentation.
7
+ */
8
+ export { collectOpenAPIRoutes } from "./routeTable.collect.js";
9
+ export { routePathVariants, wildcardParameterName, } from "./routeTable.template.js";
10
+ export { mergeRouteOpenAPI } from "./routeTable.merge.js";
11
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Collection of a router's registered routes as OpenAPI route descriptors.
3
+ */
4
+ import { type OpenAPIRouteDescriptor } from "@zudojs/openapi";
5
+ import type { HttpOpenAPIRouteSelection, HttpOpenAPIRouteSource } from "../httpOpenApi.type.js";
6
+ /**
7
+ * Lists the operations a router actually serves, as descriptors for
8
+ * `createOpenAPIDocumentFromRoutes`.
9
+ *
10
+ * Routes appear in registration order. Left out: `all()` / `"*"` routes
11
+ * (they answer every method, so no single operation describes them),
12
+ * methods OpenAPI cannot carry (`CONNECT`), hidden and excluded routes. The
13
+ * router's automatic `HEAD` and `OPTIONS` answers are never registered
14
+ * routes, so they never appear; an explicitly registered `HEAD` does.
15
+ * When two patterns document the same operation (`/a/:id` and `/a/{id}`),
16
+ * the one registered first is kept and the other reported.
17
+ */
18
+ export declare function collectOpenAPIRoutes(source: HttpOpenAPIRouteSource, selection?: HttpOpenAPIRouteSelection): readonly OpenAPIRouteDescriptor[];
19
+ //# sourceMappingURL=routeTable.collect.d.ts.map
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Collection of a router's registered routes as OpenAPI route descriptors.
3
+ */
4
+ import { isOpenAPIMethod, toOpenAPIPath, } from "@zudojs/openapi";
5
+ import { routePathVariants } from "./routeTable.template.js";
6
+ function sequenceOf(route) {
7
+ const sequence = Number(route.definition.id.split(":").pop());
8
+ return Number.isFinite(sequence) ? sequence : Number.MAX_SAFE_INTEGER;
9
+ }
10
+ function matchesFilter(route, filter) {
11
+ if (filter === undefined)
12
+ return false;
13
+ if (typeof filter === "function")
14
+ return filter(route);
15
+ return filter.some((entry) => {
16
+ if (entry instanceof RegExp)
17
+ return entry.test(route.path);
18
+ if (entry.endsWith("/*")) {
19
+ const prefix = entry.slice(0, -2);
20
+ return route.path === prefix || route.path.startsWith(`${prefix}/`);
21
+ }
22
+ return route.path === entry;
23
+ });
24
+ }
25
+ /** The route's `openapi` metadata; `false` when it is hidden. */
26
+ function documentationOf(route) {
27
+ const value = route.metadata["openapi"];
28
+ if (value === false)
29
+ return false;
30
+ if (typeof value !== "object" || value === null)
31
+ return undefined;
32
+ const metadata = value;
33
+ return metadata.hidden === true ? false : metadata;
34
+ }
35
+ /**
36
+ * Lists the operations a router actually serves, as descriptors for
37
+ * `createOpenAPIDocumentFromRoutes`.
38
+ *
39
+ * Routes appear in registration order. Left out: `all()` / `"*"` routes
40
+ * (they answer every method, so no single operation describes them),
41
+ * methods OpenAPI cannot carry (`CONNECT`), hidden and excluded routes. The
42
+ * router's automatic `HEAD` and `OPTIONS` answers are never registered
43
+ * routes, so they never appear; an explicitly registered `HEAD` does.
44
+ * When two patterns document the same operation (`/a/:id` and `/a/{id}`),
45
+ * the one registered first is kept and the other reported.
46
+ */
47
+ export function collectOpenAPIRoutes(source, selection = {}) {
48
+ const routes = [...source.compiled()].sort((a, b) => sequenceOf(a) - sequenceOf(b));
49
+ const descriptors = [];
50
+ const seen = new Map();
51
+ for (const route of routes) {
52
+ const definition = route.definition;
53
+ if (definition.method === "*" || !isOpenAPIMethod(definition.method))
54
+ continue;
55
+ const documentation = documentationOf(definition);
56
+ if (documentation === false)
57
+ continue;
58
+ if (documentation === undefined && selection.undocumented === "exclude")
59
+ continue;
60
+ if (matchesFilter(definition, selection.exclude))
61
+ continue;
62
+ const variants = routePathVariants(route, selection.wildcards);
63
+ variants.forEach((variant, index) => {
64
+ const key = `${definition.method} ${toOpenAPIPath(variant.path)}`;
65
+ const owner = seen.get(key);
66
+ if (owner !== undefined) {
67
+ selection.onRouteWarning?.(`${definition.method} ${definition.path} documents the same operation as ` +
68
+ `${owner} (${key}); only the first registered is documented.`);
69
+ return;
70
+ }
71
+ seen.set(key, `${definition.method} ${definition.path}`);
72
+ const operationId = documentation?.operationId;
73
+ descriptors.push({
74
+ ...documentation,
75
+ method: definition.method,
76
+ path: variant.path,
77
+ ...(operationId !== undefined && index > 0
78
+ ? { operationId: `${operationId}_${index}` }
79
+ : {}),
80
+ inferredParameters: [
81
+ ...variant.inferredParameters,
82
+ ...(documentation?.inferredParameters ?? []),
83
+ ],
84
+ });
85
+ });
86
+ }
87
+ return descriptors;
88
+ }
89
+ //# sourceMappingURL=routeTable.collect.js.map
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Merging of group-level and route-level OpenAPI documentation.
3
+ */
4
+ import type { HttpRouteOpenAPI } from "../../httpRouter/core/types/httpRouter.type.js";
5
+ /**
6
+ * Combines a router group's `openapi` defaults with a route's own.
7
+ *
8
+ * The route wins field by field, except that `tags` are unioned and
9
+ * `parameters` concatenated (a route parameter with the same name and
10
+ * location still replaces the group's when the document is built). `false`
11
+ * on the route hides it; `false` on the group hides every route that does
12
+ * not document itself.
13
+ */
14
+ export declare function mergeRouteOpenAPI(defaults: HttpRouteOpenAPI | undefined, route: HttpRouteOpenAPI | undefined): HttpRouteOpenAPI | undefined;
15
+ //# sourceMappingURL=routeTable.merge.d.ts.map
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Merging of group-level and route-level OpenAPI documentation.
3
+ */
4
+ function union(left, right) {
5
+ if (left === undefined)
6
+ return right;
7
+ if (right === undefined)
8
+ return left;
9
+ return [...new Set([...left, ...right])];
10
+ }
11
+ /**
12
+ * Combines a router group's `openapi` defaults with a route's own.
13
+ *
14
+ * The route wins field by field, except that `tags` are unioned and
15
+ * `parameters` concatenated (a route parameter with the same name and
16
+ * location still replaces the group's when the document is built). `false`
17
+ * on the route hides it; `false` on the group hides every route that does
18
+ * not document itself.
19
+ */
20
+ export function mergeRouteOpenAPI(defaults, route) {
21
+ if (route === undefined)
22
+ return defaults;
23
+ if (route === false || defaults === undefined || defaults === false) {
24
+ return route;
25
+ }
26
+ const tags = union(defaults.tags, route.tags);
27
+ const parameters = defaults.parameters === undefined && route.parameters === undefined
28
+ ? undefined
29
+ : [...(defaults.parameters ?? []), ...(route.parameters ?? [])];
30
+ return {
31
+ ...defaults,
32
+ ...route,
33
+ ...(tags === undefined ? {} : { tags }),
34
+ ...(parameters === undefined ? {} : { parameters }),
35
+ };
36
+ }
37
+ //# sourceMappingURL=routeTable.merge.js.map
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Translation of compiled router patterns into OpenAPI path templates.
3
+ *
4
+ * Built from the compiled segments rather than the pattern string, so every
5
+ * syntax the router accepts — `:id`, `{id}`, `:id(\\d+)`, `:id?`, `*rest` —
6
+ * is read the way the router itself reads it.
7
+ */
8
+ import type { RouteParameterMetadata } from "@zudojs/openapi";
9
+ import type { CompiledRoute } from "../../httpRouter/core/types/httpRouter.type.js";
10
+ /** How wildcard (`*rest`) segments are documented. */
11
+ export type HttpOpenAPIWildcardMode = "parameter" | "exclude";
12
+ /** One documented path for a route. */
13
+ export interface HttpOpenAPIPathVariant {
14
+ /** The OpenAPI path template, e.g. `/users/{id}`. */
15
+ readonly path: string;
16
+ /** Constraints the pattern itself declares (regex, wildcard tail). */
17
+ readonly inferredParameters: readonly RouteParameterMetadata[];
18
+ }
19
+ /** The template slot name for a wildcard; a bare `*` becomes `wildcard`. */
20
+ export declare function wildcardParameterName(name: string): string;
21
+ /**
22
+ * Lists the OpenAPI paths a compiled route answers.
23
+ *
24
+ * OpenAPI has no optional path parameters, so each optional segment doubles
25
+ * the variants (with and without it). A wildcard becomes one templated slot
26
+ * described as matching the rest of the path, or — with `wildcards:
27
+ * "exclude"` — removes the route from the document (an empty list).
28
+ */
29
+ export declare function routePathVariants(route: CompiledRoute, wildcards?: HttpOpenAPIWildcardMode): readonly HttpOpenAPIPathVariant[];
30
+ //# sourceMappingURL=routeTable.template.d.ts.map
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Translation of compiled router patterns into OpenAPI path templates.
3
+ *
4
+ * Built from the compiled segments rather than the pattern string, so every
5
+ * syntax the router accepts — `:id`, `{id}`, `:id(\\d+)`, `:id?`, `*rest` —
6
+ * is read the way the router itself reads it.
7
+ */
8
+ /** The template slot name for a wildcard; a bare `*` becomes `wildcard`. */
9
+ export function wildcardParameterName(name) {
10
+ return name === "*" || name === "" ? "wildcard" : name;
11
+ }
12
+ /**
13
+ * Lists the OpenAPI paths a compiled route answers.
14
+ *
15
+ * OpenAPI has no optional path parameters, so each optional segment doubles
16
+ * the variants (with and without it). A wildcard becomes one templated slot
17
+ * described as matching the rest of the path, or — with `wildcards:
18
+ * "exclude"` — removes the route from the document (an empty list).
19
+ */
20
+ export function routePathVariants(route, wildcards = "parameter") {
21
+ let drafts = [{ parts: [], parameters: [] }];
22
+ for (const segment of route.segments) {
23
+ if (segment.type === "literal") {
24
+ drafts = drafts.map((draft) => ({
25
+ ...draft,
26
+ parts: [...draft.parts, segment.value],
27
+ }));
28
+ continue;
29
+ }
30
+ if (segment.type === "wildcard") {
31
+ if (wildcards === "exclude")
32
+ return [];
33
+ const name = wildcardParameterName(segment.name);
34
+ const parameter = {
35
+ name,
36
+ in: "path",
37
+ description: 'The rest of the path; may contain "/".',
38
+ schema: { type: "string" },
39
+ };
40
+ drafts = drafts.map((draft) => ({
41
+ parts: [...draft.parts, `{${name}}`],
42
+ parameters: [...draft.parameters, parameter],
43
+ }));
44
+ continue;
45
+ }
46
+ const parameters = segment.pattern === undefined
47
+ ? []
48
+ : [
49
+ {
50
+ name: segment.name,
51
+ in: "path",
52
+ schema: { type: "string", pattern: segment.pattern.source },
53
+ },
54
+ ];
55
+ const withSegment = drafts.map((draft) => ({
56
+ parts: [...draft.parts, `{${segment.name}}`],
57
+ parameters: [...draft.parameters, ...parameters],
58
+ }));
59
+ drafts = segment.optional ? [...drafts, ...withSegment] : withSegment;
60
+ }
61
+ const trailing = route.strictTrailingSlash && route.expectsTrailingSlash === true ? "/" : "";
62
+ return drafts.map((draft) => ({
63
+ path: draft.parts.length === 0 ? "/" : `/${draft.parts.join("/")}${trailing}`,
64
+ inferredParameters: draft.parameters,
65
+ }));
66
+ }
67
+ //# sourceMappingURL=routeTable.template.js.map
@@ -167,6 +167,15 @@ export declare function applyProxyHeaders(headers: readonly HTTPHeader[], additi
167
167
  * with `httpTrustProxy.getClientIp` rather than copying the raw header.
168
168
  */
169
169
  export declare function setForwardedHeaders(headers: readonly HTTPHeader[], target: ProxyTarget, client?: ProxyClientContext): HTTPHeader[];
170
+ /**
171
+ * Builds an RFC 7239 `Forwarded` field value.
172
+ *
173
+ * Every parameter is escaped as a `quoted-string` when it is not a bare
174
+ * token, and the finished value is checked the same way `setForwardedHeaders`
175
+ * checks the values it writes.
176
+ *
177
+ * @throws {TypeError} If any parameter contains a control character.
178
+ */
170
179
  export declare function createForwardedHeader(address: ForwardedAddress): string;
171
180
  export declare function parseForwardedHeader(value: string | undefined | null): ForwardedAddress[];
172
181
  /**
@@ -10,7 +10,7 @@
10
10
  import { appendHeader, deleteHeader, getHeader, setHeader, } from "../httpProtocol/http.protocol.js";
11
11
  import { isValidHTTPURL, isValidHeaderValue } from "../httpValidation/index.js";
12
12
  import { isLinkLocalAddress, isLoopbackAddress, isUniqueLocalAddress, parseIpAddress, } from "../httpTrustProxy/httpTrustProxy.ip.js";
13
- import { assertSafeHeaderValue } from "../httpHeaders/security/index.js";
13
+ import { assertSafeHeaderValue, escapeHeaderQuotedString, } from "../httpHeaders/security/index.js";
14
14
  import { assertProxyPathContained } from "./httpProxy.pathGuard.js";
15
15
  /* -------------------------------------------------------------------------- */
16
16
  /* Target */
@@ -72,6 +72,16 @@ function isBlockedLiteralAddress(hostname) {
72
72
  (address.bytes[1] ?? 0) <= 127) {
73
73
  return true;
74
74
  }
75
+ /* 192.0.0.0/24 IETF protocol assignments (192.0.0.8, 192.0.0.170, …). */
76
+ if (first === 192 &&
77
+ (address.bytes[1] ?? 0) === 0 &&
78
+ (address.bytes[2] ?? 0) === 0) {
79
+ return true;
80
+ }
81
+ /* 198.18.0.0/15 benchmarking. */
82
+ if (first === 198 && ((address.bytes[1] ?? 0) & 0xfe) === 18) {
83
+ return true;
84
+ }
75
85
  return false;
76
86
  }
77
87
  const first = address.bytes[0] ?? 0;
@@ -361,6 +371,15 @@ function appendForwardedValue(headers, name, value) {
361
371
  /* -------------------------------------------------------------------------- */
362
372
  /* Standard Forwarded Header */
363
373
  /* -------------------------------------------------------------------------- */
374
+ /**
375
+ * Builds an RFC 7239 `Forwarded` field value.
376
+ *
377
+ * Every parameter is escaped as a `quoted-string` when it is not a bare
378
+ * token, and the finished value is checked the same way `setForwardedHeaders`
379
+ * checks the values it writes.
380
+ *
381
+ * @throws {TypeError} If any parameter contains a control character.
382
+ */
364
383
  export function createForwardedHeader(address) {
365
384
  const parts = [];
366
385
  if (address.for) {
@@ -375,7 +394,9 @@ export function createForwardedHeader(address) {
375
394
  if (address.protocol) {
376
395
  parts.push(`proto=${formatForwardedValue(address.protocol)}`);
377
396
  }
378
- return parts.join("; ");
397
+ const value = parts.join("; ");
398
+ assertSafeHeaderValue(value);
399
+ return value;
379
400
  }
380
401
  export function parseForwardedHeader(value) {
381
402
  if (!value || value.trim().length === 0) {
@@ -534,11 +555,22 @@ function formatForwardedIdentifier(value) {
534
555
  }
535
556
  return formatForwardedValue(value);
536
557
  }
558
+ /**
559
+ * Emits an RFC 7239 parameter value.
560
+ *
561
+ * Wrapping a value in quotes does not neutralise a CR or LF — the control
562
+ * character survives into the field value and an attacker-chosen header
563
+ * follows it on the wire. `escapeHeaderQuotedString` rejects those characters
564
+ * rather than escaping them, which is what every other quoted-parameter
565
+ * emitter in this package already uses.
566
+ *
567
+ * @throws {TypeError} If the value contains a forbidden control character.
568
+ */
537
569
  function formatForwardedValue(value) {
538
570
  if (/^[A-Za-z0-9._:-]+$/.test(value)) {
539
571
  return value;
540
572
  }
541
- return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
573
+ return `"${escapeHeaderQuotedString(value)}"`;
542
574
  }
543
575
  function unquoteForwardedValue(value) {
544
576
  if (value.length >= 2 && value.startsWith('"') && value.endsWith('"')) {
@@ -1,7 +1,16 @@
1
1
  /**
2
2
  * @zudojs/http/httpQuery
3
3
  *
4
- * HTTP query string parsing and utilities.
4
+ * HTTP query string parsing, request accessors, and serialization.
5
+ *
6
+ * Every parser in this module enforces the same four limits
7
+ * ({@link QueryLimitOptions}) and drops `__proto__`, `constructor` and
8
+ * `prototype`, on both the string and the `URLSearchParams` entry points.
9
+ * Exceeding a limit throws {@link HTTPQueryLimitError}, which carries a 414
10
+ * status code.
5
11
  */
6
- export * from "./http.query.js";
12
+ export * from "./queryTypes/index.js";
13
+ export * from "./queryParse/index.js";
14
+ export * from "./queryRequest/index.js";
15
+ export * from "./querySerialize/index.js";
7
16
  //# sourceMappingURL=index.d.ts.map
@@ -1,7 +1,16 @@
1
1
  /**
2
2
  * @zudojs/http/httpQuery
3
3
  *
4
- * HTTP query string parsing and utilities.
4
+ * HTTP query string parsing, request accessors, and serialization.
5
+ *
6
+ * Every parser in this module enforces the same four limits
7
+ * ({@link QueryLimitOptions}) and drops `__proto__`, `constructor` and
8
+ * `prototype`, on both the string and the `URLSearchParams` entry points.
9
+ * Exceeding a limit throws {@link HTTPQueryLimitError}, which carries a 414
10
+ * status code.
5
11
  */
6
- export * from "./http.query.js";
12
+ export * from "./queryTypes/index.js";
13
+ export * from "./queryParse/index.js";
14
+ export * from "./queryRequest/index.js";
15
+ export * from "./querySerialize/index.js";
7
16
  //# sourceMappingURL=index.js.map