1
+ import { Readable } from 'node:stream' ;
2
+ import type streamWeb from 'node:stream/web' ;
3
+ import { isString } from '@aws-lambda-powertools/commons/typeutils' ;
1
4
import type { APIGatewayProxyEvent , APIGatewayProxyResult } from 'aws-lambda' ;
2
5
import type {
3
6
CompressionOptions ,
7
+ ExtendedAPIGatewayProxyResult ,
8
+ ExtendedAPIGatewayProxyResultBody ,
4
9
HandlerResponse ,
5
10
HttpStatusCode ,
6
11
} from '../types/rest.js' ;
7
12
import { 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' ;
9
18
10
19
/**
11
20
* Creates a request body from API Gateway event body, handling base64 decoding if needed.
@@ -72,18 +81,17 @@ const proxyEventToWebRequest = (event: APIGatewayProxyEvent): Request => {
72
81
} ;
73
82
74
83
/**
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.
76
86
*
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
79
89
*/
80
- const webResponseToProxyResult = async (
81
- response : Response
82
- ) : Promise < APIGatewayProxyResult > => {
90
+ const webHeadersToApiGatewayV1Headers = ( webHeaders : Headers ) => {
83
91
const headers : Record < string , string > = { } ;
84
92
const multiValueHeaders : Record < string , Array < string > > = { } ;
85
93
86
- for ( const [ key , value ] of response . headers . entries ( ) ) {
94
+ for ( const [ key , value ] of webHeaders . entries ( ) ) {
87
95
const values = value . split ( ',' ) . map ( ( v ) => v . trimStart ( ) ) ;
88
96
if ( values . length > 1 ) {
89
97
multiValueHeaders [ key ] = values ;
@@ -92,6 +100,25 @@ const webResponseToProxyResult = async (
92
100
}
93
101
}
94
102
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
+
95
122
// Check if response contains compressed/binary content
96
123
const contentEncoding = response . headers . get (
97
124
'content-encoding'
@@ -129,18 +156,47 @@ const webResponseToProxyResult = async (
129
156
return result ;
130
157
} ;
131
158
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
+
132
186
/**
133
187
* Converts a handler response to a Web API Response object.
134
188
* Handles APIGatewayProxyResult, Response objects, and plain objects.
135
189
*
136
190
* @param response - The handler response (APIGatewayProxyResult, Response, or plain object)
137
191
* @param resHeaders - Optional headers to be included in the response
192
+ * @returns A Web API Response object
138
193
*/
139
194
const handlerResultToWebResponse = (
140
195
response : HandlerResponse ,
141
196
resHeaders ?: Headers
142
197
) : Response => {
143
198
if ( response instanceof Response ) {
199
+ if ( resHeaders === undefined ) return response ;
144
200
const headers = new Headers ( resHeaders ) ;
145
201
for ( const [ key , value ] of response . headers . entries ( ) ) {
146
202
headers . set ( key , value ) ;
@@ -154,22 +210,15 @@ const handlerResultToWebResponse = (
154
210
const headers = new Headers ( resHeaders ) ;
155
211
headers . set ( 'Content-Type' , 'application/json' ) ;
156
212
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 ) ;
163
215
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 ;
171
220
172
- return new Response ( response . body , {
221
+ return new Response ( body , {
173
222
status : response . statusCode ,
174
223
headers,
175
224
} ) ;
@@ -189,8 +238,24 @@ const handlerResultToProxyResult = async (
189
238
response : HandlerResponse ,
190
239
statusCode : HttpStatusCode = HttpStatusCodes . OK
191
240
) : 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
+ }
194
259
}
195
260
if ( response instanceof Response ) {
196
261
return await webResponseToProxyResult ( response ) ;
@@ -203,9 +268,43 @@ const handlerResultToProxyResult = async (
203
268
} ;
204
269
} ;
205
270
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
+
206
303
export {
207
304
proxyEventToWebRequest ,
208
305
webResponseToProxyResult ,
209
306
handlerResultToWebResponse ,
210
307
handlerResultToProxyResult ,
308
+ bodyToNodeStream ,
309
+ webHeadersToApiGatewayV1Headers ,
211
310
} ;
0 commit comments