1+ import { Readable } from 'node:stream' ;
2+ import type streamWeb from 'node:stream/web' ;
3+ import { isString } from '@aws-lambda-powertools/commons/typeutils' ;
14import type { APIGatewayProxyEvent , APIGatewayProxyResult } from 'aws-lambda' ;
25import type {
36 CompressionOptions ,
7+ ExtendedAPIGatewayProxyResultBody ,
48 HandlerResponse ,
59 HttpStatusCode ,
610} from '../types/rest.js' ;
711import { COMPRESSION_ENCODING_TYPES , HttpStatusCodes } from './constants.js' ;
8- import { isAPIGatewayProxyResult } from './utils.js' ;
12+ import {
13+ isExtendedAPIGatewayProxyResult ,
14+ isNodeReadableStream ,
15+ isWebReadableStream ,
16+ } from './utils.js' ;
917
1018/**
1119 * Creates a request body from API Gateway event body, handling base64 decoding if needed.
@@ -72,18 +80,17 @@ const proxyEventToWebRequest = (event: APIGatewayProxyEvent): Request => {
7280} ;
7381
7482/**
75- * Converts a Web API Response object to an API Gateway proxy result.
83+ * Converts Web API Headers to API Gateway v1 headers format.
84+ * Splits multi-value headers by comma and organizes them into separate objects.
7685 *
77- * @param response - The Web API Response object
78- * @returns An API Gateway proxy result
86+ * @param webHeaders - The Web API Headers object
87+ * @returns Object containing headers and multiValueHeaders
7988 */
80- const webResponseToProxyResult = async (
81- response : Response
82- ) : Promise < APIGatewayProxyResult > => {
89+ const webHeadersToApiGatewayV1Headers = ( webHeaders : Headers ) => {
8390 const headers : Record < string , string > = { } ;
8491 const multiValueHeaders : Record < string , Array < string > > = { } ;
8592
86- for ( const [ key , value ] of response . headers . entries ( ) ) {
93+ for ( const [ key , value ] of webHeaders . entries ( ) ) {
8794 const values = value . split ( ',' ) . map ( ( v ) => v . trimStart ( ) ) ;
8895 if ( values . length > 1 ) {
8996 multiValueHeaders [ key ] = values ;
@@ -92,6 +99,25 @@ const webResponseToProxyResult = async (
9299 }
93100 }
94101
102+ return {
103+ headers,
104+ multiValueHeaders,
105+ } ;
106+ } ;
107+
108+ /**
109+ * Converts a Web API Response object to an API Gateway proxy result.
110+ *
111+ * @param response - The Web API Response object
112+ * @returns An API Gateway proxy result
113+ */
114+ const webResponseToProxyResult = async (
115+ response : Response
116+ ) : Promise < APIGatewayProxyResult > => {
117+ const { headers, multiValueHeaders } = webHeadersToApiGatewayV1Headers (
118+ response . headers
119+ ) ;
120+
95121 // Check if response contains compressed/binary content
96122 const contentEncoding = response . headers . get (
97123 'content-encoding'
@@ -135,12 +161,14 @@ const webResponseToProxyResult = async (
135161 *
136162 * @param response - The handler response (APIGatewayProxyResult, Response, or plain object)
137163 * @param resHeaders - Optional headers to be included in the response
164+ * @returns A Web API Response object
138165 */
139166const handlerResultToWebResponse = (
140167 response : HandlerResponse ,
141168 resHeaders ?: Headers
142169) : Response => {
143170 if ( response instanceof Response ) {
171+ if ( resHeaders === undefined ) return response ;
144172 const headers = new Headers ( resHeaders ) ;
145173 for ( const [ key , value ] of response . headers . entries ( ) ) {
146174 headers . set ( key , value ) ;
@@ -154,7 +182,7 @@ const handlerResultToWebResponse = (
154182 const headers = new Headers ( resHeaders ) ;
155183 headers . set ( 'Content-Type' , 'application/json' ) ;
156184
157- if ( isAPIGatewayProxyResult ( response ) ) {
185+ if ( isExtendedAPIGatewayProxyResult ( response ) ) {
158186 for ( const [ key , value ] of Object . entries ( response . headers ?? { } ) ) {
159187 if ( value != null ) {
160188 headers . set ( key , String ( value ) ) ;
@@ -169,7 +197,12 @@ const handlerResultToWebResponse = (
169197 }
170198 }
171199
172- return new Response ( response . body , {
200+ const body =
201+ response . body instanceof Readable
202+ ? ( Readable . toWeb ( response . body ) as ReadableStream )
203+ : response . body ;
204+
205+ return new Response ( body , {
173206 status : response . statusCode ,
174207 headers,
175208 } ) ;
@@ -189,8 +222,24 @@ const handlerResultToProxyResult = async (
189222 response : HandlerResponse ,
190223 statusCode : HttpStatusCode = HttpStatusCodes . OK
191224) : Promise < APIGatewayProxyResult > => {
192- if ( isAPIGatewayProxyResult ( response ) ) {
193- return response ;
225+ if ( isExtendedAPIGatewayProxyResult ( response ) ) {
226+ if ( isString ( response . body ) ) {
227+ return {
228+ ...response ,
229+ body : response . body ,
230+ } ;
231+ }
232+ if (
233+ isNodeReadableStream ( response . body ) ||
234+ isWebReadableStream ( response . body )
235+ ) {
236+ const nodeStream = bodyToNodeStream ( response . body ) ;
237+ return {
238+ ...response ,
239+ isBase64Encoded : true ,
240+ body : await nodeStreamToBase64 ( nodeStream ) ,
241+ } ;
242+ }
194243 }
195244 if ( response instanceof Response ) {
196245 return await webResponseToProxyResult ( response ) ;
@@ -203,9 +252,43 @@ const handlerResultToProxyResult = async (
203252 } ;
204253} ;
205254
255+ /**
256+ * Converts various body types to a Node.js Readable stream.
257+ * Handles Node.js streams, web streams, and string bodies.
258+ *
259+ * @param body - The body to convert (Readable, ReadableStream, or string)
260+ * @returns A Node.js Readable stream
261+ */
262+ const bodyToNodeStream = ( body : ExtendedAPIGatewayProxyResultBody ) => {
263+ if ( isNodeReadableStream ( body ) ) {
264+ return body ;
265+ }
266+ if ( isWebReadableStream ( body ) ) {
267+ return Readable . fromWeb ( body as streamWeb . ReadableStream ) ;
268+ }
269+ return Readable . from ( Buffer . from ( body as string ) ) ;
270+ } ;
271+
272+ /**
273+ * Converts a Node.js Readable stream to a base64 encoded string.
274+ * Handles both Buffer and string chunks by converting all to Buffers.
275+ *
276+ * @param stream - The Node.js Readable stream to convert
277+ * @returns A Promise that resolves to a base64 encoded string
278+ */
279+ async function nodeStreamToBase64 ( stream : Readable ) {
280+ const chunks : Buffer [ ] = [ ] ;
281+ for await ( const chunk of stream ) {
282+ chunks . push ( Buffer . isBuffer ( chunk ) ? chunk : Buffer . from ( chunk ) ) ;
283+ }
284+ return Buffer . concat ( chunks ) . toString ( 'base64' ) ;
285+ }
286+
206287export {
207288 proxyEventToWebRequest ,
208289 webResponseToProxyResult ,
209290 handlerResultToWebResponse ,
210291 handlerResultToProxyResult ,
292+ bodyToNodeStream ,
293+ webHeadersToApiGatewayV1Headers ,
211294} ;
0 commit comments