1
+ import { Readable } from 'node:stream' ;
2
+ import { pipeline } from 'node:stream/promises' ;
3
+ import type streamWeb from 'node:stream/web' ;
1
4
import type { GenericLogger } from '@aws-lambda-powertools/commons/types' ;
2
5
import {
3
6
getStringFromEnv ,
4
7
isDevMode ,
5
8
} from '@aws-lambda-powertools/commons/utils/env' ;
6
9
import type { APIGatewayProxyResult , Context } from 'aws-lambda' ;
7
- import type {
8
- HandlerResponse ,
9
- HttpStatusCode ,
10
- ResolveOptions ,
11
- } from '../types/index.js' ;
10
+ import type { ResolveStreamOptions } from '../types/common.js' ;
11
+ import type { HandlerResponse , ResolveOptions } from '../types/index.js' ;
12
12
import type {
13
13
ErrorConstructor ,
14
14
ErrorHandler ,
@@ -17,6 +17,7 @@ import type {
17
17
Middleware ,
18
18
Path ,
19
19
RequestContext ,
20
+ ResponseStream ,
20
21
RestRouteOptions ,
21
22
RestRouterOptions ,
22
23
RouteHandler ,
@@ -26,6 +27,7 @@ import {
26
27
handlerResultToProxyResult ,
27
28
handlerResultToWebResponse ,
28
29
proxyEventToWebRequest ,
30
+ webHeadersToApiGatewayV1Headers ,
29
31
} from './converters.js' ;
30
32
import { ErrorHandlerRegistry } from './ErrorHandlerRegistry.js' ;
31
33
import {
@@ -38,8 +40,9 @@ import { Route } from './Route.js';
38
40
import { RouteHandlerRegistry } from './RouteHandlerRegistry.js' ;
39
41
import {
40
42
composeMiddleware ,
43
+ HttpResponseStream ,
41
44
isAPIGatewayProxyEvent ,
42
- isAPIGatewayProxyResult ,
45
+ isExtendedAPIGatewayProxyResult ,
43
46
isHttpMethod ,
44
47
resolvePrefixedPath ,
45
48
} from './utils.js' ;
@@ -187,21 +190,19 @@ class Router {
187
190
}
188
191
189
192
/**
190
- * Resolves an API Gateway event by routing it to the appropriate handler
191
- * and converting the result to an API Gateway proxy result. Handles errors
192
- * using registered error handlers or falls back to default error handling
193
- * (500 Internal Server Error).
193
+ * Core resolution logic shared by both resolve and resolveStream methods.
194
+ * Validates the event, routes to handlers, executes middleware, and handles errors.
194
195
*
195
196
* @param event - The Lambda event to resolve
196
197
* @param context - The Lambda context
197
198
* @param options - Optional resolve options for scope binding
198
- * @returns An API Gateway proxy result
199
+ * @returns A handler response (Response, JSONObject, or ExtendedAPIGatewayProxyResult)
199
200
*/
200
- public async resolve (
201
+ async # resolve(
201
202
event : unknown ,
202
203
context : Context ,
203
204
options ?: ResolveOptions
204
- ) : Promise < APIGatewayProxyResult > {
205
+ ) : Promise < HandlerResponse > {
205
206
if ( ! isAPIGatewayProxyEvent ( event ) ) {
206
207
this . logger . error (
207
208
'Received an event that is not compatible with this resolver'
@@ -276,18 +277,79 @@ class Router {
276
277
} ) ;
277
278
278
279
// middleware result takes precedence to allow short-circuiting
279
- const result = middlewareResult ?? requestContext . res ;
280
-
281
- return handlerResultToProxyResult ( result ) ;
280
+ return middlewareResult ?? requestContext . res ;
282
281
} catch ( error ) {
283
282
this . logger . debug ( `There was an error processing the request: ${ error } ` ) ;
284
- const result = await this . handleError ( error as Error , {
283
+ return this . handleError ( error as Error , {
285
284
...requestContext ,
286
285
scope : options ?. scope ,
287
286
} ) ;
288
- const statusCode =
289
- result instanceof Response ? result . status : result . statusCode ;
290
- return handlerResultToProxyResult ( result , statusCode as HttpStatusCode ) ;
287
+ }
288
+ }
289
+
290
+ /**
291
+ * Resolves an API Gateway event by routing it to the appropriate handler
292
+ * and converting the result to an API Gateway proxy result. Handles errors
293
+ * using registered error handlers or falls back to default error handling
294
+ * (500 Internal Server Error).
295
+ *
296
+ * @param event - The Lambda event to resolve
297
+ * @param context - The Lambda context
298
+ * @param options - Optional resolve options for scope binding
299
+ * @returns An API Gateway proxy result
300
+ */
301
+ public async resolve (
302
+ event : unknown ,
303
+ context : Context ,
304
+ options ?: ResolveOptions
305
+ ) : Promise < APIGatewayProxyResult > {
306
+ const result = await this . #resolve( event , context , options ) ;
307
+ return handlerResultToProxyResult ( result ) ;
308
+ }
309
+
310
+ /**
311
+ * Resolves an API Gateway event by routing it to the appropriate handler
312
+ * and streaming the response directly to the provided response stream.
313
+ * Used for Lambda response streaming.
314
+ *
315
+ * @param event - The Lambda event to resolve
316
+ * @param context - The Lambda context
317
+ * @param options - Stream resolve options including the response stream
318
+ */
319
+ public async resolveStream (
320
+ event : unknown ,
321
+ context : Context ,
322
+ options : ResolveStreamOptions
323
+ ) : Promise < void > {
324
+ const result = await this . #resolve( event , context , options ) ;
325
+ await this . #streamHandlerResponse( result , options . responseStream ) ;
326
+ }
327
+
328
+ /**
329
+ * Streams a handler response to the Lambda response stream.
330
+ * Converts the response to a web response and pipes it through the stream.
331
+ *
332
+ * @param response - The handler response to stream
333
+ * @param responseStream - The Lambda response stream to write to
334
+ */
335
+ async #streamHandlerResponse(
336
+ response : HandlerResponse ,
337
+ responseStream : ResponseStream
338
+ ) {
339
+ const webResponse = handlerResultToWebResponse ( response ) ;
340
+ const { headers } = webHeadersToApiGatewayV1Headers ( webResponse . headers ) ;
341
+ const resStream = HttpResponseStream . from ( responseStream , {
342
+ statusCode : webResponse . status ,
343
+ headers,
344
+ } ) ;
345
+
346
+ if ( webResponse . body ) {
347
+ const nodeStream = Readable . fromWeb (
348
+ webResponse . body as streamWeb . ReadableStream
349
+ ) ;
350
+ await pipeline ( nodeStream , resStream ) ;
351
+ } else {
352
+ resStream . write ( '' ) ;
291
353
}
292
354
}
293
355
@@ -320,7 +382,7 @@ class Router {
320
382
try {
321
383
const { scope, ...reqCtx } = options ;
322
384
const body = await handler . apply ( scope ?? this , [ error , reqCtx ] ) ;
323
- if ( body instanceof Response || isAPIGatewayProxyResult ( body ) ) {
385
+ if ( body instanceof Response || isExtendedAPIGatewayProxyResult ( body ) ) {
324
386
return body ;
325
387
}
326
388
if ( ! body . statusCode ) {
0 commit comments