galbe 0.15.6 → 0.16.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 +3 -0
- package/bin/commands/build.ts +30 -19
- package/bin/commands/dev.ts +53 -5
- package/bin/commands/generate/cli/index.ts +4 -1
- package/bin/commands/generate/client.ts +61 -30
- package/bin/commands/generate/code/openapi.parser.ts +440 -163
- package/bin/commands/generate/code/route-merge.ts +26 -21
- package/bin/commands/generate/code.ts +15 -1
- package/bin/commands/generate/model.ts +4 -1
- package/bin/commands/generate/spec.ts +3 -1
- package/bin/res/client.runtime.ts +5 -0
- package/bin/util.ts +36 -90
- package/package.json +34 -9
- package/src/cookies.ts +29 -8
- package/src/extras/spec/openapi.serializer.ts +287 -99
- package/src/extras.ts +1 -1
- package/src/index.ts +377 -71
- package/src/middlewares/_auth.ts +178 -0
- package/src/middlewares/apiKey.ts +139 -0
- package/src/middlewares/basicAuth.ts +151 -0
- package/src/middlewares/bearer.ts +136 -0
- package/src/middlewares/jwt.ts +455 -0
- package/src/middlewares/logger.ts +120 -0
- package/src/middlewares/rateLimit.ts +153 -0
- package/src/middlewares/requestId.ts +94 -0
- package/src/middlewares/timing.ts +86 -0
- package/src/middlewares.ts +53 -0
- package/src/parser.ts +279 -133
- package/src/router.ts +74 -51
- package/src/routes.ts +220 -136
- package/src/schema.ts +123 -31
- package/src/server.ts +130 -70
- package/src/types.ts +366 -90
- package/src/util.ts +271 -5
- package/src/validator.compile.ts +343 -0
- package/src/validator.ts +64 -18
- package/bin/res/client.template.ts +0 -200
- package/scripts/release.ts +0 -196
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import type { MiddlewareDef, PreParseContext, PreParseHook } from '../types'
|
|
2
|
+
import type { MaybePromise } from './_auth'
|
|
3
|
+
|
|
4
|
+
import { TooManyRequestsError } from '../types'
|
|
5
|
+
|
|
6
|
+
/** All a rejection knows about itself, and all an `errorHandler` is given. */
|
|
7
|
+
export type RateLimitInfo = {
|
|
8
|
+
/** Bucket the request was accounted to — whatever `key` returned. */
|
|
9
|
+
key: string
|
|
10
|
+
/** Requests allowed per window, as configured. */
|
|
11
|
+
limit: number
|
|
12
|
+
/** Whole seconds until the request would be allowed through, at least `1`. */
|
|
13
|
+
retryAfter: number
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type RateLimitConfig = {
|
|
17
|
+
/** Requests allowed per `window`, and the burst a client may spend at once. */
|
|
18
|
+
limit: number
|
|
19
|
+
/** Seconds the bucket takes to refill completely. Fractional values are allowed. */
|
|
20
|
+
window: number
|
|
21
|
+
/**
|
|
22
|
+
* Bucket a request is accounted to. Defaults to `ctx.clientAddress`, which is
|
|
23
|
+
* the socket peer unless [`trustProxy`](https://galbe.dev/documentation/configuration#trustproxy)
|
|
24
|
+
* is configured — set it, or every client behind your proxy shares one bucket.
|
|
25
|
+
*
|
|
26
|
+
* Returning nothing exempts the request: that is how an allow-list, an
|
|
27
|
+
* internal caller or an authenticated tier opts out.
|
|
28
|
+
*/
|
|
29
|
+
key?: (ctx: PreParseContext) => string | undefined | null
|
|
30
|
+
/**
|
|
31
|
+
* Maximum number of buckets held in memory. Default `10000`. Reaching it
|
|
32
|
+
* evicts refilled buckets first, then the least recently created one.
|
|
33
|
+
*/
|
|
34
|
+
maxKeys?: number
|
|
35
|
+
/** Emit the `RateLimit-*` response headers. Default `true`. */
|
|
36
|
+
headers?: boolean
|
|
37
|
+
/**
|
|
38
|
+
* Replaces the default `429`. Return a `Response` to answer the request, or
|
|
39
|
+
* nothing to fall back to the default `429`; throwing takes the usual error
|
|
40
|
+
* handler path. A `Response` of your own carries no `Retry-After` unless you
|
|
41
|
+
* set one.
|
|
42
|
+
*/
|
|
43
|
+
errorHandler?: (info: RateLimitInfo, ctx: PreParseContext) => MaybePromise<Response | void>
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
type Bucket = { tokens: number; updated: number }
|
|
47
|
+
|
|
48
|
+
const DEFAULT_MAX_KEYS = 10_000
|
|
49
|
+
/** Buckets examined per eviction: bounded work, so one request cannot pay for a full scan. */
|
|
50
|
+
const SWEEP = 16
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* #### rateLimit
|
|
54
|
+
* Caps how often one client may call the routes it covers, as a token bucket:
|
|
55
|
+
* every key gets `limit` tokens refilled smoothly over `window` seconds, a
|
|
56
|
+
* request spends one, and a request that finds none is answered **429** with a
|
|
57
|
+
* `Retry-After`. Spending them all at once is allowed — the burst a client may
|
|
58
|
+
* take is the limit itself.
|
|
59
|
+
*
|
|
60
|
+
* It runs in the [`beforeParse`](https://galbe.dev/documentation/middleware#before-parsing)
|
|
61
|
+
* slot, so a throttled request is rejected **before its body is read**.
|
|
62
|
+
*
|
|
63
|
+
* Counters live in this process's memory, so each instance is its own limiter
|
|
64
|
+
* and each replica of your app enforces the limit on its own — `n` replicas
|
|
65
|
+
* mean `n` × `limit`. It also only covers **routed** paths: a flood against URLs
|
|
66
|
+
* that match no route never reaches a middleware. Neither is a reason to skip
|
|
67
|
+
* it, but a public-facing service wants a limiter at the edge as well.
|
|
68
|
+
*
|
|
69
|
+
* ---
|
|
70
|
+
* @example
|
|
71
|
+
* ```typescript
|
|
72
|
+
* import { rateLimit } from 'galbe/middlewares'
|
|
73
|
+
*
|
|
74
|
+
* // 100 requests per minute per client, everywhere
|
|
75
|
+
* galbe.middleware(rateLimit({ limit: 100, window: 60 }))
|
|
76
|
+
*
|
|
77
|
+
* // a tighter bucket on top, for one subtree — each instance counts on its own
|
|
78
|
+
* galbe.middleware('/auth/*', rateLimit({ limit: 5, window: 60 }))
|
|
79
|
+
*
|
|
80
|
+
* // per account rather than per address, with signed-in users exempt from it
|
|
81
|
+
* galbe.middleware('/api/*', rateLimit({
|
|
82
|
+
* limit: 1000,
|
|
83
|
+
* window: 3600,
|
|
84
|
+
* key: ctx => ctx.state.apiKey?.accountId
|
|
85
|
+
* }))
|
|
86
|
+
* ```
|
|
87
|
+
* @param config - see {@link RateLimitConfig}
|
|
88
|
+
*/
|
|
89
|
+
export const rateLimit = (config: RateLimitConfig): MiddlewareDef<{}> => {
|
|
90
|
+
const { limit, window } = config
|
|
91
|
+
if (!Number.isFinite(limit) || limit < 1) throw new SyntaxError('rateLimit: limit must be at least 1')
|
|
92
|
+
if (!Number.isFinite(window) || window <= 0) throw new SyntaxError('rateLimit: window must be a positive number')
|
|
93
|
+
const maxKeys = config.maxKeys ?? DEFAULT_MAX_KEYS
|
|
94
|
+
if (!Number.isInteger(maxKeys) || maxKeys < 1) throw new SyntaxError('rateLimit: maxKeys must be a positive integer')
|
|
95
|
+
const keyOf = config.key ?? ((ctx: PreParseContext) => ctx.clientAddress)
|
|
96
|
+
const rate = limit / window // tokens per second
|
|
97
|
+
const buckets = new Map<string, Bucket>()
|
|
98
|
+
// monotonic: a clock adjustment must not hand out tokens or freeze a bucket
|
|
99
|
+
const seconds = () => performance.now() / 1000
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Keeps the store bounded, the same discipline as the router cache — a flood
|
|
103
|
+
* of distinct keys must never grow it without limit. Refilled buckets go
|
|
104
|
+
* first, as they say nothing a fresh one would not; only when the sweep frees
|
|
105
|
+
* none does the oldest bucket go.
|
|
106
|
+
*/
|
|
107
|
+
const evict = (now: number) => {
|
|
108
|
+
let swept = 0
|
|
109
|
+
let freed = false
|
|
110
|
+
for (const [key, bucket] of buckets) {
|
|
111
|
+
if (swept++ >= SWEEP) break
|
|
112
|
+
if (bucket.tokens + (now - bucket.updated) * rate >= limit) {
|
|
113
|
+
buckets.delete(key)
|
|
114
|
+
freed = true
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (!freed) buckets.delete(buckets.keys().next().value as string)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Refills the key's bucket up to `now`, then spends a token if there is one. */
|
|
121
|
+
const consume = (key: string, now: number) => {
|
|
122
|
+
let bucket = buckets.get(key)
|
|
123
|
+
if (bucket) bucket.tokens = Math.min(limit, bucket.tokens + (now - bucket.updated) * rate)
|
|
124
|
+
else {
|
|
125
|
+
if (buckets.size >= maxKeys) evict(now)
|
|
126
|
+
buckets.set(key, (bucket = { tokens: limit, updated: now }))
|
|
127
|
+
}
|
|
128
|
+
bucket.updated = now
|
|
129
|
+
const allowed = bucket.tokens >= 1
|
|
130
|
+
if (allowed) bucket.tokens -= 1
|
|
131
|
+
return { allowed, tokens: bucket.tokens }
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const beforeParse: PreParseHook = async ctx => {
|
|
135
|
+
const key = keyOf(ctx)
|
|
136
|
+
if (!key) return
|
|
137
|
+
const { allowed, tokens } = consume(key, seconds())
|
|
138
|
+
if (config.headers !== false) {
|
|
139
|
+
ctx.set.headers['ratelimit-limit'] = String(limit)
|
|
140
|
+
ctx.set.headers['ratelimit-remaining'] = String(Math.floor(tokens))
|
|
141
|
+
ctx.set.headers['ratelimit-reset'] = String(Math.ceil((limit - tokens) / rate))
|
|
142
|
+
}
|
|
143
|
+
if (allowed) return
|
|
144
|
+
// a bucket below one token needs that fraction of a second back; HTTP
|
|
145
|
+
// counts Retry-After in whole seconds, so it always asks for at least one
|
|
146
|
+
const retryAfter = Math.ceil((1 - tokens) / rate)
|
|
147
|
+
const handled = await config.errorHandler?.({ key, limit, retryAfter }, ctx)
|
|
148
|
+
if (handled) return handled
|
|
149
|
+
throw new TooManyRequestsError(undefined, { 'retry-after': String(retryAfter) })
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return { beforeParse }
|
|
153
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import type { MiddlewareDef, PreParseHook } from '../types'
|
|
2
|
+
import type { STOptional, STString } from '../schema'
|
|
3
|
+
|
|
4
|
+
import { $T } from '../index'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* What an id may be made of: enough for UUIDs, ULIDs, nanoids and W3C trace
|
|
8
|
+
* ids, and nothing that could break a header or a log line. An inbound id that
|
|
9
|
+
* does not match is replaced rather than rejected — a malformed trace header is
|
|
10
|
+
* not the caller's request failing.
|
|
11
|
+
*/
|
|
12
|
+
const ID = /^[\w.:-]{1,128}$/
|
|
13
|
+
|
|
14
|
+
export type RequestIdConfig<N extends string = 'x-request-id', T extends boolean = true> = {
|
|
15
|
+
/** Header the id travels in, inbound and outbound. Default `x-request-id`. */
|
|
16
|
+
header?: N
|
|
17
|
+
/**
|
|
18
|
+
* Reuse a well-formed id sent by the caller, so one trace spans the services
|
|
19
|
+
* it passes through. Default `true`. Set it to `false` at the edge of a
|
|
20
|
+
* public API, where the id is the caller's to choose and yours to distrust.
|
|
21
|
+
*/
|
|
22
|
+
trustHeader?: T
|
|
23
|
+
/**
|
|
24
|
+
* Makes an id when there is none to reuse. Default `crypto.randomUUID()`.
|
|
25
|
+
* It must satisfy the same shape an inbound id does, since it ends up in a
|
|
26
|
+
* response header and in your logs.
|
|
27
|
+
*/
|
|
28
|
+
generate?: () => string
|
|
29
|
+
/** `ctx.state` key the id is stored under. Default `requestId`. */
|
|
30
|
+
stateHolder?: string
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** The header contract a trusting `requestId` instance imposes; a distrusting one reads nothing and declares nothing. */
|
|
34
|
+
export type RequestIdFragment<N extends string, T extends boolean> = T extends false
|
|
35
|
+
? {}
|
|
36
|
+
: { headers: Record<N, STOptional<STString>> }
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* #### requestId
|
|
40
|
+
* Gives every request an id — the caller's, when it sent a usable one, and a
|
|
41
|
+
* fresh UUID otherwise — puts it on `ctx.state.requestId` and echoes it in the
|
|
42
|
+
* response header. That id is what ties a log line, a trace and a support
|
|
43
|
+
* ticket to one another.
|
|
44
|
+
*
|
|
45
|
+
* It runs in the [`beforeParse`](https://galbe.dev/documentation/middleware#before-parsing)
|
|
46
|
+
* slot so the id exists before anything can reject the request: a `400` from
|
|
47
|
+
* validation or a `401` from an auth middleware registered after it carries the
|
|
48
|
+
* header too. Register it first for that reason.
|
|
49
|
+
*
|
|
50
|
+
* ---
|
|
51
|
+
* @example
|
|
52
|
+
* ```typescript
|
|
53
|
+
* import { requestId } from 'galbe/middlewares'
|
|
54
|
+
*
|
|
55
|
+
* galbe.middleware(requestId())
|
|
56
|
+
*
|
|
57
|
+
* galbe.get('/orders', ctx => {
|
|
58
|
+
* log.info({ id: ctx.state.requestId }, 'listing orders')
|
|
59
|
+
* return orders()
|
|
60
|
+
* })
|
|
61
|
+
* ```
|
|
62
|
+
* @param config - see {@link RequestIdConfig}
|
|
63
|
+
*/
|
|
64
|
+
export const requestId = <N extends string = 'x-request-id', T extends boolean = true>(
|
|
65
|
+
config: RequestIdConfig<N, T> = {}
|
|
66
|
+
): MiddlewareDef<RequestIdFragment<N, T>> => {
|
|
67
|
+
const header: string = config.header ?? 'x-request-id'
|
|
68
|
+
const stateHolder = config.stateHolder ?? 'requestId'
|
|
69
|
+
const generate = config.generate ?? (() => crypto.randomUUID())
|
|
70
|
+
const trusted = config.trustHeader !== false
|
|
71
|
+
|
|
72
|
+
const beforeParse: PreParseHook = ctx => {
|
|
73
|
+
const inbound = trusted ? ctx.request.headers.get(header) : null
|
|
74
|
+
let id: string
|
|
75
|
+
if (inbound && ID.test(inbound)) id = inbound
|
|
76
|
+
else {
|
|
77
|
+
id = generate()
|
|
78
|
+
// `generate` is caller code and the value becomes a response header:
|
|
79
|
+
// name the culprit here rather than let the Headers constructor reject it
|
|
80
|
+
// with a type error three frames away
|
|
81
|
+
if (typeof id !== 'string' || !ID.test(id))
|
|
82
|
+
throw new Error(
|
|
83
|
+
`requestId: generate() must return at most 128 characters of [A-Za-z0-9_.:-], got ${JSON.stringify(id)?.slice(0, 64)}`
|
|
84
|
+
)
|
|
85
|
+
}
|
|
86
|
+
ctx.state[stateHolder] = id
|
|
87
|
+
ctx.set.headers[header] = id
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
beforeParse,
|
|
92
|
+
...(trusted ? { schema: { headers: { [header]: $T.optional($T.string()) } } } : {}),
|
|
93
|
+
} as MiddlewareDef<RequestIdFragment<N, T>>
|
|
94
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { Context, MiddlewareDef, PreParseHook, ResponseHook } from '../types'
|
|
2
|
+
|
|
3
|
+
export type TimingConfig = {
|
|
4
|
+
/** Name of the measurement, a `Server-Timing` token. Default `total`. */
|
|
5
|
+
name?: string
|
|
6
|
+
/** Label shown next to the measurement in a browser's network panel. */
|
|
7
|
+
description?: string
|
|
8
|
+
/** Decimal places kept in the reported duration. Default `1`. */
|
|
9
|
+
precision?: number
|
|
10
|
+
/** Response header the measurement is appended to. Default `server-timing`. */
|
|
11
|
+
header?: string
|
|
12
|
+
/**
|
|
13
|
+
* Leaves a request unmeasured. It runs once the request is done, so the
|
|
14
|
+
* response status is readable and a filter can keep only the slow or the
|
|
15
|
+
* failing ones.
|
|
16
|
+
*/
|
|
17
|
+
skip?: (ctx: Context) => boolean
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** `Server-Timing` names are tokens: anything else would end the entry early. */
|
|
21
|
+
const token = (name: string) => name.replace(/[^\w-]/g, '-')
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* #### timing
|
|
25
|
+
* Reports how long the server spent on a request, in the
|
|
26
|
+
* [`Server-Timing`](https://developer.mozilla.org/docs/Web/HTTP/Headers/Server-Timing)
|
|
27
|
+
* header — the number a browser shows in its network panel, and the one a
|
|
28
|
+
* caller cannot measure itself, since its own clock also counts the network.
|
|
29
|
+
*
|
|
30
|
+
* The entry is **appended**, so measurements a route added to the same header
|
|
31
|
+
* survive alongside it. It measures the whole request: the clock starts in the
|
|
32
|
+
* `beforeParse` slot and the entry is written once a `Response` exists, so a
|
|
33
|
+
* request rejected before the hook chain — a `400` from validation, a `401`
|
|
34
|
+
* from an auth middleware — is measured too. A request that matched no route is
|
|
35
|
+
* not: a middleware runs per route, never for a `404`.
|
|
36
|
+
*
|
|
37
|
+
* The header is public, and so is what it says about your internals. Keep the
|
|
38
|
+
* names generic on a public API, or restrict it to development with `skip`.
|
|
39
|
+
*
|
|
40
|
+
* ---
|
|
41
|
+
* @example
|
|
42
|
+
* ```typescript
|
|
43
|
+
* import { timing } from 'galbe/middlewares'
|
|
44
|
+
*
|
|
45
|
+
* galbe.middleware(timing())
|
|
46
|
+
* // → Server-Timing: total;dur=12.4
|
|
47
|
+
*
|
|
48
|
+
* galbe.middleware('/api/*', timing({ name: 'api', description: 'handler', precision: 2 }))
|
|
49
|
+
* // → Server-Timing: api;dur=12.41;desc="handler"
|
|
50
|
+
* ```
|
|
51
|
+
* @param config - see {@link TimingConfig}
|
|
52
|
+
*/
|
|
53
|
+
export const timing = (config: TimingConfig = {}): MiddlewareDef<{}> => {
|
|
54
|
+
const precision = config.precision ?? 1
|
|
55
|
+
if (!Number.isInteger(precision) || precision < 0 || precision > 6)
|
|
56
|
+
throw new SyntaxError('timing: precision must be an integer between 0 and 6')
|
|
57
|
+
const description = config.description
|
|
58
|
+
if (description !== undefined && /["\\\x00-\x1f\x7f]/.test(description))
|
|
59
|
+
throw new SyntaxError('timing: description must not contain quotes, backslashes or control characters')
|
|
60
|
+
const header = token(config.header ?? 'server-timing').toLowerCase()
|
|
61
|
+
const name = token(config.name ?? 'total')
|
|
62
|
+
const suffix = description ? `;desc="${description}"` : ''
|
|
63
|
+
|
|
64
|
+
// the clock lives beside the request rather than on `ctx.state`, which is a
|
|
65
|
+
// public `Record<string, any>`: an internal marker has no business in a dump
|
|
66
|
+
// of it, and two instances on one route keep their own
|
|
67
|
+
const starts = new WeakMap<object, number>()
|
|
68
|
+
|
|
69
|
+
const beforeParse: PreParseHook = ctx => {
|
|
70
|
+
starts.set(ctx, performance.now())
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const afterHandle: ResponseHook = (response, ctx) => {
|
|
74
|
+
const start = starts.get(ctx)
|
|
75
|
+
// the post slot also runs for a request answered before the pre slot did —
|
|
76
|
+
// a plugin that threw while routing — where there is no clock to read
|
|
77
|
+
if (typeof start !== 'number' || config.skip?.(ctx)) return
|
|
78
|
+
const entry = `${name};dur=${(performance.now() - start).toFixed(precision)}${suffix}`
|
|
79
|
+
// append to whatever the response already carries, so a measurement a route
|
|
80
|
+
// added to the same header survives alongside this one
|
|
81
|
+
const measured = response.headers.get(header)
|
|
82
|
+
response.headers.set(header, measured ? `${measured}, ${entry}` : entry)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return { beforeParse, afterHandle }
|
|
86
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* #### galbe/middlewares
|
|
3
|
+
* Built-in middlewares: request contracts, security metadata and hooks, ready
|
|
4
|
+
* to register. Each one is a plain {@link MiddlewareDef}, so it is accepted
|
|
5
|
+
* everywhere a hook is — `galbe.middleware`, `galbe.group`, or the default
|
|
6
|
+
* export of a `*.middleware.ts` file.
|
|
7
|
+
*
|
|
8
|
+
* Every middleware is also importable on its own path (`galbe/middlewares/jwt`)
|
|
9
|
+
* for apps that would rather not load the whole set.
|
|
10
|
+
*
|
|
11
|
+
* ---
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* import { jwt } from 'galbe/middlewares'
|
|
15
|
+
*
|
|
16
|
+
* galbe.middleware('/api/*', jwt({ key: Bun.env.JWT_SECRET! }))
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export { AuthError } from './middlewares/_auth'
|
|
20
|
+
export type { AuthErrorCode, AuthErrorHandler } from './middlewares/_auth'
|
|
21
|
+
|
|
22
|
+
export { jwt, JwtError, signJwt } from './middlewares/jwt'
|
|
23
|
+
export type {
|
|
24
|
+
JwtAlgorithm,
|
|
25
|
+
JwtConfig,
|
|
26
|
+
JwtErrorCode,
|
|
27
|
+
JwtFragment,
|
|
28
|
+
JwtKey,
|
|
29
|
+
JwtPayload,
|
|
30
|
+
JwtSignOptions,
|
|
31
|
+
JwtSource,
|
|
32
|
+
} from './middlewares/jwt'
|
|
33
|
+
|
|
34
|
+
export { bearer } from './middlewares/bearer'
|
|
35
|
+
export type { BearerConfig, BearerFragment } from './middlewares/bearer'
|
|
36
|
+
|
|
37
|
+
export { apiKey } from './middlewares/apiKey'
|
|
38
|
+
export type { ApiKeyConfig, ApiKeyFragment } from './middlewares/apiKey'
|
|
39
|
+
|
|
40
|
+
export { basicAuth } from './middlewares/basicAuth'
|
|
41
|
+
export type { BasicAuthConfig, BasicAuthFragment } from './middlewares/basicAuth'
|
|
42
|
+
|
|
43
|
+
export { rateLimit } from './middlewares/rateLimit'
|
|
44
|
+
export type { RateLimitConfig, RateLimitInfo } from './middlewares/rateLimit'
|
|
45
|
+
|
|
46
|
+
export { requestId } from './middlewares/requestId'
|
|
47
|
+
export type { RequestIdConfig, RequestIdFragment } from './middlewares/requestId'
|
|
48
|
+
|
|
49
|
+
export { logger } from './middlewares/logger'
|
|
50
|
+
export type { LogEntry, LoggerConfig } from './middlewares/logger'
|
|
51
|
+
|
|
52
|
+
export { timing } from './middlewares/timing'
|
|
53
|
+
export type { TimingConfig } from './middlewares/timing'
|