@zudojs/middleware 0.1.0 → 1.0.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/LICENSE +21 -0
- package/README.md +163 -16
- package/dist/middlewareCore/index.d.ts +1 -1
- package/dist/middlewareCore/index.js +1 -1
- package/dist/middlewareCore/middlewareCore.compose.d.ts +36 -4
- package/dist/middlewareCore/middlewareCore.compose.js +53 -14
- package/dist/middlewareErrors/index.d.ts +1 -1
- package/dist/middlewareErrors/index.js +1 -1
- package/dist/middlewareErrors/middlewareError.base.d.ts +30 -0
- package/dist/middlewareErrors/middlewareError.base.js +43 -0
- package/dist/middlewarePipeline/middlewarePipeline.core.d.ts +3 -1
- package/dist/middlewarePipeline/middlewarePipeline.core.js +99 -21
- package/dist/middlewareTypes/index.d.ts +1 -1
- package/dist/middlewareTypes/middlewareContext.type.d.ts +81 -12
- package/dist/middlewareUtils/index.d.ts +1 -1
- package/dist/middlewareUtils/index.js +1 -1
- package/dist/middlewareUtils/middlewareUtils.builtins.d.ts +87 -8
- package/dist/middlewareUtils/middlewareUtils.builtins.js +179 -28
- package/package.json +20 -13
- package/dist/.tsbuildinfo +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/middlewareCore/index.d.ts.map +0 -1
- package/dist/middlewareCore/index.js.map +0 -1
- package/dist/middlewareCore/middlewareCore.compose.d.ts.map +0 -1
- package/dist/middlewareCore/middlewareCore.compose.js.map +0 -1
- package/dist/middlewareErrors/index.d.ts.map +0 -1
- package/dist/middlewareErrors/index.js.map +0 -1
- package/dist/middlewareErrors/middlewareError.base.d.ts.map +0 -1
- package/dist/middlewareErrors/middlewareError.base.js.map +0 -1
- package/dist/middlewarePipeline/index.d.ts.map +0 -1
- package/dist/middlewarePipeline/index.js.map +0 -1
- package/dist/middlewarePipeline/middlewarePipeline.core.d.ts.map +0 -1
- package/dist/middlewarePipeline/middlewarePipeline.core.js.map +0 -1
- package/dist/middlewareTypes/index.d.ts.map +0 -1
- package/dist/middlewareTypes/index.js.map +0 -1
- package/dist/middlewareTypes/middlewareContext.type.d.ts.map +0 -1
- package/dist/middlewareTypes/middlewareContext.type.js.map +0 -1
- package/dist/middlewareTypes/middlewareDefinition.type.d.ts.map +0 -1
- package/dist/middlewareTypes/middlewareDefinition.type.js.map +0 -1
- package/dist/middlewareUtils/index.d.ts.map +0 -1
- package/dist/middlewareUtils/index.js.map +0 -1
- package/dist/middlewareUtils/middlewareUtils.builtins.d.ts.map +0 -1
- package/dist/middlewareUtils/middlewareUtils.builtins.js.map +0 -1
|
@@ -3,8 +3,18 @@
|
|
|
3
3
|
*
|
|
4
4
|
* @module middlewarePipeline/middlewarePipeline
|
|
5
5
|
*/
|
|
6
|
-
import {
|
|
6
|
+
import { resolveNamedMiddleware } from "../middlewareCore/middlewareCore.compose.js";
|
|
7
|
+
import { MiddlewareAbortedError, MiddlewareLimitExceededError, MiddlewareNextCalledMultipleTimesError, } from "../middlewareErrors/middlewareError.base.js";
|
|
7
8
|
const DEFAULT_MAX = 50;
|
|
9
|
+
/** Name recorded for a failure thrown by the final handler. */
|
|
10
|
+
const HANDLER_NAME = "handler";
|
|
11
|
+
function resolveErrorMode(options) {
|
|
12
|
+
if (options?.errorMode)
|
|
13
|
+
return options.errorMode;
|
|
14
|
+
if (options?.stopOnError === false)
|
|
15
|
+
return "throw";
|
|
16
|
+
return "capture";
|
|
17
|
+
}
|
|
8
18
|
/**
|
|
9
19
|
* Create a middleware pipeline that tracks execution.
|
|
10
20
|
*
|
|
@@ -12,33 +22,93 @@ const DEFAULT_MAX = 50;
|
|
|
12
22
|
* @param handler - Final handler function
|
|
13
23
|
* @param options - Pipeline configuration
|
|
14
24
|
* @returns Pipeline execution function
|
|
25
|
+
* @throws MiddlewareLimitExceededError if more middleware are enabled than
|
|
26
|
+
* `maxMiddleware` allows
|
|
15
27
|
*/
|
|
16
28
|
export function createPipeline(middlewareList, handler, options) {
|
|
17
|
-
const resolved =
|
|
29
|
+
const resolved = resolveNamedMiddleware(middlewareList);
|
|
18
30
|
const maxMiddleware = options?.maxMiddleware ?? DEFAULT_MAX;
|
|
19
|
-
const
|
|
31
|
+
const errorMode = resolveErrorMode(options);
|
|
32
|
+
const signal = options?.signal;
|
|
20
33
|
if (resolved.length > maxMiddleware) {
|
|
21
|
-
throw new
|
|
34
|
+
throw new MiddlewareLimitExceededError(resolved.length, maxMiddleware);
|
|
22
35
|
}
|
|
23
|
-
const enabledNames = middlewareList
|
|
24
|
-
.filter((mw) => mw.enabled !== false)
|
|
25
|
-
.sort((a, b) => (a.priority ?? 100) - (b.priority ?? 100))
|
|
26
|
-
.map((mw) => mw.name);
|
|
27
36
|
return async (context) => {
|
|
28
37
|
const startTime = performance.now();
|
|
29
38
|
const executed = [];
|
|
39
|
+
const errors = [];
|
|
30
40
|
let index = -1;
|
|
41
|
+
/**
|
|
42
|
+
* The error the final handler threw, if it did. Middleware still sees the
|
|
43
|
+
* error itself — wrapping it would break `catch (e) { if (e instanceof
|
|
44
|
+
* HttpError) … }` in user middleware — so identity is what marks it as
|
|
45
|
+
* the handler's, and that is what stops `errorMode: "continue"` from
|
|
46
|
+
* treating it as a middleware failure it can step past.
|
|
47
|
+
*/
|
|
48
|
+
let handlerError;
|
|
49
|
+
function throwIfAborted() {
|
|
50
|
+
if (signal?.aborted) {
|
|
51
|
+
throw new MiddlewareAbortedError(signal.reason);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/** Errors that `"continue"` must never swallow. */
|
|
55
|
+
function isFatal(error) {
|
|
56
|
+
return ((handlerError !== undefined && error === handlerError.error) ||
|
|
57
|
+
error instanceof MiddlewareAbortedError ||
|
|
58
|
+
error instanceof MiddlewareNextCalledMultipleTimesError);
|
|
59
|
+
}
|
|
31
60
|
async function dispatch(i) {
|
|
32
61
|
if (i <= index) {
|
|
33
|
-
|
|
62
|
+
// dispatch(i) is invoked by the middleware at i - 1, so that is the
|
|
63
|
+
// one that called next() again.
|
|
64
|
+
const name = resolved[i - 1]?.name ?? `middleware[${i - 1}]`;
|
|
65
|
+
throw new MiddlewareNextCalledMultipleTimesError(name);
|
|
34
66
|
}
|
|
35
67
|
index = i;
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
68
|
+
throwIfAborted();
|
|
69
|
+
if (i >= resolved.length) {
|
|
70
|
+
try {
|
|
71
|
+
return await handler(context);
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
handlerError = { error };
|
|
75
|
+
throw error;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const mw = resolved[i];
|
|
79
|
+
executed.push(mw.name);
|
|
80
|
+
if (errorMode !== "continue") {
|
|
81
|
+
return mw.handler(context, () => dispatch(i + 1));
|
|
82
|
+
}
|
|
83
|
+
let advanced = false;
|
|
84
|
+
// Boxed rather than a bare `TResult | undefined`, so "the downstream
|
|
85
|
+
// chain produced a value" is distinguishable from "it produced
|
|
86
|
+
// `undefined`" without an assertion.
|
|
87
|
+
let downstream;
|
|
88
|
+
try {
|
|
89
|
+
return await mw.handler(context, async () => {
|
|
90
|
+
advanced = true;
|
|
91
|
+
const value = await dispatch(i + 1);
|
|
92
|
+
downstream = { value };
|
|
93
|
+
return value;
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
if (isFatal(error))
|
|
98
|
+
throw error;
|
|
99
|
+
if (advanced && downstream === undefined) {
|
|
100
|
+
// next() was entered but never produced a result — the middleware
|
|
101
|
+
// caught the downstream failure and threw something of its own.
|
|
102
|
+
// There is no downstream result to keep and no step left to
|
|
103
|
+
// continue to, so returning `undefined` as if it were a `TResult`
|
|
104
|
+
// would report a successful run that never happened.
|
|
105
|
+
throw error;
|
|
106
|
+
}
|
|
107
|
+
errors.push({ name: mw.name, error });
|
|
108
|
+
// The chain already ran past this middleware, so its downstream
|
|
109
|
+
// result stands; otherwise pick up at the next middleware.
|
|
110
|
+
return downstream ? downstream.value : dispatch(i + 1);
|
|
40
111
|
}
|
|
41
|
-
return handler(context);
|
|
42
112
|
}
|
|
43
113
|
try {
|
|
44
114
|
const result = await dispatch(0);
|
|
@@ -47,18 +117,26 @@ export function createPipeline(middlewareList, handler, options) {
|
|
|
47
117
|
result,
|
|
48
118
|
durationMs: performance.now() - startTime,
|
|
49
119
|
executedMiddleware: executed,
|
|
120
|
+
errors,
|
|
50
121
|
};
|
|
51
122
|
}
|
|
52
123
|
catch (error) {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
124
|
+
const fromHandler = handlerError !== undefined && error === handlerError.error;
|
|
125
|
+
if (!errors.some((failure) => failure.error === error)) {
|
|
126
|
+
errors.push({
|
|
127
|
+
name: fromHandler ? HANDLER_NAME : (executed.at(-1) ?? HANDLER_NAME),
|
|
56
128
|
error,
|
|
57
|
-
|
|
58
|
-
executedMiddleware: executed,
|
|
59
|
-
};
|
|
129
|
+
});
|
|
60
130
|
}
|
|
61
|
-
throw
|
|
131
|
+
if (errorMode === "throw")
|
|
132
|
+
throw error;
|
|
133
|
+
return {
|
|
134
|
+
success: false,
|
|
135
|
+
error,
|
|
136
|
+
durationMs: performance.now() - startTime,
|
|
137
|
+
executedMiddleware: executed,
|
|
138
|
+
errors,
|
|
139
|
+
};
|
|
62
140
|
}
|
|
63
141
|
};
|
|
64
142
|
}
|
|
@@ -4,5 +4,5 @@
|
|
|
4
4
|
* @module middlewareTypes
|
|
5
5
|
*/
|
|
6
6
|
export { type Middleware, type NamedMiddleware, type MiddlewareFactory, } from "./middlewareDefinition.type.js";
|
|
7
|
-
export { type PipelineResult, type PipelineOptions, } from "./middlewareContext.type.js";
|
|
7
|
+
export { type PipelineResult, type PipelineSuccess, type PipelineFailure, type PipelineMiddlewareFailure, type PipelineErrorMode, type PipelineOptions, } from "./middlewareContext.type.js";
|
|
8
8
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -4,27 +4,96 @@
|
|
|
4
4
|
* @module middlewareTypes/middlewareContext
|
|
5
5
|
*/
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
7
|
+
* How a pipeline reacts to a middleware or handler that throws.
|
|
8
|
+
*
|
|
9
|
+
* - `"capture"` — stop the chain and report the failure as a
|
|
10
|
+
* {@link PipelineFailure}. The default.
|
|
11
|
+
* - `"throw"` — stop the chain and let the error propagate to the caller.
|
|
12
|
+
* - `"continue"` — record the failure, skip the rest of the failing
|
|
13
|
+
* middleware, and continue with the next one. The handler still runs.
|
|
14
|
+
* A handler that throws is always captured or thrown; there is nothing
|
|
15
|
+
* left to continue to.
|
|
8
16
|
*/
|
|
9
|
-
export
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
readonly
|
|
16
|
-
/**
|
|
17
|
+
export type PipelineErrorMode = "capture" | "throw" | "continue";
|
|
18
|
+
/**
|
|
19
|
+
* A failure recorded while the pipeline was running.
|
|
20
|
+
*/
|
|
21
|
+
export interface PipelineMiddlewareFailure {
|
|
22
|
+
/** Name of the middleware that threw, or `"handler"` for the final handler. */
|
|
23
|
+
readonly name: string;
|
|
24
|
+
/** The thrown value. */
|
|
25
|
+
readonly error: unknown;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* A pipeline run that reached the handler and completed.
|
|
29
|
+
*/
|
|
30
|
+
export interface PipelineSuccess<TResult> {
|
|
31
|
+
readonly success: true;
|
|
32
|
+
/** The value returned by the handler. */
|
|
33
|
+
readonly result: TResult;
|
|
34
|
+
/** Always absent on a successful run. Present in the type so that
|
|
35
|
+
* `result.error` is legal to read before narrowing. */
|
|
36
|
+
readonly error?: undefined;
|
|
37
|
+
/** Execution time in milliseconds. */
|
|
17
38
|
readonly durationMs: number;
|
|
18
|
-
/** Names of middleware that
|
|
39
|
+
/** Names of middleware that started executing, in execution order. */
|
|
19
40
|
readonly executedMiddleware: readonly string[];
|
|
41
|
+
/** Failures swallowed under `errorMode: "continue"`. Empty otherwise. */
|
|
42
|
+
readonly errors: readonly PipelineMiddlewareFailure[];
|
|
20
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* A pipeline run that ended in a captured failure.
|
|
46
|
+
*/
|
|
47
|
+
export interface PipelineFailure {
|
|
48
|
+
readonly success: false;
|
|
49
|
+
/** Always absent on a failed run. */
|
|
50
|
+
readonly result?: undefined;
|
|
51
|
+
/** The error that ended the run. */
|
|
52
|
+
readonly error: unknown;
|
|
53
|
+
/** Execution time in milliseconds. */
|
|
54
|
+
readonly durationMs: number;
|
|
55
|
+
/** Names of middleware that started executing, in execution order. */
|
|
56
|
+
readonly executedMiddleware: readonly string[];
|
|
57
|
+
/** Every failure recorded during the run, including {@link PipelineFailure.error}. */
|
|
58
|
+
readonly errors: readonly PipelineMiddlewareFailure[];
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Result of executing a middleware pipeline.
|
|
62
|
+
*
|
|
63
|
+
* Discriminated on `success`, so narrowing gives a non-optional `result`:
|
|
64
|
+
*
|
|
65
|
+
* ```ts
|
|
66
|
+
* const outcome = await pipeline(context);
|
|
67
|
+
* if (outcome.success) {
|
|
68
|
+
* use(outcome.result); // TResult, not TResult | undefined
|
|
69
|
+
* }
|
|
70
|
+
* ```
|
|
71
|
+
*/
|
|
72
|
+
export type PipelineResult<TResult> = PipelineSuccess<TResult> | PipelineFailure;
|
|
21
73
|
/**
|
|
22
74
|
* Options for creating a middleware pipeline.
|
|
23
75
|
*/
|
|
24
76
|
export interface PipelineOptions {
|
|
25
|
-
/** Maximum number of middleware allowed */
|
|
77
|
+
/** Maximum number of enabled middleware allowed. Default: 50. */
|
|
26
78
|
readonly maxMiddleware?: number;
|
|
27
|
-
/**
|
|
79
|
+
/**
|
|
80
|
+
* How the pipeline reacts to an error. Default: `"capture"`.
|
|
81
|
+
*
|
|
82
|
+
* @see PipelineErrorMode
|
|
83
|
+
*/
|
|
84
|
+
readonly errorMode?: PipelineErrorMode;
|
|
85
|
+
/**
|
|
86
|
+
* Legacy alias for {@link PipelineOptions.errorMode}: `true` maps to
|
|
87
|
+
* `"capture"` and `false` to `"throw"`. Ignored when `errorMode` is set.
|
|
88
|
+
*
|
|
89
|
+
* @deprecated Use `errorMode` — the name describes the wrong axis, since
|
|
90
|
+
* both settings stop the chain.
|
|
91
|
+
*/
|
|
28
92
|
readonly stopOnError?: boolean;
|
|
93
|
+
/**
|
|
94
|
+
* Aborts the run. Checked before each middleware and before the handler;
|
|
95
|
+
* an aborted pipeline fails with a `MiddlewareAbortedError`.
|
|
96
|
+
*/
|
|
97
|
+
readonly signal?: AbortSignal;
|
|
29
98
|
}
|
|
30
99
|
//# sourceMappingURL=middlewareContext.type.d.ts.map
|
|
@@ -3,5 +3,5 @@
|
|
|
3
3
|
*
|
|
4
4
|
* @module middlewareUtils
|
|
5
5
|
*/
|
|
6
|
-
export { loggingMiddleware, errorMiddleware, timeoutMiddleware, rateLimitMiddleware, } from "./middlewareUtils.builtins.js";
|
|
6
|
+
export { loggingMiddleware, errorMiddleware, timeoutMiddleware, rateLimitMiddleware, sanitizeLogValue, type LoggingContext, type LoggingOptions, type TimeoutOptions, type RateLimitOptions, type RateLimitState, type RateLimitMiddleware, } from "./middlewareUtils.builtins.js";
|
|
7
7
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -3,5 +3,5 @@
|
|
|
3
3
|
*
|
|
4
4
|
* @module middlewareUtils
|
|
5
5
|
*/
|
|
6
|
-
export { loggingMiddleware, errorMiddleware, timeoutMiddleware, rateLimitMiddleware, } from "./middlewareUtils.builtins.js";
|
|
6
|
+
export { loggingMiddleware, errorMiddleware, timeoutMiddleware, rateLimitMiddleware, sanitizeLogValue, } from "./middlewareUtils.builtins.js";
|
|
7
7
|
//# sourceMappingURL=index.js.map
|
|
@@ -9,30 +9,109 @@ export interface LoggingContext {
|
|
|
9
9
|
readonly path?: string;
|
|
10
10
|
readonly method?: string;
|
|
11
11
|
}
|
|
12
|
+
/**
|
|
13
|
+
* Make a caller-supplied value safe to write into a single-line log.
|
|
14
|
+
*
|
|
15
|
+
* CR and LF above all are what let a request path forge extra log lines, so
|
|
16
|
+
* control characters are escaped rather than dropped — the original stays
|
|
17
|
+
* visible without being able to break out of its line.
|
|
18
|
+
*/
|
|
19
|
+
export declare function sanitizeLogValue(value: unknown, maxLength?: number): string;
|
|
20
|
+
/** Options for {@link loggingMiddleware}. */
|
|
21
|
+
export interface LoggingOptions {
|
|
22
|
+
/**
|
|
23
|
+
* Include the failing error's message in the completion line.
|
|
24
|
+
* Defaults to `false`: error messages routinely carry connection strings,
|
|
25
|
+
* tokens and user data, and the pipeline already surfaces the error object
|
|
26
|
+
* to the caller.
|
|
27
|
+
*/
|
|
28
|
+
readonly includeErrorMessage?: boolean;
|
|
29
|
+
/** Longest field value written before truncation. Default: 256. */
|
|
30
|
+
readonly maxFieldLength?: number;
|
|
31
|
+
}
|
|
12
32
|
/**
|
|
13
33
|
* Create a logging middleware.
|
|
14
34
|
*
|
|
15
|
-
* Logs request start, completion, and errors.
|
|
35
|
+
* Logs request start, completion, and errors. Every interpolated field is
|
|
36
|
+
* escaped, so a path containing newlines cannot forge log lines.
|
|
16
37
|
*/
|
|
17
|
-
export declare function loggingMiddleware(logger?: (msg: string) => void): NamedMiddleware<LoggingContext>;
|
|
38
|
+
export declare function loggingMiddleware<TResult = void>(logger?: (msg: string) => void, options?: LoggingOptions): NamedMiddleware<LoggingContext, TResult>;
|
|
18
39
|
/**
|
|
19
40
|
* Create an error-handling middleware.
|
|
20
41
|
*
|
|
21
|
-
*
|
|
42
|
+
* Reports errors through `onError` and rethrows them. A reporter that throws
|
|
43
|
+
* cannot replace the error it was reporting — its own failure goes to
|
|
44
|
+
* `onReporterError` instead.
|
|
22
45
|
*/
|
|
23
|
-
export declare function errorMiddleware(onError?: (error: unknown, ctx: unknown) => void): NamedMiddleware<unknown>;
|
|
46
|
+
export declare function errorMiddleware<TResult = void>(onError?: (error: unknown, ctx: unknown) => void, onReporterError?: (error: unknown) => void): NamedMiddleware<unknown, TResult>;
|
|
47
|
+
/** Options for {@link timeoutMiddleware}. */
|
|
48
|
+
export interface TimeoutOptions {
|
|
49
|
+
/** Name reported in the timeout error. Default: `"timeout"`. */
|
|
50
|
+
readonly name?: string;
|
|
51
|
+
}
|
|
24
52
|
/**
|
|
25
53
|
* Create a timeout middleware.
|
|
26
54
|
*
|
|
27
|
-
* Rejects if the
|
|
55
|
+
* Rejects with a {@link MiddlewareTimeoutError} if the rest of the pipeline
|
|
56
|
+
* takes too long. The timer is always cleared, so a fast request leaves
|
|
57
|
+
* nothing pending on the event loop, and the losing promise stays handled so
|
|
58
|
+
* a late rejection cannot surface as an unhandled rejection.
|
|
59
|
+
*
|
|
60
|
+
* The downstream work is not cancelled — nothing in the middleware contract
|
|
61
|
+
* can cancel it. Use the pipeline's `signal` option, or carry an
|
|
62
|
+
* `AbortSignal` on your own context, when the work itself needs to stop.
|
|
28
63
|
*/
|
|
29
|
-
export declare function timeoutMiddleware<TContext>(timeoutMs: number): NamedMiddleware<TContext>;
|
|
64
|
+
export declare function timeoutMiddleware<TContext, TResult = void>(timeoutMs: number, options?: TimeoutOptions): NamedMiddleware<TContext, TResult>;
|
|
65
|
+
/** Options for {@link rateLimitMiddleware}. */
|
|
66
|
+
export interface RateLimitOptions {
|
|
67
|
+
/**
|
|
68
|
+
* Maximum number of distinct keys tracked at once. When the limit is
|
|
69
|
+
* reached, expired keys are swept and — if that is not enough — the
|
|
70
|
+
* least-recently-seen keys are evicted. Default: 10,000.
|
|
71
|
+
*
|
|
72
|
+
* Keys are usually caller-controlled (an IP, an API key, a tenant), so an
|
|
73
|
+
* unbounded map is a memory sink an attacker can drive.
|
|
74
|
+
*/
|
|
75
|
+
readonly maxKeys?: number;
|
|
76
|
+
/** How often expired keys are swept, in milliseconds. Default: 60,000. */
|
|
77
|
+
readonly sweepIntervalMs?: number;
|
|
78
|
+
/**
|
|
79
|
+
* Reject requests that arrive with no key instead of pooling them into a
|
|
80
|
+
* single shared bucket. Default: `false`, preserving the shared bucket.
|
|
81
|
+
*/
|
|
82
|
+
readonly rejectUnkeyed?: boolean;
|
|
83
|
+
}
|
|
84
|
+
/** A rate limiter's live view of one key. */
|
|
85
|
+
export interface RateLimitState {
|
|
86
|
+
/** Requests counted in the current window. */
|
|
87
|
+
readonly count: number;
|
|
88
|
+
/** Milliseconds until the oldest request leaves the window. */
|
|
89
|
+
readonly retryAfterMs: number;
|
|
90
|
+
}
|
|
91
|
+
/** The middleware returned by {@link rateLimitMiddleware}. */
|
|
92
|
+
export interface RateLimitMiddleware<TContext, TResult = void> extends NamedMiddleware<TContext, TResult> {
|
|
93
|
+
/** Inspect a key's current window. Exposed for metrics and tests. */
|
|
94
|
+
readonly inspect: (key: string) => RateLimitState | undefined;
|
|
95
|
+
/** Number of keys currently tracked. */
|
|
96
|
+
readonly size: () => number;
|
|
97
|
+
/** Drop all tracked keys. */
|
|
98
|
+
readonly reset: () => void;
|
|
99
|
+
}
|
|
30
100
|
/**
|
|
31
101
|
* Create a rate-limiting middleware.
|
|
32
102
|
*
|
|
33
|
-
*
|
|
103
|
+
* Implements a true sliding window: each key keeps the timestamps of its
|
|
104
|
+
* requests within `windowMs`, so a client cannot burst `2 × maxRequests`
|
|
105
|
+
* across a window boundary the way a fixed-window counter allows.
|
|
106
|
+
*
|
|
107
|
+
* Rejections throw {@link MiddlewareRateLimitError}, which carries
|
|
108
|
+
* `retryAfterMs` for an HTTP adapter to turn into a 429 with `Retry-After`.
|
|
109
|
+
*
|
|
110
|
+
* State is per-instance and in-process. Behind more than one instance of a
|
|
111
|
+
* service, each process enforces its own limit; use a shared store for a
|
|
112
|
+
* cluster-wide one.
|
|
34
113
|
*/
|
|
35
114
|
export declare function rateLimitMiddleware<TContext extends {
|
|
36
115
|
readonly key?: string;
|
|
37
|
-
}>(maxRequests: number, windowMs: number):
|
|
116
|
+
}, TResult = void>(maxRequests: number, windowMs: number, options?: RateLimitOptions): RateLimitMiddleware<TContext, TResult>;
|
|
38
117
|
//# sourceMappingURL=middlewareUtils.builtins.d.ts.map
|