diff --git a/e2e/tests/aws-lambda.ts b/e2e/tests/aws-lambda.ts index 8cd0af2780..1338805397 100644 --- a/e2e/tests/aws-lambda.ts +++ b/e2e/tests/aws-lambda.ts @@ -69,7 +69,7 @@ export const awsLambdaDeployment: DeploymentConfiguration<{ 'func', { role: lambdaRole.arn, - runtime: 'nodejs18.x', + runtime: 'nodejs20.x', handler: 'index.handler', code: new pulumi.asset.AssetArchive({ 'index.js': new pulumi.asset.FileAsset( diff --git a/examples/aws-lambda/.gitignore b/examples/aws-lambda/.gitignore index 42e755ee2f..e5b1d8011e 100644 --- a/examples/aws-lambda/.gitignore +++ b/examples/aws-lambda/.gitignore @@ -2,6 +2,7 @@ !scripts/*.js !jest.config.js *.d.ts +!awslambda.d.ts node_modules # CDK asset staging directory diff --git a/examples/aws-lambda/lambda/awslambda.d.ts b/examples/aws-lambda/lambda/awslambda.d.ts new file mode 100644 index 0000000000..d2177763d5 --- /dev/null +++ b/examples/aws-lambda/lambda/awslambda.d.ts @@ -0,0 +1,28 @@ +import { Writable } from 'node:stream'; +import { APIGatewayEvent, Context, Handler } from 'aws-lambda'; + +declare global { + namespace awslambda { + export namespace HttpResponseStream { + function from( + responseStream: ResponseStream, + metadata: { + statusCode?: number; + headers?: Record; + }, + ): ResponseStream; + } + + export type ResponseStream = Writable & { + setContentType(type: string): void; + }; + + export type StreamifyHandler = ( + event: APIGatewayEvent, + responseStream: ResponseStream, + context: Context, + ) => Promise; + + export function streamifyResponse(handler: StreamifyHandler): Handler; + } +} diff --git a/examples/aws-lambda/lambda/graphql.ts b/examples/aws-lambda/lambda/graphql.ts index 51e06c9d7b..2b0cfdde9d 100644 --- a/examples/aws-lambda/lambda/graphql.ts +++ b/examples/aws-lambda/lambda/graphql.ts @@ -1,4 +1,5 @@ -import { APIGatewayEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; +import { pipeline } from 'stream/promises'; +import { APIGatewayEvent, Context } from 'aws-lambda'; import { createSchema, createYoga } from 'graphql-yoga'; const yoga = createYoga<{ @@ -21,33 +22,45 @@ const yoga = createYoga<{ }), }); -export async function handler( - event: APIGatewayEvent, - lambdaContext: Context, -): Promise { - const response = await yoga.fetch( - event.path + - '?' + - new URLSearchParams((event.queryStringParameters as Record) || {}).toString(), - { - method: event.httpMethod, - headers: event.headers as HeadersInit, - body: event.body - ? Buffer.from(event.body, event.isBase64Encoded ? 'base64' : 'utf8') - : undefined, - }, - { - event, - lambdaContext, - }, - ); +export const handler = awslambda.streamifyResponse( + async function handler(event, res, lambdaContext) { + const response = await yoga.fetch( + // Construct the URL + event.path + + '?' + + // Parse query string parameters + new URLSearchParams( + (event.queryStringParameters as Record) || {}, + ).toString(), + { + method: event.httpMethod, + headers: event.headers as HeadersInit, + // Parse the body + body: event.body + ? Buffer.from(event.body, event.isBase64Encoded ? 'base64' : 'utf8') + : undefined, + }, + { + event, + lambdaContext, + res, + }, + ); + + // Create the metadata object for the response + const metadata = { + statusCode: response.status, + headers: Object.fromEntries(response.headers.entries()), + }; - const responseHeaders = Object.fromEntries(response.headers.entries()); + // Attach the metadata to the response stream + const resWithMetadata = awslambda.HttpResponseStream.from(res, metadata); - return { - statusCode: response.status, - headers: responseHeaders, - body: await response.text(), - isBase64Encoded: false, - }; -} + // Pipe the response body to the response stream + if (response.body) { + await pipeline(response.body, resWithMetadata); + } else { + resWithMetadata.end(); + } + }, +); diff --git a/examples/aws-lambda/lib/graphql-lambda-stack.ts b/examples/aws-lambda/lib/graphql-lambda-stack.ts index 2a47506c53..fc3f1b5f19 100644 --- a/examples/aws-lambda/lib/graphql-lambda-stack.ts +++ b/examples/aws-lambda/lib/graphql-lambda-stack.ts @@ -10,7 +10,7 @@ export class GraphqlLambdaStack extends cdk.Stack { const graphqlLambda = new lambda.Function(this, 'graphqlLambda', { code: lambda.Code.fromAsset(path.join(__dirname, '../lambda')), handler: 'graphql.handler', - runtime: lambda.Runtime.NODEJS_14_X, + runtime: lambda.Runtime.NODEJS_20_X, }); new apiGateway.LambdaRestApi(this, 'graphqlEndpoint', { diff --git a/examples/aws-lambda/package.json b/examples/aws-lambda/package.json index cca1c4f8a0..93784c0103 100644 --- a/examples/aws-lambda/package.json +++ b/examples/aws-lambda/package.json @@ -22,13 +22,10 @@ "source-map-support": "^0.5.16" }, "devDependencies": { - "@aws-cdk/assert": "2.68.0", - "@types/aws-lambda": "8.10.116", + "@types/aws-lambda": "8.10.140", "@types/node": "18.16.16", "aws-cdk": "2.83.0", - "aws-cdk-lib": "2.83.0", "esbuild": "0.17.19", - "ts-node": "10.9.1", "typescript": "5.5.3" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 333d1120e2..157ef7c471 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -285,27 +285,18 @@ importers: specifier: ^0.5.16 version: 0.5.21 devDependencies: - '@aws-cdk/assert': - specifier: 2.68.0 - version: 2.68.0(aws-cdk-lib@2.83.0(constructs@3.4.311))(constructs@3.4.311)(jest@29.5.0(@types/node@18.16.16)(ts-node@10.9.1(@swc/core@1.3.37)(@types/node@18.16.16)(typescript@5.5.3))) '@types/aws-lambda': - specifier: 8.10.116 - version: 8.10.116 + specifier: 8.10.140 + version: 8.10.140 '@types/node': specifier: 18.16.16 version: 18.16.16 aws-cdk: specifier: 2.83.0 version: 2.83.0 - aws-cdk-lib: - specifier: 2.83.0 - version: 2.83.0(constructs@3.4.311) esbuild: specifier: 0.17.19 version: 0.17.19 - ts-node: - specifier: 10.9.1 - version: 10.9.1(@swc/core@1.3.37)(@types/node@18.16.16)(typescript@5.5.3) typescript: specifier: 5.5.3 version: 5.5.3 @@ -2289,23 +2280,6 @@ packages: resolution: {integrity: sha512-xhlTqH0m31mnsG0tIP4ETgfSB6gXDaYYsUWTrlUV93fFQPI9dd8hE0Ot6MHLCtqgB32hwJAC3YZMWlXZw7AleA==} engines: {node: '>=14'} - '@aws-cdk/assert@2.68.0': - resolution: {integrity: sha512-bEztvoYdVp17I/ClYRGZa4wlEP/qNNq4Q+Z7EKwRL0cLDmvq4EI1m1N8LhUPAH7B6YXp5d1164gC6Nr0lV8bbA==} - engines: {node: '>= 14.15.0'} - peerDependencies: - aws-cdk-lib: ^2.68.0 - constructs: ^10.0.0 - jest: '>=26.6.3' - - '@aws-cdk/asset-awscli-v1@2.2.183': - resolution: {integrity: sha512-D+E0drcgCjDn43GjsATIpkMeZlFoQFmf86GSlIF0AtNyl0IVrNKsKs/7J4oESrCTTCLL/x9vS7mG9e1ZrKbLpw==} - - '@aws-cdk/asset-kubectl-v20@2.1.1': - resolution: {integrity: sha512-U1ntiX8XiMRRRH5J1IdC+1t5CE89015cwyt5U63Cpk0GnMlN5+h9WsWMlKlPXZR4rdq/m806JRlBMRpBUB2Dhw==} - - '@aws-cdk/asset-node-proxy-agent-v5@2.0.153': - resolution: {integrity: sha512-EL2XCW6C3szBPHI4XWQsb68pqc719nR+8sfsEhyjzBNn0idgZg2ViujI3/1/jWnJwHPnvMOKQcmr/bOKzCPAgA==} - '@aws-cdk/assets@1.203.0': resolution: {integrity: sha512-aEexr1PEZPqTcBXiKLwmiWYtpoC6vPpsGAqe39U6lovXP675qO6/VTpE26AUtS0sDkuhCBnXDQR5H49e0+A+jQ==} engines: {node: '>= 14.15.0'} @@ -2637,9 +2611,6 @@ packages: '@aws-cdk/core': 1.203.0 constructs: ^3.3.69 - '@aws-cdk/cfnspec@2.68.0': - resolution: {integrity: sha512-g062ljKOvMaeEgp2GR2ewoF3BzeGYpu+AA7UvN/SN+2S0detSwU+qHlxSFeTe0DLyCFaMttNEh81VmYCfiHtpg==} - '@aws-cdk/cloud-assembly-schema@1.203.0': resolution: {integrity: sha512-r252InZ8Oh7q7ztriaA3n6F48QOFVfNcT/KO4XOlYyt1xDWRMENDYf+D+DVr6O5klcaa3ivvvDT7DRuW3xdVOQ==} engines: {node: '>= 14.15.0'} @@ -2647,10 +2618,6 @@ packages: - jsonschema - semver - '@aws-cdk/cloudformation-diff@2.68.0': - resolution: {integrity: sha512-JnX0sygxNHWU3aKdzSus25B1TuKYWDwnNL2tw3svZvfHcw3Nwz857JTOn/yNOJxT7cZbCbOqNPrOT6Xv+LrxTQ==} - engines: {node: '>= 14.15.0'} - '@aws-cdk/core@1.203.0': resolution: {integrity: sha512-3/quPwnGWKHm/Bzna/du5WP5a/Wp/NYqDyL1rJ1A3EPpsRQYywJPj77+M8nG5sD5qNIoFbhN7Q5aee+bcS7GGA==} engines: {node: '>= 14.15.0'} @@ -7114,6 +7081,9 @@ packages: '@types/aws-lambda@8.10.116': resolution: {integrity: sha512-LSvIyxYCsIMOiBnb5D6HTf7JXLCh3KPiZWL6Pkn1MqV/v5OoP42GDqn5H4wHKGGKN0mJB+4y1r0oat1dLBAkuA==} + '@types/aws-lambda@8.10.140': + resolution: {integrity: sha512-4Dh3dk2TUcbdfHrX0Al90mNGJDvA9NBiTQPzbrjGi/dLxzKCGOYgT8YQ47jUKNFALkAJAadifq0pzyjIUlhVhg==} + '@types/babel__core@7.20.5': resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} @@ -8034,10 +8004,6 @@ packages: asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} - at-least-node@1.0.0: - resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==} - engines: {node: '>= 4.0.0'} - atob@2.1.2: resolution: {integrity: sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==} engines: {node: '>= 4.5.0'} @@ -8065,23 +8031,6 @@ packages: avvio@8.2.1: resolution: {integrity: sha512-TAlMYvOuwGyLK3PfBb5WKBXZmXz2fVCgv23d6zZFdle/q3gPjmxBaeuC0pY0Dzs5PWMSgfqqEZkrye19GlDTgw==} - aws-cdk-lib@2.83.0: - resolution: {integrity: sha512-ZVirllZyKkl1v6bvpCiQyWFEgCKlCuQrcLxSsaSQPABfC3Oa4OfwPOrWuanxAwovMBlyOGjQ+D5K056d5pHJUg==} - engines: {node: '>= 14.15.0'} - peerDependencies: - constructs: ^10.0.0 - bundledDependencies: - - '@balena/dockerignore' - - case - - fs-extra - - ignore - - jsonschema - - minimatch - - punycode - - semver - - table - - yaml - aws-cdk@2.83.0: resolution: {integrity: sha512-UB9foPqsQkUeFHP5ZOjxqg0N+g53MPJnpxyKeMhDQDIiS9kG1LqVyHUwHNzq0PSk4kdwH9g50keu6zslb1iA1w==} engines: {node: '>= 14.15.0'} @@ -8492,9 +8441,6 @@ packages: chardet@0.7.0: resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} - charenc@0.0.2: - resolution: {integrity: sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==} - check-more-types@2.24.0: resolution: {integrity: sha512-Pj779qHxV2tuapviy1bSZNEL1maXr13bPYpsvSDB68HlYcYuhlDrmGd63i0JHMCLKzc7rUSNIrpdJlhVlNwrxA==} engines: {node: '>= 0.8.0'} @@ -8938,9 +8884,6 @@ packages: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} engines: {node: '>= 8'} - crypt@0.0.2: - resolution: {integrity: sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==} - crypto-hash@3.0.0: resolution: {integrity: sha512-5l5xGtzuvGTU28GXxGV1JYVFou68buZWpkV1Fx5hIDRPnfbQ8KzabTlNIuDIeSCYGVPFehupzDqlnbXm2IXmdQ==} engines: {node: '>=18'} @@ -10340,10 +10283,6 @@ packages: resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} engines: {node: '>=6 <7 || >=8'} - fs-extra@9.1.0: - resolution: {integrity: sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==} - engines: {node: '>=10'} - fs-minipass@2.1.0: resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} engines: {node: '>= 8'} @@ -12039,9 +11978,6 @@ packages: lodash.transform@4.6.0: resolution: {integrity: sha512-LO37ZnhmBVx0GvOU/caQuipEh4GN82TcWv3yHlebGDgOxbxiwwzW5Pcx2AcvpIv2WmvmSMoC492yQFNhy/l/UQ==} - lodash.truncate@4.4.2: - resolution: {integrity: sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==} - lodash.union@4.6.0: resolution: {integrity: sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==} @@ -12237,9 +12173,6 @@ packages: resolution: {integrity: sha512-BUiRtTtV39LIJwinWBjqVsU9xhdnz7/i889V859IBFpuqGAj6LuOvHv5XLbgZ2R7ptJoJaEcxkv88/h25T7Ciw==} engines: {node: '>=8'} - md5@2.3.0: - resolution: {integrity: sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==} - mdast-util-find-and-replace@3.0.1: resolution: {integrity: sha512-SG21kZHGC3XRTSUhtofZkBzZTJNM5ecCi0SK2IMKmSXR8vO3peL+kb1O0z7Zl83jKtutG4k5Wv/W7V3/YHvzPA==} @@ -15339,10 +15272,6 @@ packages: resolution: {integrity: sha512-L1dapNV6vu2s/4Sputv8xGsCdAVlb5nRDMFU/E27D44l5U6cw1g0dGd45uLc+OXjNMmF4ntiMdCimzcjFKQI8Q==} engines: {node: ^14.18.0 || >=16.0.0} - table@6.8.1: - resolution: {integrity: sha512-Y4X9zqrCftUhMeH2EptSSERdVKt/nEdijTOacGD/97EKjhQ/Qs8RTlEGABSJNNN8lac9kheH+af7yAkEWlgneA==} - engines: {node: '>=10.0.0'} - tabtab@3.0.2: resolution: {integrity: sha512-jANKmUe0sIQc/zTALTBy186PoM/k6aPrh3A7p6AaAfF6WPSbTx1JYeGIGH162btpH+mmVEXln+UxwViZHO2Jhg==} @@ -16903,19 +16832,6 @@ snapshots: transitivePeerDependencies: - encoding - '@aws-cdk/assert@2.68.0(aws-cdk-lib@2.83.0(constructs@3.4.311))(constructs@3.4.311)(jest@29.5.0(@types/node@18.16.16)(ts-node@10.9.1(@swc/core@1.3.37)(@types/node@18.16.16)(typescript@5.5.3)))': - dependencies: - '@aws-cdk/cloudformation-diff': 2.68.0 - aws-cdk-lib: 2.83.0(constructs@3.4.311) - constructs: 3.4.311 - jest: 29.5.0(@types/node@18.16.16)(ts-node@10.9.1(@swc/core@1.3.37)(@types/node@18.16.16)(typescript@5.5.3)) - - '@aws-cdk/asset-awscli-v1@2.2.183': {} - - '@aws-cdk/asset-kubectl-v20@2.1.1': {} - - '@aws-cdk/asset-node-proxy-agent-v5@2.0.153': {} - '@aws-cdk/assets@1.203.0(@aws-cdk/core@1.203.0(@aws-cdk/cloud-assembly-schema@1.203.0)(@aws-cdk/cx-api@1.203.0(@aws-cdk/cloud-assembly-schema@1.203.0))(@aws-cdk/region-info@1.203.0)(constructs@3.4.311))(@aws-cdk/cx-api@1.203.0(@aws-cdk/cloud-assembly-schema@1.203.0))(constructs@3.4.311)': dependencies: '@aws-cdk/core': 1.203.0(@aws-cdk/cloud-assembly-schema@1.203.0)(@aws-cdk/cx-api@1.203.0(@aws-cdk/cloud-assembly-schema@1.203.0))(@aws-cdk/region-info@1.203.0)(constructs@3.4.311) @@ -17190,22 +17106,8 @@ snapshots: '@aws-cdk/core': 1.203.0(@aws-cdk/cloud-assembly-schema@1.203.0)(@aws-cdk/cx-api@1.203.0(@aws-cdk/cloud-assembly-schema@1.203.0))(@aws-cdk/region-info@1.203.0)(constructs@3.4.311) constructs: 3.4.311 - '@aws-cdk/cfnspec@2.68.0': - dependencies: - fs-extra: 9.1.0 - md5: 2.3.0 - '@aws-cdk/cloud-assembly-schema@1.203.0': {} - '@aws-cdk/cloudformation-diff@2.68.0': - dependencies: - '@aws-cdk/cfnspec': 2.68.0 - chalk: 4.1.2 - diff: 5.1.0 - fast-deep-equal: 3.1.3 - string-width: 4.2.3 - table: 6.8.1 - '@aws-cdk/core@1.203.0(@aws-cdk/cloud-assembly-schema@1.203.0)(@aws-cdk/cx-api@1.203.0(@aws-cdk/cloud-assembly-schema@1.203.0))(@aws-cdk/region-info@1.203.0)(constructs@3.4.311)': dependencies: '@aws-cdk/cloud-assembly-schema': 1.203.0 @@ -20337,40 +20239,6 @@ snapshots: jest-util: 29.5.0 slash: 3.0.0 - '@jest/core@29.5.0(ts-node@10.9.1(@swc/core@1.3.37)(@types/node@18.16.16)(typescript@5.5.3))': - dependencies: - '@jest/console': 29.5.0 - '@jest/reporters': 29.5.0 - '@jest/test-result': 29.5.0 - '@jest/transform': 29.5.0 - '@jest/types': 29.5.0 - '@types/node': 18.16.16 - ansi-escapes: 4.3.2 - chalk: 4.1.2 - ci-info: 3.8.0 - exit: 0.1.2 - graceful-fs: 4.2.11 - jest-changed-files: 29.5.0 - jest-config: 29.5.0(@types/node@18.16.16)(ts-node@10.9.1(@swc/core@1.3.37)(@types/node@18.16.16)(typescript@5.5.3)) - jest-haste-map: 29.5.0 - jest-message-util: 29.5.0 - jest-regex-util: 29.4.3 - jest-resolve: 29.5.0 - jest-resolve-dependencies: 29.5.0 - jest-runner: 29.5.0 - jest-runtime: 29.5.0 - jest-snapshot: 29.5.0 - jest-util: 29.5.0 - jest-validate: 29.5.0 - jest-watcher: 29.5.0 - micromatch: 4.0.5 - pretty-format: 29.5.0 - slash: 3.0.0 - strip-ansi: 6.0.1 - transitivePeerDependencies: - - supports-color - - ts-node - '@jest/core@29.5.0(ts-node@10.9.2(@types/node@18.16.16)(typescript@5.5.3))': dependencies: '@jest/console': 29.5.0 @@ -23158,6 +23026,8 @@ snapshots: '@types/aws-lambda@8.10.116': {} + '@types/aws-lambda@8.10.140': {} + '@types/babel__core@7.20.5': dependencies: '@babel/parser': 7.24.7 @@ -24301,8 +24171,6 @@ snapshots: asynckit@0.4.0: {} - at-least-node@1.0.0: {} - atob@2.1.2: {} atomic-sleep@1.0.0: {} @@ -24329,13 +24197,6 @@ snapshots: transitivePeerDependencies: - supports-color - aws-cdk-lib@2.83.0(constructs@3.4.311): - dependencies: - '@aws-cdk/asset-awscli-v1': 2.2.183 - '@aws-cdk/asset-kubectl-v20': 2.1.1 - '@aws-cdk/asset-node-proxy-agent-v5': 2.0.153 - constructs: 3.4.311 - aws-cdk@2.83.0: optionalDependencies: fsevents: 2.3.2 @@ -24945,8 +24806,6 @@ snapshots: chardet@0.7.0: {} - charenc@0.0.2: {} - check-more-types@2.24.0: {} chokidar@3.5.3: @@ -25426,8 +25285,6 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 - crypt@0.0.2: {} - crypto-hash@3.0.0: {} crypto-random-string@4.0.0: @@ -27417,13 +27274,6 @@ snapshots: jsonfile: 4.0.0 universalify: 0.1.2 - fs-extra@9.1.0: - dependencies: - at-least-node: 1.0.0 - graceful-fs: 4.2.11 - jsonfile: 6.1.0 - universalify: 2.0.0 - fs-minipass@2.1.0: dependencies: minipass: 3.3.4 @@ -28740,25 +28590,6 @@ snapshots: transitivePeerDependencies: - supports-color - jest-cli@29.5.0(@types/node@18.16.16)(ts-node@10.9.1(@swc/core@1.3.37)(@types/node@18.16.16)(typescript@5.5.3)): - dependencies: - '@jest/core': 29.5.0(ts-node@10.9.1(@swc/core@1.3.37)(@types/node@18.16.16)(typescript@5.5.3)) - '@jest/test-result': 29.5.0 - '@jest/types': 29.5.0 - chalk: 4.1.2 - exit: 0.1.2 - graceful-fs: 4.2.11 - import-local: 3.1.0 - jest-config: 29.5.0(@types/node@18.16.16)(ts-node@10.9.1(@swc/core@1.3.37)(@types/node@18.16.16)(typescript@5.5.3)) - jest-util: 29.5.0 - jest-validate: 29.5.0 - prompts: 2.4.2 - yargs: 17.7.2 - transitivePeerDependencies: - - '@types/node' - - supports-color - - ts-node - jest-cli@29.5.0(@types/node@18.16.16)(ts-node@10.9.2(@types/node@18.16.16)(typescript@5.5.3)): dependencies: '@jest/core': 29.5.0(ts-node@10.9.2(@types/node@18.16.16)(typescript@5.5.3)) @@ -28778,36 +28609,6 @@ snapshots: - supports-color - ts-node - jest-config@29.5.0(@types/node@18.16.16)(ts-node@10.9.1(@swc/core@1.3.37)(@types/node@18.16.16)(typescript@5.5.3)): - dependencies: - '@babel/core': 7.22.1 - '@jest/test-sequencer': 29.5.0 - '@jest/types': 29.5.0 - babel-jest: 29.5.0(@babel/core@7.22.1) - chalk: 4.1.2 - ci-info: 3.8.0 - deepmerge: 4.3.1 - glob: 7.2.3 - graceful-fs: 4.2.11 - jest-circus: 29.5.0 - jest-environment-node: 29.5.0 - jest-get-type: 29.4.3 - jest-regex-util: 29.4.3 - jest-resolve: 29.5.0 - jest-runner: 29.5.0 - jest-util: 29.5.0 - jest-validate: 29.5.0 - micromatch: 4.0.5 - parse-json: 5.2.0 - pretty-format: 29.5.0 - slash: 3.0.0 - strip-json-comments: 3.1.1 - optionalDependencies: - '@types/node': 18.16.16 - ts-node: 10.9.1(@swc/core@1.3.37)(@types/node@18.16.16)(typescript@5.5.3) - transitivePeerDependencies: - - supports-color - jest-config@29.5.0(@types/node@18.16.16)(ts-node@10.9.2(@types/node@18.16.16)(typescript@5.5.3)): dependencies: '@babel/core': 7.22.1 @@ -29073,17 +28874,6 @@ snapshots: merge-stream: 2.0.0 supports-color: 8.1.1 - jest@29.5.0(@types/node@18.16.16)(ts-node@10.9.1(@swc/core@1.3.37)(@types/node@18.16.16)(typescript@5.5.3)): - dependencies: - '@jest/core': 29.5.0(ts-node@10.9.1(@swc/core@1.3.37)(@types/node@18.16.16)(typescript@5.5.3)) - '@jest/types': 29.5.0 - import-local: 3.1.0 - jest-cli: 29.5.0(@types/node@18.16.16)(ts-node@10.9.1(@swc/core@1.3.37)(@types/node@18.16.16)(typescript@5.5.3)) - transitivePeerDependencies: - - '@types/node' - - supports-color - - ts-node - jest@29.5.0(@types/node@18.16.16)(ts-node@10.9.2(@types/node@18.16.16)(typescript@5.5.3)): dependencies: '@jest/core': 29.5.0(ts-node@10.9.2(@types/node@18.16.16)(typescript@5.5.3)) @@ -29534,8 +29324,6 @@ snapshots: lodash.transform@4.6.0: {} - lodash.truncate@4.4.2: {} - lodash.union@4.6.0: {} lodash.uniq@4.5.0: {} @@ -29768,12 +29556,6 @@ snapshots: dependencies: blueimp-md5: 2.19.0 - md5@2.3.0: - dependencies: - charenc: 0.0.2 - crypt: 0.0.2 - is-buffer: 1.1.6 - mdast-util-find-and-replace@3.0.1: dependencies: '@types/mdast': 4.0.0 @@ -33860,14 +33642,6 @@ snapshots: '@pkgr/utils': 2.3.1 tslib: 2.6.3 - table@6.8.1: - dependencies: - ajv: 8.12.0 - lodash.truncate: 4.4.2 - slice-ansi: 4.0.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 - tabtab@3.0.2: dependencies: debug: 4.3.4(supports-color@9.2.3) diff --git a/website/src/pages/docs/integrations/integration-with-aws-lambda.mdx b/website/src/pages/docs/integrations/integration-with-aws-lambda.mdx index ad46250e3f..a5ff435860 100644 --- a/website/src/pages/docs/integrations/integration-with-aws-lambda.mdx +++ b/website/src/pages/docs/integrations/integration-with-aws-lambda.mdx @@ -18,7 +18,8 @@ npm i aws-lambda graphql-yoga graphql ## Example ```ts filename="graphql.ts" -import { APIGatewayEvent, APIGatewayProxyResult, Context } from 'aws-lambda' +import { pipeline } from 'stream/promises' +import { APIGatewayEvent, Context } from 'aws-lambda' import { createSchema, createYoga } from 'graphql-yoga' const yoga = createYoga<{ @@ -41,34 +42,79 @@ const yoga = createYoga<{ }) }) -export async function handler( - event: APIGatewayEvent, - lambdaContext: Context -): Promise { - const response = await yoga.fetch( - event.path + - '?' + - new URLSearchParams((event.queryStringParameters as Record) || {}).toString(), - { - method: event.httpMethod, - headers: event.headers as HeadersInit, - body: event.body - ? Buffer.from(event.body, event.isBase64Encoded ? 'base64' : 'utf8') - : undefined - }, - { - event, - lambdaContext +export const handler = awslambda.streamifyResponse( + async function handler(event, res, lambdaContext) { + const response = await yoga.fetch( + // Construct the URL + event.path + + '?' + + // Parse query string parameters + new URLSearchParams( + (event.queryStringParameters as Record) || {} + ).toString(), + { + method: event.httpMethod, + headers: event.headers as HeadersInit, + // Parse the body + body: event.body + ? Buffer.from(event.body, event.isBase64Encoded ? 'base64' : 'utf8') + : undefined + }, + { + event, + lambdaContext, + res + } + ) + + // Create the metadata object for the response + const metadata = { + statusCode: response.status, + headers: Object.fromEntries(response.headers.entries()) + } + + // Attach the metadata to the response stream + const resWithMetadata = awslambda.HttpResponseStream.from(res, metadata) + + // Pipe the response body to the response stream + if (response.body) { + await pipeline(response.body, resWithMetadata) + } else { + resWithMetadata.end() + } + } +) +``` + +If you have typings issues, you might need to add the following extra types; + +```ts filename="awslambda.d.ts" +import { Writable } from 'node:stream' +import { APIGatewayEvent, Context, Handler } from 'aws-lambda' + +declare global { + namespace awslambda { + export namespace HttpResponseStream { + function from( + responseStream: ResponseStream, + metadata: { + statusCode?: number + headers?: Record + } + ): ResponseStream + } + + export type ResponseStream = Writable & { + setContentType(type: string): void } - ) - const responseHeaders = Object.fromEntries(response.headers.entries()) + export type StreamifyHandler = ( + event: APIGatewayEvent, + responseStream: ResponseStream, + context: Context + ) => Promise - return { - statusCode: response.status, - headers: responseHeaders, - body: await response.text(), - isBase64Encoded: false + export function streamifyResponse(handler: StreamifyHandler): Handler } } ```