@zudojs/middleware 0.1.0 → 1.0.1
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 +187 -28
- package/package.json +24 -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
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Zudojs Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @zudojs/middleware
|
|
2
2
|
|
|
3
|
-
Composable middleware pipeline with composition,
|
|
3
|
+
Composable middleware pipeline with composition, priority ordering, execution tracking, error handling, and built-in middleware.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -12,28 +12,175 @@ npm install @zudojs/middleware
|
|
|
12
12
|
|
|
13
13
|
```typescript
|
|
14
14
|
import {
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
createPipeline,
|
|
16
|
+
timeoutMiddleware,
|
|
17
|
+
loggingMiddleware,
|
|
18
|
+
rateLimitMiddleware,
|
|
17
19
|
} from "@zudojs/middleware";
|
|
18
20
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
interface RequestContext {
|
|
22
|
+
readonly method?: string;
|
|
23
|
+
readonly path?: string;
|
|
24
|
+
readonly key?: string;
|
|
25
|
+
}
|
|
24
26
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
+
// The handler runs after every middleware has called next().
|
|
28
|
+
const pipeline = createPipeline<RequestContext, string>(
|
|
29
|
+
[
|
|
30
|
+
timeoutMiddleware(5_000),
|
|
31
|
+
loggingMiddleware(),
|
|
32
|
+
rateLimitMiddleware(100, 60_000),
|
|
33
|
+
],
|
|
34
|
+
async (context) => `handled ${context.method} ${context.path}`,
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
const outcome = await pipeline({
|
|
38
|
+
method: "GET",
|
|
39
|
+
path: "/users",
|
|
40
|
+
key: "client-1",
|
|
27
41
|
});
|
|
42
|
+
|
|
43
|
+
if (outcome.success) {
|
|
44
|
+
console.log(outcome.result); // "handled GET /users"
|
|
45
|
+
} else {
|
|
46
|
+
console.error(outcome.error);
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
`PipelineResult` is a discriminated union: narrowing on `success` gives you a
|
|
51
|
+
non-optional `result`, so no `!` is needed on the happy path.
|
|
52
|
+
|
|
53
|
+
## Composition without the result wrapper
|
|
54
|
+
|
|
55
|
+
`compose` chains middleware around a handler and returns the handler's value
|
|
56
|
+
directly. Use it when you want the raw function rather than a
|
|
57
|
+
`PipelineResult`.
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
import { compose } from "@zudojs/middleware";
|
|
61
|
+
import type { Middleware } from "@zudojs/middleware";
|
|
62
|
+
|
|
63
|
+
const auth: Middleware<RequestContext, string> = async (context, next) => {
|
|
64
|
+
if (context.key === undefined) throw new Error("unauthenticated");
|
|
65
|
+
return next();
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const handle = compose([auth], async (context) => `hello ${context.key}`);
|
|
69
|
+
await handle({ key: "client-1" });
|
|
28
70
|
```
|
|
29
71
|
|
|
30
|
-
##
|
|
72
|
+
## Priority and toggling
|
|
73
|
+
|
|
74
|
+
`NamedMiddleware` carries a name, a numeric `priority` (lower runs earlier,
|
|
75
|
+
default 100) and an `enabled` flag. `createPipeline` filters and orders them
|
|
76
|
+
for you, and reports which ones ran.
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
const pipeline = createPipeline(
|
|
80
|
+
[
|
|
81
|
+
{ name: "auth", handler: auth, priority: 10 },
|
|
82
|
+
{ name: "audit", handler: audit, priority: 90, enabled: false },
|
|
83
|
+
],
|
|
84
|
+
handler,
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
const outcome = await pipeline(context);
|
|
88
|
+
outcome.executedMiddleware; // ["auth"] — "audit" is disabled
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Ordering is stable, so middleware sharing a priority keeps its declared order.
|
|
92
|
+
|
|
93
|
+
## Error handling
|
|
94
|
+
|
|
95
|
+
`errorMode` decides what a failure does:
|
|
96
|
+
|
|
97
|
+
| Mode | Behaviour |
|
|
98
|
+
| --------------------- | ----------------------------------------------------------------------------------------------------- |
|
|
99
|
+
| `"capture"` (default) | Stop and return `{ success: false, error }`. |
|
|
100
|
+
| `"throw"` | Stop and let the error propagate to the caller. |
|
|
101
|
+
| `"continue"` | Record the failure, skip past the failing middleware, keep going. A handler failure is never skipped. |
|
|
102
|
+
|
|
103
|
+
Every failure is also collected in `outcome.errors`, each tagged with the name
|
|
104
|
+
of the middleware that threw (or `"handler"`).
|
|
105
|
+
|
|
106
|
+
> **`"continue"` is not for security middleware.** Stepping past a middleware
|
|
107
|
+
> that threw means an authentication or authorization step that failed is
|
|
108
|
+
> stepped past too, and the handler still runs. A run can come back
|
|
109
|
+
> `success: true` with entries in `outcome.errors`, so under this mode check
|
|
110
|
+
> `outcome.errors` as well as `outcome.success`. Use `"capture"` (the default)
|
|
111
|
+
> or `"throw"` for any chain where a failing step must stop the request.
|
|
112
|
+
|
|
113
|
+
A middleware that catches a downstream failure and throws its own error in its
|
|
114
|
+
place ends the run: there is no downstream result left to keep, so the
|
|
115
|
+
pipeline reports the failure rather than a result that never existed.
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
const pipeline = createPipeline(list, handler, { errorMode: "continue" });
|
|
119
|
+
const outcome = await pipeline(context);
|
|
120
|
+
outcome.errors; // [{ name: "audit", error: … }]
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Cancellation
|
|
124
|
+
|
|
125
|
+
Pass an `AbortSignal` to stop a pipeline between steps. The signal is checked
|
|
126
|
+
before each middleware and before the handler; an aborted run fails with a
|
|
127
|
+
`MiddlewareAbortedError`.
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
const controller = new AbortController();
|
|
131
|
+
const pipeline = createPipeline(list, handler, { signal: controller.signal });
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Built-in middleware
|
|
135
|
+
|
|
136
|
+
- **`loggingMiddleware(logger?, options?)`** — logs start and completion.
|
|
137
|
+
Every interpolated field is escaped, so a path containing newlines cannot
|
|
138
|
+
forge log lines. Error messages are omitted unless you opt in with
|
|
139
|
+
`includeErrorMessage`.
|
|
140
|
+
- **`errorMiddleware(onError?, onReporterError?)`** — reports errors and
|
|
141
|
+
rethrows them. A reporter that throws cannot replace the original error.
|
|
142
|
+
- **`timeoutMiddleware(timeoutMs, options?)`** — fails with
|
|
143
|
+
`MiddlewareTimeoutError`. The timer is always cleared, so a fast request
|
|
144
|
+
leaves nothing pending on the event loop. `options.name` names the
|
|
145
|
+
middleware itself, so two timeouts in one pipeline are distinguishable in
|
|
146
|
+
`executedMiddleware` and in `outcome.errors`.
|
|
147
|
+
- **`rateLimitMiddleware(maxRequests, windowMs, options?)`** — a true sliding
|
|
148
|
+
window keyed on `context.key`, with key eviction and a configurable cap.
|
|
149
|
+
Rejections throw `MiddlewareRateLimitError`, which carries `retryAfterMs`
|
|
150
|
+
for a `Retry-After` header.
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
import { MiddlewareRateLimitError } from "@zudojs/middleware";
|
|
154
|
+
|
|
155
|
+
if (outcome.error instanceof MiddlewareRateLimitError) {
|
|
156
|
+
respond(429, { retryAfterMs: outcome.error.retryAfterMs });
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Rate-limit state is per-instance and in-process: behind more than one replica,
|
|
161
|
+
each process enforces its own limit.
|
|
162
|
+
|
|
163
|
+
## Timing
|
|
164
|
+
|
|
165
|
+
`withTiming` wraps any middleware and reports slow executions. It is
|
|
166
|
+
transparent — the return value and any error pass through unchanged.
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
const timed = withTiming("db-lookup", lookup, {
|
|
170
|
+
thresholdMs: 50,
|
|
171
|
+
logger: (message) => logger.warn(message),
|
|
172
|
+
});
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## Errors
|
|
176
|
+
|
|
177
|
+
All errors extend `MiddlewareError` (itself a `BaseError` from
|
|
178
|
+
`@zudojs/errors`), so a single `instanceof` catches everything from this
|
|
179
|
+
package:
|
|
31
180
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
- Context propagation
|
|
36
|
-
- Early termination support
|
|
181
|
+
`MiddlewareTimeoutError` · `MiddlewareNextCalledMultipleTimesError` ·
|
|
182
|
+
`MiddlewareLimitExceededError` · `MiddlewareDepthExceededError` ·
|
|
183
|
+
`MiddlewareRateLimitError` · `MiddlewareAbortedError`
|
|
37
184
|
|
|
38
185
|
## Use Cases
|
|
39
186
|
|
|
@@ -3,5 +3,5 @@
|
|
|
3
3
|
*
|
|
4
4
|
* @module middlewareCore
|
|
5
5
|
*/
|
|
6
|
-
export { compose, resolveMiddleware, withTiming, } from "./middlewareCore.compose.js";
|
|
6
|
+
export { compose, resolveMiddleware, resolveNamedMiddleware, withTiming, MAX_DEPTH, type ComposeOptions, type TimingOptions, } from "./middlewareCore.compose.js";
|
|
7
7
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -3,5 +3,5 @@
|
|
|
3
3
|
*
|
|
4
4
|
* @module middlewareCore
|
|
5
5
|
*/
|
|
6
|
-
export { compose, resolveMiddleware, withTiming, } from "./middlewareCore.compose.js";
|
|
6
|
+
export { compose, resolveMiddleware, resolveNamedMiddleware, withTiming, MAX_DEPTH, } from "./middlewareCore.compose.js";
|
|
7
7
|
//# sourceMappingURL=index.js.map
|
|
@@ -7,6 +7,13 @@
|
|
|
7
7
|
* Each middleware calls `next()` to proceed to the next one.
|
|
8
8
|
*/
|
|
9
9
|
import type { Middleware, NamedMiddleware } from "../middlewareTypes/middlewareDefinition.type.js";
|
|
10
|
+
/** Default ceiling on how deeply a composed chain may nest. */
|
|
11
|
+
export declare const MAX_DEPTH = 100;
|
|
12
|
+
/** Options for {@link compose}. */
|
|
13
|
+
export interface ComposeOptions {
|
|
14
|
+
/** Maximum chain depth. Default: {@link MAX_DEPTH}. */
|
|
15
|
+
readonly maxDepth?: number;
|
|
16
|
+
}
|
|
10
17
|
/**
|
|
11
18
|
* Compose an array of middleware into a single function.
|
|
12
19
|
*
|
|
@@ -15,18 +22,43 @@ import type { Middleware, NamedMiddleware } from "../middlewareTypes/middlewareD
|
|
|
15
22
|
*
|
|
16
23
|
* @param middlewareList - Array of middleware functions
|
|
17
24
|
* @param handler - The final handler to execute after all middleware
|
|
25
|
+
* @param options - Composition limits
|
|
18
26
|
* @returns Composed function
|
|
27
|
+
* @throws MiddlewareDepthExceededError if the chain is longer than `maxDepth`
|
|
19
28
|
*/
|
|
20
|
-
export declare function compose<TContext, TResult>(middlewareList: readonly Middleware<TContext, TResult>[], handler: (context: TContext) => Promise<TResult
|
|
29
|
+
export declare function compose<TContext, TResult>(middlewareList: readonly Middleware<TContext, TResult>[], handler: (context: TContext) => Promise<TResult>, options?: ComposeOptions): (context: TContext) => Promise<TResult>;
|
|
21
30
|
/**
|
|
22
|
-
*
|
|
31
|
+
* Filter disabled middleware and sort the rest by priority.
|
|
32
|
+
*
|
|
33
|
+
* Ordering is stable, so middleware sharing a priority keeps its input order.
|
|
34
|
+
*
|
|
35
|
+
* @param middlewareList - Array of named middleware
|
|
36
|
+
* @returns Enabled middleware, in execution order, with names intact
|
|
37
|
+
*/
|
|
38
|
+
export declare function resolveNamedMiddleware<TContext, TResult>(middlewareList: readonly NamedMiddleware<TContext, TResult>[]): NamedMiddleware<TContext, TResult>[];
|
|
39
|
+
/**
|
|
40
|
+
* Sort and filter named middleware by priority, returning bare handlers.
|
|
41
|
+
*
|
|
42
|
+
* Prefer {@link resolveNamedMiddleware} when the names are needed — this is a
|
|
43
|
+
* thin projection of it.
|
|
23
44
|
*
|
|
24
45
|
* @param middlewareList - Array of named middleware
|
|
25
46
|
* @returns Sorted and filtered array of middleware handler functions
|
|
26
47
|
*/
|
|
27
48
|
export declare function resolveMiddleware<TContext, TResult>(middlewareList: readonly NamedMiddleware<TContext, TResult>[]): Middleware<TContext, TResult>[];
|
|
49
|
+
/** Options for {@link withTiming}. */
|
|
50
|
+
export interface TimingOptions {
|
|
51
|
+
/** Log only when the middleware takes at least this long. Default: 100ms. */
|
|
52
|
+
readonly thresholdMs?: number;
|
|
53
|
+
/** Where to report slow middleware. Default: `console.warn`. */
|
|
54
|
+
readonly logger?: (message: string) => void;
|
|
55
|
+
}
|
|
28
56
|
/**
|
|
29
|
-
*
|
|
57
|
+
* Wrap a middleware so that slow executions are reported.
|
|
58
|
+
*
|
|
59
|
+
* The wrapper is transparent: the inner middleware's return value and any
|
|
60
|
+
* error it throws pass through unchanged, and the timing is reported either
|
|
61
|
+
* way.
|
|
30
62
|
*/
|
|
31
|
-
export declare function withTiming<TContext>(name: string, middleware: Middleware<TContext,
|
|
63
|
+
export declare function withTiming<TContext, TResult = void>(name: string, middleware: Middleware<TContext, TResult>, options?: TimingOptions): NamedMiddleware<TContext, TResult>;
|
|
32
64
|
//# sourceMappingURL=middlewareCore.compose.d.ts.map
|
|
@@ -6,7 +6,9 @@
|
|
|
6
6
|
* Middleware executes in order (first added = first executed).
|
|
7
7
|
* Each middleware calls `next()` to proceed to the next one.
|
|
8
8
|
*/
|
|
9
|
-
|
|
9
|
+
import { MiddlewareDepthExceededError, MiddlewareNextCalledMultipleTimesError, } from "../middlewareErrors/middlewareError.base.js";
|
|
10
|
+
/** Default ceiling on how deeply a composed chain may nest. */
|
|
11
|
+
export const MAX_DEPTH = 100;
|
|
10
12
|
/**
|
|
11
13
|
* Compose an array of middleware into a single function.
|
|
12
14
|
*
|
|
@@ -15,9 +17,15 @@ const MAX_DEPTH = 100;
|
|
|
15
17
|
*
|
|
16
18
|
* @param middlewareList - Array of middleware functions
|
|
17
19
|
* @param handler - The final handler to execute after all middleware
|
|
20
|
+
* @param options - Composition limits
|
|
18
21
|
* @returns Composed function
|
|
22
|
+
* @throws MiddlewareDepthExceededError if the chain is longer than `maxDepth`
|
|
19
23
|
*/
|
|
20
|
-
export function compose(middlewareList, handler) {
|
|
24
|
+
export function compose(middlewareList, handler, options) {
|
|
25
|
+
const maxDepth = options?.maxDepth ?? MAX_DEPTH;
|
|
26
|
+
if (middlewareList.length > maxDepth) {
|
|
27
|
+
throw new MiddlewareDepthExceededError(maxDepth);
|
|
28
|
+
}
|
|
21
29
|
if (middlewareList.length === 0) {
|
|
22
30
|
return handler;
|
|
23
31
|
}
|
|
@@ -25,7 +33,12 @@ export function compose(middlewareList, handler) {
|
|
|
25
33
|
let index = -1;
|
|
26
34
|
async function dispatch(i) {
|
|
27
35
|
if (i <= index) {
|
|
28
|
-
|
|
36
|
+
// dispatch(i) is invoked by the middleware at i - 1, so that is the
|
|
37
|
+
// one that called next() again.
|
|
38
|
+
throw new MiddlewareNextCalledMultipleTimesError(`middleware[${i - 1}]`);
|
|
39
|
+
}
|
|
40
|
+
if (i > maxDepth) {
|
|
41
|
+
throw new MiddlewareDepthExceededError(maxDepth);
|
|
29
42
|
}
|
|
30
43
|
index = i;
|
|
31
44
|
if (i < middlewareList.length) {
|
|
@@ -38,29 +51,55 @@ export function compose(middlewareList, handler) {
|
|
|
38
51
|
};
|
|
39
52
|
}
|
|
40
53
|
/**
|
|
41
|
-
*
|
|
54
|
+
* Filter disabled middleware and sort the rest by priority.
|
|
55
|
+
*
|
|
56
|
+
* Ordering is stable, so middleware sharing a priority keeps its input order.
|
|
42
57
|
*
|
|
43
58
|
* @param middlewareList - Array of named middleware
|
|
44
|
-
* @returns
|
|
59
|
+
* @returns Enabled middleware, in execution order, with names intact
|
|
45
60
|
*/
|
|
46
|
-
export function
|
|
61
|
+
export function resolveNamedMiddleware(middlewareList) {
|
|
47
62
|
return middlewareList
|
|
48
63
|
.filter((mw) => mw.enabled !== false)
|
|
49
|
-
.sort((a, b) => (a.priority ?? 100) - (b.priority ?? 100))
|
|
50
|
-
.map((mw) => mw.handler);
|
|
64
|
+
.sort((a, b) => (a.priority ?? 100) - (b.priority ?? 100));
|
|
51
65
|
}
|
|
52
66
|
/**
|
|
53
|
-
*
|
|
67
|
+
* Sort and filter named middleware by priority, returning bare handlers.
|
|
68
|
+
*
|
|
69
|
+
* Prefer {@link resolveNamedMiddleware} when the names are needed — this is a
|
|
70
|
+
* thin projection of it.
|
|
71
|
+
*
|
|
72
|
+
* @param middlewareList - Array of named middleware
|
|
73
|
+
* @returns Sorted and filtered array of middleware handler functions
|
|
54
74
|
*/
|
|
55
|
-
export function
|
|
75
|
+
export function resolveMiddleware(middlewareList) {
|
|
76
|
+
return resolveNamedMiddleware(middlewareList).map((mw) => mw.handler);
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Wrap a middleware so that slow executions are reported.
|
|
80
|
+
*
|
|
81
|
+
* The wrapper is transparent: the inner middleware's return value and any
|
|
82
|
+
* error it throws pass through unchanged, and the timing is reported either
|
|
83
|
+
* way.
|
|
84
|
+
*/
|
|
85
|
+
export function withTiming(name, middleware, options) {
|
|
86
|
+
const thresholdMs = options?.thresholdMs ?? 100;
|
|
87
|
+
const log = options?.logger ??
|
|
88
|
+
((message) => {
|
|
89
|
+
console.warn(message);
|
|
90
|
+
});
|
|
56
91
|
return {
|
|
57
92
|
name,
|
|
58
93
|
handler: async (ctx, next) => {
|
|
59
94
|
const start = performance.now();
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
95
|
+
try {
|
|
96
|
+
return await middleware(ctx, next);
|
|
97
|
+
}
|
|
98
|
+
finally {
|
|
99
|
+
const duration = performance.now() - start;
|
|
100
|
+
if (duration >= thresholdMs) {
|
|
101
|
+
log(`[middleware] ${name} took ${duration.toFixed(1)}ms`);
|
|
102
|
+
}
|
|
64
103
|
}
|
|
65
104
|
},
|
|
66
105
|
};
|
|
@@ -3,5 +3,5 @@
|
|
|
3
3
|
*
|
|
4
4
|
* @module middlewareErrors
|
|
5
5
|
*/
|
|
6
|
-
export { MiddlewareError, MiddlewareTimeoutError, MiddlewareNextCalledMultipleTimesError, } from "./middlewareError.base.js";
|
|
6
|
+
export { MiddlewareError, MiddlewareTimeoutError, MiddlewareNextCalledMultipleTimesError, MiddlewareLimitExceededError, MiddlewareDepthExceededError, MiddlewareRateLimitError, MiddlewareAbortedError, } from "./middlewareError.base.js";
|
|
7
7
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -3,5 +3,5 @@
|
|
|
3
3
|
*
|
|
4
4
|
* @module middlewareErrors
|
|
5
5
|
*/
|
|
6
|
-
export { MiddlewareError, MiddlewareTimeoutError, MiddlewareNextCalledMultipleTimesError, } from "./middlewareError.base.js";
|
|
6
|
+
export { MiddlewareError, MiddlewareTimeoutError, MiddlewareNextCalledMultipleTimesError, MiddlewareLimitExceededError, MiddlewareDepthExceededError, MiddlewareRateLimitError, MiddlewareAbortedError, } from "./middlewareError.base.js";
|
|
7
7
|
//# sourceMappingURL=index.js.map
|
|
@@ -25,4 +25,34 @@ export declare class MiddlewareTimeoutError extends MiddlewareError {
|
|
|
25
25
|
export declare class MiddlewareNextCalledMultipleTimesError extends MiddlewareError {
|
|
26
26
|
constructor(middlewareName: string);
|
|
27
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* Error thrown when a pipeline is configured with more middleware than allowed.
|
|
30
|
+
*/
|
|
31
|
+
export declare class MiddlewareLimitExceededError extends MiddlewareError {
|
|
32
|
+
constructor(count: number, maximum: number);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Error thrown when the middleware chain nests deeper than the allowed limit.
|
|
36
|
+
*/
|
|
37
|
+
export declare class MiddlewareDepthExceededError extends MiddlewareError {
|
|
38
|
+
constructor(maxDepth: number);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Error thrown when a rate limit is exceeded.
|
|
42
|
+
*
|
|
43
|
+
* `retryAfterMs` tells the caller how long to wait before retrying, which is
|
|
44
|
+
* what an HTTP adapter needs to emit a `Retry-After` header alongside a 429.
|
|
45
|
+
*/
|
|
46
|
+
export declare class MiddlewareRateLimitError extends MiddlewareError {
|
|
47
|
+
readonly retryAfterMs: number;
|
|
48
|
+
readonly limit: number;
|
|
49
|
+
readonly windowMs: number;
|
|
50
|
+
constructor(limit: number, windowMs: number, retryAfterMs: number);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Error thrown when a pipeline is aborted through its `AbortSignal`.
|
|
54
|
+
*/
|
|
55
|
+
export declare class MiddlewareAbortedError extends MiddlewareError {
|
|
56
|
+
constructor(reason?: unknown);
|
|
57
|
+
}
|
|
28
58
|
//# sourceMappingURL=middlewareError.base.d.ts.map
|
|
@@ -40,4 +40,47 @@ export class MiddlewareNextCalledMultipleTimesError extends MiddlewareError {
|
|
|
40
40
|
});
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* Error thrown when a pipeline is configured with more middleware than allowed.
|
|
45
|
+
*/
|
|
46
|
+
export class MiddlewareLimitExceededError extends MiddlewareError {
|
|
47
|
+
constructor(count, maximum) {
|
|
48
|
+
super(`Pipeline has ${count} middleware, exceeding the maximum of ${maximum}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Error thrown when the middleware chain nests deeper than the allowed limit.
|
|
53
|
+
*/
|
|
54
|
+
export class MiddlewareDepthExceededError extends MiddlewareError {
|
|
55
|
+
constructor(maxDepth) {
|
|
56
|
+
super(`Middleware chain exceeded the maximum depth of ${maxDepth}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Error thrown when a rate limit is exceeded.
|
|
61
|
+
*
|
|
62
|
+
* `retryAfterMs` tells the caller how long to wait before retrying, which is
|
|
63
|
+
* what an HTTP adapter needs to emit a `Retry-After` header alongside a 429.
|
|
64
|
+
*/
|
|
65
|
+
export class MiddlewareRateLimitError extends MiddlewareError {
|
|
66
|
+
retryAfterMs;
|
|
67
|
+
limit;
|
|
68
|
+
windowMs;
|
|
69
|
+
constructor(limit, windowMs, retryAfterMs) {
|
|
70
|
+
super(`Rate limit exceeded: ${limit} requests per ${windowMs}ms`, {
|
|
71
|
+
middlewareName: "rate-limit",
|
|
72
|
+
});
|
|
73
|
+
this.retryAfterMs = retryAfterMs;
|
|
74
|
+
this.limit = limit;
|
|
75
|
+
this.windowMs = windowMs;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Error thrown when a pipeline is aborted through its `AbortSignal`.
|
|
80
|
+
*/
|
|
81
|
+
export class MiddlewareAbortedError extends MiddlewareError {
|
|
82
|
+
constructor(reason) {
|
|
83
|
+
super("Middleware pipeline aborted", { cause: reason });
|
|
84
|
+
}
|
|
85
|
+
}
|
|
43
86
|
//# sourceMappingURL=middlewareError.base.js.map
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* @module middlewarePipeline/middlewarePipeline
|
|
5
5
|
*/
|
|
6
|
-
import type { NamedMiddleware } from "../middlewareTypes/middlewareDefinition.type.js";
|
|
7
6
|
import type { PipelineResult, PipelineOptions } from "../middlewareTypes/middlewareContext.type.js";
|
|
7
|
+
import type { NamedMiddleware } from "../middlewareTypes/middlewareDefinition.type.js";
|
|
8
8
|
/**
|
|
9
9
|
* Create a middleware pipeline that tracks execution.
|
|
10
10
|
*
|
|
@@ -12,6 +12,8 @@ import type { PipelineResult, PipelineOptions } from "../middlewareTypes/middlew
|
|
|
12
12
|
* @param handler - Final handler function
|
|
13
13
|
* @param options - Pipeline configuration
|
|
14
14
|
* @returns Pipeline execution function
|
|
15
|
+
* @throws MiddlewareLimitExceededError if more middleware are enabled than
|
|
16
|
+
* `maxMiddleware` allows
|
|
15
17
|
*/
|
|
16
18
|
export declare function createPipeline<TContext, TResult>(middlewareList: readonly NamedMiddleware<TContext, TResult>[], handler: (context: TContext) => Promise<TResult>, options?: PipelineOptions): (context: TContext) => Promise<PipelineResult<TResult>>;
|
|
17
19
|
//# sourceMappingURL=middlewarePipeline.core.d.ts.map
|