├── .gitignore ├── README.md ├── diagrams ├── README.md ├── api-custom-authorizer.drawio.svg ├── api-service-integration.drawio.svg ├── api-webhook-proxy.drawio.svg ├── api-with-database-data-api.drawio.svg ├── api-with-database-vpc.drawio.svg ├── cross-account-event-bridge.drawio.svg ├── event-pipeline.drawio.svg ├── queue-lambda-dlq.drawio.svg ├── s3-index-dynamo.drawio.svg ├── s3-multiple-subscriptions.drawio.svg ├── s3-single-subscription.drawio.svg ├── s3-upload-with-multiple-subscriptions.drawio.svg ├── s3-upload.drawio.svg └── ssm-infra-parameters.drawio.svg └── examples ├── cdk ├── .gitignore ├── .npmignore ├── .prettierignore ├── .prettierrc.js ├── README.md ├── bin │ └── cdk.ts ├── cdk.json ├── jest.config.js ├── lambdas │ ├── echo │ │ └── index.ts │ └── signed-url │ │ └── index.ts ├── lib │ ├── database-stack.ts │ ├── network-stack.ts │ ├── s3-multiple-subscriptions-stack.ts │ ├── s3-single-subscription-stack.ts │ └── s3-upload-file-stack.ts ├── package.json ├── test │ └── cdk.test.ts ├── tsconfig.json └── yarn.lock ├── clients ├── .gitignore ├── package.json ├── tsconfig.json ├── upload-file-s3.ts └── yarn.lock └── serverless ├── api-authorizer ├── .babelrc ├── .gitignore ├── .prettierignore ├── .prettierrc.js ├── package.json ├── serverless.yml ├── src │ ├── authorizer │ │ └── index.ts │ └── echo │ │ └── index.ts ├── tsconfig.json ├── tslint.json ├── webpack.config.js └── yarn.lock ├── api-database ├── .babelrc ├── .gitignore ├── .prettierignore ├── .prettierrc.js ├── package.json ├── serverless.yml ├── src │ ├── data-api │ │ ├── database.ts │ │ └── index.ts │ └── postgres │ │ ├── database.ts │ │ └── index.ts ├── tsconfig.json ├── tslint.json ├── webpack.config.js └── yarn.lock └── api-service-integration ├── .babelrc ├── .gitignore ├── .prettierignore ├── .prettierrc.js ├── package.json ├── serverless.yml ├── src └── echo │ └── index.ts ├── tsconfig.json ├── tslint.json ├── webpack.config.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .tmp 3 | node_modules 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MaaS Global AWS Design Patterns 2 | 3 | Table of Contents 4 | 5 | - [Introduction](#introduction) 6 | - [API](#api) 7 | * [API with a custom authorizer](#api-with-a-custom-authorizer) 8 | * [API with a database connection using PostgreSQL protocol](#api-with-a-database-connection-using-postgresql-protocol) 9 | * [API with an Aurora connection using Data API](#api-with-an-aurora-connection-using-data-api) 10 | * [API with asynchronous processing](#api-with-asynchronous-processing) 11 | * [Webhook Proxy](#webhook-proxy) 12 | - [S3](#s3) 13 | * [S3 bucket event trigger with multiple subscriptions](#s3-bucket-event-trigger-with-multiple-subscriptions) 14 | * [S3 bucket event trigger with a single subscription](#s3-bucket-event-trigger-with-a-single-subscription) 15 | * [S3 object index in a DynamoDB table](#s3-object-index-in-a-dynamodb-table) 16 | * [S3 File Upload with API Gateway](#s3-file-upload-with-api-gateway) 17 | - [Queues and Pipelines](#queues-and-pipelines) 18 | * [Dead Letter Queue (DLQ)](#dead-letter-queue-dlq) 19 | * [High-volume event pipeline)](#high-volume-event-pipeline) 20 | - [Deployment](#deployment) 21 | * [Usage of Systems Manager Parameter Store in Deployment](#usage-of-systems-manager-parameter-store-in-deployment) 22 | 23 | # Introduction 24 | 25 | This is a collection of backend infrastructure and architecture design patterns that are considered best practices when we do development in MaaS Global. These patterns can be combined to build larger architectures. 26 | 27 | # API 28 | 29 | ## API with a custom authorizer 30 | 31 | A custom authorizer Lambda is a function that validates a header from the requests. Usually, this header name is Authorization and the value is a Json Web Token which is signed and expires after a certain time. 32 | 33 |  34 | 35 | Serverless Framework example: [examples/serverless/api-authorizer/serverless.yml](examples/serverless/api-authorizer/serverless.yml) 36 | 37 | ## API with a database connection using PostgreSQL protocol 38 | 39 | When connecting to a database with PostgreSQL protocol, the Lambda function needs to be in the same VPC. The VPC should be divided into an application tier and a data tier. The application tier subnets should be at the least in two availability zones when running the production payload. 40 | 41 |  42 | 43 | Serverless example: [examples/serverless/api-database/serverless.yml](examples/serverless/api-database/serverless.yml#L57-L69) 44 | 45 | ## API with an Aurora connection using Data API 46 | 47 | The Amazon Aurora database cluster allows connection using the Data API. It doesn’t require a persistent connection, but instead, it uses a secured HTTP endpoint. In some cases, the connection might have more latency than e.g. PostgresSQL connection, but the cold start time of the Lambda function is shorter when it doesn’t have to be inside the VPC. 48 | 49 |  50 | 51 | Serverless example: [examples/serverless/api-database/serverless.yml](examples/serverless/api-database/serverless.yml#L50-L56) 52 | 53 | ### Performance comparison 54 | 55 | The performance of the data api and the postgresql protocol is quite even. The results of a quick performance test using artillery.io confirms it. 56 | 57 | ```yaml 58 | POSTGRES: 59 | Scenarios launched: 1000 60 | Scenarios completed: 1000 61 | Requests completed: 1000 62 | Mean response/sec: 95.24 63 | Response time (msec): 64 | min: 65 65 | max: 2337 # VPC Lambda cold start 66 | median: 78 67 | p95: 133 68 | p99: 1509.5 69 | Scenario counts: 70 | 0: 1000 (100%) 71 | Codes: 72 | 200: 1000 73 | 74 | DATA-API: 75 | Scenarios launched: 1000 76 | Scenarios completed: 1000 77 | Requests completed: 1000 78 | Mean response/sec: 95.51 79 | Response time (msec): 80 | min: 61 81 | max: 335 82 | median: 75 83 | p95: 99 84 | p99: 162 85 | Scenario counts: 86 | 0: 1000 (100%) 87 | Codes: 88 | 200: 1000 89 | ``` 90 | 91 | ## API with asynchronous processing 92 | 93 | If API doesn't need to send the response synchronously, the compute layer might not be needed. Amazon API Gateway supports service integrations, where e.g. messages to the SQS queue can be sent straight from the request. 94 | 95 |  96 | 97 | Serverless example: [examples/serverless/api-service-integration/serverless.yml](examples/serverless/api-service-integration/serverless.yml) 98 | 99 | ## Webhook Proxy 100 | 101 | In some cases, webhooks needs to be distributed asynchronously to multiple recipients. For example some SaaS services has hard limit how many webhook URLs can be defined to their system, and when developing with multiple development stages, that limit is reached quickly. To avoid defining those recipients beforehand, the payload can be pushed forward e.g. using DynamoDB streams. 102 | 103 | In this approach, the API gateway receives the webhook and responses with 200 status code. The webhook payload is stored to a DynamoDB table that has streams enabled. The development stage Lambda functions listens that stream and processes the payload if it's meant to that stage. 104 | 105 |  106 | 107 | # S3 108 | 109 | ## S3 bucket event trigger with multiple subscriptions 110 | 111 | To subscribe to multiple sources when the object is created, modified, or deleted, the use of SNS topic is the easiest way. 112 | 113 |  114 | 115 | CDK example: [examples/cdk/lib/s3-multiple-subscriptions-stack.ts](examples/cdk/lib/s3-multiple-subscriptions-stack.ts) 116 | 117 | ## S3 bucket event trigger with a single subscription 118 | 119 | If it’s known that there is no possibility that multiple processors would subscribe to S3 events, an SQS or a Lambda trigger can be used in the subscription. 120 | 121 |  122 | 123 | CDK example: [examples/cdk/lib/s3-single-subscription-stack.ts](examples/cdk/lib/s3-single-subscription-stack.ts) 124 | 125 | ## S3 object index in a DynamoDB table 126 | 127 | The raw event data in S3 buckets, for example, logs or other events, can contain data that needs to be anonymized or deleted based on legislation. The amount of data might be so big that it doesn't make sense to go through the whole bucket when someone requests data removal. In this kind of situation storing an index of objects to a DynamoDB table is one option. The table contains metadata of the object, which user's data is in that file, and things like that, which can be used in the DynamoDB query to identify files that need to be processed. 128 | 129 |  130 | 131 | ## S3 File Upload with API Gateway 132 | 133 | When uploading files that are larger than API Gateway can handle, the following pattern is a one way to achieve it. The API Gateway and a Lambda function are used to generate a signed URL for the upload and returned to the client. That URL is then used to upload the file to the S3 bucket. 134 | 135 |  136 | 137 | CDK example: [examples/cdk/lib/s3-upload-file-stack.ts](examples/cdk/lib/s3-upload-file-stack.ts) 138 | 139 | Client example: [examples/clients/upload-file-s3.ts](examples/clients/upload-file-s3.ts) 140 | 141 | # Queues and Pipelines 142 | 143 | ## Dead Letter Queue (DLQ) 144 | 145 | Asynchronously triggered Lambdas retries the execution automatically on failure a few times. To catch failed execution a dead letter queue is an option that can be used to notify failures, retry executions later, and keep the history of failed executions. 146 | 147 |  148 | 149 | ## High-volume event pipeline 150 | 151 | The fan-in approach should be used with high-volume event pipelines. The event emitter can be, e.g. Lambda functions or CloudWatch logs, which needs to be stored and processed later. 152 | 153 |  154 | 155 | ## Cross-account Amazon EventBridge 156 | 157 | The fan-in approach can also be used with a cross-account Amazon EventBridge pattern. For example, CloudWatch alarms from multiple AWS accounts can be pushed to a separate account that handles the alarms centralized. 158 | 159 |  160 | 161 | # Deployment 162 | 163 | ## Usage of Systems Manager Parameter Store in Deployment 164 | 165 | Sharing parameters between service deployments can be done multiple ways, for example, exporting output from the CloudFormation template and importing that to the next stack. In this approach, changes that require replacement in the exporting stack will cause issues - exported params cannot be changed if other stack imports those. Removing the importing stack will cause long downtime. 166 | 167 | One option is to write parameters to the SSM parameter store and use those in the deployment that requires them. Of course, when the parameters change in the exporting stack, there will be downtime, but it will be less than with the removal and redeploy approach. 168 | 169 | This also allows stacks to be portable between accounts, and don't rely on stack names to be the same. 170 | 171 |  172 | 173 | CDK example (export VPC details): [examples/cdk/lib/network-stack.ts](examples/cdk/lib/network-stack.ts#L33-L44) 174 | 175 | Serverless example (import VPC details): [examples/serverless/api-database/serverless.yml](examples/serverless/api-database/serverless.yml#L64-L65) 176 | 177 | CDK example (export database details): [examples/cdk/lib/database-stack.ts](examples/cdk/lib/database-stack.ts#L66-L84) 178 | 179 | Serverless example (import database details): [examples/serverless/api-database/serverless.yml](examples/serverless/api-database/serverless.yml#L12-L35) 180 | Serverless example (import database details): [examples/serverless/api-database/src/data-api/database.ts](examples/serverless/api-database/src/data-api/database.ts#L31-L36) 181 | -------------------------------------------------------------------------------- /diagrams/README.md: -------------------------------------------------------------------------------- 1 | Diagrams are made with vscode-drawio plugin https://marketplace.visualstudio.com/items?itemName=hediet.vscode-drawio 2 | -------------------------------------------------------------------------------- /diagrams/api-custom-authorizer.drawio.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | Internet 23 | 24 | 25 | 26 | 27 | 28 | Internet 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | API Gateway 42 | 43 | 44 | 45 | 46 | 47 | API Gateway 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | Authorizer Lambda 60 | 61 | 62 | 63 | 64 | 65 | Authorizer La... 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | Integration Lambda 78 | 79 | 80 | 81 | 82 | 83 | Integration L... 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | Viewer does not support full SVG 1.1 94 | 95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /diagrams/api-service-integration.drawio.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | Internet 23 | 24 | 25 | 26 | 27 | 28 | Internet 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | API Gateway 42 | 43 | 44 | 45 | 46 | 47 | API Gateway 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | Integration SQS 61 | 62 | 63 | 64 | 65 | 66 | Integration S... 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | Consumer Lambda 79 | 80 | 81 | 82 | 83 | 84 | Consumer Lamb... 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | Viewer does not support full SVG 1.1 94 | 95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /diagrams/event-pipeline.drawio.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | Event Emitter 27 | 28 | 29 | 30 | 31 | 32 | Event Emitter 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | Event Emitter 46 | 47 | 48 | 49 | 50 | 51 | Event Emitter 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | Event Emitter 65 | 66 | 67 | 68 | 69 | 70 | Event Emitter 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | Kinesis Firehose 84 | 85 | 86 | 87 | 88 | 89 | Kinesis Fireh... 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | S3 Bucket 102 | 103 | Raw event storage 104 | 105 | 106 | 107 | 108 | 109 | S3 Bucket... 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | Viewer does not support full SVG 1.1 119 | 120 | 121 | 122 | 123 | -------------------------------------------------------------------------------- /diagrams/queue-lambda-dlq.drawio.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | Failed execution 25 | 26 | 27 | 28 | 29 | 30 | Failed execution 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | Asynchronous 43 | 44 | Lambda 45 | 46 | 47 | 48 | 49 | 50 | Asynchronous... 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | SQS 64 | 65 | Dead Letter Queue 66 | 67 | 68 | 69 | 70 | 71 | SQS... 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | Scheduled 84 | 85 | Retry Execution 86 | 87 | 88 | 89 | 90 | 91 | Scheduled... 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | Cloudwatch alarm 104 | 105 | based on 106 | 107 | DLQ metrics 108 | 109 | 110 | 111 | 112 | 113 | Cloudwatch al... 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | Viewer does not support full SVG 1.1 123 | 124 | 125 | 126 | 127 | -------------------------------------------------------------------------------- /diagrams/s3-multiple-subscriptions.drawio.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | S3 Bucket 31 | 32 | 33 | 34 | 35 | 36 | S3 Bucket 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | SNS Topic 52 | 53 | 54 | 55 | 56 | 57 | SNS Topic 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | Lambda 70 | 71 | 72 | 73 | 74 | 75 | Lambda 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | SQS 88 | 89 | 90 | 91 | 92 | 93 | SQS 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | Firehose 106 | 107 | 108 | 109 | 110 | 111 | Firehose 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | Viewer does not support full SVG 1.1 121 | 122 | 123 | 124 | 125 | -------------------------------------------------------------------------------- /diagrams/s3-single-subscription.drawio.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | S3 Bucket 23 | 24 | 25 | 26 | 27 | 28 | S3 Bucket 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | Lambda 41 | 42 | 43 | 44 | 45 | 46 | Lambda 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | Viewer does not support full SVG 1.1 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /diagrams/s3-upload-with-multiple-subscriptions.drawio.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /diagrams/s3-upload.drawio.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | Upload the file 26 | 27 | using signed URL 28 | 29 | 30 | 31 | 32 | 33 | Upload the file... 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | Internet 46 | 47 | 48 | 49 | 50 | 51 | Internet 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | API Gateway 65 | 66 | 67 | 68 | 69 | 70 | API Gateway 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | S3 Bucket 83 | 84 | 85 | 86 | 87 | 88 | S3 Bucket 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | Lambda 103 | 104 | Get signed URL 105 | 106 | 107 | 108 | 109 | 110 | 111 | Lambda... 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | Viewer does not support full SVG 1.1 121 | 122 | 123 | 124 | 125 | -------------------------------------------------------------------------------- /diagrams/ssm-infra-parameters.drawio.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | Fetch infra parameters 17 | 18 | 19 | 20 | 21 | 22 | Fetch infra parameters 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | SSM Parameter Store 35 | 36 | 37 | 38 | 39 | 40 | SSM Parameter... 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | Store infra parameters 52 | 53 | 54 | 55 | 56 | 57 | Store infra parameters 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | Serverless/CDK 70 | 71 | CloudFormation 72 | 73 | 74 | 75 | 76 | 77 | Serverless/CD... 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | Serverless/CDK 90 | 91 | CloudFormation 92 | 93 | 94 | 95 | 96 | 97 | Serverless/CD... 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | Viewer does not support full SVG 1.1 107 | 108 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /examples/cdk/.gitignore: -------------------------------------------------------------------------------- 1 | *.js 2 | !jest.config.js 3 | !.prettierrc.js 4 | *.d.ts 5 | node_modules 6 | 7 | # CDK asset staging directory 8 | .cdk.staging 9 | cdk.out 10 | -------------------------------------------------------------------------------- /examples/cdk/.npmignore: -------------------------------------------------------------------------------- 1 | *.ts 2 | !*.d.ts 3 | 4 | # CDK asset staging directory 5 | .cdk.staging 6 | cdk.out 7 | -------------------------------------------------------------------------------- /examples/cdk/.prettierignore: -------------------------------------------------------------------------------- 1 | # Ignore artifacts: 2 | build 3 | coverage 4 | .cdk.staging 5 | cdk.out 6 | -------------------------------------------------------------------------------- /examples/cdk/.prettierrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = require('eslint-config-maasglobal/.prettierrc'); 4 | -------------------------------------------------------------------------------- /examples/cdk/README.md: -------------------------------------------------------------------------------- 1 | # Welcome to your CDK TypeScript project! 2 | 3 | This is a blank project for TypeScript development with CDK. 4 | 5 | The `cdk.json` file tells the CDK Toolkit how to execute your app. 6 | 7 | ## Useful commands 8 | 9 | - `yarn build` compile typescript to js 10 | - `yarn watch` watch for changes and compile 11 | - `yarn test` perform the jest unit tests 12 | - `yarn cdk deploy` deploy this stack to your default AWS account/region 13 | - `yarn cdk diff` compare deployed stack with current state 14 | - `yarn cdk synth` emits the synthesized CloudFormation template 15 | -------------------------------------------------------------------------------- /examples/cdk/bin/cdk.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import 'source-map-support/register'; 3 | import * as cdk from 'aws-cdk-lib'; 4 | import { NetworkStack } from '../lib/network-stack'; 5 | import { DatabaseStack } from '../lib/database-stack'; 6 | import { S3MultipleSubscriptionsStack } from '../lib/s3-multiple-subscriptions-stack'; 7 | import { S3SingleSubscriptionStack } from '../lib/s3-single-subscription-stack'; 8 | import { S3UploadFileStack } from '../lib/s3-upload-file-stack'; 9 | 10 | const app = new cdk.App(); 11 | const network = new NetworkStack(app, 'example-network', {}); 12 | new DatabaseStack(app, 'example-database', { vpc: network.vpc }); 13 | new S3MultipleSubscriptionsStack(app, 'example-s3-multiple-subscriptions', {}); 14 | new S3SingleSubscriptionStack(app, 'example-s3-single-subscription', {}); 15 | new S3UploadFileStack(app, 'example-s3-upload-file', {}); 16 | -------------------------------------------------------------------------------- /examples/cdk/cdk.json: -------------------------------------------------------------------------------- 1 | { 2 | "app": "npx ts-node --prefer-ts-exts bin/cdk.ts", 3 | "context": { 4 | "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, 5 | "@aws-cdk/core:stackRelativeExports": "true", 6 | "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, 7 | "@aws-cdk/aws-lambda:recognizeVersionProps": true, 8 | "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /examples/cdk/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 | -------------------------------------------------------------------------------- /examples/cdk/lambdas/echo/index.ts: -------------------------------------------------------------------------------- 1 | import { APIGatewayProxyEventV2, SNSEvent, SQSEvent } from 'aws-lambda'; 2 | 3 | export const handler = async (event: SQSEvent | SNSEvent | APIGatewayProxyEventV2) => { 4 | console.log(JSON.stringify(event, null, 2)); 5 | return { 6 | statusCode: 200, 7 | body: JSON.stringify({ event }), 8 | }; 9 | }; 10 | -------------------------------------------------------------------------------- /examples/cdk/lambdas/signed-url/index.ts: -------------------------------------------------------------------------------- 1 | import { APIGatewayProxyEvent } from 'aws-lambda'; 2 | import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3'; 3 | import { getSignedUrl } from '@aws-sdk/s3-request-presigner'; 4 | import { v4 as uuidv4 } from 'uuid'; 5 | 6 | const client = new S3Client({ 7 | region: process.env.AWS_REGION || process.env.AWS_DEFAULT_REGION || 'eu-west-1', 8 | }); 9 | export const handler = async (event: APIGatewayProxyEvent) => { 10 | console.log(JSON.stringify(event, null, 2)); 11 | const url = await getSignedUrl( 12 | client, 13 | new PutObjectCommand({ 14 | Bucket: process.env.BUCKET, 15 | Key: `upload/${uuidv4()}`, 16 | }), 17 | { expiresIn: 900 } 18 | ); 19 | return { 20 | statusCode: 200, 21 | body: JSON.stringify({ url }), 22 | }; 23 | }; 24 | -------------------------------------------------------------------------------- /examples/cdk/lib/database-stack.ts: -------------------------------------------------------------------------------- 1 | import { aws_ec2, aws_rds, aws_secretsmanager, aws_ssm, Duration, Stack, StackProps } from 'aws-cdk-lib'; 2 | import { Construct } from 'constructs'; 3 | 4 | interface DatabaseProps extends StackProps { 5 | vpc: aws_ec2.IVpc; 6 | } 7 | 8 | export class DatabaseStack extends Stack { 9 | constructor(scope: Construct, id: string, props: DatabaseProps) { 10 | super(scope, id, props); 11 | const { vpc } = props; 12 | 13 | const secret = new aws_secretsmanager.Secret(this, 'ClusterSecret', { 14 | generateSecretString: { 15 | secretStringTemplate: JSON.stringify({ username: 'master_user' }), 16 | generateStringKey: 'password', 17 | excludePunctuation: true, 18 | includeSpace: false, 19 | }, 20 | }); 21 | 22 | const clusterSecurityGroup = new aws_ec2.SecurityGroup(this, 'ClusterSecurityGroup', { vpc }); 23 | 24 | vpc.privateSubnets.forEach((privateSubnet) => { 25 | clusterSecurityGroup.addIngressRule( 26 | aws_ec2.Peer.ipv4(privateSubnet.ipv4CidrBlock), 27 | aws_ec2.Port.tcp(5432), 28 | `Allow private subnet ${privateSubnet.availabilityZone}` 29 | ); 30 | }); 31 | 32 | const cluster = new aws_rds.ServerlessCluster(this, 'Cluster', { 33 | enableDataApi: true, 34 | deletionProtection: true, 35 | engine: aws_rds.DatabaseClusterEngine.AURORA_POSTGRESQL, 36 | parameterGroup: aws_rds.ParameterGroup.fromParameterGroupName( 37 | this, 38 | 'ParameterGroup', 39 | 'default.aurora-postgresql10' 40 | ), 41 | securityGroups: [clusterSecurityGroup], 42 | vpc, 43 | vpcSubnets: { 44 | subnetGroupName: 'data', 45 | }, 46 | scaling: { 47 | autoPause: Duration.minutes(15), 48 | minCapacity: aws_rds.AuroraCapacityUnit.ACU_2, 49 | maxCapacity: aws_rds.AuroraCapacityUnit.ACU_16, 50 | }, 51 | credentials: aws_rds.Credentials.fromSecret(secret), 52 | }); 53 | 54 | new aws_ssm.StringParameter(this, 'ClusterSecretArnParameter', { 55 | stringValue: secret.secretArn, 56 | parameterName: '/examples/infra/database/secret/arn', 57 | }); 58 | 59 | new aws_ssm.StringParameter(this, 'ClusterEndpointHostParameter', { 60 | stringValue: cluster.clusterEndpoint.hostname, 61 | parameterName: '/examples/infra/database/endpoint/hostname', 62 | }); 63 | 64 | new aws_ssm.StringParameter(this, 'ClusterClusterArnParameter', { 65 | stringValue: cluster.clusterArn, 66 | parameterName: '/examples/infra/database/cluster/arn', 67 | }); 68 | 69 | new aws_ssm.StringParameter(this, 'ClusterSecurityGroupIdParameter', { 70 | stringValue: clusterSecurityGroup.securityGroupId, 71 | parameterName: '/examples/infra/database/security-group/id', 72 | }); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /examples/cdk/lib/network-stack.ts: -------------------------------------------------------------------------------- 1 | import { aws_ec2, aws_ssm, Stack, StackProps } from 'aws-cdk-lib'; 2 | import { Construct } from 'constructs'; 3 | 4 | export class NetworkStack extends Stack { 5 | public vpc: aws_ec2.Vpc; 6 | constructor(scope: Construct, id: string, props?: StackProps) { 7 | super(scope, id, props); 8 | 9 | // Create three tier VPC 10 | this.vpc = new aws_ec2.Vpc(this, 'ContentVPC', { 11 | cidr: '10.1.0.0/16', 12 | maxAzs: 2, 13 | subnetConfiguration: [ 14 | { 15 | cidrMask: 24, 16 | name: 'public', 17 | subnetType: aws_ec2.SubnetType.PUBLIC, 18 | }, 19 | { 20 | cidrMask: 24, 21 | name: 'application', 22 | subnetType: aws_ec2.SubnetType.PRIVATE_WITH_NAT, 23 | }, 24 | { 25 | cidrMask: 24, 26 | name: 'data', 27 | subnetType: aws_ec2.SubnetType.PRIVATE_ISOLATED, 28 | }, 29 | ], 30 | }); 31 | 32 | // Export vpc id 33 | new aws_ssm.StringParameter(this, 'VpcIdParameter', { 34 | stringValue: this.vpc.vpcId, 35 | parameterName: '/examples/infra/vpc/id', 36 | }); 37 | 38 | // Export private subnets 39 | this.vpc.privateSubnets.forEach((privateSubnet, index) => { 40 | new aws_ssm.StringParameter(this, `SubnetIdParameter${index}`, { 41 | stringValue: privateSubnet.subnetId, 42 | parameterName: `/examples/infra/vpc/private-subnet-${index}/id`, 43 | }); 44 | }); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /examples/cdk/lib/s3-multiple-subscriptions-stack.ts: -------------------------------------------------------------------------------- 1 | import { 2 | aws_lambda, 3 | aws_lambda_event_sources, 4 | aws_lambda_nodejs, 5 | aws_s3, 6 | aws_s3_notifications, 7 | aws_sns, 8 | aws_sns_subscriptions, 9 | aws_sqs, 10 | Duration, 11 | Stack, 12 | StackProps, 13 | } from 'aws-cdk-lib'; 14 | import { Construct } from 'constructs'; 15 | import * as path from 'path'; 16 | 17 | export class S3MultipleSubscriptionsStack extends Stack { 18 | constructor(scope: Construct, id: string, props?: StackProps) { 19 | super(scope, id, props); 20 | 21 | // an S3 source bucket 22 | const bucket = new aws_s3.Bucket(this, 'Bucket', {}); 23 | 24 | // an SNS topic for S3 event subscription 25 | const topic = new aws_sns.Topic(this, 'Topic', { 26 | topicName: 's3-multiple-subscriptions', 27 | displayName: 's3-multiple-subscriptions', 28 | }); 29 | 30 | bucket.addEventNotification(aws_s3.EventType.OBJECT_CREATED, new aws_s3_notifications.SnsDestination(topic)); 31 | 32 | // a Lambda function as a subscription 33 | const echoLambda = new aws_lambda_nodejs.NodejsFunction(this, 'EchoLambda', { 34 | runtime: aws_lambda.Runtime.NODEJS_14_X, 35 | handler: 'handler', 36 | entry: path.join(__dirname, '..', 'lambdas', 'echo', 'index.ts'), 37 | timeout: Duration.seconds(10), 38 | bundling: { 39 | sourceMap: false, 40 | minify: false, 41 | target: 'es2020', 42 | externalModules: ['aws-sdk'], 43 | }, 44 | }); 45 | 46 | echoLambda.addEventSource(new aws_lambda_event_sources.SnsEventSource(topic)); 47 | 48 | // an SQS queue as a subscription 49 | const queue = new aws_sqs.Queue(this, 'Queue', { 50 | queueName: 's3-multiple-subscriptions', 51 | retentionPeriod: Duration.hours(2), // clean up test queue after 2 hours 52 | }); 53 | 54 | topic.addSubscription(new aws_sns_subscriptions.SqsSubscription(queue)); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /examples/cdk/lib/s3-single-subscription-stack.ts: -------------------------------------------------------------------------------- 1 | import { aws_lambda, aws_lambda_nodejs, aws_s3, aws_s3_notifications, Duration, Stack, StackProps } from 'aws-cdk-lib'; 2 | import { Construct } from 'constructs'; 3 | import * as path from 'path'; 4 | 5 | export class S3SingleSubscriptionStack extends Stack { 6 | constructor(scope: Construct, id: string, props?: StackProps) { 7 | super(scope, id, props); 8 | 9 | // an S3 source bucket, object lock can be used to prevent double uploads 10 | const bucket = new aws_s3.Bucket(this, 'Bucket', {}); 11 | 12 | // a Lambda function as a subscription 13 | const echoLambda = new aws_lambda_nodejs.NodejsFunction(this, 'EchoLambda', { 14 | runtime: aws_lambda.Runtime.NODEJS_14_X, 15 | handler: 'handler', 16 | entry: path.join(__dirname, '..', 'lambdas', 'echo', 'index.ts'), 17 | timeout: Duration.seconds(10), 18 | bundling: { 19 | sourceMap: false, 20 | minify: false, 21 | target: 'es2020', 22 | externalModules: ['aws-sdk'], 23 | }, 24 | }); 25 | 26 | bucket.addEventNotification( 27 | aws_s3.EventType.OBJECT_CREATED, 28 | new aws_s3_notifications.LambdaDestination(echoLambda) 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /examples/cdk/lib/s3-upload-file-stack.ts: -------------------------------------------------------------------------------- 1 | import { aws_apigateway, aws_lambda, aws_lambda_nodejs, aws_s3, Duration, Stack, StackProps } from 'aws-cdk-lib'; 2 | import { Construct } from 'constructs'; 3 | import * as path from 'path'; 4 | 5 | export class S3UploadFileStack extends Stack { 6 | constructor(scope: Construct, id: string, props?: StackProps) { 7 | super(scope, id, props); 8 | 9 | const bucket = new aws_s3.Bucket(this, 'Bucket', {}); 10 | 11 | const signedUrlLambda = new aws_lambda_nodejs.NodejsFunction(this, 'SignedUrlLambda', { 12 | runtime: aws_lambda.Runtime.NODEJS_14_X, 13 | handler: 'handler', 14 | entry: path.join(__dirname, '..', 'lambdas', 'signed-url', 'index.ts'), 15 | timeout: Duration.seconds(10), 16 | environment: { 17 | BUCKET: bucket.bucketName, 18 | }, 19 | bundling: { 20 | sourceMap: false, 21 | minify: false, 22 | target: 'es2020', 23 | externalModules: ['aws-sdk'], 24 | }, 25 | }); 26 | 27 | bucket.grantWrite(signedUrlLambda); 28 | 29 | // Proxy API Gateway 30 | const api = new aws_apigateway.LambdaRestApi(this, 'ProxyApiGateway', { 31 | handler: signedUrlLambda, 32 | restApiName: 'example-s3-upload', 33 | apiKeySourceType: aws_apigateway.ApiKeySourceType.HEADER, 34 | defaultMethodOptions: { 35 | apiKeyRequired: true, 36 | }, 37 | }); 38 | const apiKey = api.addApiKey('example-signed-url-api-key'); 39 | const usagePlan = api.addUsagePlan('example-s3-upload'); 40 | usagePlan.addApiKey(apiKey); 41 | usagePlan.addApiStage({ stage: api.deploymentStage }); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /examples/cdk/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cdk", 3 | "version": "0.1.0", 4 | "bin": { 5 | "cdk": "bin/cdk.js" 6 | }, 7 | "scripts": { 8 | "build": "tsc", 9 | "watch": "tsc -w", 10 | "test": "jest", 11 | "cdk": "cdk", 12 | "prettier": "prettier --write ." 13 | }, 14 | "devDependencies": { 15 | "@types/jest": "^27.0.3", 16 | "@types/node": "16.11.12", 17 | "@types/uuid": "^8.3.3", 18 | "aws-cdk": "2.1.0", 19 | "eslint-config-maasglobal": "^4.0.1", 20 | "jest": "^27.4.3", 21 | "prettier": "^2.5.1", 22 | "ts-jest": "^27.1.1", 23 | "ts-node": "^10.4.0", 24 | "typescript": "~4.5.2" 25 | }, 26 | "dependencies": { 27 | "@aws-sdk/client-s3": "^3.44.0", 28 | "@aws-sdk/s3-request-presigner": "^3.44.0", 29 | "@types/aws-lambda": "^8.10.86", 30 | "aws-cdk-lib": "2.1.0", 31 | "aws-lambda": "^1.0.7", 32 | "constructs": "^10.0.10", 33 | "source-map-support": "^0.5.21", 34 | "uuid": "^8.3.2" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /examples/cdk/test/cdk.test.ts: -------------------------------------------------------------------------------- 1 | import * as cdk from 'aws-cdk-lib'; 2 | import * as Cdk from '../lib/cdk-stack'; 3 | 4 | test('Empty Stack', () => { 5 | const app = new cdk.App(); 6 | // WHEN 7 | const stack = new Cdk.CdkStack(app, 'MyTestStack'); 8 | // THEN 9 | const actual = app.synth().getStackArtifact(stack.artifactId).template; 10 | expect(actual.Resources ?? {}).toEqual({}); 11 | }); 12 | -------------------------------------------------------------------------------- /examples/cdk/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2018", 4 | "module": "commonjs", 5 | "lib": ["es2018"], 6 | "declaration": true, 7 | "strict": true, 8 | "noImplicitAny": true, 9 | "strictNullChecks": true, 10 | "noImplicitThis": true, 11 | "alwaysStrict": true, 12 | "noUnusedLocals": false, 13 | "noUnusedParameters": false, 14 | "noImplicitReturns": true, 15 | "noFallthroughCasesInSwitch": false, 16 | "inlineSourceMap": true, 17 | "inlineSources": true, 18 | "experimentalDecorators": true, 19 | "strictPropertyInitialization": false, 20 | "typeRoots": ["./node_modules/@types"] 21 | }, 22 | "exclude": ["node_modules", "cdk.out"] 23 | } 24 | -------------------------------------------------------------------------------- /examples/clients/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /examples/clients/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "clients", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "dependencies": { 7 | "got": "^11.8.2" 8 | }, 9 | "devDependencies": { 10 | "ts-node": "^10.4.0", 11 | "typescript": "^4.4.4" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /examples/clients/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2018", 4 | "module": "commonjs", 5 | "lib": ["es2018"], 6 | "declaration": true, 7 | "strict": true, 8 | "noImplicitAny": true, 9 | "strictNullChecks": true, 10 | "noImplicitThis": true, 11 | "alwaysStrict": true, 12 | "noUnusedLocals": false, 13 | "noUnusedParameters": false, 14 | "noImplicitReturns": true, 15 | "noFallthroughCasesInSwitch": false, 16 | "inlineSourceMap": true, 17 | "inlineSources": true, 18 | "experimentalDecorators": true, 19 | "strictPropertyInitialization": false, 20 | "typeRoots": ["./node_modules/@types"] 21 | }, 22 | "exclude": ["node_modules", "cdk.out"] 23 | } 24 | -------------------------------------------------------------------------------- /examples/clients/upload-file-s3.ts: -------------------------------------------------------------------------------- 1 | import got from "got"; 2 | 3 | (async () => { 4 | // Fetch a signed URL. Pass the API URL and x api key as environmental variables 5 | const response = await got.get(process.env.API_URL as string, { 6 | headers: { 7 | "x-api-key": process.env.X_API_KEY as string, 8 | }, 9 | }); 10 | const { url } = JSON.parse((await response).body); 11 | // Create a dummy payload 12 | const payload = Buffer.from( 13 | JSON.stringify({ date: new Date().toISOString() }) 14 | ); 15 | // Upload the payload to the S3 bucket 16 | try { 17 | const { statusCode } = await got.put(url, { body: payload }); 18 | console.log("upload complete", statusCode); 19 | } catch (error) { 20 | console.log(error); 21 | } 22 | })(); 23 | -------------------------------------------------------------------------------- /examples/clients/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@cspotcode/source-map-consumer@0.8.0": 6 | version "0.8.0" 7 | resolved "https://registry.yarnpkg.com/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz#33bf4b7b39c178821606f669bbc447a6a629786b" 8 | integrity sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg== 9 | 10 | "@cspotcode/source-map-support@0.7.0": 11 | version "0.7.0" 12 | resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz#4789840aa859e46d2f3173727ab707c66bf344f5" 13 | integrity sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA== 14 | dependencies: 15 | "@cspotcode/source-map-consumer" "0.8.0" 16 | 17 | "@sindresorhus/is@^4.0.0": 18 | version "4.2.0" 19 | resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.2.0.tgz#667bfc6186ae7c9e0b45a08960c551437176e1ca" 20 | integrity sha512-VkE3KLBmJwcCaVARtQpfuKcKv8gcBmUubrfHGF84dXuuW6jgsRYxPtzcIhPyK9WAPpRt2/xY6zkD9MnRaJzSyw== 21 | 22 | "@szmarczak/http-timer@^4.0.5": 23 | version "4.0.6" 24 | resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-4.0.6.tgz#b4a914bb62e7c272d4e5989fe4440f812ab1d807" 25 | integrity sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w== 26 | dependencies: 27 | defer-to-connect "^2.0.0" 28 | 29 | "@tsconfig/node10@^1.0.7": 30 | version "1.0.8" 31 | resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.8.tgz#c1e4e80d6f964fbecb3359c43bd48b40f7cadad9" 32 | integrity sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg== 33 | 34 | "@tsconfig/node12@^1.0.7": 35 | version "1.0.9" 36 | resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.9.tgz#62c1f6dee2ebd9aead80dc3afa56810e58e1a04c" 37 | integrity sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw== 38 | 39 | "@tsconfig/node14@^1.0.0": 40 | version "1.0.1" 41 | resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.1.tgz#95f2d167ffb9b8d2068b0b235302fafd4df711f2" 42 | integrity sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg== 43 | 44 | "@tsconfig/node16@^1.0.2": 45 | version "1.0.2" 46 | resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.2.tgz#423c77877d0569db20e1fc80885ac4118314010e" 47 | integrity sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA== 48 | 49 | "@types/cacheable-request@^6.0.1": 50 | version "6.0.2" 51 | resolved "https://registry.yarnpkg.com/@types/cacheable-request/-/cacheable-request-6.0.2.tgz#c324da0197de0a98a2312156536ae262429ff6b9" 52 | integrity sha512-B3xVo+dlKM6nnKTcmm5ZtY/OL8bOAOd2Olee9M1zft65ox50OzjEHW91sDiU9j6cvW8Ejg1/Qkf4xd2kugApUA== 53 | dependencies: 54 | "@types/http-cache-semantics" "*" 55 | "@types/keyv" "*" 56 | "@types/node" "*" 57 | "@types/responselike" "*" 58 | 59 | "@types/http-cache-semantics@*": 60 | version "4.0.1" 61 | resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz#0ea7b61496902b95890dc4c3a116b60cb8dae812" 62 | integrity sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ== 63 | 64 | "@types/keyv@*": 65 | version "3.1.3" 66 | resolved "https://registry.yarnpkg.com/@types/keyv/-/keyv-3.1.3.tgz#1c9aae32872ec1f20dcdaee89a9f3ba88f465e41" 67 | integrity sha512-FXCJgyyN3ivVgRoml4h94G/p3kY+u/B86La+QptcqJaWtBWtmc6TtkNfS40n9bIvyLteHh7zXOtgbobORKPbDg== 68 | dependencies: 69 | "@types/node" "*" 70 | 71 | "@types/node@*": 72 | version "16.11.4" 73 | resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.4.tgz#90771124822d6663814f7c1c9b45a6654d8fd964" 74 | integrity sha512-TMgXmy0v2xWyuCSCJM6NCna2snndD8yvQF67J29ipdzMcsPa9u+o0tjF5+EQNdhcuZplYuouYqpc4zcd5I6amQ== 75 | 76 | "@types/responselike@*", "@types/responselike@^1.0.0": 77 | version "1.0.0" 78 | resolved "https://registry.yarnpkg.com/@types/responselike/-/responselike-1.0.0.tgz#251f4fe7d154d2bad125abe1b429b23afd262e29" 79 | integrity sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA== 80 | dependencies: 81 | "@types/node" "*" 82 | 83 | acorn-walk@^8.1.1: 84 | version "8.2.0" 85 | resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" 86 | integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== 87 | 88 | acorn@^8.4.1: 89 | version "8.5.0" 90 | resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.5.0.tgz#4512ccb99b3698c752591e9bb4472e38ad43cee2" 91 | integrity sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q== 92 | 93 | arg@^4.1.0: 94 | version "4.1.3" 95 | resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" 96 | integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== 97 | 98 | cacheable-lookup@^5.0.3: 99 | version "5.0.4" 100 | resolved "https://registry.yarnpkg.com/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz#5a6b865b2c44357be3d5ebc2a467b032719a7005" 101 | integrity sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA== 102 | 103 | cacheable-request@^7.0.1: 104 | version "7.0.2" 105 | resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-7.0.2.tgz#ea0d0b889364a25854757301ca12b2da77f91d27" 106 | integrity sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew== 107 | dependencies: 108 | clone-response "^1.0.2" 109 | get-stream "^5.1.0" 110 | http-cache-semantics "^4.0.0" 111 | keyv "^4.0.0" 112 | lowercase-keys "^2.0.0" 113 | normalize-url "^6.0.1" 114 | responselike "^2.0.0" 115 | 116 | clone-response@^1.0.2: 117 | version "1.0.2" 118 | resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b" 119 | integrity sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws= 120 | dependencies: 121 | mimic-response "^1.0.0" 122 | 123 | create-require@^1.1.0: 124 | version "1.1.1" 125 | resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" 126 | integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== 127 | 128 | decompress-response@^6.0.0: 129 | version "6.0.0" 130 | resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc" 131 | integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ== 132 | dependencies: 133 | mimic-response "^3.1.0" 134 | 135 | defer-to-connect@^2.0.0: 136 | version "2.0.1" 137 | resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-2.0.1.tgz#8016bdb4143e4632b77a3449c6236277de520587" 138 | integrity sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg== 139 | 140 | diff@^4.0.1: 141 | version "4.0.2" 142 | resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" 143 | integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== 144 | 145 | end-of-stream@^1.1.0: 146 | version "1.4.4" 147 | resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" 148 | integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== 149 | dependencies: 150 | once "^1.4.0" 151 | 152 | get-stream@^5.1.0: 153 | version "5.2.0" 154 | resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" 155 | integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== 156 | dependencies: 157 | pump "^3.0.0" 158 | 159 | got@^11.8.2: 160 | version "11.8.2" 161 | resolved "https://registry.yarnpkg.com/got/-/got-11.8.2.tgz#7abb3959ea28c31f3576f1576c1effce23f33599" 162 | integrity sha512-D0QywKgIe30ODs+fm8wMZiAcZjypcCodPNuMz5H9Mny7RJ+IjJ10BdmGW7OM7fHXP+O7r6ZwapQ/YQmMSvB0UQ== 163 | dependencies: 164 | "@sindresorhus/is" "^4.0.0" 165 | "@szmarczak/http-timer" "^4.0.5" 166 | "@types/cacheable-request" "^6.0.1" 167 | "@types/responselike" "^1.0.0" 168 | cacheable-lookup "^5.0.3" 169 | cacheable-request "^7.0.1" 170 | decompress-response "^6.0.0" 171 | http2-wrapper "^1.0.0-beta.5.2" 172 | lowercase-keys "^2.0.0" 173 | p-cancelable "^2.0.0" 174 | responselike "^2.0.0" 175 | 176 | http-cache-semantics@^4.0.0: 177 | version "4.1.0" 178 | resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390" 179 | integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ== 180 | 181 | http2-wrapper@^1.0.0-beta.5.2: 182 | version "1.0.3" 183 | resolved "https://registry.yarnpkg.com/http2-wrapper/-/http2-wrapper-1.0.3.tgz#b8f55e0c1f25d4ebd08b3b0c2c079f9590800b3d" 184 | integrity sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg== 185 | dependencies: 186 | quick-lru "^5.1.1" 187 | resolve-alpn "^1.0.0" 188 | 189 | json-buffer@3.0.1: 190 | version "3.0.1" 191 | resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" 192 | integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== 193 | 194 | keyv@^4.0.0: 195 | version "4.0.3" 196 | resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.0.3.tgz#4f3aa98de254803cafcd2896734108daa35e4254" 197 | integrity sha512-zdGa2TOpSZPq5mU6iowDARnMBZgtCqJ11dJROFi6tg6kTn4nuUdU09lFyLFSaHrWqpIJ+EBq4E8/Dc0Vx5vLdA== 198 | dependencies: 199 | json-buffer "3.0.1" 200 | 201 | lowercase-keys@^2.0.0: 202 | version "2.0.0" 203 | resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" 204 | integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA== 205 | 206 | make-error@^1.1.1: 207 | version "1.3.6" 208 | resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" 209 | integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== 210 | 211 | mimic-response@^1.0.0: 212 | version "1.0.1" 213 | resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" 214 | integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== 215 | 216 | mimic-response@^3.1.0: 217 | version "3.1.0" 218 | resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" 219 | integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== 220 | 221 | normalize-url@^6.0.1: 222 | version "6.1.0" 223 | resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a" 224 | integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A== 225 | 226 | once@^1.3.1, once@^1.4.0: 227 | version "1.4.0" 228 | resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" 229 | integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= 230 | dependencies: 231 | wrappy "1" 232 | 233 | p-cancelable@^2.0.0: 234 | version "2.1.1" 235 | resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-2.1.1.tgz#aab7fbd416582fa32a3db49859c122487c5ed2cf" 236 | integrity sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg== 237 | 238 | pump@^3.0.0: 239 | version "3.0.0" 240 | resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" 241 | integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== 242 | dependencies: 243 | end-of-stream "^1.1.0" 244 | once "^1.3.1" 245 | 246 | quick-lru@^5.1.1: 247 | version "5.1.1" 248 | resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932" 249 | integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA== 250 | 251 | resolve-alpn@^1.0.0: 252 | version "1.2.1" 253 | resolved "https://registry.yarnpkg.com/resolve-alpn/-/resolve-alpn-1.2.1.tgz#b7adbdac3546aaaec20b45e7d8265927072726f9" 254 | integrity sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g== 255 | 256 | responselike@^2.0.0: 257 | version "2.0.0" 258 | resolved "https://registry.yarnpkg.com/responselike/-/responselike-2.0.0.tgz#26391bcc3174f750f9a79eacc40a12a5c42d7723" 259 | integrity sha512-xH48u3FTB9VsZw7R+vvgaKeLKzT6jOogbQhEe/jewwnZgzPcnyWui2Av6JpoYZF/91uueC+lqhWqeURw5/qhCw== 260 | dependencies: 261 | lowercase-keys "^2.0.0" 262 | 263 | ts-node@^10.4.0: 264 | version "10.4.0" 265 | resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.4.0.tgz#680f88945885f4e6cf450e7f0d6223dd404895f7" 266 | integrity sha512-g0FlPvvCXSIO1JDF6S232P5jPYqBkRL9qly81ZgAOSU7rwI0stphCgd2kLiCrU9DjQCrJMWEqcNSjQL02s6d8A== 267 | dependencies: 268 | "@cspotcode/source-map-support" "0.7.0" 269 | "@tsconfig/node10" "^1.0.7" 270 | "@tsconfig/node12" "^1.0.7" 271 | "@tsconfig/node14" "^1.0.0" 272 | "@tsconfig/node16" "^1.0.2" 273 | acorn "^8.4.1" 274 | acorn-walk "^8.1.1" 275 | arg "^4.1.0" 276 | create-require "^1.1.0" 277 | diff "^4.0.1" 278 | make-error "^1.1.1" 279 | yn "3.1.1" 280 | 281 | typescript@^4.4.4: 282 | version "4.4.4" 283 | resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.4.4.tgz#2cd01a1a1f160704d3101fd5a58ff0f9fcb8030c" 284 | integrity sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA== 285 | 286 | wrappy@1: 287 | version "1.0.2" 288 | resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" 289 | integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= 290 | 291 | yn@3.1.1: 292 | version "3.1.1" 293 | resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" 294 | integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== 295 | -------------------------------------------------------------------------------- /examples/serverless/api-authorizer/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "targets": { 7 | "node": "14" 8 | } 9 | } 10 | ], 11 | ["@babel/preset-typescript"] 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /examples/serverless/api-authorizer/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .serverless 3 | .webpack* 4 | -------------------------------------------------------------------------------- /examples/serverless/api-authorizer/.prettierignore: -------------------------------------------------------------------------------- 1 | # Ignore artifacts: 2 | build 3 | coverage 4 | .webpack* 5 | .serverless* 6 | -------------------------------------------------------------------------------- /examples/serverless/api-authorizer/.prettierrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = require('eslint-config-maasglobal/.prettierrc'); 4 | -------------------------------------------------------------------------------- /examples/serverless/api-authorizer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "api-authorizer", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "scripts": { 7 | "test": "jest", 8 | "prettier": "prettier --write ." 9 | }, 10 | "devDependencies": { 11 | "@babel/core": "^7.16.0", 12 | "@babel/preset-env": "^7.16.4", 13 | "@babel/preset-typescript": "^7.16.0", 14 | "@types/aws-lambda": "^8.10.86", 15 | "@types/jest": "^27.0.3", 16 | "@types/node": "^16.11.12", 17 | "babel-loader": "^8.2.3", 18 | "cache-loader": "^4.1.0", 19 | "eslint-config-maasglobal": "^4.0.1", 20 | "fork-ts-checker-webpack-plugin": "^6.5.0", 21 | "jest": "^27.4.3", 22 | "prettier": "^2.5.1", 23 | "serverless": "^2.68.0", 24 | "serverless-webpack": "^5.6.0", 25 | "ts-node": "^10.4.0", 26 | "tslint": "^6.1.3", 27 | "typescript": "^4.5.2", 28 | "webpack": "^5.65.0", 29 | "webpack-node-externals": "^3.0.0" 30 | }, 31 | "dependencies": { 32 | "aws-lambda": "^1.0.7" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /examples/serverless/api-authorizer/serverless.yml: -------------------------------------------------------------------------------- 1 | service: api-authorizer 2 | 3 | frameworkVersion: '2' 4 | 5 | provider: 6 | name: aws 7 | runtime: nodejs14.x 8 | lambdaHashingVersion: 20201221 9 | region: eu-west-1 10 | httpApi: 11 | authorizers: 12 | authorizer: 13 | type: request 14 | enableSimpleResponses: true 15 | functionName: authorizer 16 | 17 | package: 18 | individually: true 19 | 20 | plugins: 21 | - serverless-webpack 22 | 23 | custom: 24 | webpack: 25 | webpackConfig: 'webpack.config.js' 26 | packager: 'yarn' 27 | includeModules: true 28 | 29 | functions: 30 | authorizer: 31 | handler: src/authorizer/index.handler 32 | echo: 33 | handler: src/echo/index.handler 34 | events: 35 | - httpApi: 36 | method: get 37 | path: /echo 38 | authorizer: 39 | name: authorizer 40 | -------------------------------------------------------------------------------- /examples/serverless/api-authorizer/src/authorizer/index.ts: -------------------------------------------------------------------------------- 1 | export const handler = async () => { 2 | const isAuthorized = true; 3 | return { 4 | isAuthorized, 5 | context: {}, 6 | }; 7 | }; 8 | -------------------------------------------------------------------------------- /examples/serverless/api-authorizer/src/echo/index.ts: -------------------------------------------------------------------------------- 1 | import { APIGatewayProxyEventV2 } from 'aws-lambda'; 2 | 3 | export const handler = async (event: APIGatewayProxyEventV2) => { 4 | console.log(JSON.stringify(event, null, 2)); 5 | return { 6 | statusCode: 200, 7 | body: JSON.stringify({ event }), 8 | headers: { 9 | 'Content-Type': 'application/json', 10 | }, 11 | }; 12 | }; 13 | -------------------------------------------------------------------------------- /examples/serverless/api-authorizer/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "preserveConstEnums": true, 4 | "strictNullChecks": true, 5 | "inlineSources": true, 6 | "inlineSourceMap": true, 7 | "sourceRoot": "/", 8 | "target": "ES2020", 9 | "outDir": ".build", 10 | "module": "commonjs", 11 | "moduleResolution": "node", 12 | "esModuleInterop": true, 13 | "lib": ["ES2020", "DOM"], 14 | "rootDir": "./src", 15 | "resolveJsonModule": true 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/serverless/api-authorizer/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "extends": ["tslint:recommended"], 4 | "jsRules": {}, 5 | "rules": { 6 | "no-console": false 7 | }, 8 | "rulesDirectory": [] 9 | } 10 | -------------------------------------------------------------------------------- /examples/serverless/api-authorizer/webpack.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-var-requires */ 2 | const path = require('path'); 3 | const nodeExternals = require('webpack-node-externals'); 4 | const slsw = require('serverless-webpack'); 5 | const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); 6 | 7 | const isLocal = slsw.lib.webpack.isLocal; 8 | 9 | module.exports = { 10 | mode: isLocal ? 'development' : 'production', 11 | entry: slsw.lib.entries, 12 | externals: [nodeExternals()], 13 | devtool: 'source-map', 14 | resolve: { 15 | extensions: ['.js', '.jsx', '.json', '.ts', '.tsx'], 16 | }, 17 | output: { 18 | libraryTarget: 'commonjs2', 19 | path: path.join(__dirname, '.webpack'), 20 | filename: '[name].js', 21 | }, 22 | target: 'node', 23 | module: { 24 | rules: [ 25 | { 26 | // Include ts, tsx, js, and jsx files. 27 | test: /\.(ts|js)x?$/, 28 | exclude: /node_modules/, 29 | use: [ 30 | { 31 | loader: 'cache-loader', 32 | options: { 33 | cacheDirectory: path.resolve('.webpackCache'), 34 | }, 35 | }, 36 | 'babel-loader', 37 | ], 38 | }, 39 | ], 40 | }, 41 | plugins: [new ForkTsCheckerWebpackPlugin()], 42 | }; 43 | -------------------------------------------------------------------------------- /examples/serverless/api-database/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "targets": { 7 | "node": "14" 8 | } 9 | } 10 | ], 11 | ["@babel/preset-typescript"] 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /examples/serverless/api-database/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .serverless 3 | .webpack* 4 | -------------------------------------------------------------------------------- /examples/serverless/api-database/.prettierignore: -------------------------------------------------------------------------------- 1 | # Ignore artifacts: 2 | build 3 | coverage 4 | .webpack* 5 | .serverless* 6 | -------------------------------------------------------------------------------- /examples/serverless/api-database/.prettierrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = require('eslint-config-maasglobal/.prettierrc'); 4 | -------------------------------------------------------------------------------- /examples/serverless/api-database/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "api-database", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "scripts": { 7 | "test": "jest", 8 | "prettier": "prettier --write ." 9 | }, 10 | "devDependencies": { 11 | "@babel/core": "^7.16.0", 12 | "@babel/preset-env": "^7.16.4", 13 | "@babel/preset-typescript": "^7.16.0", 14 | "@types/aws-lambda": "^8.10.86", 15 | "@types/jest": "^27.0.3", 16 | "@types/node": "^16.11.12", 17 | "babel-loader": "^8.2.3", 18 | "cache-loader": "^4.1.0", 19 | "eslint-config-maasglobal": "^4.0.1", 20 | "fork-ts-checker-webpack-plugin": "^6.5.0", 21 | "jest": "^27.4.3", 22 | "prettier": "^2.5.1", 23 | "serverless": "^2.68.0", 24 | "serverless-webpack": "^5.6.0", 25 | "ts-node": "^10.4.0", 26 | "tslint": "^6.1.3", 27 | "typescript": "^4.5.2", 28 | "webpack": "^5.65.0", 29 | "webpack-node-externals": "^3.0.0" 30 | }, 31 | "dependencies": { 32 | "@aws-sdk/client-secrets-manager": "^3.43.0", 33 | "@aws-sdk/client-ssm": "^3.44.0", 34 | "aws-lambda": "^1.0.7", 35 | "knex": "0.95.14", 36 | "knex-aurora-data-api-client": "^1.6.5", 37 | "pg": "^8.7.1" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /examples/serverless/api-database/serverless.yml: -------------------------------------------------------------------------------- 1 | service: api-database 2 | 3 | frameworkVersion: '2' 4 | 5 | provider: 6 | name: aws 7 | runtime: nodejs14.x 8 | lambdaHashingVersion: 20201221 9 | region: eu-west-1 10 | environment: 11 | AWS_NODEJS_CONNECTION_REUSE_ENABLED: '1' 12 | iam: 13 | role: 14 | statements: 15 | - Effect: Allow 16 | Action: 17 | - ssm:GetParameter 18 | Resource: 19 | - !Sub 'arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/examples/infra/database/cluster/arn' 20 | - !Sub 'arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/examples/infra/database/endpoint/hostname' 21 | - !Sub 'arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/examples/infra/database/secret/arn' 22 | - Effect: 'Allow' 23 | Action: 24 | - secretsmanager:GetSecretValue 25 | Resource: 26 | - ${ssm:/examples/infra/database/secret/arn} 27 | - Effect: Allow 28 | Action: 29 | - rds-data:BatchExecuteStatement 30 | - rds-data:BeginTransaction 31 | - rds-data:CommitTransaction 32 | - rds-data:ExecuteStatement 33 | - rds-data:RollbackTransaction 34 | Resource: 35 | - ${ssm:/examples/infra/database/cluster/arn} 36 | package: 37 | individually: true 38 | 39 | plugins: 40 | - serverless-webpack 41 | 42 | custom: 43 | webpack: 44 | webpackConfig: 'webpack.config.js' 45 | packager: 'yarn' 46 | includeModules: 47 | forceInclude: 48 | - pg 49 | functions: 50 | data-api: 51 | handler: src/data-api/index.handler 52 | timeout: 28 53 | events: 54 | - httpApi: 55 | method: get 56 | path: /data-api 57 | postgres: 58 | handler: src/postgres/index.handler 59 | timeout: 28 60 | vpc: 61 | securityGroupIds: 62 | - !Ref SecurityGroup 63 | subnetIds: 64 | - ${ssm:/examples/infra/vpc/private-subnet-0/id} 65 | - ${ssm:/examples/infra/vpc/private-subnet-1/id} 66 | events: 67 | - httpApi: 68 | method: get 69 | path: /postgres 70 | 71 | resources: 72 | Resources: 73 | SecurityGroup: 74 | Type: AWS::EC2::SecurityGroup 75 | Properties: 76 | GroupName: example-lambda 77 | GroupDescription: example-lambda 78 | VpcId: ${ssm:/examples/infra/vpc/id} 79 | -------------------------------------------------------------------------------- /examples/serverless/api-database/src/data-api/database.ts: -------------------------------------------------------------------------------- 1 | import Knex from 'knex'; 2 | // @ts-ignore 3 | import knexDataApiClient from 'knex-aurora-data-api-client'; 4 | import { SSMClient, GetParameterCommand } from '@aws-sdk/client-ssm'; 5 | 6 | const region = process.env.AWS_REGION || process.env.AWS_DEFAULT_REGION || 'eu-west-1'; 7 | 8 | const ssm = new SSMClient({ 9 | region, 10 | }); 11 | 12 | const client = (params?: any) => { 13 | const _knex = Knex({ 14 | client: knexDataApiClient.postgres, 15 | connection: Object.assign( 16 | { 17 | // @ts-ignore 18 | secretArn: process.env.SECRET_ARN, 19 | resourceArn: process.env.CLUSTER_ARN, 20 | database: 'postgres', 21 | region, 22 | }, 23 | params.connection || {} 24 | ), 25 | }); 26 | return _knex; 27 | }; 28 | 29 | export const knex = async () => { 30 | const clusterArnParameter = await ssm.send(new GetParameterCommand({ Name: '/examples/infra/database/cluster/arn' })); 31 | const secretParameter = await ssm.send(new GetParameterCommand({ Name: '/examples/infra/database/secret/arn' })); 32 | 33 | if (!clusterArnParameter.Parameter?.Value || !secretParameter.Parameter?.Value) { 34 | throw new Error('Failed to fetch SSM parameters'); 35 | } 36 | 37 | return client({ 38 | connection: { 39 | secretArn: secretParameter.Parameter?.Value, 40 | resourceArn: clusterArnParameter.Parameter?.Value, 41 | }, 42 | }); 43 | }; 44 | -------------------------------------------------------------------------------- /examples/serverless/api-database/src/data-api/index.ts: -------------------------------------------------------------------------------- 1 | import { APIGatewayProxyEventV2 } from 'aws-lambda'; 2 | import { Knex } from 'knex'; 3 | import { knex } from './database'; 4 | 5 | let client: Knex; 6 | 7 | async function initClient() { 8 | if (!client) { 9 | client = await knex(); 10 | } 11 | } 12 | 13 | export const handler = async (event: APIGatewayProxyEventV2) => { 14 | console.log(JSON.stringify(event, null, 2)); 15 | await initClient(); 16 | const postgresUsers = await client.select('usename').from('pg_catalog.pg_user'); 17 | console.log(postgresUsers); 18 | return { 19 | statusCode: 200, 20 | body: JSON.stringify({ userCount: postgresUsers.length }), 21 | headers: { 22 | 'Content-Type': 'application/json', 23 | }, 24 | }; 25 | }; 26 | -------------------------------------------------------------------------------- /examples/serverless/api-database/src/postgres/database.ts: -------------------------------------------------------------------------------- 1 | import Knex from 'knex'; 2 | import pg from 'pg'; 3 | import { SSMClient, GetParameterCommand } from '@aws-sdk/client-ssm'; 4 | import { SecretsManagerClient, GetSecretValueCommand } from '@aws-sdk/client-secrets-manager'; 5 | 6 | const region = process.env.AWS_REGION || process.env.AWS_DEFAULT_REGION || 'eu-west-1'; 7 | 8 | const ssm = new SSMClient({ 9 | region, 10 | }); 11 | 12 | const secretsManager = new SecretsManagerClient({ region }); 13 | 14 | const client = ({ host, user, password }) => { 15 | const _knex = Knex({ 16 | client: 'pg', 17 | connection: Object.assign({ 18 | host, 19 | port: 5432, 20 | user, 21 | password, 22 | database: 'postgres', 23 | }), 24 | }); 25 | return _knex; 26 | }; 27 | 28 | export const knex = async () => { 29 | const clusterNameParameter = await ssm.send( 30 | new GetParameterCommand({ 31 | Name: '/examples/infra/database/endpoint/hostname', 32 | }) 33 | ); 34 | const secretParameter = await ssm.send(new GetParameterCommand({ Name: '/examples/infra/database/secret/arn' })); 35 | 36 | const databaseCredentials = await secretsManager.send( 37 | new GetSecretValueCommand({ 38 | SecretId: secretParameter.Parameter?.Value, 39 | }) 40 | ); 41 | 42 | const { username, password } = JSON.parse(databaseCredentials.SecretString as string); 43 | 44 | if (!clusterNameParameter.Parameter?.Value || !secretParameter.Parameter?.Value) { 45 | throw new Error('Failed to fetch SSM parameters'); 46 | } 47 | 48 | return client({ 49 | host: clusterNameParameter.Parameter?.Value, 50 | user: username, 51 | password, 52 | }); 53 | }; 54 | -------------------------------------------------------------------------------- /examples/serverless/api-database/src/postgres/index.ts: -------------------------------------------------------------------------------- 1 | import { APIGatewayProxyEventV2 } from 'aws-lambda'; 2 | import { Knex } from 'knex'; 3 | import { knex } from './database'; 4 | 5 | let client: Knex; 6 | 7 | async function initClient() { 8 | if (!client) { 9 | client = await knex(); 10 | } 11 | } 12 | 13 | export const handler = async (event: APIGatewayProxyEventV2) => { 14 | console.log(JSON.stringify(event, null, 2)); 15 | await initClient(); 16 | const postgresUsers = await client.select('usename').from('pg_catalog.pg_user'); 17 | console.log(postgresUsers); 18 | return { 19 | statusCode: 200, 20 | body: JSON.stringify({ userCount: postgresUsers.length }), 21 | headers: { 22 | 'Content-Type': 'application/json', 23 | }, 24 | }; 25 | }; 26 | -------------------------------------------------------------------------------- /examples/serverless/api-database/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "preserveConstEnums": true, 4 | "strictNullChecks": true, 5 | "inlineSources": true, 6 | "inlineSourceMap": true, 7 | "sourceRoot": "/", 8 | "target": "ES2020", 9 | "outDir": ".build", 10 | "module": "commonjs", 11 | "moduleResolution": "node", 12 | "esModuleInterop": true, 13 | "lib": ["ES2020", "DOM"], 14 | "rootDir": "./src", 15 | "resolveJsonModule": true 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/serverless/api-database/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "extends": ["tslint:recommended"], 4 | "jsRules": {}, 5 | "rules": { 6 | "no-console": false 7 | }, 8 | "rulesDirectory": [] 9 | } 10 | -------------------------------------------------------------------------------- /examples/serverless/api-database/webpack.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-var-requires */ 2 | const path = require('path'); 3 | const nodeExternals = require('webpack-node-externals'); 4 | const slsw = require('serverless-webpack'); 5 | const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); 6 | 7 | const isLocal = slsw.lib.webpack.isLocal; 8 | 9 | module.exports = { 10 | mode: isLocal ? 'development' : 'production', 11 | entry: slsw.lib.entries, 12 | externals: [nodeExternals()], 13 | devtool: 'source-map', 14 | resolve: { 15 | extensions: ['.js', '.jsx', '.json', '.ts', '.tsx'], 16 | }, 17 | output: { 18 | libraryTarget: 'commonjs2', 19 | path: path.join(__dirname, '.webpack'), 20 | filename: '[name].js', 21 | }, 22 | target: 'node', 23 | module: { 24 | rules: [ 25 | { 26 | // Include ts, tsx, js, and jsx files. 27 | test: /\.(ts|js)x?$/, 28 | exclude: /node_modules/, 29 | use: [ 30 | { 31 | loader: 'cache-loader', 32 | options: { 33 | cacheDirectory: path.resolve('.webpackCache'), 34 | }, 35 | }, 36 | 'babel-loader', 37 | ], 38 | }, 39 | ], 40 | }, 41 | plugins: [new ForkTsCheckerWebpackPlugin()], 42 | }; 43 | -------------------------------------------------------------------------------- /examples/serverless/api-service-integration/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "targets": { 7 | "node": "14" 8 | } 9 | } 10 | ], 11 | ["@babel/preset-typescript"] 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /examples/serverless/api-service-integration/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .serverless 3 | .webpack* 4 | -------------------------------------------------------------------------------- /examples/serverless/api-service-integration/.prettierignore: -------------------------------------------------------------------------------- 1 | # Ignore artifacts: 2 | build 3 | coverage 4 | .webpack* 5 | .serverless* 6 | -------------------------------------------------------------------------------- /examples/serverless/api-service-integration/.prettierrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = require('eslint-config-maasglobal/.prettierrc'); 4 | -------------------------------------------------------------------------------- /examples/serverless/api-service-integration/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "api-authorizer", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "scripts": { 7 | "test": "jest", 8 | "prettier": "prettier --write ." 9 | }, 10 | "devDependencies": { 11 | "@babel/core": "^7.16.0", 12 | "@babel/preset-env": "^7.16.4", 13 | "@babel/preset-typescript": "^7.16.0", 14 | "@types/aws-lambda": "^8.10.86", 15 | "@types/jest": "^27.0.3", 16 | "@types/node": "^16.11.12", 17 | "babel-loader": "^8.2.3", 18 | "cache-loader": "^4.1.0", 19 | "eslint-config-maasglobal": "^4.0.1", 20 | "fork-ts-checker-webpack-plugin": "^6.5.0", 21 | "jest": "^27.4.3", 22 | "prettier": "^2.5.1", 23 | "serverless": "^2.68.0", 24 | "serverless-apigateway-service-proxy": "^1.14.0", 25 | "serverless-webpack": "^5.6.0", 26 | "ts-node": "^10.4.0", 27 | "tslint": "^6.1.3", 28 | "typescript": "^4.5.2", 29 | "webpack": "^5.65.0", 30 | "webpack-node-externals": "^3.0.0" 31 | }, 32 | "dependencies": { 33 | "aws-lambda": "^1.0.7" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /examples/serverless/api-service-integration/serverless.yml: -------------------------------------------------------------------------------- 1 | service: api-service-integration 2 | 3 | frameworkVersion: '2' 4 | 5 | provider: 6 | name: aws 7 | runtime: nodejs14.x 8 | lambdaHashingVersion: 20201221 9 | region: eu-west-1 10 | 11 | package: 12 | individually: true 13 | 14 | plugins: 15 | - serverless-webpack 16 | - serverless-apigateway-service-proxy 17 | 18 | custom: 19 | webpack: 20 | webpackConfig: 'webpack.config.js' 21 | packager: 'yarn' 22 | includeModules: true 23 | apiGatewayServiceProxies: 24 | - sqs: 25 | path: /sqs 26 | method: post 27 | queueName: !GetAtt SQSQueue.QueueName 28 | cors: true 29 | response: 30 | template: 31 | success: | 32 | { 33 | "success": true 34 | } 35 | serverError: | 36 | { 37 | "success": false, 38 | "errorMessage": "Server Error" 39 | } 40 | clientError: | 41 | { 42 | "success": false, 43 | "errorMessage": "Client Error" 44 | } 45 | functions: 46 | echo: 47 | handler: src/echo/index.handler 48 | events: 49 | - sqs: 50 | arn: !GetAtt SQSQueue.Arn 51 | batchSize: 10 52 | maximumBatchingWindow: 10 53 | 54 | resources: 55 | Resources: 56 | SQSQueue: 57 | Type: 'AWS::SQS::Queue' 58 | -------------------------------------------------------------------------------- /examples/serverless/api-service-integration/src/echo/index.ts: -------------------------------------------------------------------------------- 1 | import { SQSEvent } from 'aws-lambda'; 2 | 3 | export const handler = async (event: SQSEvent) => { 4 | console.log(JSON.stringify(event, null, 2)); 5 | }; 6 | -------------------------------------------------------------------------------- /examples/serverless/api-service-integration/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "preserveConstEnums": true, 4 | "strictNullChecks": true, 5 | "inlineSources": true, 6 | "inlineSourceMap": true, 7 | "sourceRoot": "/", 8 | "target": "ES2020", 9 | "outDir": ".build", 10 | "module": "commonjs", 11 | "moduleResolution": "node", 12 | "esModuleInterop": true, 13 | "lib": ["ES2020", "DOM"], 14 | "rootDir": "./src", 15 | "resolveJsonModule": true 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/serverless/api-service-integration/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "extends": ["tslint:recommended"], 4 | "jsRules": {}, 5 | "rules": { 6 | "no-console": false 7 | }, 8 | "rulesDirectory": [] 9 | } 10 | -------------------------------------------------------------------------------- /examples/serverless/api-service-integration/webpack.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-var-requires */ 2 | const path = require('path'); 3 | const nodeExternals = require('webpack-node-externals'); 4 | const slsw = require('serverless-webpack'); 5 | const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); 6 | 7 | const isLocal = slsw.lib.webpack.isLocal; 8 | 9 | module.exports = { 10 | mode: isLocal ? 'development' : 'production', 11 | entry: slsw.lib.entries, 12 | externals: [nodeExternals()], 13 | devtool: 'source-map', 14 | resolve: { 15 | extensions: ['.js', '.jsx', '.json', '.ts', '.tsx'], 16 | }, 17 | output: { 18 | libraryTarget: 'commonjs2', 19 | path: path.join(__dirname, '.webpack'), 20 | filename: '[name].js', 21 | }, 22 | target: 'node', 23 | module: { 24 | rules: [ 25 | { 26 | // Include ts, tsx, js, and jsx files. 27 | test: /\.(ts|js)x?$/, 28 | exclude: /node_modules/, 29 | use: [ 30 | { 31 | loader: 'cache-loader', 32 | options: { 33 | cacheDirectory: path.resolve('.webpackCache'), 34 | }, 35 | }, 36 | 'babel-loader', 37 | ], 38 | }, 39 | ], 40 | }, 41 | plugins: [new ForkTsCheckerWebpackPlugin()], 42 | }; 43 | --------------------------------------------------------------------------------