diff --git a/src/presets/aws-lambda/runtime/aws-lambda-streaming.ts b/src/presets/aws-lambda/runtime/aws-lambda-streaming.ts index 73e6a101c9..04b50da215 100644 --- a/src/presets/aws-lambda/runtime/aws-lambda-streaming.ts +++ b/src/presets/aws-lambda/runtime/aws-lambda-streaming.ts @@ -1,78 +1,94 @@ import type { - APIGatewayProxyEvent, APIGatewayProxyEventV2, - APIGatewayProxyResult, - APIGatewayProxyResultV2, Context, + APIGatewayProxyStructuredResultV2, } from "aws-lambda"; import "#internal/nitro/virtual/polyfill"; import { withQuery } from "ufo"; import { nitroApp } from "#internal/nitro/app"; import { normalizeLambdaIncomingHeaders, - normalizeLambdaOutgoingBody, normalizeLambdaOutgoingHeaders, } from "#internal/nitro/utils.lambda"; -import { normalizeCookieHeader } from "#internal/nitro/utils"; -export async function handler( - event: APIGatewayProxyEvent, - context: Context -): Promise; -export async function handler( - event: APIGatewayProxyEventV2, - context: Context -): Promise; -export async function handler( - event: APIGatewayProxyEvent | APIGatewayProxyEventV2, - context: Context -): Promise { - const query = { - ...event.queryStringParameters, - ...(event as APIGatewayProxyEvent).multiValueQueryStringParameters, - }; - const url = withQuery( - (event as APIGatewayProxyEvent).path || - (event as APIGatewayProxyEventV2).rawPath, - query - ); - const method = - (event as APIGatewayProxyEvent).httpMethod || - (event as APIGatewayProxyEventV2).requestContext?.http?.method || - "get"; +declare global { + // eslint-disable-next-line @typescript-eslint/no-namespace + namespace awslambda { + // https://docs.aws.amazon.com/lambda/latest/dg/configuration-response-streaming.html + function streamifyResponse( + handler: ( + event: APIGatewayProxyEventV2, + responseStream: NodeJS.WritableStream, + context: Context + ) => Promise + ): any; - if ("cookies" in event && event.cookies) { - event.headers.cookie = event.cookies.join(";"); + // eslint-disable-next-line @typescript-eslint/no-namespace + namespace HttpResponseStream { + function from( + stream: NodeJS.WritableStream, + metadata: { + statusCode: APIGatewayProxyStructuredResultV2["statusCode"]; + headers: APIGatewayProxyStructuredResultV2["headers"]; + } + ): NodeJS.WritableStream; + } } +} - const r = await nitroApp.localCall({ - event, - url, - context, - headers: normalizeLambdaIncomingHeaders(event.headers) as Record< - string, - string | string[] - >, - method, - query, - body: event.isBase64Encoded - ? Buffer.from(event.body || "", "base64").toString("utf8") - : event.body, - }); +export const handler = awslambda.streamifyResponse( + async (event: APIGatewayProxyEventV2, responseStream, context) => { + const query = { + ...event.queryStringParameters, + }; + const url = withQuery(event.rawPath, query); + const method = event.requestContext?.http?.method || "get"; - // ApiGateway v2 https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html#http-api-develop-integrations-lambda.v2 - const isApiGwV2 = "cookies" in event || "rawPath" in event; - const awsBody = await normalizeLambdaOutgoingBody(r.body, r.headers); - const cookies = normalizeCookieHeader(r.headers["set-cookie"]); - return { - ...(cookies.length > 0 && { - ...(isApiGwV2 - ? { cookies } - : { multiValueHeaders: { "set-cookie": cookies } }), - }), - statusCode: r.status, - headers: normalizeLambdaOutgoingHeaders(r.headers, true), - body: awsBody.body, - isBase64Encoded: awsBody.type === "binary", - }; -} + if ("cookies" in event && event.cookies) { + event.headers.cookie = event.cookies.join(";"); + } + + const r = await nitroApp.localCall({ + event, + url, + context, + headers: normalizeLambdaIncomingHeaders(event.headers) as Record< + string, + string | string[] + >, + method, + query, + body: event.isBase64Encoded + ? Buffer.from(event.body || "", "base64").toString("utf8") + : event.body, + }); + const httpResponseMetadata = { + statusCode: r.status, + headers: { + ...normalizeLambdaOutgoingHeaders(r.headers, true), + "Transfer-Encoding": "chunked", + }, + }; + if (r.body) { + const reader = r.body as ReadableStream; + const writer = awslambda.HttpResponseStream.from( + responseStream, + httpResponseMetadata + ); + await streamToNodeStream(reader.getReader(), responseStream); + writer.end(); + } + } +); + +const streamToNodeStream = async ( + reader: ReadableStreamDefaultReader, + writer: NodeJS.WritableStream +) => { + let readResult = await reader.read(); + while (!readResult.done) { + writer.write(readResult.value); + readResult = await reader.read(); + } + writer.end(); +}; diff --git a/src/presets/aws-lambda/runtime/aws-lambda.ts b/src/presets/aws-lambda/runtime/aws-lambda.ts index 04b50da215..73e6a101c9 100644 --- a/src/presets/aws-lambda/runtime/aws-lambda.ts +++ b/src/presets/aws-lambda/runtime/aws-lambda.ts @@ -1,94 +1,78 @@ import type { + APIGatewayProxyEvent, APIGatewayProxyEventV2, + APIGatewayProxyResult, + APIGatewayProxyResultV2, Context, - APIGatewayProxyStructuredResultV2, } from "aws-lambda"; import "#internal/nitro/virtual/polyfill"; import { withQuery } from "ufo"; import { nitroApp } from "#internal/nitro/app"; import { normalizeLambdaIncomingHeaders, + normalizeLambdaOutgoingBody, normalizeLambdaOutgoingHeaders, } from "#internal/nitro/utils.lambda"; +import { normalizeCookieHeader } from "#internal/nitro/utils"; -declare global { - // eslint-disable-next-line @typescript-eslint/no-namespace - namespace awslambda { - // https://docs.aws.amazon.com/lambda/latest/dg/configuration-response-streaming.html - function streamifyResponse( - handler: ( - event: APIGatewayProxyEventV2, - responseStream: NodeJS.WritableStream, - context: Context - ) => Promise - ): any; +export async function handler( + event: APIGatewayProxyEvent, + context: Context +): Promise; +export async function handler( + event: APIGatewayProxyEventV2, + context: Context +): Promise; +export async function handler( + event: APIGatewayProxyEvent | APIGatewayProxyEventV2, + context: Context +): Promise { + const query = { + ...event.queryStringParameters, + ...(event as APIGatewayProxyEvent).multiValueQueryStringParameters, + }; + const url = withQuery( + (event as APIGatewayProxyEvent).path || + (event as APIGatewayProxyEventV2).rawPath, + query + ); + const method = + (event as APIGatewayProxyEvent).httpMethod || + (event as APIGatewayProxyEventV2).requestContext?.http?.method || + "get"; - // eslint-disable-next-line @typescript-eslint/no-namespace - namespace HttpResponseStream { - function from( - stream: NodeJS.WritableStream, - metadata: { - statusCode: APIGatewayProxyStructuredResultV2["statusCode"]; - headers: APIGatewayProxyStructuredResultV2["headers"]; - } - ): NodeJS.WritableStream; - } + if ("cookies" in event && event.cookies) { + event.headers.cookie = event.cookies.join(";"); } -} - -export const handler = awslambda.streamifyResponse( - async (event: APIGatewayProxyEventV2, responseStream, context) => { - const query = { - ...event.queryStringParameters, - }; - const url = withQuery(event.rawPath, query); - const method = event.requestContext?.http?.method || "get"; - - if ("cookies" in event && event.cookies) { - event.headers.cookie = event.cookies.join(";"); - } - const r = await nitroApp.localCall({ - event, - url, - context, - headers: normalizeLambdaIncomingHeaders(event.headers) as Record< - string, - string | string[] - >, - method, - query, - body: event.isBase64Encoded - ? Buffer.from(event.body || "", "base64").toString("utf8") - : event.body, - }); - const httpResponseMetadata = { - statusCode: r.status, - headers: { - ...normalizeLambdaOutgoingHeaders(r.headers, true), - "Transfer-Encoding": "chunked", - }, - }; - if (r.body) { - const reader = r.body as ReadableStream; - const writer = awslambda.HttpResponseStream.from( - responseStream, - httpResponseMetadata - ); - await streamToNodeStream(reader.getReader(), responseStream); - writer.end(); - } - } -); + const r = await nitroApp.localCall({ + event, + url, + context, + headers: normalizeLambdaIncomingHeaders(event.headers) as Record< + string, + string | string[] + >, + method, + query, + body: event.isBase64Encoded + ? Buffer.from(event.body || "", "base64").toString("utf8") + : event.body, + }); -const streamToNodeStream = async ( - reader: ReadableStreamDefaultReader, - writer: NodeJS.WritableStream -) => { - let readResult = await reader.read(); - while (!readResult.done) { - writer.write(readResult.value); - readResult = await reader.read(); - } - writer.end(); -}; + // ApiGateway v2 https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html#http-api-develop-integrations-lambda.v2 + const isApiGwV2 = "cookies" in event || "rawPath" in event; + const awsBody = await normalizeLambdaOutgoingBody(r.body, r.headers); + const cookies = normalizeCookieHeader(r.headers["set-cookie"]); + return { + ...(cookies.length > 0 && { + ...(isApiGwV2 + ? { cookies } + : { multiValueHeaders: { "set-cookie": cookies } }), + }), + statusCode: r.status, + headers: normalizeLambdaOutgoingHeaders(r.headers, true), + body: awsBody.body, + isBase64Encoded: awsBody.type === "binary", + }; +}