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+ ExtendedAPIGatewayProxyResult ,
8+ ExtendedAPIGatewayProxyResultBody ,
49 HandlerResponse ,
510 HttpStatusCode ,
611} from '../types/rest.js' ;
712import { COMPRESSION_ENCODING_TYPES , HttpStatusCodes } from './constants.js' ;
8- import { isAPIGatewayProxyResult } from './utils.js' ;
13+ import {
14+ isExtendedAPIGatewayProxyResult ,
15+ isNodeReadableStream ,
16+ isWebReadableStream ,
17+ } from './utils.js' ;
918
1019/**
1120 * Creates a request body from API Gateway event body, handling base64 decoding if needed.
@@ -72,18 +81,17 @@ const proxyEventToWebRequest = (event: APIGatewayProxyEvent): Request => {
7281} ;
7382
7483/**
75- * Converts a Web API Response object to an API Gateway proxy result.
84+ * Converts Web API Headers to API Gateway v1 headers format.
85+ * Splits multi-value headers by comma and organizes them into separate objects.
7686 *
77- * @param response - The Web API Response object
78- * @returns An API Gateway proxy result
87+ * @param webHeaders - The Web API Headers object
88+ * @returns Object containing headers and multiValueHeaders
7989 */
80- const webResponseToProxyResult = async (
81- response : Response
82- ) : Promise < APIGatewayProxyResult > => {
90+ const webHeadersToApiGatewayV1Headers = ( webHeaders : Headers ) => {
8391 const headers : Record < string , string > = { } ;
8492 const multiValueHeaders : Record < string , Array < string > > = { } ;
8593
86- for ( const [ key , value ] of response . headers . entries ( ) ) {
94+ for ( const [ key , value ] of webHeaders . entries ( ) ) {
8795 const values = value . split ( ',' ) . map ( ( v ) => v . trimStart ( ) ) ;
8896 if ( values . length > 1 ) {
8997 multiValueHeaders [ key ] = values ;
@@ -92,6 +100,25 @@ const webResponseToProxyResult = async (
92100 }
93101 }
94102
103+ return {
104+ headers,
105+ multiValueHeaders,
106+ } ;
107+ } ;
108+
109+ /**
110+ * Converts a Web API Response object to an API Gateway proxy result.
111+ *
112+ * @param response - The Web API Response object
113+ * @returns An API Gateway proxy result
114+ */
115+ const webResponseToProxyResult = async (
116+ response : Response
117+ ) : Promise < APIGatewayProxyResult > => {
118+ const { headers, multiValueHeaders } = webHeadersToApiGatewayV1Headers (
119+ response . headers
120+ ) ;
121+
95122 // Check if response contains compressed/binary content
96123 const contentEncoding = response . headers . get (
97124 'content-encoding'
@@ -129,18 +156,47 @@ const webResponseToProxyResult = async (
129156 return result ;
130157} ;
131158
159+ /**
160+ * Adds headers from an ExtendedAPIGatewayProxyResult to a Headers object.
161+ *
162+ * @param headers - The Headers object to mutate
163+ * @param response - The response containing headers to add
164+ * @remarks This function mutates the headers object by adding entries from
165+ * response.headers and response.multiValueHeaders
166+ */
167+ function addProxyEventHeaders (
168+ headers : Headers ,
169+ response : ExtendedAPIGatewayProxyResult
170+ ) {
171+ for ( const [ key , value ] of Object . entries ( response . headers ?? { } ) ) {
172+ if ( value != null ) {
173+ headers . set ( key , String ( value ) ) ;
174+ }
175+ }
176+
177+ for ( const [ key , values ] of Object . entries (
178+ response . multiValueHeaders ?? { }
179+ ) ) {
180+ for ( const value of values ?? [ ] ) {
181+ headers . append ( key , String ( value ) ) ;
182+ }
183+ }
184+ }
185+
132186/**
133187 * Converts a handler response to a Web API Response object.
134188 * Handles APIGatewayProxyResult, Response objects, and plain objects.
135189 *
136190 * @param response - The handler response (APIGatewayProxyResult, Response, or plain object)
137191 * @param resHeaders - Optional headers to be included in the response
192+ * @returns A Web API Response object
138193 */
139194const handlerResultToWebResponse = (
140195 response : HandlerResponse ,
141196 resHeaders ?: Headers
142197) : Response => {
143198 if ( response instanceof Response ) {
199+ if ( resHeaders === undefined ) return response ;
144200 const headers = new Headers ( resHeaders ) ;
145201 for ( const [ key , value ] of response . headers . entries ( ) ) {
146202 headers . set ( key , value ) ;
@@ -154,22 +210,15 @@ const handlerResultToWebResponse = (
154210 const headers = new Headers ( resHeaders ) ;
155211 headers . set ( 'Content-Type' , 'application/json' ) ;
156212
157- if ( isAPIGatewayProxyResult ( response ) ) {
158- for ( const [ key , value ] of Object . entries ( response . headers ?? { } ) ) {
159- if ( value != null ) {
160- headers . set ( key , String ( value ) ) ;
161- }
162- }
213+ if ( isExtendedAPIGatewayProxyResult ( response ) ) {
214+ addProxyEventHeaders ( headers , response ) ;
163215
164- for ( const [ key , values ] of Object . entries (
165- response . multiValueHeaders ?? { }
166- ) ) {
167- for ( const value of values ?? [ ] ) {
168- headers . append ( key , String ( value ) ) ;
169- }
170- }
216+ const body =
217+ response . body instanceof Readable
218+ ? ( Readable . toWeb ( response . body ) as ReadableStream )
219+ : response . body ;
171220
172- return new Response ( response . body , {
221+ return new Response ( body , {
173222 status : response . statusCode ,
174223 headers,
175224 } ) ;
@@ -189,8 +238,24 @@ const handlerResultToProxyResult = async (
189238 response : HandlerResponse ,
190239 statusCode : HttpStatusCode = HttpStatusCodes . OK
191240) : Promise < APIGatewayProxyResult > => {
192- if ( isAPIGatewayProxyResult ( response ) ) {
193- return response ;
241+ if ( isExtendedAPIGatewayProxyResult ( response ) ) {
242+ if ( isString ( response . body ) ) {
243+ return {
244+ ...response ,
245+ body : response . body ,
246+ } ;
247+ }
248+ if (
249+ isNodeReadableStream ( response . body ) ||
250+ isWebReadableStream ( response . body )
251+ ) {
252+ const nodeStream = bodyToNodeStream ( response . body ) ;
253+ return {
254+ ...response ,
255+ isBase64Encoded : true ,
256+ body : await nodeStreamToBase64 ( nodeStream ) ,
257+ } ;
258+ }
194259 }
195260 if ( response instanceof Response ) {
196261 return await webResponseToProxyResult ( response ) ;
@@ -203,9 +268,43 @@ const handlerResultToProxyResult = async (
203268 } ;
204269} ;
205270
271+ /**
272+ * Converts various body types to a Node.js Readable stream.
273+ * Handles Node.js streams, web streams, and string bodies.
274+ *
275+ * @param body - The body to convert (Readable, ReadableStream, or string)
276+ * @returns A Node.js Readable stream
277+ */
278+ const bodyToNodeStream = ( body : ExtendedAPIGatewayProxyResultBody ) => {
279+ if ( isNodeReadableStream ( body ) ) {
280+ return body ;
281+ }
282+ if ( isWebReadableStream ( body ) ) {
283+ return Readable . fromWeb ( body as streamWeb . ReadableStream ) ;
284+ }
285+ return Readable . from ( Buffer . from ( body as string ) ) ;
286+ } ;
287+
288+ /**
289+ * Converts a Node.js Readable stream to a base64 encoded string.
290+ * Handles both Buffer and string chunks by converting all to Buffers.
291+ *
292+ * @param stream - The Node.js Readable stream to convert
293+ * @returns A Promise that resolves to a base64 encoded string
294+ */
295+ async function nodeStreamToBase64 ( stream : Readable ) {
296+ const chunks : Buffer [ ] = [ ] ;
297+ for await ( const chunk of stream ) {
298+ chunks . push ( Buffer . isBuffer ( chunk ) ? chunk : Buffer . from ( chunk ) ) ;
299+ }
300+ return Buffer . concat ( chunks ) . toString ( 'base64' ) ;
301+ }
302+
206303export {
207304 proxyEventToWebRequest ,
208305 webResponseToProxyResult ,
209306 handlerResultToWebResponse ,
210307 handlerResultToProxyResult ,
308+ bodyToNodeStream ,
309+ webHeadersToApiGatewayV1Headers ,
211310} ;
0 commit comments