@zudojs/http 1.3.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +221 -0
- package/dist/httpAdapter/node/httpNode.adapter.d.ts +2 -1
- package/dist/httpAdapter/node/httpNode.adapter.js +17 -2
- package/dist/httpAdapter/node/httpNode.request.js +6 -0
- package/dist/httpAdapter/node/httpNode.type.d.ts +14 -0
- package/dist/httpClient/httpClient.retry.d.ts +17 -12
- package/dist/httpClient/httpClient.retry.js +35 -10
- package/dist/httpClient/httpClient.type.d.ts +14 -0
- package/dist/httpErrors/httpError.base.js +2 -2
- package/dist/httpErrors/httpError.util.d.ts +8 -0
- package/dist/httpErrors/httpError.util.js +12 -0
- package/dist/httpFetchMount/httpFetchMount.core.d.ts +25 -0
- package/dist/httpFetchMount/httpFetchMount.core.js +84 -0
- package/dist/httpFetchMount/httpFetchMount.request.d.ts +21 -0
- package/dist/httpFetchMount/httpFetchMount.request.js +100 -0
- package/dist/httpFetchMount/httpFetchMount.type.d.ts +56 -0
- package/dist/httpFetchMount/httpFetchMount.type.js +5 -0
- package/dist/httpFetchMount/index.d.ts +11 -0
- package/dist/httpFetchMount/index.js +10 -0
- package/dist/httpMiddleware/builtin/rateLimit/httpMiddleware.rateLimit.d.ts +6 -3
- package/dist/httpMiddleware/builtin/rateLimit/httpMiddleware.rateLimit.js +34 -6
- package/dist/httpMiddleware/httpMiddleware.type.d.ts +9 -1
- package/dist/httpMiddleware/pipeline/httpPipeline.execution.js +22 -45
- package/dist/httpMiddleware/pipeline/httpPipeline.guardResponse.d.ts +36 -0
- package/dist/httpMiddleware/pipeline/httpPipeline.guardResponse.js +57 -0
- package/dist/httpMiddleware/pipeline/httpPipeline.helper.d.ts +2 -1
- package/dist/httpMiddleware/pipeline/httpPipeline.helper.js +10 -0
- package/dist/httpMiddleware/pipeline/index.d.ts +1 -0
- package/dist/httpMiddleware/pipeline/index.js +1 -0
- package/dist/httpOpenApi/httpOpenApi.document.d.ts +44 -0
- package/dist/httpOpenApi/httpOpenApi.document.js +61 -0
- package/dist/httpOpenApi/httpOpenApi.mount.d.ts +31 -0
- package/dist/httpOpenApi/httpOpenApi.mount.js +58 -0
- package/dist/httpOpenApi/httpOpenApi.type.d.ts +54 -0
- package/dist/httpOpenApi/httpOpenApi.type.js +5 -0
- package/dist/httpOpenApi/index.d.ts +13 -0
- package/dist/httpOpenApi/index.js +12 -0
- package/dist/httpOpenApi/routeTable/index.d.ts +11 -0
- package/dist/httpOpenApi/routeTable/index.js +11 -0
- package/dist/httpOpenApi/routeTable/routeTable.collect.d.ts +19 -0
- package/dist/httpOpenApi/routeTable/routeTable.collect.js +89 -0
- package/dist/httpOpenApi/routeTable/routeTable.merge.d.ts +15 -0
- package/dist/httpOpenApi/routeTable/routeTable.merge.js +37 -0
- package/dist/httpOpenApi/routeTable/routeTable.template.d.ts +30 -0
- package/dist/httpOpenApi/routeTable/routeTable.template.js +67 -0
- package/dist/httpRequest/httpRequest.context.d.ts +8 -0
- package/dist/httpRequest/httpRequest.context.js +12 -0
- package/dist/httpRequest/index.d.ts +1 -0
- package/dist/httpRequest/index.js +1 -0
- package/dist/httpRequest/requestId/httpRequest.requestId.d.ts +25 -0
- package/dist/httpRequest/requestId/httpRequest.requestId.js +34 -0
- package/dist/httpRequest/requestId/index.d.ts +7 -0
- package/dist/httpRequest/requestId/index.js +7 -0
- package/dist/httpResponse/httpResponse.writer.js +15 -0
- package/dist/httpRouter/core/factory/httpRoute.factory.base.d.ts +6 -3
- package/dist/httpRouter/core/factory/httpRoute.factory.base.js +24 -11
- package/dist/httpRouter/core/group/httpRouterGroup.core.js +13 -0
- package/dist/httpRouter/core/register/httpRouter.register.js +4 -1
- package/dist/httpRouter/core/types/httpRouter.type.d.ts +31 -1
- package/dist/httpRouter/core/util/httpRoute.util.d.ts +8 -0
- package/dist/httpRouter/core/util/httpRoute.util.js +16 -0
- package/dist/httpRouter/dispatch/httpRoute.dispatcher.js +20 -3
- package/dist/httpSecurity/httpSecurity.config.js +4 -1
- package/dist/httpServer/factory/httpServer.factory.d.ts +10 -9
- package/dist/httpServer/factory/httpServer.factory.js +8 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/package.json +11 -8
|
@@ -0,0 +1,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,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
|
},
|