@mpen/routekit 0.1.0 → 0.1.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.
- package/dist/bin.d.mts +4 -0
- package/dist/client/react.d.mts +178 -0
- package/dist/client/react.mjs +142 -0
- package/dist/client.d.mts +433 -0
- package/dist/client.mjs +264 -0
- package/dist/content-BuDOmhH_.mjs +102 -0
- package/dist/core-CzUCxvGk.d.mts +140 -0
- package/dist/core-DbmQauwS.mjs +81 -0
- package/dist/handlers.d.mts +72 -0
- package/dist/handlers.mjs +153 -0
- package/dist/index.d.mts +3 -0
- package/dist/index.mjs +1152 -0
- package/dist/middleware.d.mts +388 -0
- package/dist/middleware.mjs +1222 -0
- package/dist/request-Dn0zc-xm.mjs +1025 -0
- package/dist/response/content.d.mts +79 -0
- package/dist/response/content.mjs +2 -0
- package/dist/response/json-rpc.d.mts +1 -0
- package/dist/response/json-rpc.mjs +1 -0
- package/dist/response/problem/valibot.d.mts +230 -0
- package/dist/response/problem/valibot.mjs +258 -0
- package/dist/response/problem.d.mts +415 -0
- package/dist/response/problem.mjs +183 -0
- package/dist/response/status.d.mts +45 -0
- package/dist/response/status.mjs +2 -0
- package/dist/responses-B379Ep9Y.d.mts +296 -0
- package/dist/responses-BpVrgeYi.mjs +101 -0
- package/dist/router-Cwb7ak0J.d.mts +1819 -0
- package/dist/routes.d.mts +282 -0
- package/dist/routes.mjs +311 -0
- package/dist/status-C-8mw-FB.mjs +59 -0
- package/dist/valibot-D7liFYyB.d.mts +290 -0
- package/dist/valibot-Du97X-TS.mjs +326 -0
- package/package.json +8 -2
- package/src/bin/gen-api-client.test.ts +0 -70
- package/src/bin/gen-api-client.ts +0 -986
- package/src/client/headers.ts +0 -31
- package/src/client/index.ts +0 -8
- package/src/client/promise.ts +0 -11
- package/src/client/react/index.test.tsx +0 -266
- package/src/client/react/index.ts +0 -431
- package/src/client/responses.test.ts +0 -151
- package/src/client/responses.ts +0 -278
- package/src/client/transport.ts +0 -74
- package/src/client/transports/body-codec.ts +0 -61
- package/src/client/transports/fetch.ts +0 -113
- package/src/client/tsconfig.json +0 -9
- package/src/client/types.ts +0 -15
- package/src/client/url.ts +0 -31
- package/src/index.ts +0 -63
- package/src/router/fetch-types.ts +0 -13
- package/src/router/handlers/index.ts +0 -2
- package/src/router/handlers/openapi/index.ts +0 -2
- package/src/router/handlers/openapi/openapi.ts +0 -293
- package/src/router/integration/zod-openapi.test.ts +0 -74
- package/src/router/lib/charset.test.ts +0 -22
- package/src/router/lib/charset.ts +0 -133
- package/src/router/lib/collections.ts +0 -3
- package/src/router/lib/format.test.ts +0 -67
- package/src/router/lib/format.ts +0 -35
- package/src/router/lib/host.ts +0 -4
- package/src/router/lib/json-schema.ts +0 -6
- package/src/router/lib/media-type.test.ts +0 -122
- package/src/router/lib/media-type.ts +0 -289
- package/src/router/lib/pathname.test.ts +0 -18
- package/src/router/lib/pathname.ts +0 -19
- package/src/router/lib/route-names.ts +0 -70
- package/src/router/lib/route-normalize.test.ts +0 -36
- package/src/router/lib/route-normalize.ts +0 -67
- package/src/router/lib/schema-merge.ts +0 -56
- package/src/router/middleware/accept-ctx.test.ts +0 -33
- package/src/router/middleware/accept-ctx.ts +0 -12
- package/src/router/middleware/body-limit.test.ts +0 -112
- package/src/router/middleware/body-limit.ts +0 -121
- package/src/router/middleware/content-type-context.ts +0 -0
- package/src/router/middleware/cors.test.ts +0 -269
- package/src/router/middleware/cors.ts +0 -490
- package/src/router/middleware/csrf.test.ts +0 -106
- package/src/router/middleware/csrf.ts +0 -192
- package/src/router/middleware/define.ts +0 -249
- package/src/router/middleware/index.ts +0 -34
- package/src/router/middleware/jsxhtml-response.ts +0 -0
- package/src/router/middleware/oas-swagger.ts +0 -0
- package/src/router/middleware/rate-limit.test.ts +0 -886
- package/src/router/middleware/rate-limit.ts +0 -920
- package/src/router/middleware/request-id-ctx.test.ts +0 -183
- package/src/router/middleware/request-id-ctx.ts +0 -135
- package/src/router/middleware/request-logger-format.test.ts +0 -16
- package/src/router/middleware/request-logger-format.ts +0 -269
- package/src/router/middleware/request-logger.test.ts +0 -267
- package/src/router/middleware/request-logger.ts +0 -131
- package/src/router/middleware/start-time-ctx.ts +0 -5
- package/src/router/request.ts +0 -611
- package/src/router/response/core.ts +0 -181
- package/src/router/response/directives.ts +0 -233
- package/src/router/response/formats/content/bodyless.ts +0 -54
- package/src/router/response/formats/content/content.ts +0 -79
- package/src/router/response/formats/content/index.ts +0 -2
- package/src/router/response/formats/json-rpc/index.ts +0 -2
- package/src/router/response/formats/problem/badRequest.ts +0 -90
- package/src/router/response/formats/problem/conflict.ts +0 -90
- package/src/router/response/formats/problem/created.ts +0 -40
- package/src/router/response/formats/problem/index.ts +0 -27
- package/src/router/response/formats/problem/notFound.ts +0 -90
- package/src/router/response/formats/problem/permissionDenied.ts +0 -90
- package/src/router/response/formats/problem/problem.test.ts +0 -888
- package/src/router/response/formats/problem/rateLimited.ts +0 -90
- package/src/router/response/formats/problem/responses.ts +0 -219
- package/src/router/response/formats/problem/root-errors.ts +0 -48
- package/src/router/response/formats/problem/sessionExpired.ts +0 -90
- package/src/router/response/formats/problem/types.ts +0 -170
- package/src/router/response/formats/problem/unauthenticated.ts +0 -90
- package/src/router/response/formats/problem/valibot.ts +0 -410
- package/src/router/response/formats/status/index.ts +0 -1
- package/src/router/response/formats/status/responses.ts +0 -59
- package/src/router/response/formats/status/status.test.ts +0 -21
- package/src/router/response/framers.ts +0 -85
- package/src/router/response/index.ts +0 -28
- package/src/router/response/openapi.test.ts +0 -96
- package/src/router/response/openapi.ts +0 -1
- package/src/router/response/serializers.ts +0 -66
- package/src/router/response/stream.ts +0 -35
- package/src/router/router.test.ts +0 -1571
- package/src/router/router.ts +0 -1965
- package/src/router/routes/index.ts +0 -46
- package/src/router/routes/valibot/index.ts +0 -18
- package/src/router/routes/valibot/valibot.ts +0 -1393
- package/src/router/routes/valibot.test.ts +0 -286
- package/src/router/routes/zod/index.ts +0 -18
- package/src/router/routes/zod/zod.ts +0 -1318
- package/src/router/routes/zod.test.ts +0 -280
- package/src/router/server-interface.ts +0 -31
- package/src/router/types.ts +0 -657
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { a as response } from "./core-DbmQauwS.mjs";
|
|
2
|
+
import { CommonContentTypes, CommonHeaders, HttpStatus } from "@mpen/http";
|
|
3
|
+
//#region src/router/lib/format.ts
|
|
4
|
+
const FULL_WIDE_FORMAT = new Intl.NumberFormat("en-US", {
|
|
5
|
+
useGrouping: false,
|
|
6
|
+
maximumFractionDigits: 20
|
|
7
|
+
});
|
|
8
|
+
const DECIMAL_STRING = /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?$/;
|
|
9
|
+
const MAX_SAFE_INTEGER_STRING = String(Number.MAX_SAFE_INTEGER);
|
|
10
|
+
const MIN_SAFE_INTEGER_STRING = String(Number.MIN_SAFE_INTEGER);
|
|
11
|
+
/**
|
|
12
|
+
* Formats a number with full decimal places.
|
|
13
|
+
*
|
|
14
|
+
* e.g. `1e21` formats as "1000000000000000000000" instead of "1e+21"
|
|
15
|
+
*
|
|
16
|
+
* @param n The number to format.
|
|
17
|
+
*/
|
|
18
|
+
function fullWide(n) {
|
|
19
|
+
if (typeof n === "bigint") return n.toString();
|
|
20
|
+
if (typeof n === "string") {
|
|
21
|
+
if (n === "Infinity" || n === "+Infinity") return MAX_SAFE_INTEGER_STRING;
|
|
22
|
+
if (n === "-Infinity") return MIN_SAFE_INTEGER_STRING;
|
|
23
|
+
if (!DECIMAL_STRING.test(n)) return "0";
|
|
24
|
+
return FULL_WIDE_FORMAT.format(n);
|
|
25
|
+
}
|
|
26
|
+
if (n === Number.POSITIVE_INFINITY) return MAX_SAFE_INTEGER_STRING;
|
|
27
|
+
if (n === Number.NEGATIVE_INFINITY) return MIN_SAFE_INTEGER_STRING;
|
|
28
|
+
if (!Number.isFinite(n)) return "0";
|
|
29
|
+
return FULL_WIDE_FORMAT.format(n);
|
|
30
|
+
}
|
|
31
|
+
//#endregion
|
|
32
|
+
//#region src/router/response/formats/content/content.ts
|
|
33
|
+
function text(value, init = {}) {
|
|
34
|
+
const responseHeaders = new Headers(init.headers);
|
|
35
|
+
responseHeaders.set(CommonHeaders.CONTENT_TYPE, CommonContentTypes.PLAIN_TEXT);
|
|
36
|
+
responseHeaders.set(CommonHeaders.CONTENT_LENGTH, fullWide(new TextEncoder().encode(value).length));
|
|
37
|
+
return response(value, {
|
|
38
|
+
...init,
|
|
39
|
+
headers: responseHeaders,
|
|
40
|
+
status: init.status ?? HttpStatus.OK
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
function html(value, init = {}) {
|
|
44
|
+
const responseHeaders = new Headers(init.headers);
|
|
45
|
+
responseHeaders.set(CommonHeaders.CONTENT_TYPE, CommonContentTypes.HTML);
|
|
46
|
+
responseHeaders.set(CommonHeaders.CONTENT_LENGTH, fullWide(new TextEncoder().encode(value).length));
|
|
47
|
+
return response(value, {
|
|
48
|
+
...init,
|
|
49
|
+
headers: responseHeaders,
|
|
50
|
+
status: init.status ?? HttpStatus.OK
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
//#endregion
|
|
54
|
+
//#region src/router/response/formats/content/bodyless.ts
|
|
55
|
+
/**
|
|
56
|
+
* Create an empty response with the provided status.
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```ts
|
|
60
|
+
* return empty(HttpStatus.ACCEPTED)
|
|
61
|
+
* ```
|
|
62
|
+
*
|
|
63
|
+
* @param statusCode - HTTP status code to use.
|
|
64
|
+
* @param init - Response headers.
|
|
65
|
+
* @returns Routekit logical response with no body.
|
|
66
|
+
*/
|
|
67
|
+
function empty(statusCode = HttpStatus.NO_CONTENT, init = {}) {
|
|
68
|
+
return response(void 0, {
|
|
69
|
+
...init,
|
|
70
|
+
status: statusCode
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Create a `204 No Content` response.
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* ```ts
|
|
78
|
+
* return noContent()
|
|
79
|
+
* ```
|
|
80
|
+
*
|
|
81
|
+
* @returns Routekit logical response with no body.
|
|
82
|
+
*/
|
|
83
|
+
function noContent() {
|
|
84
|
+
return empty(HttpStatus.NO_CONTENT);
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Create a redirect response.
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* ```ts
|
|
91
|
+
* return redirect('/login')
|
|
92
|
+
* ```
|
|
93
|
+
*
|
|
94
|
+
* @param url - Redirect target URL.
|
|
95
|
+
* @param statusCode - Redirect status code. Defaults to `302`.
|
|
96
|
+
* @returns Routekit logical response with a `Location` header.
|
|
97
|
+
*/
|
|
98
|
+
function redirect(url, statusCode = HttpStatus.FOUND) {
|
|
99
|
+
return empty(statusCode, { headers: { Location: url } });
|
|
100
|
+
}
|
|
101
|
+
//#endregion
|
|
102
|
+
export { text as a, html as i, noContent as n, redirect as r, empty as t };
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { HttpStatus } from "@mpen/http";
|
|
2
|
+
|
|
3
|
+
//#region src/router/fetch-types.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Headers initializer accepted by the active Fetch runtime.
|
|
6
|
+
*
|
|
7
|
+
* @internal
|
|
8
|
+
*/
|
|
9
|
+
type RouterHeadersInit = NonNullable<ConstructorParameters<typeof Headers>[0]>;
|
|
10
|
+
/**
|
|
11
|
+
* Response body initializer accepted by the active Fetch runtime.
|
|
12
|
+
*
|
|
13
|
+
* @internal
|
|
14
|
+
*/
|
|
15
|
+
type RouterBodyInit = NonNullable<ConstructorParameters<typeof Response>[0]>;
|
|
16
|
+
//#endregion
|
|
17
|
+
//#region src/router/response/core.d.ts
|
|
18
|
+
declare const routekitResponseBrand: unique symbol;
|
|
19
|
+
declare const routekitBodyBrand: unique symbol;
|
|
20
|
+
/**
|
|
21
|
+
* Logical route result used by Routekit before it is finalized into a native [`Response`]{@link Response}.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```ts
|
|
25
|
+
* const result = ok({payload: 'ready'})
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
interface RoutekitResponse<T = unknown, Status extends number = number> {
|
|
29
|
+
readonly [routekitResponseBrand]: true;
|
|
30
|
+
/**
|
|
31
|
+
* HTTP status code to use for the finalized response.
|
|
32
|
+
*/
|
|
33
|
+
status: Status;
|
|
34
|
+
/**
|
|
35
|
+
* Headers to include in the finalized response.
|
|
36
|
+
*/
|
|
37
|
+
headers: Headers;
|
|
38
|
+
/**
|
|
39
|
+
* Logical or represented response body.
|
|
40
|
+
*/
|
|
41
|
+
body: T;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Options accepted by logical response helpers.
|
|
45
|
+
*/
|
|
46
|
+
interface RoutekitResponseInit<Status extends number = number> {
|
|
47
|
+
/**
|
|
48
|
+
* HTTP status code for the response. Defaults to `200`.
|
|
49
|
+
*/
|
|
50
|
+
status?: Status;
|
|
51
|
+
/**
|
|
52
|
+
* Headers to include in the finalized response.
|
|
53
|
+
*/
|
|
54
|
+
headers?: RouterHeadersInit;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Final body wrapper returned from a handler or generator.
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* ```ts
|
|
61
|
+
* return body({payload: 'done'})
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
interface RoutekitBody<T = unknown> {
|
|
65
|
+
readonly [routekitBodyBrand]: true;
|
|
66
|
+
/**
|
|
67
|
+
* Logical response body value.
|
|
68
|
+
*/
|
|
69
|
+
value: T;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Test whether a value can be passed directly to the native [`Response`]{@link Response} constructor.
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* ```ts
|
|
76
|
+
* isResponseBodyInit('ok')
|
|
77
|
+
* ```
|
|
78
|
+
*
|
|
79
|
+
* @param value - Value to inspect.
|
|
80
|
+
* @returns Whether `value` is a native response body.
|
|
81
|
+
*/
|
|
82
|
+
declare function isResponseBodyInit(value: unknown): value is RouterBodyInit | null | undefined;
|
|
83
|
+
/**
|
|
84
|
+
* Test whether a value is a Routekit logical response.
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* ```ts
|
|
88
|
+
* if (isRoutekitResponse(result)) result.headers.set('x-request-id', id)
|
|
89
|
+
* ```
|
|
90
|
+
*
|
|
91
|
+
* @param value - Value to inspect.
|
|
92
|
+
* @returns Whether `value` was created by a Routekit response helper.
|
|
93
|
+
*/
|
|
94
|
+
declare function isRoutekitResponse(value: unknown): value is RoutekitResponse;
|
|
95
|
+
/**
|
|
96
|
+
* Test whether a value is a final body wrapper.
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* ```ts
|
|
100
|
+
* const value = isRoutekitBody(result) ? result.value : result
|
|
101
|
+
* ```
|
|
102
|
+
*
|
|
103
|
+
* @param value - Value to inspect.
|
|
104
|
+
* @returns Whether `value` was created by [`body`]{@link body}.
|
|
105
|
+
*/
|
|
106
|
+
declare function isRoutekitBody(value: unknown): value is RoutekitBody;
|
|
107
|
+
/**
|
|
108
|
+
* Create a logical response.
|
|
109
|
+
*
|
|
110
|
+
* If `headers` contains `Content-Type`, the body must be compatible with the native
|
|
111
|
+
* [`Response`]{@link Response} constructor because the router will skip negotiation.
|
|
112
|
+
*
|
|
113
|
+
* @example
|
|
114
|
+
* ```ts
|
|
115
|
+
* return response({ok: true}, {status: 202})
|
|
116
|
+
* ```
|
|
117
|
+
*
|
|
118
|
+
* @param responseBody - Logical or represented response body.
|
|
119
|
+
* @param init - Response status and headers.
|
|
120
|
+
* @returns Routekit logical response.
|
|
121
|
+
*/
|
|
122
|
+
declare function response<const T>(responseBody: T): RoutekitResponse<T, HttpStatus.OK>;
|
|
123
|
+
declare function response<const T>(responseBody: T, init: Omit<RoutekitResponseInit, 'status'>): RoutekitResponse<T, HttpStatus.OK>;
|
|
124
|
+
declare function response<const T, const Status extends number>(responseBody: T, init: RoutekitResponseInit<Status> & {
|
|
125
|
+
status: Status;
|
|
126
|
+
}): RoutekitResponse<T, Status>;
|
|
127
|
+
/**
|
|
128
|
+
* Wrap a returned value as the final response body.
|
|
129
|
+
*
|
|
130
|
+
* @example
|
|
131
|
+
* ```ts
|
|
132
|
+
* return body({payload: 'done'})
|
|
133
|
+
* ```
|
|
134
|
+
*
|
|
135
|
+
* @param value - Logical response body.
|
|
136
|
+
* @returns Final body wrapper.
|
|
137
|
+
*/
|
|
138
|
+
declare function body<const T>(value: T): RoutekitBody<T>;
|
|
139
|
+
//#endregion
|
|
140
|
+
export { isResponseBodyInit as a, response as c, body as i, RouterBodyInit as l, RoutekitResponse as n, isRoutekitBody as o, RoutekitResponseInit as r, isRoutekitResponse as s, RoutekitBody as t, RouterHeadersInit as u };
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { CommonHeaders, HttpStatus } from "@mpen/http";
|
|
2
|
+
//#region src/router/response/core.ts
|
|
3
|
+
const routekitResponseBrand = Symbol("RoutekitResponse");
|
|
4
|
+
const routekitBodyBrand = Symbol("RoutekitBody");
|
|
5
|
+
function hasContentType(headers) {
|
|
6
|
+
return headers.has(CommonHeaders.CONTENT_TYPE);
|
|
7
|
+
}
|
|
8
|
+
function isArrayBufferView(value) {
|
|
9
|
+
return ArrayBuffer.isView(value);
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Test whether a value can be passed directly to the native [`Response`]{@link Response} constructor.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* isResponseBodyInit('ok')
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* @param value - Value to inspect.
|
|
20
|
+
* @returns Whether `value` is a native response body.
|
|
21
|
+
*/
|
|
22
|
+
function isResponseBodyInit(value) {
|
|
23
|
+
return value == null || typeof value === "string" || value instanceof ReadableStream || value instanceof Blob || value instanceof FormData || value instanceof URLSearchParams || value instanceof ArrayBuffer || isArrayBufferView(value);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Test whether a value is a Routekit logical response.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```ts
|
|
30
|
+
* if (isRoutekitResponse(result)) result.headers.set('x-request-id', id)
|
|
31
|
+
* ```
|
|
32
|
+
*
|
|
33
|
+
* @param value - Value to inspect.
|
|
34
|
+
* @returns Whether `value` was created by a Routekit response helper.
|
|
35
|
+
*/
|
|
36
|
+
function isRoutekitResponse(value) {
|
|
37
|
+
return !!value && value[routekitResponseBrand] === true;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Test whether a value is a final body wrapper.
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```ts
|
|
44
|
+
* const value = isRoutekitBody(result) ? result.value : result
|
|
45
|
+
* ```
|
|
46
|
+
*
|
|
47
|
+
* @param value - Value to inspect.
|
|
48
|
+
* @returns Whether `value` was created by [`body`]{@link body}.
|
|
49
|
+
*/
|
|
50
|
+
function isRoutekitBody(value) {
|
|
51
|
+
return !!value && value[routekitBodyBrand] === true;
|
|
52
|
+
}
|
|
53
|
+
function response(responseBody, init = {}) {
|
|
54
|
+
const responseHeaders = new Headers(init.headers);
|
|
55
|
+
if (hasContentType(responseHeaders) && !isResponseBodyInit(responseBody)) throw new TypeError("Routekit response has Content-Type set, so body must be a native Response body.");
|
|
56
|
+
return {
|
|
57
|
+
[routekitResponseBrand]: true,
|
|
58
|
+
status: init.status ?? HttpStatus.OK,
|
|
59
|
+
headers: responseHeaders,
|
|
60
|
+
body: responseBody
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Wrap a returned value as the final response body.
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* ```ts
|
|
68
|
+
* return body({payload: 'done'})
|
|
69
|
+
* ```
|
|
70
|
+
*
|
|
71
|
+
* @param value - Logical response body.
|
|
72
|
+
* @returns Final body wrapper.
|
|
73
|
+
*/
|
|
74
|
+
function body(value) {
|
|
75
|
+
return {
|
|
76
|
+
[routekitBodyBrand]: true,
|
|
77
|
+
value
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
//#endregion
|
|
81
|
+
export { response as a, isRoutekitResponse as i, isResponseBodyInit as n, isRoutekitBody as r, body as t };
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { m as Handler } from "./router-Cwb7ak0J.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/router/handlers/openapi/openapi.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* OpenAPI document `info` section.
|
|
6
|
+
*/
|
|
7
|
+
type OpenApiInfo = {
|
|
8
|
+
title: string;
|
|
9
|
+
version: string;
|
|
10
|
+
description?: string;
|
|
11
|
+
termsOfService?: string;
|
|
12
|
+
contact?: Record<string, unknown>;
|
|
13
|
+
license?: Record<string, unknown>;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* OpenAPI server definition.
|
|
17
|
+
*/
|
|
18
|
+
type OpenApiServer = {
|
|
19
|
+
url: string;
|
|
20
|
+
description?: string;
|
|
21
|
+
variables?: Record<string, unknown>;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* OpenAPI operation object.
|
|
25
|
+
*/
|
|
26
|
+
type OpenApiOperation = Record<string, unknown>;
|
|
27
|
+
/**
|
|
28
|
+
* OpenAPI paths dictionary keyed by pathname then method.
|
|
29
|
+
*/
|
|
30
|
+
type OpenApiPaths = Record<string, Record<string, OpenApiOperation>>;
|
|
31
|
+
/**
|
|
32
|
+
* OpenAPI document returned by the `openapi` plugin handler.
|
|
33
|
+
*/
|
|
34
|
+
type OpenApiDocument = {
|
|
35
|
+
openapi: string;
|
|
36
|
+
info: OpenApiInfo;
|
|
37
|
+
servers?: OpenApiServer[];
|
|
38
|
+
paths: OpenApiPaths;
|
|
39
|
+
components?: Record<string, unknown>;
|
|
40
|
+
security?: Array<Record<string, string[]>>;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Options used to build an OpenAPI document from registered routes.
|
|
44
|
+
*/
|
|
45
|
+
type OpenApiOptions = {
|
|
46
|
+
info: OpenApiInfo;
|
|
47
|
+
servers?: OpenApiServer[];
|
|
48
|
+
components?: Record<string, unknown>;
|
|
49
|
+
security?: Array<Record<string, string[]>>;
|
|
50
|
+
openapi?: string;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Create an OpenAPI response handler that reflects the active router.
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* ```ts
|
|
57
|
+
* router.add({
|
|
58
|
+
* path: '/swagger.json',
|
|
59
|
+
* method: HttpMethod.GET,
|
|
60
|
+
* handler: openapi({
|
|
61
|
+
* info: {title: 'Example API', version: '1.0.0'},
|
|
62
|
+
* servers: [{url: 'https://api.example.com'}],
|
|
63
|
+
* }),
|
|
64
|
+
* })
|
|
65
|
+
* ```
|
|
66
|
+
*
|
|
67
|
+
* @param options - OpenAPI document options for info, servers, and optional components/security.
|
|
68
|
+
* @returns A route handler that returns the generated OpenAPI JSON document.
|
|
69
|
+
*/
|
|
70
|
+
declare function openapi(options: OpenApiOptions): Handler<OpenApiDocument>;
|
|
71
|
+
//#endregion
|
|
72
|
+
export { type OpenApiDocument, type OpenApiInfo, type OpenApiOptions, type OpenApiServer, openapi };
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { n as ok } from "./status-C-8mw-FB.mjs";
|
|
2
|
+
import { HttpMethod, StatusText } from "@mpen/http";
|
|
3
|
+
//#region src/router/handlers/openapi/openapi.ts
|
|
4
|
+
const DEFAULT_OPENAPI_VERSION = "3.0.3";
|
|
5
|
+
const DEFAULT_METHODS = [
|
|
6
|
+
HttpMethod.GET,
|
|
7
|
+
HttpMethod.PUT,
|
|
8
|
+
HttpMethod.POST,
|
|
9
|
+
HttpMethod.DELETE,
|
|
10
|
+
HttpMethod.OPTIONS,
|
|
11
|
+
HttpMethod.HEAD,
|
|
12
|
+
HttpMethod.PATCH,
|
|
13
|
+
HttpMethod.TRACE
|
|
14
|
+
];
|
|
15
|
+
function routePathToOpenApi(pathname) {
|
|
16
|
+
return pathname.replace(/:([A-Za-z0-9_]+)/g, "{$1}");
|
|
17
|
+
}
|
|
18
|
+
function normalizeOpenApiMethods(route) {
|
|
19
|
+
const rawMethods = route.method ? Array.isArray(route.method) ? route.method : [route.method] : DEFAULT_METHODS;
|
|
20
|
+
const normalized = /* @__PURE__ */ new Set();
|
|
21
|
+
for (const method of rawMethods) {
|
|
22
|
+
if (method === HttpMethod.CONNECT) continue;
|
|
23
|
+
normalized.add(method.toLowerCase());
|
|
24
|
+
}
|
|
25
|
+
return [...normalized];
|
|
26
|
+
}
|
|
27
|
+
function buildParameterEntries(schema, location) {
|
|
28
|
+
const properties = schema.properties;
|
|
29
|
+
const requiredList = Array.isArray(schema.required) ? schema.required.filter((value) => typeof value === "string") : [];
|
|
30
|
+
if (properties && typeof properties === "object") return Object.entries(properties).map(([name, propSchema]) => ({
|
|
31
|
+
name,
|
|
32
|
+
in: location,
|
|
33
|
+
required: location === "path" ? true : requiredList.includes(name),
|
|
34
|
+
schema: propSchema ?? {}
|
|
35
|
+
}));
|
|
36
|
+
return [{
|
|
37
|
+
name: location,
|
|
38
|
+
in: location,
|
|
39
|
+
required: location === "path",
|
|
40
|
+
schema
|
|
41
|
+
}];
|
|
42
|
+
}
|
|
43
|
+
function openApiRequestContentTypes(route) {
|
|
44
|
+
if (!route.accept || route.accept.length === 0) return ["application/json"];
|
|
45
|
+
const normalized = /* @__PURE__ */ new Set();
|
|
46
|
+
for (const accept of route.accept) normalized.add(accept.type);
|
|
47
|
+
return [...normalized];
|
|
48
|
+
}
|
|
49
|
+
function openApiResponseContentTypes() {
|
|
50
|
+
return ["application/json"];
|
|
51
|
+
}
|
|
52
|
+
function defaultResponseDescription(status) {
|
|
53
|
+
const numericStatus = Number(status);
|
|
54
|
+
if (Number.isInteger(numericStatus)) return StatusText[numericStatus] ?? String(status);
|
|
55
|
+
return status;
|
|
56
|
+
}
|
|
57
|
+
function buildOperationFromSchema(route) {
|
|
58
|
+
const schema = route.schema;
|
|
59
|
+
const operation = {};
|
|
60
|
+
if (!schema) {
|
|
61
|
+
operation.responses = { 200: { description: "OK" } };
|
|
62
|
+
return operation;
|
|
63
|
+
}
|
|
64
|
+
const parameters = [];
|
|
65
|
+
if (schema.request?.query) parameters.push(...buildParameterEntries(schema.request.query, "query"));
|
|
66
|
+
if (schema.request?.path) parameters.push(...buildParameterEntries(schema.request.path, "path"));
|
|
67
|
+
if (parameters.length > 0) operation.parameters = parameters;
|
|
68
|
+
if (schema.request?.body !== void 0) operation.requestBody = {
|
|
69
|
+
required: true,
|
|
70
|
+
content: Object.fromEntries(openApiRequestContentTypes(route).map((contentType) => [contentType, { schema: schema.request.body }]))
|
|
71
|
+
};
|
|
72
|
+
if (schema.response?.body && Object.keys(schema.response.body).length > 0) {
|
|
73
|
+
const contentTypes = openApiResponseContentTypes();
|
|
74
|
+
operation.responses = Object.fromEntries(Object.entries(schema.response.body).flatMap(([status, responseSchema]) => {
|
|
75
|
+
if (responseSchema === void 0) return [];
|
|
76
|
+
const response = { description: defaultResponseDescription(status) };
|
|
77
|
+
response.content = Object.fromEntries(contentTypes.map((contentType) => [contentType, { schema: responseSchema }]));
|
|
78
|
+
return [[status, response]];
|
|
79
|
+
}));
|
|
80
|
+
} else operation.responses = { 200: { description: "OK" } };
|
|
81
|
+
return operation;
|
|
82
|
+
}
|
|
83
|
+
function mergeOpenApiOperations(generated, custom) {
|
|
84
|
+
if (!custom) return generated;
|
|
85
|
+
const merged = {
|
|
86
|
+
...generated,
|
|
87
|
+
...custom
|
|
88
|
+
};
|
|
89
|
+
const generatedParameters = Array.isArray(generated.parameters) ? generated.parameters : [];
|
|
90
|
+
const customParameters = Array.isArray(custom.parameters) ? custom.parameters : [];
|
|
91
|
+
if (generatedParameters.length > 0 || customParameters.length > 0) merged.parameters = [...customParameters, ...generatedParameters];
|
|
92
|
+
if (generated.requestBody && custom.requestBody) {
|
|
93
|
+
const generatedRequestBody = generated.requestBody;
|
|
94
|
+
const customRequestBody = custom.requestBody;
|
|
95
|
+
merged.requestBody = {
|
|
96
|
+
...generatedRequestBody,
|
|
97
|
+
...customRequestBody,
|
|
98
|
+
content: {
|
|
99
|
+
...generatedRequestBody.content ?? {},
|
|
100
|
+
...customRequestBody.content ?? {}
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
if (generated.responses && custom.responses) merged.responses = {
|
|
105
|
+
...generated.responses,
|
|
106
|
+
...custom.responses
|
|
107
|
+
};
|
|
108
|
+
return merged;
|
|
109
|
+
}
|
|
110
|
+
function buildOperation(route) {
|
|
111
|
+
return mergeOpenApiOperations(buildOperationFromSchema(route), route.meta?.openapi);
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Create an OpenAPI response handler that reflects the active router.
|
|
115
|
+
*
|
|
116
|
+
* @example
|
|
117
|
+
* ```ts
|
|
118
|
+
* router.add({
|
|
119
|
+
* path: '/swagger.json',
|
|
120
|
+
* method: HttpMethod.GET,
|
|
121
|
+
* handler: openapi({
|
|
122
|
+
* info: {title: 'Example API', version: '1.0.0'},
|
|
123
|
+
* servers: [{url: 'https://api.example.com'}],
|
|
124
|
+
* }),
|
|
125
|
+
* })
|
|
126
|
+
* ```
|
|
127
|
+
*
|
|
128
|
+
* @param options - OpenAPI document options for info, servers, and optional components/security.
|
|
129
|
+
* @returns A route handler that returns the generated OpenAPI JSON document.
|
|
130
|
+
*/
|
|
131
|
+
function openapi(options) {
|
|
132
|
+
return function openapiHandler() {
|
|
133
|
+
const routes = this.getRoutes();
|
|
134
|
+
const paths = {};
|
|
135
|
+
for (const route of routes) {
|
|
136
|
+
const pathPattern = routePathToOpenApi(route.path.pathname);
|
|
137
|
+
const methods = normalizeOpenApiMethods(route);
|
|
138
|
+
if (methods.length === 0) continue;
|
|
139
|
+
const pathItem = paths[pathPattern] ?? (paths[pathPattern] = {});
|
|
140
|
+
for (const method of methods) pathItem[method] = buildOperation(route);
|
|
141
|
+
}
|
|
142
|
+
return ok({
|
|
143
|
+
openapi: options.openapi ?? DEFAULT_OPENAPI_VERSION,
|
|
144
|
+
info: options.info,
|
|
145
|
+
paths,
|
|
146
|
+
...options.servers ? { servers: options.servers } : {},
|
|
147
|
+
...options.components ? { components: options.components } : {},
|
|
148
|
+
...options.security ? { security: options.security } : {}
|
|
149
|
+
});
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
//#endregion
|
|
153
|
+
export { openapi };
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { $ as formDataRequestBodyParser, A as RequestMiddlewareEntry, B as RouterMountOptions, C as MiddlewareInput, Ct as isStreamDirective, D as RequestBodyParserInput, Dt as jsonLinesFramer, Et as StreamFramer, F as RouteMeta, G as RequestBodyParser, H as RequestBodyFormData, I as RouteOptions, J as RoutekitRequest, K as RequestBodyStream, L as RoutePath, M as ResponseBodySerializerInput, N as Route, O as RequestContext, Ot as sseFramer, P as RouteMatch, Q as defaultRequestBodyParsers, R as RouteSchema, S as MiddlewareEntry, St as isStatusDirective, T as NormalizedRoute, Tt as stream, U as RequestBodyLengthMismatchError, V as RequestBodyError, W as RequestBodyParseContext, X as UnsupportedRequestBodyMediaTypeError, Y as RoutekitRequestBody, Z as createRoutekitRequest, _ as JsonObjectSchema, _t as headers, a as MiddlewareResponseDeclaration, at as createStartStream, b as MediaType, bt as isHeadersDirective, c as AcceptMediaRange, ct as jsonResponseBodySerializer, d as ContentType, dt as HeadersDirective, et as jsonRequestBodyParser, f as ContextFromMiddlewareInput, ft as RoutekitYield, gt as head, h as HandlerContext, ht as chunk, i as MiddlewareControls, it as createAsyncStream, j as RequestMiddlewareInput, k as RequestMiddleware, l as AddedContextFromMiddlewareInput, lt as ChunkDirective, m as Handler, mt as StreamDirective, n as DeclaredMiddleware, nt as textRequestBodyParser, o as MiddlewareResponseDeclarations, ot as ResponseBodySerializer, p as ContextMiddleware, pt as StatusDirective, q as RequestBodyTooLargeError, r as DefineMiddlewareOptions, rt as urlEncodedRequestBodyParser, s as defineMiddleware, st as defaultResponseBodySerializers, t as Router, tt as responseFromRequestBodyError, u as AnyContext, ut as HeadDirective, v as JsonSchema, vt as isChunkDirective, w as MiddlewareList, wt as status, x as Middleware, xt as isRoutekitDirective, yt as isHeadDirective, z as RouterExtension } from "./router-Cwb7ak0J.mjs";
|
|
2
|
+
import { a as isResponseBodyInit, c as response, i as body, n as RoutekitResponse, o as isRoutekitBody, r as RoutekitResponseInit, s as isRoutekitResponse, t as RoutekitBody } from "./core-CzUCxvGk.mjs";
|
|
3
|
+
export { type AcceptMediaRange, type AddedContextFromMiddlewareInput, type AnyContext, ChunkDirective, type ContentType, type ContextFromMiddlewareInput, type ContextMiddleware, type DeclaredMiddleware, type DefineMiddlewareOptions, type Handler, type HandlerContext, HeadDirective, HeadersDirective, type JsonObjectSchema, type JsonSchema, type MediaType, type Middleware, type MiddlewareControls, type MiddlewareEntry, type MiddlewareInput, type MiddlewareList, type MiddlewareResponseDeclaration, type MiddlewareResponseDeclarations, type NormalizedRoute, RequestBodyError, type RequestBodyFormData, RequestBodyLengthMismatchError, type RequestBodyParseContext, type RequestBodyParser, type RequestBodyParserInput, type RequestBodyStream, RequestBodyTooLargeError, type RequestContext, type RequestMiddleware, type RequestMiddlewareEntry, type RequestMiddlewareInput, ResponseBodySerializer, type ResponseBodySerializerInput, type Route, type RouteMatch, type RouteMeta, type RouteOptions, type RoutePath, type RouteSchema, RoutekitBody, type RoutekitRequest, type RoutekitRequestBody, RoutekitResponse, RoutekitResponseInit, RoutekitYield, Router, type RouterExtension, type RouterMountOptions, StatusDirective, StreamDirective, StreamFramer, UnsupportedRequestBodyMediaTypeError, body, chunk, createAsyncStream, createRoutekitRequest, createStartStream, defaultRequestBodyParsers, defaultResponseBodySerializers, defineMiddleware, formDataRequestBodyParser, head, headers, isChunkDirective, isHeadDirective, isHeadersDirective, isResponseBodyInit, isRoutekitBody, isRoutekitDirective, isRoutekitResponse, isStatusDirective, isStreamDirective, jsonLinesFramer, jsonRequestBodyParser, jsonResponseBodySerializer, response, responseFromRequestBodyError, sseFramer, status, stream, textRequestBodyParser, urlEncodedRequestBodyParser };
|