@zudojs/http 1.3.0 → 1.4.2

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 +222 -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 +15 -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,44 @@
1
+ /**
2
+ * OpenAPI document generation from a router's registered routes.
3
+ */
4
+ import { type OpenAPIDocument, type OpenAPIManager } from "@zudojs/openapi";
5
+ import type { HttpOpenAPIOptions, HttpOpenAPIRouteSource } from "./httpOpenApi.type.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 declare function generateOpenAPIDocument(router: HttpOpenAPIRouteSource, options: HttpOpenAPIOptions): OpenAPIDocument;
31
+ /** A manager that follows a router's route table. */
32
+ export interface HttpRouterOpenAPI {
33
+ /** The manager, refreshed from the router. */
34
+ manager(): OpenAPIManager;
35
+ /** The current document, regenerated only when the route table changed. */
36
+ document(): OpenAPIDocument;
37
+ }
38
+ /**
39
+ * Creates an `OpenAPIManager` kept in step with `router`: each access checks
40
+ * the route table and re-reads it only when a route was added or removed,
41
+ * so serving the document stays cheap while never going stale.
42
+ */
43
+ export declare function createRouterOpenAPI(router: HttpOpenAPIRouteSource, options: HttpOpenAPIOptions): HttpRouterOpenAPI;
44
+ //# sourceMappingURL=httpOpenApi.document.d.ts.map
@@ -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
@@ -66,7 +66,15 @@ export declare class HttpRequestContext {
66
66
  private protocolValue;
67
67
  private hostnameValue;
68
68
  private portValue;
69
+ private readonly signalValue;
69
70
  constructor(init: RequestContextInit);
71
+ /**
72
+ * Aborts when the request is abandoned — for the Node adapter, when the
73
+ * client disconnects before the response finished. `RequestContextInit`
74
+ * always accepted a `signal`, but the constructor dropped it, so the
75
+ * router handed every handler a signal that could never fire.
76
+ */
77
+ get signal(): AbortSignal | undefined;
70
78
  get headers(): RequestHeaders;
71
79
  hasHeader(name: string): boolean;
72
80
  getHeader(name: string): string | undefined;
@@ -45,6 +45,7 @@ export class HttpRequestContext {
45
45
  protocolValue;
46
46
  hostnameValue;
47
47
  portValue;
48
+ signalValue;
48
49
  constructor(init) {
49
50
  this.id = init.id ?? generateRequestId();
50
51
  this.method = normalizeMethod(init.method);
@@ -61,6 +62,16 @@ export class HttpRequestContext {
61
62
  this.protocolValue = init.protocol;
62
63
  this.hostnameValue = init.hostname;
63
64
  this.portValue = init.port;
65
+ this.signalValue = init.signal;
66
+ }
67
+ /**
68
+ * Aborts when the request is abandoned — for the Node adapter, when the
69
+ * client disconnects before the response finished. `RequestContextInit`
70
+ * always accepted a `signal`, but the constructor dropped it, so the
71
+ * router handed every handler a signal that could never fire.
72
+ */
73
+ get signal() {
74
+ return this.signalValue;
64
75
  }
65
76
  /* ------------------------------------------------------------------------ */
66
77
  /* Headers */
@@ -290,6 +301,7 @@ export class HttpRequestContext {
290
301
  protocol: this.protocolValue,
291
302
  hostname: this.hostnameValue,
292
303
  port: this.portValue,
304
+ signal: this.signalValue,
293
305
  state: {
294
306
  ...Object.fromEntries(this.stateMap),
295
307
  },
@@ -6,4 +6,5 @@
6
6
  export * from "./http.request.js";
7
7
  export * from "./httpRequest.context.js";
8
8
  export * from "./target/index.js";
9
+ export * from "./requestId/index.js";
9
10
  //# sourceMappingURL=index.d.ts.map
@@ -6,4 +6,5 @@
6
6
  export * from "./http.request.js";
7
7
  export * from "./httpRequest.context.js";
8
8
  export * from "./target/index.js";
9
+ export * from "./requestId/index.js";
9
10
  //# sourceMappingURL=index.js.map