├── .gitignore ├── .npmignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── bin └── amazon-cloudfront-cache-graphql.ts ├── cdk.json ├── container ├── Dockerfile ├── Gemfile └── app.rb ├── imgs └── architecture.png ├── jest.config.js ├── lambda ├── convert-http-method.py └── disallowed_headers.py ├── lib └── amazon-cloudfront-cache-graphql-stack.ts ├── package-lock.json ├── package.json └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | *.js 2 | !jest.config.js 3 | *.d.ts 4 | node_modules 5 | 6 | # CDK asset staging directory 7 | .cdk.staging 8 | cdk.out 9 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | *.ts 2 | !*.d.ts 3 | 4 | # CDK asset staging directory 5 | .cdk.staging 6 | cdk.out 7 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *main* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 60 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 10 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 11 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 12 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 13 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 14 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 15 | 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cache GraphQL on Amazon CloudFront 2 | 3 | Amazon CloudFront does not cache GraphQL queries. This is because GraphQL queries are internally HTTP POST requests. However, for non-mutation queries, there are cases where you may want to cache them in edge. In this repository, show you how to cache GraphQL query responses in Amazon CloudFront by converting the HTTP method in Lambda@Edge. 4 | 5 | ## Architecture and Cache Strategy 6 | 7 | ![](/imgs/architecture.png) 8 | 9 | ### How to convey the request payload? 10 | 11 | First, when making a GET request to Amazon CloudFront, it is not possible to include a payload in the HTTP body. Therefore, it is necessary to pass the request body as the value of the query parameter or header. Due to Amazon CloudFront restrictions, the query parameter can only be 128 characters long, so the header value, which can be up to 1783 characters long, is used. In addition, by splitting the payload into 5 parts, we are able to cache GraphQL payloads up to 1783 * 5 = 8915 bytes. For payloads larger than that, we bypass the request to the origin. 12 | 13 | ## Deployment 14 | 15 | We will deploy the project using [AWS CDK](https://docs.aws.amazon.com/cdk/v2/guide/getting_started.html). First, we need to bootstrap it. Run the following command in the root directory. 16 | 17 | ```bash 18 | npx cdk bootstrap --region us-east-1 19 | ``` 20 | 21 | Since Lambda@Edge will be deployed to us-east-1, you need to specify the region to bootstrap. If you want to deploy a stack other than Lambda@Edge to a region other than us-east-1, bootstrap it in the same way as the above command. The region where the stack other than Lambda@Edge will be deployed are defined in [/bin/amazon-cloud-front-cache-graphql.ts](/bin/amazon-cloudfront-cache-graphql.ts). 22 | 23 | Run the following command to deploy the all stacks. 24 | ```bash 25 | npx cdk deploy --all 26 | ``` 27 | 28 | If the deployment is successful, the CloudFront endpoint will be output. 29 | 30 | ## Check the Behavior 31 | 32 | Run the following command multiple times. If the response string is the same, then the cache is working. If you change the query even slightly, the cache key will change and you will get a different string. Also, the TTL is set to 1 minute, so if more than 1 minute passes, it should be a different string. Replace the `` to the deployed endpoint. 33 | 34 | ```bash 35 | curl -XPOST -d '{"query":"xxx"}' -H "Content-Type:application/json" /queries 36 | ``` 37 | 38 | ## For the Production Workloads 39 | 40 | There are a few things that need to be considered before applying this to production workloads. 41 | 42 | First, let's talk about mutations. If a GraphQL query contains a mutation, the request should not be cached. Therefore, if there is a possibility that the query is a mutation, we need to parse the query in Lambda@Edge and check if it is a mutation or not. If it is a mutation, bypass the the request to the origin. 43 | 44 | Next, let's talk about authentication. If authentication is included, that is, if the response is user-specific, the authentication header must also be a cache key. In that case, add the authentication header to the allowed headers in CachePolicy. This is to avoid security incidents where cached content is returned without any authentication. Simply put, it is to prevent one user's cached content from becoming another user's response. 45 | 46 | ## Cleanup 47 | 48 | Run the following command. 49 | 50 | ```bash 51 | cdk destroy --all 52 | ``` 53 | 54 | ## Security 55 | 56 | See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more information. 57 | 58 | ## License 59 | 60 | This library is licensed under the MIT-0 License. See the LICENSE file. 61 | -------------------------------------------------------------------------------- /bin/amazon-cloudfront-cache-graphql.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import 'source-map-support/register'; 3 | import * as cdk from 'aws-cdk-lib'; 4 | import { AmazonCloudfrontCacheGraphqlStack } from '../lib/amazon-cloudfront-cache-graphql-stack'; 5 | 6 | const app = new cdk.App(); 7 | new AmazonCloudfrontCacheGraphqlStack(app, 'AmazonCloudfrontCacheGraphqlStack', { 8 | env: { 9 | region: 'us-east-1', 10 | }, 11 | }); 12 | -------------------------------------------------------------------------------- /cdk.json: -------------------------------------------------------------------------------- 1 | { 2 | "app": "npx ts-node --prefer-ts-exts bin/amazon-cloudfront-cache-graphql.ts", 3 | "watch": { 4 | "include": [ 5 | "**" 6 | ], 7 | "exclude": [ 8 | "README.md", 9 | "cdk*.json", 10 | "**/*.d.ts", 11 | "**/*.js", 12 | "tsconfig.json", 13 | "package*.json", 14 | "yarn.lock", 15 | "node_modules", 16 | "test" 17 | ] 18 | }, 19 | "context": { 20 | "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, 21 | "@aws-cdk/core:stackRelativeExports": true, 22 | "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, 23 | "@aws-cdk/aws-lambda:recognizeVersionProps": true, 24 | "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, 25 | "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, 26 | "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true, 27 | "@aws-cdk/core:target-partitions": [ 28 | "aws", 29 | "aws-cn" 30 | ] 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /container/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ruby:3.1.0 2 | 3 | WORKDIR /app 4 | 5 | EXPOSE 80 6 | 7 | RUN bundle config set --local path 'vendor/bundle' 8 | 9 | COPY Gemfile . 10 | 11 | RUN bundle install 12 | 13 | COPY *.rb . 14 | 15 | ENTRYPOINT ["bundle", "exec", "ruby", "app.rb"] 16 | -------------------------------------------------------------------------------- /container/Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "sinatra" 4 | gem "puma" 5 | -------------------------------------------------------------------------------- /container/app.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | require 'sinatra' 3 | require 'securerandom' 4 | 5 | # 6 | # GraphQL Mock Server 7 | # 8 | # The endpoint returns random string. 9 | # 10 | 11 | set :bind, '0.0.0.0' 12 | set :port, 80 13 | 14 | # Healthcheck Endpoint 15 | get '/health' do 16 | '' 17 | end 18 | 19 | # GET Endpoint 20 | get '/' do 21 | # debug 22 | p params 23 | p request.env 24 | 25 | SecureRandom.alphanumeric(8) 26 | end 27 | 28 | # GraphQL Endpoint 29 | post '/queries' do 30 | # Some internal heavy graphql query execution 31 | sleep(rand(0.5..1.0)) 32 | 33 | # debug 34 | p params 35 | p request.env 36 | 37 | if request.body.size > 0 38 | request.body.rewind 39 | p request.body.read 40 | end 41 | 42 | SecureRandom.alphanumeric(8) 43 | end 44 | -------------------------------------------------------------------------------- /imgs/architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-cloudfront-cache-graphql/01415997b50f99fb4540138b962250fb9227cd56/imgs/architecture.png -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testEnvironment: 'node', 3 | roots: ['/test'], 4 | testMatch: ['**/*.test.ts'], 5 | transform: { 6 | '^.+\\.tsx?$': 'ts-jest' 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /lambda/convert-http-method.py: -------------------------------------------------------------------------------- 1 | import urllib.request 2 | import base64 3 | from disallowed_headers import is_blacklisted_or_readonly_header 4 | 5 | # Max cacheable size on each header. 6 | CACHE_PAYLOAD_SIZE_LIMIT = 1783 7 | 8 | # Max splitted number for request payload. 9 | NUM_SPLIT_MAX = 5 10 | 11 | # GraphQL endpoint 12 | GRAPHQL_ENDPOINT = '/queries' 13 | 14 | 15 | # Execute http request 16 | def http_request(endpoint, method='GET', headers={}, data=None): 17 | req = urllib.request.Request(endpoint, method=method, headers=headers, data=data) 18 | res = urllib.request.urlopen(req) 19 | res_code = res.status 20 | res_body = res.read().decode('utf-8') 21 | res_headers = response_headers(res) 22 | 23 | return { 24 | 'status': res_code, 25 | 'headers': remove_disallowed_headers(res_headers), 26 | 'body': res_body 27 | } 28 | 29 | 30 | # Convert urllib response header to cloudfront response header format. 31 | def response_headers(res): 32 | return { 33 | key.lower(): [{'key': key, 'value': val}] 34 | for key, val in res.headers.items() 35 | } 36 | 37 | 38 | # Remove disallowed headers on lambda@edge 39 | def remove_disallowed_headers(headers): 40 | return { 41 | key: val 42 | for key, val in headers.items() 43 | if not is_blacklisted_or_readonly_header(key) 44 | } 45 | 46 | 47 | # Split request payload 48 | def split_payload(data): 49 | cursor = 0 50 | 51 | payloads = [] 52 | 53 | while True: 54 | if len(data[cursor:]) <= CACHE_PAYLOAD_SIZE_LIMIT: 55 | payloads.append(data[cursor:]) 56 | break 57 | else: 58 | payloads.append(data[cursor:cursor+CACHE_PAYLOAD_SIZE_LIMIT]) 59 | cursor += CACHE_PAYLOAD_SIZE_LIMIT 60 | 61 | if len(payloads) < NUM_SPLIT_MAX: 62 | for _ in range(NUM_SPLIT_MAX - len(payloads)): 63 | payloads.append('') 64 | 65 | return payloads 66 | 67 | 68 | # Lambda@Edge handler 69 | def handler(event, context): 70 | cloud_front_event = event['Records'][0]['cf'] 71 | cloud_front_domain_name = cloud_front_event['config']['distributionDomainName'] 72 | request = cloud_front_event['request'] 73 | origin_domain_name = request['origin']['custom']['domainName'] 74 | 75 | if request['uri'] == GRAPHQL_ENDPOINT: 76 | 77 | # Convert POST to GET 78 | if request['method'] == 'POST': 79 | data = request['body']['data'] 80 | payloads = split_payload(data) 81 | 82 | # If number of splitted payloads exceeds NUM_SPLIT_MAX, the payload is too big to be cached. 83 | if len(payloads) > NUM_SPLIT_MAX: 84 | return request 85 | 86 | headers = { 87 | 'Payload0': payloads[0], 88 | 'Payload1': payloads[1], 89 | 'Payload2': payloads[2], 90 | 'Payload3': payloads[3], 91 | 'Payload4': payloads[4], 92 | } 93 | 94 | # Execute GET request to CloudFront (Itself.) 95 | return http_request(f'https://{cloud_front_domain_name}{GRAPHQL_ENDPOINT}', headers=headers) 96 | 97 | # Convert GET to POST 98 | elif request['method'] == 'GET': 99 | # Check the existance of Payload0 ~ Payload4 headers 100 | if 'payload0' not in request['headers'] or \ 101 | 'payload1' not in request['headers'] or \ 102 | 'payload2' not in request['headers'] or \ 103 | 'payload3' not in request['headers'] or \ 104 | 'payload4' not in request['headers']: 105 | return request 106 | 107 | # Concat the splitted payloads 108 | payload = \ 109 | request['headers']['payload0'][0]['value'] + \ 110 | request['headers']['payload1'][0]['value'] + \ 111 | request['headers']['payload2'][0]['value'] + \ 112 | request['headers']['payload3'][0]['value'] + \ 113 | request['headers']['payload4'][0]['value'] 114 | 115 | # Decode base64 payload 116 | data = base64.b64decode(payload) 117 | 118 | headers = { 119 | 'Content-Type': 'application/json', 120 | 'Content-Length': len(data), 121 | } 122 | 123 | # Execute POST request to origin 124 | return http_request(f'http://{origin_domain_name}{GRAPHQL_ENDPOINT}', method='POST', data=data, headers=headers) 125 | 126 | # Bypass request 127 | return request 128 | -------------------------------------------------------------------------------- /lambda/disallowed_headers.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | # Disallowed headers on edge functions 4 | # https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/edge-functions-restrictions.html#edge-function-restrictions-all 5 | _BLACKLISTED_HEADERS = [ 6 | 'Connection', 7 | 'Expect', 8 | 'Keep-Alive', 9 | 'Proxy-Authenticate', 10 | 'Proxy-Authorization', 11 | 'Proxy-Connection', 12 | 'Trailer', 13 | 'Upgrade', 14 | 'X-Accel-Buffering', 15 | 'X-Accel-Charset', 16 | 'X-Accel-Limit-Rate', 17 | 'X-Accel-Redirect', 18 | # X-Amz-Cf-*, 19 | 'X-Amzn-Auth', 20 | 'X-Amzn-Cf-Billing', 21 | 'X-Amzn-Cf-Id', 22 | 'X-Amzn-Cf-Xff', 23 | 'X-Amzn-Errortype', 24 | 'X-Amzn-Fle-Profile', 25 | 'X-Amzn-Header-Count', 26 | 'X-Amzn-Header-Order', 27 | 'X-Amzn-Lambda-Integration-Tag', 28 | 'X-Amzn-RequestId', 29 | 'X-Cache', 30 | # X-Edge-*, 31 | 'X-Forwarded-Proto', 32 | 'X-Real-IP' 33 | ] 34 | 35 | blacklisted_headers_lower = [header.lower() for header in _BLACKLISTED_HEADERS] 36 | 37 | 38 | def is_blacklisted_header(header_name): 39 | pattern = re.compile(r'^x-(amz-cf|edge)-+') 40 | return header_name.lower() in blacklisted_headers_lower or pattern.match(header_name.lower()) 41 | 42 | 43 | # Read-only headers in origin request events (Lambda@Edge only) 44 | # https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/edge-functions-restrictions.html#edge-function-restrictions-all 45 | _READONLY_HEADERS = [ 46 | 'Accept-Encoding', 47 | 'Content-Length', 48 | 'If-Modified-Since', 49 | 'If-None-Match', 50 | 'If-Range', 51 | 'If-Unmodified-Since', 52 | 'Transfer-Encoding', 53 | 'Via' 54 | ] 55 | 56 | readonly_headers_lower = [header.lower() for header in _READONLY_HEADERS] 57 | 58 | 59 | def is_readonly_header(header_name): 60 | return header_name.lower() in readonly_headers_lower 61 | 62 | 63 | def is_blacklisted_or_readonly_header(header_name): 64 | return is_blacklisted_header(header_name) or is_readonly_header(header_name) 65 | -------------------------------------------------------------------------------- /lib/amazon-cloudfront-cache-graphql-stack.ts: -------------------------------------------------------------------------------- 1 | import { Stack, StackProps, CfnOutput, Duration } from 'aws-cdk-lib'; 2 | import { Construct } from 'constructs'; 3 | import * as ec2 from 'aws-cdk-lib/aws-ec2'; 4 | import * as ecs from 'aws-cdk-lib/aws-ecs'; 5 | import * as ecsPatterns from 'aws-cdk-lib/aws-ecs-patterns'; 6 | import * as cfn from 'aws-cdk-lib/aws-cloudfront'; 7 | import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; 8 | import * as lambda from 'aws-cdk-lib/aws-lambda'; 9 | import * as path from 'path'; 10 | 11 | export class AmazonCloudfrontCacheGraphqlStack extends Stack { 12 | constructor(scope: Construct, id: string, props?: StackProps) { 13 | super(scope, id, props); 14 | 15 | const vpc = new ec2.Vpc(this, 'Vpc'); 16 | const cluster = new ecs.Cluster(this, 'Cluster', { 17 | vpc, 18 | }); 19 | 20 | const albFargate = 21 | new ecsPatterns.ApplicationLoadBalancedFargateService(this, 'AlbFargate', { 22 | cluster, 23 | desiredCount: 1, 24 | taskImageOptions: { 25 | image: ecs.ContainerImage.fromAsset(path.join(__dirname, '..', 'container')), 26 | }, 27 | }); 28 | 29 | albFargate.targetGroup.configureHealthCheck({ path: '/health' }); 30 | 31 | const cachePolicy = new cfn.CachePolicy(this, 'CachePolicy', { 32 | headerBehavior: cfn.CacheHeaderBehavior.allowList('Payload0', 'Payload1', 'Payload2', 'Payload3', 'Payload4'), 33 | minTtl: Duration.minutes(1), 34 | defaultTtl: Duration.minutes(1), 35 | }); 36 | 37 | const convertHttpMethod = new cfn.experimental.EdgeFunction(this, 'ConvertHttpMethodLambdaEdge', { 38 | runtime: lambda.Runtime.PYTHON_3_9, 39 | handler: 'convert-http-method.handler', 40 | code: lambda.Code.fromAsset(path.join(__dirname, '..', 'lambda')), 41 | }); 42 | 43 | const edgeDistribution = new cfn.Distribution(this, 'EdgeDistribution', { 44 | defaultBehavior: { 45 | origin: new origins.LoadBalancerV2Origin(albFargate.loadBalancer, { 46 | protocolPolicy: cfn.OriginProtocolPolicy.HTTP_ONLY, 47 | }), 48 | originRequestPolicy: new cfn.OriginRequestPolicy(this, 'OriginRequestPolicy', { 49 | headerBehavior: cfn.OriginRequestHeaderBehavior.allowList('auth-key'), 50 | }), 51 | allowedMethods: cfn.AllowedMethods.ALLOW_ALL, 52 | edgeLambdas: [ 53 | { 54 | functionVersion: convertHttpMethod.currentVersion, 55 | eventType: cfn.LambdaEdgeEventType.ORIGIN_REQUEST, 56 | includeBody: true, 57 | }, 58 | ], 59 | cachePolicy, 60 | }, 61 | }); 62 | 63 | new CfnOutput(this, 'EdgeDistributionEndpoint', { 64 | value: `https://${edgeDistribution.domainName}`, 65 | }); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "amazon-cloudfront-cache-graphql", 3 | "version": "0.1.0", 4 | "bin": { 5 | "amazon-cloudfront-cache-graphql": "bin/amazon-cloudfront-cache-graphql.js" 6 | }, 7 | "scripts": { 8 | "build": "tsc", 9 | "watch": "tsc -w", 10 | "test": "jest", 11 | "cdk": "cdk" 12 | }, 13 | "devDependencies": { 14 | "@types/jest": "^26.0.10", 15 | "@types/node": "10.17.27", 16 | "jest": "^26.4.2", 17 | "ts-jest": "^26.2.0", 18 | "aws-cdk": "2.18.0", 19 | "ts-node": "^9.0.0", 20 | "typescript": "~3.9.7" 21 | }, 22 | "dependencies": { 23 | "aws-cdk-lib": "2.80.0", 24 | "constructs": "^10.0.0", 25 | "source-map-support": "^0.5.16" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2018", 4 | "module": "commonjs", 5 | "lib": [ 6 | "es2018" 7 | ], 8 | "declaration": true, 9 | "strict": true, 10 | "noImplicitAny": true, 11 | "strictNullChecks": true, 12 | "noImplicitThis": true, 13 | "alwaysStrict": true, 14 | "noUnusedLocals": false, 15 | "noUnusedParameters": false, 16 | "noImplicitReturns": true, 17 | "noFallthroughCasesInSwitch": false, 18 | "inlineSourceMap": true, 19 | "inlineSources": true, 20 | "experimentalDecorators": true, 21 | "strictPropertyInitialization": false, 22 | "typeRoots": [ 23 | "./node_modules/@types" 24 | ] 25 | }, 26 | "exclude": [ 27 | "node_modules", 28 | "cdk.out" 29 | ] 30 | } 31 | --------------------------------------------------------------------------------