@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
|
@@ -1,192 +0,0 @@
|
|
|
1
|
-
import { HttpStatus } from '@mpen/http'
|
|
2
|
-
import { text } from '../response/formats/content'
|
|
3
|
-
import { isLocalhost } from '../lib/host'
|
|
4
|
-
import { defineMiddleware, type DeclaredMiddleware } from './define'
|
|
5
|
-
import type { AnyContext, OneOrMany } from '../types'
|
|
6
|
-
|
|
7
|
-
type AllowedOriginEntry = { kind: 'origin'; value: string } | { kind: 'host'; value: string }
|
|
8
|
-
|
|
9
|
-
export interface CsrfOptions {
|
|
10
|
-
/**
|
|
11
|
-
* Origins that are allowed even when they are cross-site.
|
|
12
|
-
* Values may be full origins (`https://app.example.com`) or bare hosts (`app.example.com`).
|
|
13
|
-
*/
|
|
14
|
-
allowedOrigins?: OneOrMany<string | URL>
|
|
15
|
-
/**
|
|
16
|
-
* Allow localhost and loopback origins for local development.
|
|
17
|
-
*/
|
|
18
|
-
allowLocalhost?: boolean
|
|
19
|
-
/**
|
|
20
|
-
* Allow requests without the Origin header (useful for curl).
|
|
21
|
-
*/
|
|
22
|
-
allowMissingOrigin?: boolean
|
|
23
|
-
/**
|
|
24
|
-
* Allow requests without Sec-Fetch-* headers (useful for curl).
|
|
25
|
-
*/
|
|
26
|
-
allowMissingFetchMetadata?: boolean
|
|
27
|
-
/**
|
|
28
|
-
* Convenience flag that enables localhost + missing header allowances.
|
|
29
|
-
*/
|
|
30
|
-
dev?: boolean
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const fetchDestHeader = 'sec-fetch-dest'
|
|
34
|
-
const fetchModeHeader = 'sec-fetch-mode'
|
|
35
|
-
const fetchSiteHeader = 'sec-fetch-site'
|
|
36
|
-
|
|
37
|
-
function parseOrigin(originHeader: string | null): URL | null {
|
|
38
|
-
if (!originHeader) return null
|
|
39
|
-
const trimmed = originHeader.trim()
|
|
40
|
-
if (!trimmed || trimmed === 'null') return null
|
|
41
|
-
try {
|
|
42
|
-
return new URL(trimmed)
|
|
43
|
-
} catch {
|
|
44
|
-
return null
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function normalizeAllowedOrigins(allowed?: OneOrMany<string | URL>): AllowedOriginEntry[] {
|
|
49
|
-
if (!allowed) return []
|
|
50
|
-
const entries = Array.isArray(allowed) ? allowed : [allowed]
|
|
51
|
-
const normalized: AllowedOriginEntry[] = []
|
|
52
|
-
for (const entry of entries) {
|
|
53
|
-
if (entry instanceof URL) {
|
|
54
|
-
normalized.push({ kind: 'origin', value: entry.origin })
|
|
55
|
-
continue
|
|
56
|
-
}
|
|
57
|
-
const trimmed = entry.trim()
|
|
58
|
-
if (!trimmed) continue
|
|
59
|
-
if (trimmed.includes('://')) {
|
|
60
|
-
try {
|
|
61
|
-
normalized.push({ kind: 'origin', value: new URL(trimmed).origin })
|
|
62
|
-
} catch {
|
|
63
|
-
continue
|
|
64
|
-
}
|
|
65
|
-
} else {
|
|
66
|
-
normalized.push({ kind: 'host', value: trimmed.replace(/\/+$/, '') })
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
return normalized
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
function isOriginAllowlisted(origin: URL, allowlist: AllowedOriginEntry[]): boolean {
|
|
73
|
-
for (const entry of allowlist) {
|
|
74
|
-
if (entry.kind === 'origin') {
|
|
75
|
-
if (origin.origin === entry.value) return true
|
|
76
|
-
continue
|
|
77
|
-
}
|
|
78
|
-
if (entry.value.includes(':')) {
|
|
79
|
-
if (origin.host === entry.value) return true
|
|
80
|
-
} else if (origin.hostname === entry.value) {
|
|
81
|
-
return true
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
return false
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
function isIpAddress(hostname: string): boolean {
|
|
88
|
-
if (hostname.includes(':')) return true
|
|
89
|
-
return /^[0-9.]+$/.test(hostname)
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function siteBase(hostname: string): string {
|
|
93
|
-
const lower = hostname.toLowerCase()
|
|
94
|
-
if (lower === 'localhost' || isIpAddress(lower)) return lower
|
|
95
|
-
const parts = lower.split('.').filter(Boolean)
|
|
96
|
-
if (parts.length <= 2) return lower
|
|
97
|
-
return parts.slice(-2).join('.')
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function isSameSite(origin: URL, requestUrl: URL): boolean {
|
|
101
|
-
return siteBase(origin.hostname) === siteBase(requestUrl.hostname)
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Enforce CSRF protection using Fetch Metadata and Origin headers.
|
|
106
|
-
*
|
|
107
|
-
* By default, only same-site fetch() requests are allowed. Set `allowedOrigins` to permit
|
|
108
|
-
* explicit cross-site origins. Enable `dev` to allow localhost and curl-style requests
|
|
109
|
-
* that omit Fetch Metadata or Origin headers.
|
|
110
|
-
*
|
|
111
|
-
* @example
|
|
112
|
-
* ```ts
|
|
113
|
-
* router.use(csrf())
|
|
114
|
-
* router.use(csrf({allowedOrigins: ['https://app.example.com']}))
|
|
115
|
-
* router.use(csrf({dev: true}))
|
|
116
|
-
* ```
|
|
117
|
-
*
|
|
118
|
-
* @param options - Configuration for allowed origins and dev-friendly relaxations.
|
|
119
|
-
* @returns Middleware that rejects requests failing CSRF checks.
|
|
120
|
-
*/
|
|
121
|
-
export function csrf<Ctx extends object = AnyContext>(
|
|
122
|
-
options: CsrfOptions = {},
|
|
123
|
-
): DeclaredMiddleware<{}, Ctx> {
|
|
124
|
-
const allowLocalhost = options.allowLocalhost ?? options.dev ?? false
|
|
125
|
-
const allowMissingOrigin = options.allowMissingOrigin ?? options.dev ?? false
|
|
126
|
-
const allowMissingFetchMetadata = options.allowMissingFetchMetadata ?? options.dev ?? false
|
|
127
|
-
const allowedOrigins = normalizeAllowedOrigins(options.allowedOrigins)
|
|
128
|
-
|
|
129
|
-
return defineMiddleware({
|
|
130
|
-
responses: {
|
|
131
|
-
[HttpStatus.FORBIDDEN]: {
|
|
132
|
-
schema: { type: 'string' },
|
|
133
|
-
parse(value: unknown): string {
|
|
134
|
-
if (typeof value !== 'string') {
|
|
135
|
-
throw new TypeError('CSRF rejection responses must contain a string body.')
|
|
136
|
-
}
|
|
137
|
-
return value
|
|
138
|
-
},
|
|
139
|
-
},
|
|
140
|
-
},
|
|
141
|
-
async run(ctx, { next, forward, respond }) {
|
|
142
|
-
const requestUrl = ctx.request.url
|
|
143
|
-
const originHeader = ctx.request.headers.get('origin')
|
|
144
|
-
const originUrl = parseOrigin(originHeader)
|
|
145
|
-
|
|
146
|
-
const fetchDest = ctx.request.headers.get(fetchDestHeader)
|
|
147
|
-
const fetchMode = ctx.request.headers.get(fetchModeHeader)
|
|
148
|
-
const fetchSite = ctx.request.headers.get(fetchSiteHeader)
|
|
149
|
-
|
|
150
|
-
const fetchMetadataMissing = !fetchDest || !fetchMode || !fetchSite
|
|
151
|
-
if (fetchMetadataMissing && !allowMissingFetchMetadata) {
|
|
152
|
-
return respond(text('Forbidden', { status: HttpStatus.FORBIDDEN }))
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
if (fetchDest && fetchDest !== 'empty') {
|
|
156
|
-
return respond(text('Forbidden', { status: HttpStatus.FORBIDDEN }))
|
|
157
|
-
}
|
|
158
|
-
if (fetchMode && fetchMode !== 'cors' && fetchMode !== 'same-origin') {
|
|
159
|
-
return respond(text('Forbidden', { status: HttpStatus.FORBIDDEN }))
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
const originIsAllowlisted = originUrl
|
|
163
|
-
? isOriginAllowlisted(originUrl, allowedOrigins)
|
|
164
|
-
: false
|
|
165
|
-
const originIsLocalhost = originUrl
|
|
166
|
-
? allowLocalhost && isLocalhost(originUrl.hostname)
|
|
167
|
-
: false
|
|
168
|
-
const originIsSameSite = originUrl ? isSameSite(originUrl, requestUrl) : false
|
|
169
|
-
|
|
170
|
-
if (!originUrl && !allowMissingOrigin) {
|
|
171
|
-
return respond(text('Forbidden', { status: HttpStatus.FORBIDDEN }))
|
|
172
|
-
}
|
|
173
|
-
if (originUrl && !(originIsAllowlisted || originIsLocalhost || originIsSameSite)) {
|
|
174
|
-
return respond(text('Forbidden', { status: HttpStatus.FORBIDDEN }))
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
if (fetchSite) {
|
|
178
|
-
if (fetchSite === 'same-origin' || fetchSite === 'same-site') {
|
|
179
|
-
// ok
|
|
180
|
-
} else if (fetchSite === 'cross-site') {
|
|
181
|
-
if (!originIsAllowlisted && !originIsLocalhost) {
|
|
182
|
-
return respond(text('Forbidden', { status: HttpStatus.FORBIDDEN }))
|
|
183
|
-
}
|
|
184
|
-
} else {
|
|
185
|
-
return respond(text('Forbidden', { status: HttpStatus.FORBIDDEN }))
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
return forward(await next())
|
|
190
|
-
},
|
|
191
|
-
})
|
|
192
|
-
}
|
|
@@ -1,249 +0,0 @@
|
|
|
1
|
-
import { response, type RoutekitResponse } from '../response'
|
|
2
|
-
import type { AnyContext, HandlerContext, HandlerResult, JsonSchema, RouteSchema } from '../types'
|
|
3
|
-
import { mergeRouteSchemas } from '../lib/schema-merge'
|
|
4
|
-
|
|
5
|
-
const declaredMiddlewareBrand: unique symbol = Symbol('DeclaredMiddleware')
|
|
6
|
-
const middlewareActionBrand: unique symbol = Symbol('MiddlewareAction')
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* A response declaration owned by middleware.
|
|
10
|
-
*
|
|
11
|
-
* @example
|
|
12
|
-
* ```ts
|
|
13
|
-
* const forbidden = {
|
|
14
|
-
* schema: { type: 'string' },
|
|
15
|
-
* parse: value => String(value),
|
|
16
|
-
* }
|
|
17
|
-
* ```
|
|
18
|
-
*
|
|
19
|
-
* @typeParam Body - Parsed response body produced by the declaration.
|
|
20
|
-
*/
|
|
21
|
-
export interface MiddlewareResponseDeclaration<Body = unknown> {
|
|
22
|
-
/**
|
|
23
|
-
* JSON Schema emitted for metadata consumers such as OpenAPI and client generation.
|
|
24
|
-
*/
|
|
25
|
-
readonly schema: JsonSchema
|
|
26
|
-
/**
|
|
27
|
-
* Validate and optionally parse a locally originated response body.
|
|
28
|
-
*
|
|
29
|
-
* @param value - Body supplied through `respond(...)`.
|
|
30
|
-
* @returns The validated response body.
|
|
31
|
-
*/
|
|
32
|
-
parse(value: unknown): Body
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Status-keyed response declarations owned by middleware.
|
|
37
|
-
*/
|
|
38
|
-
export type MiddlewareResponseDeclarations = Partial<
|
|
39
|
-
Record<number | 'default', MiddlewareResponseDeclaration<any>>
|
|
40
|
-
>
|
|
41
|
-
|
|
42
|
-
type DeclaredBody<Declaration> =
|
|
43
|
-
Declaration extends MiddlewareResponseDeclaration<infer Body> ? Body : never
|
|
44
|
-
|
|
45
|
-
type DeclaredResponse<Declarations extends MiddlewareResponseDeclarations> = {
|
|
46
|
-
[Status in keyof Declarations]-?: NonNullable<
|
|
47
|
-
Declarations[Status]
|
|
48
|
-
> extends MiddlewareResponseDeclaration<any>
|
|
49
|
-
? RoutekitResponse<
|
|
50
|
-
DeclaredBody<NonNullable<Declarations[Status]>>,
|
|
51
|
-
Status extends number ? Status : number
|
|
52
|
-
>
|
|
53
|
-
: never
|
|
54
|
-
}[keyof Declarations]
|
|
55
|
-
|
|
56
|
-
/** @internal */
|
|
57
|
-
export interface MiddlewareAction {
|
|
58
|
-
readonly [middlewareActionBrand]: true
|
|
59
|
-
readonly result: HandlerResult
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Controls passed to a declared middleware implementation.
|
|
64
|
-
*
|
|
65
|
-
* @typeParam Declarations - Locally originated response declarations.
|
|
66
|
-
*/
|
|
67
|
-
export interface MiddlewareControls<Declarations extends MiddlewareResponseDeclarations> {
|
|
68
|
-
/**
|
|
69
|
-
* Invoke downstream middleware and the route handler.
|
|
70
|
-
*
|
|
71
|
-
* @returns The downstream result before it is finalized into a Fetch response.
|
|
72
|
-
*/
|
|
73
|
-
next(): Promise<HandlerResult>
|
|
74
|
-
/**
|
|
75
|
-
* Forward an unchanged or decorated downstream result.
|
|
76
|
-
*
|
|
77
|
-
* @param result - Result obtained from [`MiddlewareControls.next`]{@link MiddlewareControls#next}.
|
|
78
|
-
* @returns A middleware action accepted by the declaring factory.
|
|
79
|
-
*/
|
|
80
|
-
forward(result: HandlerResult): MiddlewareAction
|
|
81
|
-
/**
|
|
82
|
-
* Return a response originated by this middleware.
|
|
83
|
-
*
|
|
84
|
-
* @param result - Logical response matching a declared body and exact status.
|
|
85
|
-
* @returns A validated terminal middleware action.
|
|
86
|
-
*/
|
|
87
|
-
respond(result: DeclaredResponse<Declarations>): MiddlewareAction
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Callable middleware carrying schema declarations for router metadata.
|
|
92
|
-
*
|
|
93
|
-
* @typeParam AddedCtx - Context values made available to downstream handlers.
|
|
94
|
-
* @typeParam Ctx - Context required before this middleware executes.
|
|
95
|
-
*/
|
|
96
|
-
export interface DeclaredMiddleware<AddedCtx extends object = {}, Ctx extends object = AnyContext> {
|
|
97
|
-
readonly [declaredMiddlewareBrand]: true
|
|
98
|
-
readonly schema?: RouteSchema
|
|
99
|
-
readonly schemaAppliesTo?: (route: { readonly method?: string | readonly string[] }) => boolean;
|
|
100
|
-
(
|
|
101
|
-
ctx: HandlerContext<Ctx & AddedCtx>,
|
|
102
|
-
next: () => Promise<HandlerResult>,
|
|
103
|
-
): Promise<HandlerResult>
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Options for [`defineMiddleware`]{@link defineMiddleware}.
|
|
108
|
-
*
|
|
109
|
-
* @typeParam AddedCtx - Context values made available to downstream handlers.
|
|
110
|
-
* @typeParam Ctx - Context required before this middleware executes.
|
|
111
|
-
* @typeParam Declarations - Status-keyed locally originated responses.
|
|
112
|
-
*/
|
|
113
|
-
export interface DefineMiddlewareOptions<
|
|
114
|
-
AddedCtx extends object,
|
|
115
|
-
Ctx extends object,
|
|
116
|
-
Declarations extends MiddlewareResponseDeclarations,
|
|
117
|
-
> {
|
|
118
|
-
/**
|
|
119
|
-
* Additional metadata contributed by the middleware, such as request schemas or
|
|
120
|
-
* downstream response boundary schemas.
|
|
121
|
-
*/
|
|
122
|
-
schema?: RouteSchema
|
|
123
|
-
/**
|
|
124
|
-
* Limit this middleware's schema contribution to matching flattened routes.
|
|
125
|
-
*
|
|
126
|
-
* @param route - Route metadata being flattened by the router.
|
|
127
|
-
* @returns Whether this middleware's schema applies to that route.
|
|
128
|
-
*/
|
|
129
|
-
schemaAppliesTo?: (route: { readonly method?: string | readonly string[] }) => boolean
|
|
130
|
-
/**
|
|
131
|
-
* Responses that this middleware may originate through `respond(...)`.
|
|
132
|
-
*/
|
|
133
|
-
responses?: Declarations
|
|
134
|
-
/**
|
|
135
|
-
* Execute middleware behavior.
|
|
136
|
-
*
|
|
137
|
-
* @param ctx - Request and route context available at execution time.
|
|
138
|
-
* @param controls - Continuation, forwarding, and terminal response operations.
|
|
139
|
-
* @returns A terminal/forward action, or void to continue automatically.
|
|
140
|
-
*/
|
|
141
|
-
run(
|
|
142
|
-
ctx: HandlerContext<Ctx & AddedCtx>,
|
|
143
|
-
controls: MiddlewareControls<Declarations>,
|
|
144
|
-
): MiddlewareAction | void | Promise<MiddlewareAction | void>
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
function declarationsSchema(
|
|
148
|
-
declarations: MiddlewareResponseDeclarations | undefined,
|
|
149
|
-
): RouteSchema | undefined {
|
|
150
|
-
if (!declarations) return undefined
|
|
151
|
-
const body = Object.fromEntries(
|
|
152
|
-
Object.entries(declarations).flatMap(([status, declaration]) =>
|
|
153
|
-
declaration ? [[status, declaration.schema]] : [],
|
|
154
|
-
),
|
|
155
|
-
)
|
|
156
|
-
return Object.keys(body).length > 0 ? { response: { body } } : undefined
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
function middlewareAction(result: HandlerResult): MiddlewareAction {
|
|
160
|
-
return { [middlewareActionBrand]: true, result }
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* Create middleware that declares and validates every response it originates.
|
|
165
|
-
*
|
|
166
|
-
* @example
|
|
167
|
-
* ```ts
|
|
168
|
-
* const auth = defineMiddleware({
|
|
169
|
-
* responses: {
|
|
170
|
-
* 401: {
|
|
171
|
-
* schema: { type: 'string' },
|
|
172
|
-
* parse: value => String(value),
|
|
173
|
-
* },
|
|
174
|
-
* },
|
|
175
|
-
* run: async (_ctx, { respond }) => respond(response('Sign in', { status: 401 })),
|
|
176
|
-
* })
|
|
177
|
-
* ```
|
|
178
|
-
*
|
|
179
|
-
* @param options - Schema declarations and middleware implementation.
|
|
180
|
-
* @returns Executable middleware carrying its metadata contribution.
|
|
181
|
-
* @typeParam AddedCtx - Context values made available to downstream handlers.
|
|
182
|
-
* @typeParam Ctx - Context required before this middleware executes.
|
|
183
|
-
* @typeParam Declarations - Status-keyed locally originated responses.
|
|
184
|
-
*/
|
|
185
|
-
export function defineMiddleware<
|
|
186
|
-
AddedCtx extends object = {},
|
|
187
|
-
Ctx extends object = AnyContext,
|
|
188
|
-
const Declarations extends MiddlewareResponseDeclarations = {},
|
|
189
|
-
>(options: DefineMiddlewareOptions<AddedCtx, Ctx, Declarations>): DeclaredMiddleware<AddedCtx, Ctx>
|
|
190
|
-
export function defineMiddleware<
|
|
191
|
-
AddedCtx extends object = {},
|
|
192
|
-
Ctx extends object = AnyContext,
|
|
193
|
-
const Declarations extends MiddlewareResponseDeclarations = {},
|
|
194
|
-
>(
|
|
195
|
-
options: DefineMiddlewareOptions<AddedCtx, Ctx, Declarations>,
|
|
196
|
-
): DeclaredMiddleware<AddedCtx, Ctx> {
|
|
197
|
-
const declarations = options.responses
|
|
198
|
-
const schema = mergeRouteSchemas(options.schema, declarationsSchema(declarations))
|
|
199
|
-
const middleware = async (
|
|
200
|
-
ctx: HandlerContext<Ctx & AddedCtx>,
|
|
201
|
-
next: () => Promise<HandlerResult>,
|
|
202
|
-
): Promise<HandlerResult> => {
|
|
203
|
-
const controls: MiddlewareControls<Declarations> = {
|
|
204
|
-
next,
|
|
205
|
-
forward: middlewareAction,
|
|
206
|
-
respond(result) {
|
|
207
|
-
const declaration = declarations?.[result.status] ?? declarations?.default
|
|
208
|
-
if (!declaration) {
|
|
209
|
-
throw new Error(
|
|
210
|
-
`Middleware returned undeclared response status ${result.status}.`,
|
|
211
|
-
)
|
|
212
|
-
}
|
|
213
|
-
const body = declaration.parse(result.body)
|
|
214
|
-
return middlewareAction(
|
|
215
|
-
response(body, {
|
|
216
|
-
status: result.status,
|
|
217
|
-
headers: result.headers,
|
|
218
|
-
}),
|
|
219
|
-
)
|
|
220
|
-
},
|
|
221
|
-
}
|
|
222
|
-
const action = await options.run(ctx, controls)
|
|
223
|
-
if (action === undefined) return await next()
|
|
224
|
-
if (!action[middlewareActionBrand]) {
|
|
225
|
-
throw new TypeError('Declared middleware must return respond(...) or forward(...).')
|
|
226
|
-
}
|
|
227
|
-
return action.result
|
|
228
|
-
}
|
|
229
|
-
Object.defineProperties(middleware, {
|
|
230
|
-
[declaredMiddlewareBrand]: { value: true },
|
|
231
|
-
schema: { value: schema },
|
|
232
|
-
schemaAppliesTo: { value: options.schemaAppliesTo },
|
|
233
|
-
})
|
|
234
|
-
return middleware as DeclaredMiddleware<AddedCtx, Ctx>
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
/**
|
|
238
|
-
* Test whether a middleware value carries schema declarations.
|
|
239
|
-
*
|
|
240
|
-
* @param value - Middleware value to inspect.
|
|
241
|
-
* @returns Whether `value` was created by [`defineMiddleware`]{@link defineMiddleware}.
|
|
242
|
-
* @internal
|
|
243
|
-
*/
|
|
244
|
-
export function isDeclaredMiddleware(value: unknown): value is DeclaredMiddleware<any, any> {
|
|
245
|
-
return (
|
|
246
|
-
typeof value === 'function' &&
|
|
247
|
-
(value as DeclaredMiddleware<any, any>)[declaredMiddlewareBrand] === true
|
|
248
|
-
)
|
|
249
|
-
}
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
export { requestIdCtx } from './request-id-ctx'
|
|
2
|
-
export type { RequestIdCtxOptions } from './request-id-ctx'
|
|
3
|
-
export { requestLogger } from './request-logger'
|
|
4
|
-
export type { RequestLoggerOptions } from './request-logger'
|
|
5
|
-
export {
|
|
6
|
-
formatRoutekitTerminalLogRecord,
|
|
7
|
-
transformRoutekitJsonLogRecord,
|
|
8
|
-
} from './request-logger-format'
|
|
9
|
-
export { defineMiddleware } from './define'
|
|
10
|
-
export type {
|
|
11
|
-
DeclaredMiddleware,
|
|
12
|
-
DefineMiddlewareOptions,
|
|
13
|
-
MiddlewareControls,
|
|
14
|
-
MiddlewareResponseDeclaration,
|
|
15
|
-
MiddlewareResponseDeclarations,
|
|
16
|
-
} from './define'
|
|
17
|
-
export { bodyLimit } from './body-limit'
|
|
18
|
-
export { startTimeCtx } from './start-time-ctx'
|
|
19
|
-
export { acceptCtx } from './accept-ctx'
|
|
20
|
-
export { cors } from './cors'
|
|
21
|
-
export { rateLimit } from './rate-limit'
|
|
22
|
-
export type { HttpMethod } from '@mpen/http'
|
|
23
|
-
export type {
|
|
24
|
-
AsnClass,
|
|
25
|
-
AsnRecord,
|
|
26
|
-
EndpointLimit,
|
|
27
|
-
FixedWindowCounter,
|
|
28
|
-
MethodLimit,
|
|
29
|
-
RateBucket,
|
|
30
|
-
RateLimitIdentityInput,
|
|
31
|
-
RateLimitOptions,
|
|
32
|
-
RateLimitStorage,
|
|
33
|
-
} from './rate-limit'
|
|
34
|
-
export type { CorsOptions } from './cors'
|
|
File without changes
|
|
File without changes
|