@@ -4,7 +4,13 @@ import {
44 isRegExp ,
55 isString ,
66} from '@aws-lambda-powertools/commons/typeutils' ;
7- import type { APIGatewayProxyEvent , APIGatewayProxyEventV2 } from 'aws-lambda' ;
7+ import type {
8+ APIGatewayProxyEvent ,
9+ APIGatewayProxyEventV2 ,
10+ StreamifyHandler ,
11+ } from 'aws-lambda' ;
12+ import type { Router } from '../rest/Router.js' ;
13+ import type { ResolveOptions } from '../types/index.js' ;
814import type {
915 CompiledRoute ,
1016 CompressionOptions ,
@@ -385,3 +391,76 @@ export const getStatusCode = (
385391 }
386392 return fallback ;
387393} ;
394+
395+ const streamifyResponse =
396+ globalThis . awslambda ?. streamifyResponse ??
397+ ( < TEvent = unknown , TResult = void > (
398+ handler : StreamifyHandler < TEvent , TResult >
399+ ) : StreamifyHandler < TEvent , TResult > => {
400+ return ( async ( event , responseStream , context ) => {
401+ await handler ( event , responseStream , context ) ;
402+
403+ if ( 'chunks' in responseStream && Array . isArray ( responseStream . chunks ) ) {
404+ const output = Buffer . concat ( responseStream . chunks as Buffer [ ] ) ;
405+ const nullBytes = Buffer . from ( [ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ] ) ;
406+ const separatorIndex = output . indexOf ( nullBytes ) ;
407+
408+ const preludeBuffer = output . subarray ( 0 , separatorIndex ) ;
409+ const bodyBuffer = output . subarray ( separatorIndex + 8 ) ;
410+ const prelude = JSON . parse ( preludeBuffer . toString ( ) ) ;
411+
412+ return {
413+ body : bodyBuffer . toString ( ) ,
414+ headers : prelude . headers ,
415+ statusCode : prelude . statusCode ,
416+ } as TResult ;
417+ }
418+ } ) as StreamifyHandler < TEvent , TResult > ;
419+ } ) ;
420+
421+ /**
422+ * Wraps a Router instance to create a Lambda handler that uses response streaming.
423+ *
424+ * In Lambda runtime, uses `awslambda.streamifyResponse` to enable streaming responses.
425+ * In test/local environments, returns an unwrapped handler that works with mock streams.
426+ *
427+ * @param router - The Router instance to wrap
428+ * @param options - Optional configuration including scope for decorator binding
429+ * @returns A Lambda handler that streams responses
430+ *
431+ * @example
432+ * ```typescript
433+ * import { Router, streamify } from '@aws-lambda-powertools/event-handler/experimental-rest';
434+ *
435+ * const app = new Router();
436+ * app.get('/test', () => ({ message: 'Hello' }));
437+ *
438+ * export const handler = streamify(app);
439+ * ```
440+ *
441+ * @example
442+ * ```typescript
443+ * // With scope for decorators
444+ * class Lambda {
445+ * public scope = 'my-scope';
446+ *
447+ * @app .get('/test')
448+ * public getTest() {
449+ * return { message: `${this.scope}: success` };
450+ * }
451+ *
452+ * public handler = streamify(app, { scope: this });
453+ * }
454+ * ```
455+ */
456+ export const streamify = (
457+ router : Router ,
458+ options ?: ResolveOptions
459+ ) : StreamifyHandler => {
460+ return streamifyResponse ( async ( event , responseStream , context ) => {
461+ await router . resolveStream ( event , context , {
462+ responseStream,
463+ scope : options ?. scope ,
464+ } ) ;
465+ } ) ;
466+ } ;
0 commit comments