Skip to content

Commit a476c94

Browse files
authored
Merge branch 'main' into dependabot/npm_and_yarn/main/biomejs/biome-2.2.3
2 parents a26d246 + fc87eb3 commit a476c94

File tree

4 files changed

+466
-3
lines changed

4 files changed

+466
-3
lines changed

packages/event-handler/src/rest/BaseRouter.ts

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import type {
1010
ErrorHandler,
1111
ErrorResolveOptions,
1212
HttpMethod,
13+
Middleware,
1314
Path,
1415
RouteHandler,
1516
RouteOptions,
@@ -30,13 +31,18 @@ import {
3031
} from './errors.js';
3132
import { Route } from './Route.js';
3233
import { RouteHandlerRegistry } from './RouteHandlerRegistry.js';
33-
import { isAPIGatewayProxyEvent, isHttpMethod } from './utils.js';
34+
import {
35+
composeMiddleware,
36+
isAPIGatewayProxyEvent,
37+
isHttpMethod,
38+
} from './utils.js';
3439

3540
abstract class BaseRouter {
3641
protected context: Record<string, unknown>;
3742

3843
protected readonly routeRegistry: RouteHandlerRegistry;
3944
protected readonly errorHandlerRegistry: ErrorHandlerRegistry;
45+
protected readonly middlwares: Middleware[] = [];
4046

4147
/**
4248
* A logger instance to be used for logging debug, warning, and error messages.
@@ -140,6 +146,33 @@ abstract class BaseRouter {
140146
};
141147
}
142148

149+
/**
150+
* Registers a global middleware function that will be executed for all routes.
151+
*
152+
* Global middleware executes before route-specific middleware and follows the onion model
153+
* where middleware executes in registration order before `next()` and in reverse order after `next()`.
154+
*
155+
* @param middleware - The middleware function to register globally
156+
*
157+
* @example
158+
* ```typescript
159+
* const authMiddleware: Middleware = async (params, options, next) => {
160+
* // Authentication logic
161+
* if (!isAuthenticated(options.request)) {
162+
* return new Response('Unauthorized', { status: 401 });
163+
* }
164+
* await next();
165+
* // Cleanup or logging after request completion
166+
* console.log('Request completed');
167+
* };
168+
*
169+
* router.use(authMiddleware);
170+
* ```
171+
*/
172+
public use(middleware: Middleware): void {
173+
this.middlwares.push(middleware);
174+
}
175+
143176
/**
144177
* Resolves an API Gateway event by routing it to the appropriate handler
145178
* and converting the result to an API Gateway proxy result. Handles errors
@@ -185,14 +218,28 @@ abstract class BaseRouter {
185218
throw new NotFoundError(`Route ${path} for method ${method} not found`);
186219
}
187220

188-
const result = await route.handler.apply(options?.scope ?? this, [
221+
const handler =
222+
options?.scope != null
223+
? route.handler.bind(options.scope)
224+
: route.handler;
225+
226+
const middleware = composeMiddleware([...this.middlwares]);
227+
228+
const result = await middleware(
189229
route.params,
190230
{
191231
event,
192232
context,
193233
request,
194234
},
195-
]);
235+
() => handler(route.params, { event, context, request })
236+
);
237+
238+
// In practice this we never happen because the final 'middleware' is
239+
// the handler function that allways returns HandlerResponse. However, the
240+
// type signature of of NextFunction includes undefined so we need this for
241+
// the TS compiler
242+
if (result === undefined) throw new InternalServerError();
196243

197244
return await handlerResultToProxyResult(result);
198245
} catch (error) {

packages/event-handler/src/rest/utils.ts

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@ import { isRecord, isString } from '@aws-lambda-powertools/commons/typeutils';
22
import type { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
33
import type {
44
CompiledRoute,
5+
HandlerResponse,
56
HttpMethod,
7+
Middleware,
68
Path,
9+
RequestOptions,
710
ValidationResult,
811
} from '../types/rest.js';
912
import {
@@ -105,3 +108,73 @@ export const isAPIGatewayProxyResult = (
105108
typeof result.isBase64Encoded === 'boolean')
106109
);
107110
};
111+
112+
/**
113+
* Composes multiple middleware functions into a single middleware function.
114+
*
115+
* Middleware functions are executed in order, with each middleware having the ability
116+
* to call `next()` to proceed to the next middleware in the chain. The composed middleware
117+
* follows the onion model where middleware executes in order before `next()` and in
118+
* reverse order after `next()`.
119+
*
120+
* @param middlewares - Array of middleware functions to compose
121+
* @returns A single middleware function that executes all provided middlewares in sequence
122+
*
123+
* @example
124+
* ```typescript
125+
* const middleware1: Middleware = async (params, options, next) => {
126+
* console.log('middleware1 start');
127+
* await next();
128+
* console.log('middleware1 end');
129+
* };
130+
*
131+
* const middleware2: Middleware = async (params, options, next) => {
132+
* console.log('middleware2 start');
133+
* await next();
134+
* console.log('middleware2 end');
135+
* };
136+
*
137+
* const composed: Middleware = composeMiddleware([middleware1, middleware2]);
138+
* // Execution order:
139+
* // middleware1 start
140+
* // -> middleware2 start
141+
* // -> handler
142+
* // -> middleware2 end
143+
* // -> middleware1 end
144+
* ```
145+
*/
146+
export const composeMiddleware = (middlewares: Middleware[]): Middleware => {
147+
return async (
148+
params: Record<string, string>,
149+
options: RequestOptions,
150+
next: () => Promise<HandlerResponse | void>
151+
): Promise<HandlerResponse | void> => {
152+
let index = -1;
153+
let result: HandlerResponse | undefined;
154+
155+
const dispatch = async (i: number): Promise<void> => {
156+
if (i <= index) throw new Error('next() called multiple times');
157+
index = i;
158+
159+
if (i === middlewares.length) {
160+
const nextResult = await next();
161+
if (nextResult !== undefined) {
162+
result = nextResult;
163+
}
164+
return;
165+
}
166+
167+
const middleware = middlewares[i];
168+
const middlewareResult = await middleware(params, options, () =>
169+
dispatch(i + 1)
170+
);
171+
172+
if (middlewareResult !== undefined) {
173+
result = middlewareResult;
174+
}
175+
};
176+
177+
await dispatch(0);
178+
return result;
179+
};
180+
};

packages/event-handler/src/types/rest.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,14 @@ type RouteOptions = {
7777
path: Path;
7878
};
7979

80+
type NextFunction = () => Promise<HandlerResponse | void>;
81+
82+
type Middleware = (
83+
params: Record<string, string>,
84+
options: RequestOptions,
85+
next: NextFunction
86+
) => Promise<void | HandlerResponse>;
87+
8088
type RouteRegistryOptions = {
8189
/**
8290
* A logger instance to be used for logging debug, warning, and error messages.
@@ -111,6 +119,7 @@ export type {
111119
HandlerResponse,
112120
HttpStatusCode,
113121
HttpMethod,
122+
Middleware,
114123
Path,
115124
RequestOptions,
116125
RouterOptions,

0 commit comments

Comments
 (0)