├── .github
└── workflows
│ ├── codeql-analysis.yml
│ └── tfsec.yml
├── .gitignore
├── .pre-commit-config.yaml
├── .terraform-version
├── .terraform.lock.hcl
├── README.md
├── data.tf
├── main.tf
├── outputs.tf
├── providers.tf
├── sources
├── .gitignore
├── .node-version
├── package-lock.json
├── package.json
├── src
│ ├── decorators
│ │ └── statistic.decorator.ts
│ ├── functions
│ │ ├── booking-create.function.ts
│ │ ├── booking-search.function.ts
│ │ └── flight-search.function.ts
│ ├── helpers
│ │ ├── chunk.helper.ts
│ │ └── parameter-store.helper.ts
│ ├── index.ts
│ ├── libraries
│ │ ├── datadog.library.ts
│ │ └── dynamodb.library.ts
│ └── models
│ │ ├── booking.model.ts
│ │ ├── flight.model.ts
│ │ ├── model.ts
│ │ └── transaction.model.ts
└── tsconfig.json
├── terraform.tfvars.example
└── variables.tf
/.github/workflows/codeql-analysis.yml:
--------------------------------------------------------------------------------
1 | # For most projects, this workflow file will not need changing; you simply need
2 | # to commit it to your repository.
3 | #
4 | # You may wish to alter this file to override the set of languages analyzed,
5 | # or to provide custom queries or build logic.
6 | #
7 | # ******** NOTE ********
8 | # We have attempted to detect the languages in your repository. Please check
9 | # the `language` matrix defined below to confirm you have the correct set of
10 | # supported CodeQL languages.
11 | #
12 | name: "CodeQL"
13 |
14 | on:
15 | push:
16 | branches: [ main ]
17 | pull_request:
18 | # The branches below must be a subset of the branches above
19 | branches: [ main ]
20 | schedule:
21 | - cron: '23 10 * * 5'
22 |
23 | jobs:
24 | analyze:
25 | name: Analyze
26 | runs-on: ubuntu-latest
27 | permissions:
28 | actions: read
29 | contents: read
30 | security-events: write
31 |
32 | strategy:
33 | fail-fast: false
34 | matrix:
35 | language: [ 'javascript' ]
36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
37 | # Learn more about CodeQL language support at https://git.io/codeql-language-support
38 |
39 | steps:
40 | - name: Checkout repository
41 | uses: actions/checkout@v3
42 |
43 | # Initializes the CodeQL tools for scanning.
44 | - name: Initialize CodeQL
45 | uses: github/codeql-action/init@v2
46 | with:
47 | languages: ${{ matrix.language }}
48 | # If you wish to specify custom queries, you can do so here or in a config file.
49 | # By default, queries listed here will override any specified in a config file.
50 | # Prefix the list here with "+" to use these queries and those in the config file.
51 | # queries: ./path/to/local/query, your-org/your-repo/queries@main
52 |
53 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
54 | # If this step fails, then you should remove it and run the build manually (see below)
55 | - name: Autobuild
56 | uses: github/codeql-action/autobuild@v2
57 |
58 | # ℹ️ Command-line programs to run using the OS shell.
59 | # 📚 https://git.io/JvXDl
60 |
61 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
62 | # and modify them (or add more) to build your code if your project
63 | # uses a compiled language
64 |
65 | #- run: |
66 | # make bootstrap
67 | # make release
68 |
69 | - name: Perform CodeQL Analysis
70 | uses: github/codeql-action/analyze@v2
71 |
--------------------------------------------------------------------------------
/.github/workflows/tfsec.yml:
--------------------------------------------------------------------------------
1 | # This workflow uses actions that are not certified by GitHub.
2 | # They are provided by a third-party and are governed by
3 | # separate terms of service, privacy policy, and support
4 | # documentation.
5 |
6 | name: tfsec
7 |
8 | on:
9 | push:
10 | branches: [ main ]
11 | pull_request:
12 | branches: [ main ]
13 | schedule:
14 | - cron: '41 6 * * 6'
15 |
16 | jobs:
17 | tfsec:
18 | name: Run tfsec sarif report
19 | runs-on: ubuntu-latest
20 | permissions:
21 | actions: read
22 | contents: read
23 | security-events: write
24 |
25 | steps:
26 | - name: Clone repo
27 | uses: actions/checkout@v3
28 |
29 | - name: Run tfsec
30 | uses: tfsec/tfsec-sarif-action@9a83b5c3524f825c020e356335855741fd02745f
31 | with:
32 | sarif_file: tfsec.sarif
33 |
34 | - name: Upload SARIF file
35 | uses: github/codeql-action/upload-sarif@v2
36 | with:
37 | # Path to SARIF file relative to the root of the repository
38 | sarif_file: tfsec.sarif
39 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .terraform
2 | *.tfstate
3 | *.backup
4 | *.lock.info
5 | *.tfvars
--------------------------------------------------------------------------------
/.pre-commit-config.yaml:
--------------------------------------------------------------------------------
1 | repos:
2 | - repo: https://github.com/terraform-docs/terraform-docs
3 | rev: "v0.16.0"
4 | hooks:
5 | - id: terraform-docs-go
6 | args: ["markdown", "table", "--output-file", "README.md", "."]
7 |
--------------------------------------------------------------------------------
/.terraform-version:
--------------------------------------------------------------------------------
1 | 1.1.9
2 |
--------------------------------------------------------------------------------
/.terraform.lock.hcl:
--------------------------------------------------------------------------------
1 | # This file is maintained automatically by "terraform init".
2 | # Manual edits may be lost in future updates.
3 |
4 | provider "registry.terraform.io/hashicorp/archive" {
5 | version = "2.2.0"
6 | hashes = [
7 | "h1:CIWi5G6ob7p2wWoThRQbOB8AbmFlCzp7Ka81hR3cVp0=",
8 | "zh:06bd875932288f235c16e2237142b493c2c2b6aba0e82e8c85068332a8d2a29e",
9 | "zh:0c681b481372afcaefddacc7ccdf1d3bb3a0c0d4678a526bc8b02d0c331479bc",
10 | "zh:100fc5b3fc01ea463533d7bbfb01cb7113947a969a4ec12e27f5b2be49884d6c",
11 | "zh:55c0d7ddddbd0a46d57c51fcfa9b91f14eed081a45101dbfc7fd9d2278aa1403",
12 | "zh:73a5dd68379119167934c48afa1101b09abad2deb436cd5c446733e705869d6b",
13 | "zh:841fc4ac6dc3479981330974d44ad2341deada8a5ff9e3b1b4510702dfbdbed9",
14 | "zh:91be62c9b41edb137f7f835491183628d484e9d6efa82fcb75cfa538c92791c5",
15 | "zh:acd5f442bd88d67eb948b18dc2ed421c6c3faee62d3a12200e442bfff0aa7d8b",
16 | "zh:ad5720da5524641ad718a565694821be5f61f68f1c3c5d2cfa24426b8e774bef",
17 | "zh:e63f12ea938520b3f83634fc29da28d92eed5cfbc5cc8ca08281a6a9c36cca65",
18 | "zh:f6542918faa115df46474a36aabb4c3899650bea036b5f8a5e296be6f8f25767",
19 | ]
20 | }
21 |
22 | provider "registry.terraform.io/hashicorp/aws" {
23 | version = "4.10.0"
24 | constraints = "4.10.0"
25 | hashes = [
26 | "h1:S6xGPRL08YEuBdemiYZyIBf/YwM4OCvzVuaiuU6kLjc=",
27 | "zh:0a2a7eabfeb7dbb17b7f82aff3fa2ba51e836c15e5be4f5468ea44bd1299b48d",
28 | "zh:23409c7205d13d2d68b5528e1c49e0a0455d99bbfec61eb0201142beffaa81f7",
29 | "zh:3adad2245d97816f3919778b52c58fb2de130938a3e9081358bfbb72ec478d9a",
30 | "zh:5bf100aba6332f24b1ffeae7536d5d489bb907bf774a06b95f2183089eaf1a1a",
31 | "zh:63c3a24c0c229a1d3390e6ea2454ba4d8ace9b94e086bee1dbdcf665ae969e15",
32 | "zh:6b76f5ffd920f0a750da3a4ff1d00eab18d9cd3731b009aae3df4135613bad4d",
33 | "zh:8cd6b1e6b51e8e9bbe2944bb169f113d20d1d72d07ccd1b7b83f40b3c958233e",
34 | "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425",
35 | "zh:c5c31f58fb5bd6aebc6c662a4693640ec763cb3399cce0b592101cf24ece1625",
36 | "zh:cc485410be43d6ad95d81b9e54cc4d2117aadf9bf5941165a9df26565d9cce42",
37 | "zh:cebb89c74b6a3dc6780824b1d1e2a8d16a51e75679e14ad0b830d9f7da1a3a67",
38 | "zh:e7dc427189cb491e1f96e295101964415cbf8630395ee51e396d2a811f365237",
39 | ]
40 | }
41 |
42 | provider "registry.terraform.io/hashicorp/null" {
43 | version = "3.1.1"
44 | hashes = [
45 | "h1:71sNUDvmiJcijsvfXpiLCz0lXIBSsEJjMxljt7hxMhw=",
46 | "zh:063466f41f1d9fd0dd93722840c1314f046d8760b1812fa67c34de0afcba5597",
47 | "zh:08c058e367de6debdad35fc24d97131c7cf75103baec8279aba3506a08b53faf",
48 | "zh:73ce6dff935150d6ddc6ac4a10071e02647d10175c173cfe5dca81f3d13d8afe",
49 | "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3",
50 | "zh:8fdd792a626413502e68c195f2097352bdc6a0df694f7df350ed784741eb587e",
51 | "zh:976bbaf268cb497400fd5b3c774d218f3933271864345f18deebe4dcbfcd6afa",
52 | "zh:b21b78ca581f98f4cdb7a366b03ae9db23a73dfa7df12c533d7c19b68e9e72e5",
53 | "zh:b7fc0c1615dbdb1d6fd4abb9c7dc7da286631f7ca2299fb9cd4664258ccfbff4",
54 | "zh:d1efc942b2c44345e0c29bc976594cb7278c38cfb8897b344669eafbc3cddf46",
55 | "zh:e356c245b3cd9d4789bab010893566acace682d7db877e52d40fc4ca34a50924",
56 | "zh:ea98802ba92fcfa8cf12cbce2e9e7ebe999afbf8ed47fa45fc847a098d89468b",
57 | "zh:eff8872458806499889f6927b5d954560f3d74bf20b6043409edf94d26cd906f",
58 | ]
59 | }
60 |
61 | provider "registry.terraform.io/hashicorp/random" {
62 | version = "3.1.2"
63 | hashes = [
64 | "h1:5A5VsY5wNmOZlupUcLnIoziMPn8htSZBXbP3lI7lBEM=",
65 | "zh:0daceba867b330d3f8e2c5dc895c4291845a78f31955ce1b91ab2c4d1cd1c10b",
66 | "zh:104050099efd30a630741f788f9576b19998e7a09347decbec3da0b21d64ba2d",
67 | "zh:173f4ef3fdf0c7e2564a3db0fac560e9f5afdf6afd0b75d6646af6576b122b16",
68 | "zh:41d50f975e535f968b3f37170fb07937c15b76d85ba947d0ce5e5ff9530eda65",
69 | "zh:51a5038867e5e60757ed7f513dd6a973068241190d158a81d1b69296efb9cb8d",
70 | "zh:6432a568e97a5a36cc8aebca5a7e9c879a55d3bc71d0da1ab849ad905f41c0be",
71 | "zh:6bac6501394b87138a5e17c9f3a41e46ff7833ad0ba2a96197bb7787e95b641c",
72 | "zh:6c0a7f5faacda644b022e7718e53f5868187435be6d000786d1ca05aa6683a25",
73 | "zh:74c89de3fa6ef3027efe08f8473c2baeb41b4c6cee250ba7aeb5b64e8c79800d",
74 | "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3",
75 | "zh:b29eabbf0a5298f0e95a1df214c7cfe06ea9bcf362c63b3ad2f72d85da7d4685",
76 | "zh:e891458c7a61e5b964e09616f1a4f87d0471feae1ec04cc51776e7dec1a3abce",
77 | ]
78 | }
79 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Boilerplate Lambda Typescript
2 |
3 | ## Overview
4 |
5 | This is a boilerplate to help you initiate AWS Lambda project using Typescript, in this boilerplate there are `terraform code` to provision the stacks and the initial Typescript source code in the `sources` directory
6 |
7 | ### Features
8 |
9 | - Terraform code to provision the AWS Lambda project
10 | - Typescript source code in the `sources` directory
11 | - Automatically load AWS Secrets Manager (parameter store) as environment variables
12 | - Automatically load DynamoDB (table name) as environment variables
13 | - Automatically create models for DynamoDB tables with the ability to read, write, delete, and scan
14 | - Decorator example to log the execution time of the method
15 | - Datadog example integration to stream the metrics of statistic decorator to Datadog
16 | - Lambda layer to store the dependencies of the project
17 | - Lambda scheduler to schedule the function invocation
18 |
19 | ### Project Structures
20 |
21 | ```
22 | .
23 | ├── sources
24 | │ ├── package-lock.json
25 | │ ├── package.json
26 | │ ├── src
27 | │ │ ├── decorators
28 | │ │ │ └── statistic.decorator.ts
29 | │ │ ├── functions
30 | │ │ │ ├── booking-create.function.ts
31 | │ │ │ ├── booking-search.function.ts
32 | │ │ │ └── flight-search.function.ts
33 | │ │ ├── helpers
34 | │ │ │ ├── chunk.helper.ts
35 | │ │ │ └── parameter-store.helper.ts
36 | │ │ ├── index.ts
37 | │ │ ├── libraries
38 | │ │ │ ├── datadog.library.ts
39 | │ │ │ └── dynamodb.library.ts
40 | │ │ └── models
41 | │ │ ├── booking.model.ts
42 | │ │ ├── flight.model.ts
43 | │ │ ├── model.ts
44 | │ │ └── transaction.model.ts
45 | │ └── tsconfig.json
46 | ├── README.md
47 | ├── data.tf
48 | ├── main.tf
49 | ├── providers.tf
50 | ├── terraform.tfvars.example
51 | └── variables.tf
52 | ```
53 |
54 | While doing a `terraform apply` command, theese are the things that will be created:
55 |
56 | - AWS Lambda Function, in the `main.tf` there's a logic on creating AWS Lambda function based on files with format `*.function.ts` under `sources/src/functions` directory, so the number of AWS Lambda function created is based on `*.function.ts` files
57 | - AWS System Manager Parameter Store, in the `main.tf` there's a logic on creating AWS Parameter Store with prefix set on `parameter_store_path` under `variables.tf` based on:
58 | - **parameter_store_list** attributes under `variables.tf` file
59 | - **dynamodb_table_list** attributes under `variables.tf` file which will create a Parameter Store to store the table names of DynamoDB with format `dynamodb-table-{table_name}`
60 | - **service_version** attributes under `variables.tf` file which will create a Parameter Store to store the version of the service
61 |
62 | Another context related to the Typescript source code:
63 |
64 | - **sources/src/helpers** is the collection of functional helpers such as `populateEnvironmentVariables()` you can freely add another functional helpers under this directory
65 | - **sources/src/libraries** is the collection of class helpers such as `DatadogLibrary` which contains all Datadog functionality such as `publishMetrics` and `publishEvents`, or another example `DynamoDBLibrary` which contains `putItem` and `getItem`
66 | - **sources/src/decorators** is the collection of Typescript decorators, the initial example is `@statistic` decorator which have the functionality to log the execution duration for the method that uses the decorators, the example also include the additional process to stream the statistic metrics into Datadog
67 | - **sources/src/index.ts** is a bootstraper file which contains default `exports.handlers` function, which is the default function that will be called by AWS Lambda Function, this file contains logic to create the `sources/src/functions/*.function.ts` instance and create object then call the `handler` method
68 | - **sources/src/models** is the collection of Typescript models, the initial example is `Booking` which is the model for DynamoDB table `booking` which automatically created by terraform code
69 |
70 |
71 | ## Requirements
72 |
73 | | Name | Version |
74 | |------|---------|
75 | | [terraform](#requirement\_terraform) | ~> 1.1.9 |
76 | | [aws](#requirement\_aws) | ~> 4.10.0 |
77 |
78 | ## Providers
79 |
80 | | Name | Version |
81 | |------|---------|
82 | | [archive](#provider\_archive) | 2.2.0 |
83 | | [aws](#provider\_aws) | 4.10.0 |
84 | | [null](#provider\_null) | 3.1.1 |
85 |
86 | ## Modules
87 |
88 | | Name | Source | Version |
89 | |------|--------|---------|
90 | | [dynamodb-table-name](#module\_dynamodb-table-name) | git@github.com:traveloka/terraform-aws-resource-naming.git | v0.22.0 |
91 | | [lambda-function-name](#module\_lambda-function-name) | git@github.com:traveloka/terraform-aws-resource-naming.git | v0.22.0 |
92 | | [lambda-layer-name](#module\_lambda-layer-name) | git@github.com:traveloka/terraform-aws-resource-naming.git | v0.22.0 |
93 |
94 | ## Resources
95 |
96 | | Name | Type |
97 | |------|------|
98 | | [aws_cloudwatch_event_rule.lambda-function-trigger-schedule](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_event_rule) | resource |
99 | | [aws_cloudwatch_event_target.lambda-function-trigger-schedule](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_event_target) | resource |
100 | | [aws_cloudwatch_log_group.lambda-function-log-group](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_group) | resource |
101 | | [aws_dynamodb_table.dynamodb-table](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/dynamodb_table) | resource |
102 | | [aws_iam_role.lambda-function-role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource |
103 | | [aws_iam_role_policy.function-policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy) | resource |
104 | | [aws_kms_alias.service-alias](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_alias) | resource |
105 | | [aws_kms_key.service-key](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_key) | resource |
106 | | [aws_lambda_function.lambda-function](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_function) | resource |
107 | | [aws_lambda_layer_version.lambda-layer](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_layer_version) | resource |
108 | | [aws_lambda_permission.lambda-function-trigger-schedule](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_permission) | resource |
109 | | [aws_ssm_parameter.ssm-parameter-custom](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource |
110 | | [aws_ssm_parameter.ssm-parameter-dynamodb-table](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource |
111 | | [aws_ssm_parameter.ssm-parameter-service-version](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource |
112 | | [null_resource.lambda-function-source-builder](https://registry.terraform.io/providers/hashicorp/null/latest/docs/resources/resource) | resource |
113 | | [null_resource.lambda-layer-source-builder](https://registry.terraform.io/providers/hashicorp/null/latest/docs/resources/resource) | resource |
114 | | [null_resource.typescript-source-model-builder](https://registry.terraform.io/providers/hashicorp/null/latest/docs/resources/resource) | resource |
115 | | [archive_file.lambda-function-source](https://registry.terraform.io/providers/hashicorp/archive/latest/docs/data-sources/file) | data source |
116 | | [archive_file.lambda-layer-source](https://registry.terraform.io/providers/hashicorp/archive/latest/docs/data-sources/file) | data source |
117 | | [archive_file.typescript-source](https://registry.terraform.io/providers/hashicorp/archive/latest/docs/data-sources/file) | data source |
118 | | [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source |
119 | | [aws_iam_policy_document.function-policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
120 | | [aws_region.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/region) | data source |
121 |
122 | ## Inputs
123 |
124 | | Name | Description | Type | Default | Required |
125 | |------|-------------|------|---------|:--------:|
126 | | [default\_tags](#input\_default\_tags) | The default tags for the service | `map(string)` | `{}` | no |
127 | | [dynamodb\_table\_list](#input\_dynamodb\_table\_list) | The list of dynamodb tables to be used for the service, e.g.
[
{
"name": "booking",
"key": "id"
},
{
"name": "flight",
"key": "id"
},
{
"name": "transaction",
"key": "booking\_id",
"range\_key": "flight\_id"
}
]
| list(object({
name = string,
key = string,
range_key = optional(string),
}))
| `[]` | no |
128 | | [lambda\_function\_configuration](#input\_lambda\_function\_configuration) | The custom configuration for the Lambda Function, e.g.
{
"booking-create": {
"lambda\_memory\_size": 1024,
"lambda\_timeout": 300
}
}
| map(object({
lambda_memory_size = optional(number),
lambda_timeout = optional(number),
schedule_expression = optional(string),
}))
| `{}` | no |
129 | | [parameter\_store\_list](#input\_parameter\_store\_list) | The list of parameter store keys to be used for the service, e.g.
[
"datadog-api-key",
"datadog-app-key",
"sentry-dsn",
"sentry-environment"
]
| `list(string)` | `[]` | no |
130 | | [service\_domain](#input\_service\_domain) | The 1st level of logical grouping of the service, e.g. 'api', 'web', 'db', etc. | `string` | n/a | yes |
131 | | [service\_environment](#input\_service\_environment) | The 3rd level of logical grouping of the service, e.g. 'dev', 'test', 'prod', etc. | `string` | n/a | yes |
132 | | [service\_name](#input\_service\_name) | The 2nd level of logical grouping of the service, e.g. 'my-api', 'my-web', 'my-db', etc. | `string` | n/a | yes |
133 | | [service\_version](#input\_service\_version) | The version of the service | `string` | `"v1.0.0"` | no |
134 |
135 | ## Outputs
136 |
137 | | Name | Description |
138 | |------|-------------|
139 | | [dynamodb-table-list](#output\_dynamodb-table-list) | List of DynamoDB Tables created |
140 | | [kms-alias](#output\_kms-alias) | KMS Alias created |
141 | | [kms-key](#output\_kms-key) | KMS Key created |
142 | | [lambda-function-list](#output\_lambda-function-list) | List of Lambda Functions created |
143 | | [lambda-function-role](#output\_lambda-function-role) | Lambda Function Role created |
144 | | [lambda-layer](#output\_lambda-layer) | Lambda Layer created |
145 | | [ssm-parameter-list](#output\_ssm-parameter-list) | List of SSM Parameters created |
146 |
147 |
148 | ## How to Setup
149 | To setup the example you can follow the following steps:
150 | * Copy the `terraform.tfvars.example` file to `terraform.tfvars`
151 | * Run `terraform init`
152 | * Run `terraform apply`
153 |
--------------------------------------------------------------------------------
/data.tf:
--------------------------------------------------------------------------------
1 | # get current region of the aws account
2 | data "aws_region" "current" {}
3 |
4 | # get current caller identity of the aws account
5 | data "aws_caller_identity" "current" {}
6 |
7 | # create an archive of sources/src
8 | data "archive_file" "typescript-source" {
9 | type = "zip"
10 | source_dir = "${path.module}/sources/src"
11 | output_path = "${local.temporary_build_prefix}/typescript-source.zip"
12 | }
13 |
14 | # create an archive of sources/dist
15 | data "archive_file" "lambda-function-source" {
16 | type = "zip"
17 | depends_on = [null_resource.lambda-layer-source-builder]
18 | source_dir = "${path.module}/sources/dist"
19 | output_path = "${local.temporary_build_prefix}/lambda-function-source.zip"
20 | }
21 |
22 | # create an archive of local.temporary_build_prefix/lambda-layer-source
23 | data "archive_file" "lambda-layer-source" {
24 | type = "zip"
25 | depends_on = [null_resource.lambda-layer-source-builder]
26 | source_dir = "${local.temporary_build_prefix}/lambda-layer-source"
27 | output_path = "${local.temporary_build_prefix}/lambda-layer-source-${filemd5("${path.module}/sources/package-lock.json")}.zip"
28 | }
29 |
30 | # create iam policy for the service lambda function
31 | data "aws_iam_policy_document" "function-policy" {
32 |
33 | statement {
34 | sid = "SSMParameterStoreAccess"
35 | effect = "Allow"
36 | actions = [
37 | "ssm:GetParametersByPath",
38 | ]
39 | resources = [
40 | "arn:aws:ssm:${data.aws_region.current.id}:${data.aws_caller_identity.current.id}:parameter/service/${var.service_domain}/${var.service_name}/${var.service_environment}/*",
41 | ]
42 | }
43 |
44 | statement {
45 | sid = "DynamoDBTableAccess"
46 | effect = "Allow"
47 | actions = [
48 | "dynamodb:Get*",
49 | "dynamodb:Put*",
50 | "dynamodb:Query",
51 | "dynamodb:Scan",
52 | "dynamodb:BatchWriteItem",
53 | "dynamodb:DescribeTable"
54 | ]
55 | resources = [
56 | for table in var.dynamodb_table_list : "arn:aws:dynamodb:${data.aws_region.current.id}:${data.aws_caller_identity.current.account_id}:table/${module.dynamodb-table-name[table.name].name}"
57 | ]
58 | }
59 |
60 | statement {
61 | sid = "KMSKeyAccess"
62 | effect = "Allow"
63 | actions = [
64 | "kms:Decrypt*",
65 | "kms:Encrypt*",
66 | ]
67 | resources = [
68 | aws_kms_key.service-key.arn,
69 | aws_kms_alias.service-alias.arn,
70 | ]
71 | }
72 |
73 | statement {
74 | sid = "CloudWatchLogGroupAccess"
75 | effect = "Allow"
76 | actions = [
77 | "logs:CreateLogGroup",
78 | "logs:CreateLogStream",
79 | "logs:PutLogEvents"
80 | ]
81 | resources = [
82 | "arn:aws:logs:${data.aws_region.current.id}:${data.aws_caller_identity.current.account_id}:*",
83 | "arn:aws:logs:${data.aws_region.current.id}:${data.aws_caller_identity.current.account_id}:log-group:/aws/lambda/*:*",
84 | ]
85 | }
86 |
87 | }
88 |
--------------------------------------------------------------------------------
/main.tf:
--------------------------------------------------------------------------------
1 | locals {
2 |
3 | # lambda runtime
4 | lambda_runtime = "nodejs14.x"
5 |
6 | # this is will generate the list of function name based on files residing in the functions directory
7 | # this is also parse the name of the function and generate the function name
8 | functions = toset([
9 | for file in fileset("${path.module}/sources/src/functions", "*.function.ts") : lower(replace(split(".", split("/", file)[length(split("/", file)) - 1])[0], "[^a-zA-Z0-9]", "-"))
10 | ])
11 |
12 | # temporary path prefix to store the generated files
13 | temporary_build_prefix = "/tmp/${data.aws_caller_identity.current.id}/${data.aws_region.current.id}/${var.service_name}"
14 |
15 | # define the default tags for the resources
16 | default_tags = merge({
17 | "Name" : "${var.service_domain}-${var.service_name}-${var.service_environment}",
18 | "Environment" : "${var.service_environment}",
19 | "Service" : "${var.service_name}",
20 | "ServiceName" : "${var.service_name}",
21 | "ServiceDomain" : "${var.service_domain}",
22 | "ServiceVersion" : "${var.service_version}",
23 | }, var.default_tags)
24 |
25 | # define resources prefix name
26 | resource_prefix_name = substr(var.service_name, 0, length(var.service_domain)) == var.service_domain ? "${var.service_name}-${var.service_environment}" : "${var.service_domain}-${var.service_name}-${var.service_environment}"
27 |
28 | }
29 |
30 | # naming section to standardize the naming of the resources
31 | # naming for Lambda Layer
32 | module "lambda-layer-name" {
33 | source = "git@github.com:traveloka/terraform-aws-resource-naming.git?ref=v0.22.0"
34 | name_prefix = "${local.resource_prefix_name}-layer"
35 | resource_type = "lambda_function"
36 | }
37 |
38 | # naming for Lambda Function
39 | module "lambda-function-name" {
40 | for_each = local.functions
41 | source = "git@github.com:traveloka/terraform-aws-resource-naming.git?ref=v0.22.0"
42 | name_prefix = "${local.resource_prefix_name}-${each.value}"
43 | resource_type = "lambda_function"
44 | }
45 |
46 | # naming for DynamoDB Table
47 | module "dynamodb-table-name" {
48 | for_each = { for table in var.dynamodb_table_list : table.name => table }
49 | source = "git@github.com:traveloka/terraform-aws-resource-naming.git?ref=v0.22.0"
50 | name_prefix = "${local.resource_prefix_name}-${each.value.name}"
51 | resource_type = "dynamodb_table"
52 | }
53 |
54 | # build a TypeScript model based on DynamoDB table list
55 | # this will generate a TypeScript model for each DynamoDB table
56 | # and store it in the sources/src/models directory with format of .module.ts
57 | # the TypeScript model will only be generated if the model file does not exist
58 | resource "null_resource" "typescript-source-model-builder" {
59 | for_each = { for table in var.dynamodb_table_list : table.name => table }
60 | depends_on = [null_resource.lambda-layer-source-builder]
61 | provisioner "local-exec" {
62 | working_dir = "${path.module}/sources"
63 | command = < ${path.module}/src/models/${each.value.name}.model.ts
67 | echo "" >> ${path.module}/src/models/${each.value.name}.model.ts
68 | echo "export class ${join("", [for name_component in split("-", each.value.name) : "${upper(substr(name_component, 0, 1))}${substr(name_component, 1, length(name_component))}"])}Model extends Model {" >> ${path.module}/src/models/${each.value.name}.model.ts
69 | echo " public ${each.value.key}: string;" >> ${path.module}/src/models/${each.value.name}.model.ts
70 | echo " // insert your model properties here" >> ${path.module}/src/models/${each.value.name}.model.ts
71 | echo " // e.g. public name: string;" >> ${path.module}/src/models/${each.value.name}.model.ts
72 | echo "}" >> ${path.module}/src/models/${each.value.name}.model.ts
73 | fi
74 | EOT
75 | }
76 | triggers = {
77 | timestamp = timestamp()
78 | }
79 | }
80 |
81 | # build the dist directory using npm run build
82 | # this will generate the Javascript from Typescript source code using TypeScript compiler
83 | # this builder will only run when:
84 | # - there's a change in the source code directory
85 | # - there's a change in service domain value
86 | # - there's a change in service name value
87 | # - there's no existing dist directory
88 | resource "null_resource" "lambda-function-source-builder" {
89 | depends_on = [null_resource.typescript-source-model-builder]
90 | provisioner "local-exec" {
91 | working_dir = "${path.module}/sources"
92 | command = "npm run build"
93 | }
94 | triggers = {
95 | lambda-function-md5 = data.archive_file.typescript-source.output_md5
96 | service_domain = var.service_domain
97 | service_name = var.service_name
98 | file_dist = fileexists("${path.module}/sources/dist/index.js") ? "${path.module}/sources/dist/index.js" : timestamp()
99 | }
100 | }
101 |
102 | # build the dependencies directory using npm install
103 | # this will generate the dependencies file from the package.json file
104 | # this will be used as Lambda Layer archive file
105 | # this builder will only run when:
106 | # - there's a change in package.json file
107 | # - there's a change in package-lock.json file
108 | # - there's a change in service domain value
109 | # - there's a change in service name value
110 | # - there's no existing dependencies directory
111 | resource "null_resource" "lambda-layer-source-builder" {
112 | provisioner "local-exec" {
113 | working_dir = "${path.module}/sources"
114 | command = < table }
209 |
210 | name = module.dynamodb-table-name[each.value.name].name
211 | billing_mode = "PAY_PER_REQUEST"
212 | hash_key = each.value.key
213 | range_key = each.value.range_key != null ? each.value.range_key : null
214 | tags = local.default_tags
215 |
216 | point_in_time_recovery {
217 | enabled = true
218 | }
219 |
220 | server_side_encryption {
221 | enabled = true
222 | kms_key_arn = aws_kms_key.service-key.arn
223 | }
224 |
225 | attribute {
226 | name = each.value.key
227 | type = "S"
228 | }
229 |
230 | dynamic "attribute" {
231 | for_each = each.value.range_key != null ? [each.value.range_key] : []
232 | content {
233 | name = each.value.range_key
234 | type = "S"
235 | }
236 | }
237 |
238 | }
239 |
240 | # create SSM Parameter Store to store DynamoDB table name based on the var.dynamodb_table_list value
241 | # this parameter will be used to load the environment variables to get the actual DynamoDB table name
242 | # the environment variables will be create with this format DYNAMODB_TABLE_
243 | resource "aws_ssm_parameter" "ssm-parameter-dynamodb-table" {
244 | for_each = { for table in var.dynamodb_table_list : table.name => table }
245 | name = "/service/${var.service_domain}/${var.service_name}/${var.service_environment}/dynamodb-table-${each.value.name}"
246 | key_id = aws_kms_alias.service-alias.id
247 | type = "SecureString"
248 | value = module.dynamodb-table-name[each.value.name].name
249 | tags = local.default_tags
250 | }
251 |
252 | # create SSM Parameter Store based on the var.ssm_parameter_list value
253 | # this parameter will be used to load the environment variables to get the actual SSM parameter value
254 | resource "aws_ssm_parameter" "ssm-parameter-custom" {
255 | for_each = toset(var.parameter_store_list)
256 | name = "/service/${var.service_domain}/${var.service_name}/${var.service_environment}/${each.value}"
257 | key_id = aws_kms_alias.service-alias.id
258 | type = "SecureString"
259 | value = "placeholder"
260 | tags = local.default_tags
261 | lifecycle {
262 | ignore_changes = [value]
263 | }
264 | }
265 |
266 | # create SSM Parameter Store to store the service version
267 | # this parameter will be used to load the environment variables to get the actual service version
268 | # the environment variables will be create with this format SERVICE_VERSION
269 | resource "aws_ssm_parameter" "ssm-parameter-service-version" {
270 | name = "/service/${var.service_domain}/${var.service_name}/${var.service_environment}/service-version"
271 | key_id = aws_kms_alias.service-alias.id
272 | type = "String"
273 | value = var.service_version
274 | tags = local.default_tags
275 | }
276 |
277 | # attach the Lambda Function policy to the Lambda Function Role
278 | resource "aws_iam_role_policy" "function-policy" {
279 | for_each = aws_iam_role.lambda-function-role
280 | name = "function-policy"
281 | role = each.value.name
282 | policy = data.aws_iam_policy_document.function-policy.json
283 | }
284 |
285 | # create the Cloudwatch Event Rule for the Lambda Function with schedule_expression attribute
286 | resource "aws_cloudwatch_event_rule" "lambda-function-trigger-schedule" {
287 | for_each = {
288 | for name, configuration in var.lambda_function_configuration : name => configuration
289 | if configuration.schedule_expression != ""
290 | }
291 | description = "Lambda Function trigger schedule for ${each.key}"
292 | event_bus_name = "default"
293 | is_enabled = true
294 | name = each.key
295 | schedule_expression = each.value.schedule_expression
296 | tags = local.default_tags
297 | }
298 |
299 | # set the Cloudwatch Event Rule target to the Lambda Function
300 | resource "aws_cloudwatch_event_target" "lambda-function-trigger-schedule" {
301 | for_each = aws_cloudwatch_event_rule.lambda-function-trigger-schedule
302 | arn = aws_lambda_function.lambda-function[each.key].arn
303 | event_bus_name = "default"
304 | rule = aws_cloudwatch_event_rule.lambda-function-trigger-schedule[each.key].name
305 | target_id = "lambda-function-trigger-schedule-${each.key}"
306 | }
307 |
308 | # give the permission to the Cloudwatch Event Rule to invoke the Lambda Function
309 | resource "aws_lambda_permission" "lambda-function-trigger-schedule" {
310 | depends_on = [aws_lambda_function.lambda-function]
311 | for_each = aws_cloudwatch_event_rule.lambda-function-trigger-schedule
312 | statement_id = "AllowLambdaFunctionTriggerSchedule"
313 | action = "lambda:InvokeFunction"
314 | function_name = aws_lambda_function.lambda-function[each.key].function_name
315 | principal = "events.amazonaws.com"
316 | source_arn = aws_cloudwatch_event_rule.lambda-function-trigger-schedule[each.key].arn
317 | }
318 |
--------------------------------------------------------------------------------
/outputs.tf:
--------------------------------------------------------------------------------
1 | output "lambda-function-list" {
2 | value = aws_lambda_function.lambda-function
3 | description = "List of Lambda Functions created"
4 | }
5 |
6 | output "dynamodb-table-list" {
7 | value = aws_dynamodb_table.dynamodb-table
8 | description = "List of DynamoDB Tables created"
9 | }
10 |
11 | output "ssm-parameter-list" {
12 | value = merge(
13 | aws_ssm_parameter.ssm-parameter-dynamodb-table,
14 | aws_ssm_parameter.ssm-parameter-custom,
15 | aws_ssm_parameter.ssm-parameter-service-version
16 | )
17 | sensitive = true
18 | description = "List of SSM Parameters created"
19 | }
20 |
21 | output "kms-key" {
22 | value = aws_kms_key.service-key
23 | description = "KMS Key created"
24 | }
25 |
26 | output "kms-alias" {
27 | value = aws_kms_alias.service-alias
28 | description = "KMS Alias created"
29 | }
30 |
31 | output "lambda-function-role" {
32 | value = aws_iam_role.lambda-function-role
33 | description = "Lambda Function Role created"
34 | }
35 |
36 | output "lambda-layer" {
37 | value = aws_lambda_layer_version.lambda-layer
38 | description = "Lambda Layer created"
39 | }
40 |
--------------------------------------------------------------------------------
/providers.tf:
--------------------------------------------------------------------------------
1 | terraform {
2 |
3 | required_version = "~> 1.1.9"
4 | experiments = [module_variable_optional_attrs]
5 |
6 | required_providers {
7 | aws = {
8 | source = "hashicorp/aws"
9 | version = "~> 4.10.0"
10 | }
11 | }
12 |
13 | }
14 |
15 | provider "aws" {
16 | region = "ap-southeast-1"
17 | }
18 |
--------------------------------------------------------------------------------
/sources/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
--------------------------------------------------------------------------------
/sources/.node-version:
--------------------------------------------------------------------------------
1 | 14.19.1
--------------------------------------------------------------------------------
/sources/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "boilerplate-lambda-typescript",
3 | "version": "1.0.0",
4 | "lockfileVersion": 1,
5 | "requires": true,
6 | "dependencies": {
7 | "@aws-crypto/ie11-detection": {
8 | "version": "2.0.0",
9 | "resolved": "https://registry.npmjs.org/@aws-crypto/ie11-detection/-/ie11-detection-2.0.0.tgz",
10 | "integrity": "sha512-pkVXf/dq6PITJ0jzYZ69VhL8VFOFoPZLZqtU/12SGnzYuJOOGNfF41q9GxdI1yqC8R13Rq3jOLKDFpUJFT5eTA==",
11 | "dev": true,
12 | "requires": {
13 | "tslib": "^1.11.1"
14 | },
15 | "dependencies": {
16 | "tslib": {
17 | "version": "1.14.1",
18 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
19 | "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
20 | "dev": true
21 | }
22 | }
23 | },
24 | "@aws-crypto/sha256-browser": {
25 | "version": "2.0.0",
26 | "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-2.0.0.tgz",
27 | "integrity": "sha512-rYXOQ8BFOaqMEHJrLHul/25ckWH6GTJtdLSajhlqGMx0PmSueAuvboCuZCTqEKlxR8CQOwRarxYMZZSYlhRA1A==",
28 | "dev": true,
29 | "requires": {
30 | "@aws-crypto/ie11-detection": "^2.0.0",
31 | "@aws-crypto/sha256-js": "^2.0.0",
32 | "@aws-crypto/supports-web-crypto": "^2.0.0",
33 | "@aws-crypto/util": "^2.0.0",
34 | "@aws-sdk/types": "^3.1.0",
35 | "@aws-sdk/util-locate-window": "^3.0.0",
36 | "@aws-sdk/util-utf8-browser": "^3.0.0",
37 | "tslib": "^1.11.1"
38 | },
39 | "dependencies": {
40 | "tslib": {
41 | "version": "1.14.1",
42 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
43 | "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
44 | "dev": true
45 | }
46 | }
47 | },
48 | "@aws-crypto/sha256-js": {
49 | "version": "2.0.0",
50 | "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-2.0.0.tgz",
51 | "integrity": "sha512-VZY+mCY4Nmrs5WGfitmNqXzaE873fcIZDu54cbaDaaamsaTOP1DBImV9F4pICc3EHjQXujyE8jig+PFCaew9ig==",
52 | "dev": true,
53 | "requires": {
54 | "@aws-crypto/util": "^2.0.0",
55 | "@aws-sdk/types": "^3.1.0",
56 | "tslib": "^1.11.1"
57 | },
58 | "dependencies": {
59 | "tslib": {
60 | "version": "1.14.1",
61 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
62 | "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
63 | "dev": true
64 | }
65 | }
66 | },
67 | "@aws-crypto/supports-web-crypto": {
68 | "version": "2.0.0",
69 | "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-2.0.0.tgz",
70 | "integrity": "sha512-Ge7WQ3E0OC7FHYprsZV3h0QIcpdyJLvIeg+uTuHqRYm8D6qCFJoiC+edSzSyFiHtZf+NOQDJ1q46qxjtzIY2nA==",
71 | "dev": true,
72 | "requires": {
73 | "tslib": "^1.11.1"
74 | },
75 | "dependencies": {
76 | "tslib": {
77 | "version": "1.14.1",
78 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
79 | "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
80 | "dev": true
81 | }
82 | }
83 | },
84 | "@aws-crypto/util": {
85 | "version": "2.0.1",
86 | "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-2.0.1.tgz",
87 | "integrity": "sha512-JJmFFwvbm08lULw4Nm5QOLg8+lAQeC8aCXK5xrtxntYzYXCGfHwUJ4Is3770Q7HmICsXthGQ+ZsDL7C2uH3yBQ==",
88 | "dev": true,
89 | "requires": {
90 | "@aws-sdk/types": "^3.1.0",
91 | "@aws-sdk/util-utf8-browser": "^3.0.0",
92 | "tslib": "^1.11.1"
93 | },
94 | "dependencies": {
95 | "tslib": {
96 | "version": "1.14.1",
97 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
98 | "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
99 | "dev": true
100 | }
101 | }
102 | },
103 | "@aws-sdk/abort-controller": {
104 | "version": "3.55.0",
105 | "resolved": "https://registry.npmjs.org/@aws-sdk/abort-controller/-/abort-controller-3.55.0.tgz",
106 | "integrity": "sha512-rCcTxJDEFnmvo/PgbhCRv24/Uv03lEGfRslKZq7SjaMcOubflS/ZXYaMEgsjYHgAT0zlpSsyCIkJXmhFaM7H7w==",
107 | "dev": true,
108 | "requires": {
109 | "@aws-sdk/types": "3.55.0",
110 | "tslib": "^2.3.1"
111 | }
112 | },
113 | "@aws-sdk/client-dynamodb": {
114 | "version": "3.76.0",
115 | "resolved": "https://registry.npmjs.org/@aws-sdk/client-dynamodb/-/client-dynamodb-3.76.0.tgz",
116 | "integrity": "sha512-U8Jfn6hi7r76EtZDJbzVOpySXvYKxk8RoMdXdebworwvTuLbFfgefahY2A5BUY7ByMsgOcD5WwPkJURY9y5VYg==",
117 | "dev": true,
118 | "requires": {
119 | "@aws-crypto/sha256-browser": "2.0.0",
120 | "@aws-crypto/sha256-js": "2.0.0",
121 | "@aws-sdk/client-sts": "3.76.0",
122 | "@aws-sdk/config-resolver": "3.75.0",
123 | "@aws-sdk/credential-provider-node": "3.76.0",
124 | "@aws-sdk/fetch-http-handler": "3.58.0",
125 | "@aws-sdk/hash-node": "3.55.0",
126 | "@aws-sdk/invalid-dependency": "3.55.0",
127 | "@aws-sdk/middleware-content-length": "3.58.0",
128 | "@aws-sdk/middleware-endpoint-discovery": "3.75.0",
129 | "@aws-sdk/middleware-host-header": "3.58.0",
130 | "@aws-sdk/middleware-logger": "3.55.0",
131 | "@aws-sdk/middleware-retry": "3.75.0",
132 | "@aws-sdk/middleware-serde": "3.55.0",
133 | "@aws-sdk/middleware-signing": "3.58.0",
134 | "@aws-sdk/middleware-stack": "3.55.0",
135 | "@aws-sdk/middleware-user-agent": "3.58.0",
136 | "@aws-sdk/node-config-provider": "3.75.0",
137 | "@aws-sdk/node-http-handler": "3.76.0",
138 | "@aws-sdk/protocol-http": "3.58.0",
139 | "@aws-sdk/smithy-client": "3.72.0",
140 | "@aws-sdk/types": "3.55.0",
141 | "@aws-sdk/url-parser": "3.55.0",
142 | "@aws-sdk/util-base64-browser": "3.58.0",
143 | "@aws-sdk/util-base64-node": "3.55.0",
144 | "@aws-sdk/util-body-length-browser": "3.55.0",
145 | "@aws-sdk/util-body-length-node": "3.55.0",
146 | "@aws-sdk/util-defaults-mode-browser": "3.72.0",
147 | "@aws-sdk/util-defaults-mode-node": "3.75.0",
148 | "@aws-sdk/util-user-agent-browser": "3.58.0",
149 | "@aws-sdk/util-user-agent-node": "3.75.0",
150 | "@aws-sdk/util-utf8-browser": "3.55.0",
151 | "@aws-sdk/util-utf8-node": "3.55.0",
152 | "@aws-sdk/util-waiter": "3.55.0",
153 | "tslib": "^2.3.1",
154 | "uuid": "^8.3.2"
155 | }
156 | },
157 | "@aws-sdk/client-ssm": {
158 | "version": "3.76.0",
159 | "resolved": "https://registry.npmjs.org/@aws-sdk/client-ssm/-/client-ssm-3.76.0.tgz",
160 | "integrity": "sha512-V8DroRS035FWBjWLEuNcDOdJlPj/z3ytK0mO3b0BWNCt7pflCjaVFl5DSPEBKII21CphJuXAsICaDMxr4VL8TA==",
161 | "dev": true,
162 | "requires": {
163 | "@aws-crypto/sha256-browser": "2.0.0",
164 | "@aws-crypto/sha256-js": "2.0.0",
165 | "@aws-sdk/client-sts": "3.76.0",
166 | "@aws-sdk/config-resolver": "3.75.0",
167 | "@aws-sdk/credential-provider-node": "3.76.0",
168 | "@aws-sdk/fetch-http-handler": "3.58.0",
169 | "@aws-sdk/hash-node": "3.55.0",
170 | "@aws-sdk/invalid-dependency": "3.55.0",
171 | "@aws-sdk/middleware-content-length": "3.58.0",
172 | "@aws-sdk/middleware-host-header": "3.58.0",
173 | "@aws-sdk/middleware-logger": "3.55.0",
174 | "@aws-sdk/middleware-retry": "3.75.0",
175 | "@aws-sdk/middleware-serde": "3.55.0",
176 | "@aws-sdk/middleware-signing": "3.58.0",
177 | "@aws-sdk/middleware-stack": "3.55.0",
178 | "@aws-sdk/middleware-user-agent": "3.58.0",
179 | "@aws-sdk/node-config-provider": "3.75.0",
180 | "@aws-sdk/node-http-handler": "3.76.0",
181 | "@aws-sdk/protocol-http": "3.58.0",
182 | "@aws-sdk/smithy-client": "3.72.0",
183 | "@aws-sdk/types": "3.55.0",
184 | "@aws-sdk/url-parser": "3.55.0",
185 | "@aws-sdk/util-base64-browser": "3.58.0",
186 | "@aws-sdk/util-base64-node": "3.55.0",
187 | "@aws-sdk/util-body-length-browser": "3.55.0",
188 | "@aws-sdk/util-body-length-node": "3.55.0",
189 | "@aws-sdk/util-defaults-mode-browser": "3.72.0",
190 | "@aws-sdk/util-defaults-mode-node": "3.75.0",
191 | "@aws-sdk/util-user-agent-browser": "3.58.0",
192 | "@aws-sdk/util-user-agent-node": "3.75.0",
193 | "@aws-sdk/util-utf8-browser": "3.55.0",
194 | "@aws-sdk/util-utf8-node": "3.55.0",
195 | "@aws-sdk/util-waiter": "3.55.0",
196 | "tslib": "^2.3.1",
197 | "uuid": "^8.3.2"
198 | }
199 | },
200 | "@aws-sdk/client-sso": {
201 | "version": "3.76.0",
202 | "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.76.0.tgz",
203 | "integrity": "sha512-trwzJWGxeagYAzo+1/JgcU/pM1vpKHW5rkbasDO5ZC4zHAlSwVhlU7yxGjYXsnobjkvf7zqTQhAxmOuMNWMFew==",
204 | "dev": true,
205 | "requires": {
206 | "@aws-crypto/sha256-browser": "2.0.0",
207 | "@aws-crypto/sha256-js": "2.0.0",
208 | "@aws-sdk/config-resolver": "3.75.0",
209 | "@aws-sdk/fetch-http-handler": "3.58.0",
210 | "@aws-sdk/hash-node": "3.55.0",
211 | "@aws-sdk/invalid-dependency": "3.55.0",
212 | "@aws-sdk/middleware-content-length": "3.58.0",
213 | "@aws-sdk/middleware-host-header": "3.58.0",
214 | "@aws-sdk/middleware-logger": "3.55.0",
215 | "@aws-sdk/middleware-retry": "3.75.0",
216 | "@aws-sdk/middleware-serde": "3.55.0",
217 | "@aws-sdk/middleware-stack": "3.55.0",
218 | "@aws-sdk/middleware-user-agent": "3.58.0",
219 | "@aws-sdk/node-config-provider": "3.75.0",
220 | "@aws-sdk/node-http-handler": "3.76.0",
221 | "@aws-sdk/protocol-http": "3.58.0",
222 | "@aws-sdk/smithy-client": "3.72.0",
223 | "@aws-sdk/types": "3.55.0",
224 | "@aws-sdk/url-parser": "3.55.0",
225 | "@aws-sdk/util-base64-browser": "3.58.0",
226 | "@aws-sdk/util-base64-node": "3.55.0",
227 | "@aws-sdk/util-body-length-browser": "3.55.0",
228 | "@aws-sdk/util-body-length-node": "3.55.0",
229 | "@aws-sdk/util-defaults-mode-browser": "3.72.0",
230 | "@aws-sdk/util-defaults-mode-node": "3.75.0",
231 | "@aws-sdk/util-user-agent-browser": "3.58.0",
232 | "@aws-sdk/util-user-agent-node": "3.75.0",
233 | "@aws-sdk/util-utf8-browser": "3.55.0",
234 | "@aws-sdk/util-utf8-node": "3.55.0",
235 | "tslib": "^2.3.1"
236 | }
237 | },
238 | "@aws-sdk/client-sts": {
239 | "version": "3.76.0",
240 | "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.76.0.tgz",
241 | "integrity": "sha512-rrzau4y7VO9q/F6ZRuJAdZV5oKggjgJuUKGSGssYkLgO2BDblcR1ObUNetSyFsGPoSWnDhg0TjFJnlFFlIBplA==",
242 | "dev": true,
243 | "requires": {
244 | "@aws-crypto/sha256-browser": "2.0.0",
245 | "@aws-crypto/sha256-js": "2.0.0",
246 | "@aws-sdk/config-resolver": "3.75.0",
247 | "@aws-sdk/credential-provider-node": "3.76.0",
248 | "@aws-sdk/fetch-http-handler": "3.58.0",
249 | "@aws-sdk/hash-node": "3.55.0",
250 | "@aws-sdk/invalid-dependency": "3.55.0",
251 | "@aws-sdk/middleware-content-length": "3.58.0",
252 | "@aws-sdk/middleware-host-header": "3.58.0",
253 | "@aws-sdk/middleware-logger": "3.55.0",
254 | "@aws-sdk/middleware-retry": "3.75.0",
255 | "@aws-sdk/middleware-sdk-sts": "3.58.0",
256 | "@aws-sdk/middleware-serde": "3.55.0",
257 | "@aws-sdk/middleware-signing": "3.58.0",
258 | "@aws-sdk/middleware-stack": "3.55.0",
259 | "@aws-sdk/middleware-user-agent": "3.58.0",
260 | "@aws-sdk/node-config-provider": "3.75.0",
261 | "@aws-sdk/node-http-handler": "3.76.0",
262 | "@aws-sdk/protocol-http": "3.58.0",
263 | "@aws-sdk/smithy-client": "3.72.0",
264 | "@aws-sdk/types": "3.55.0",
265 | "@aws-sdk/url-parser": "3.55.0",
266 | "@aws-sdk/util-base64-browser": "3.58.0",
267 | "@aws-sdk/util-base64-node": "3.55.0",
268 | "@aws-sdk/util-body-length-browser": "3.55.0",
269 | "@aws-sdk/util-body-length-node": "3.55.0",
270 | "@aws-sdk/util-defaults-mode-browser": "3.72.0",
271 | "@aws-sdk/util-defaults-mode-node": "3.75.0",
272 | "@aws-sdk/util-user-agent-browser": "3.58.0",
273 | "@aws-sdk/util-user-agent-node": "3.75.0",
274 | "@aws-sdk/util-utf8-browser": "3.55.0",
275 | "@aws-sdk/util-utf8-node": "3.55.0",
276 | "entities": "2.2.0",
277 | "fast-xml-parser": "3.19.0",
278 | "tslib": "^2.3.1"
279 | }
280 | },
281 | "@aws-sdk/config-resolver": {
282 | "version": "3.75.0",
283 | "resolved": "https://registry.npmjs.org/@aws-sdk/config-resolver/-/config-resolver-3.75.0.tgz",
284 | "integrity": "sha512-sM1tygyXTEU8+UXAOs9353+lYoaWdtxPtxfC4zQsQUi0zUYCyO8jO7bNBo277uF82jkGwkraUL/F0ZN7KyzjSQ==",
285 | "dev": true,
286 | "requires": {
287 | "@aws-sdk/signature-v4": "3.58.0",
288 | "@aws-sdk/types": "3.55.0",
289 | "@aws-sdk/util-config-provider": "3.55.0",
290 | "@aws-sdk/util-middleware": "3.55.0",
291 | "tslib": "^2.3.1"
292 | }
293 | },
294 | "@aws-sdk/credential-provider-env": {
295 | "version": "3.55.0",
296 | "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.55.0.tgz",
297 | "integrity": "sha512-4AIIXEdvinLlWNFtrUbUgoB7dkuV04RTcTruVWI4Ub4WSsuSCa72ZU1vqyvcEAOgGGLBmcSaGTWByjiD2sGcGA==",
298 | "dev": true,
299 | "requires": {
300 | "@aws-sdk/property-provider": "3.55.0",
301 | "@aws-sdk/types": "3.55.0",
302 | "tslib": "^2.3.1"
303 | }
304 | },
305 | "@aws-sdk/credential-provider-imds": {
306 | "version": "3.75.0",
307 | "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-imds/-/credential-provider-imds-3.75.0.tgz",
308 | "integrity": "sha512-woqM/cZCnPvlel6t5o79CqT8doXe/7tSH5j8RPpfkYUwfdQwQqpjNqcO2QfkVzq4WsKfRZ92U00BhXsWDUZRfg==",
309 | "dev": true,
310 | "requires": {
311 | "@aws-sdk/node-config-provider": "3.75.0",
312 | "@aws-sdk/property-provider": "3.55.0",
313 | "@aws-sdk/types": "3.55.0",
314 | "@aws-sdk/url-parser": "3.55.0",
315 | "tslib": "^2.3.1"
316 | }
317 | },
318 | "@aws-sdk/credential-provider-ini": {
319 | "version": "3.76.0",
320 | "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.76.0.tgz",
321 | "integrity": "sha512-2je7+yjAilgwB/jZwPnhW0P8McmuZoY29A9v45SZxRSW2yABuEUJ3EvcoieUXXNRRnEz96BrldpUHDC8VhXPJw==",
322 | "dev": true,
323 | "requires": {
324 | "@aws-sdk/credential-provider-env": "3.55.0",
325 | "@aws-sdk/credential-provider-imds": "3.75.0",
326 | "@aws-sdk/credential-provider-sso": "3.76.0",
327 | "@aws-sdk/credential-provider-web-identity": "3.55.0",
328 | "@aws-sdk/property-provider": "3.55.0",
329 | "@aws-sdk/shared-ini-file-loader": "3.75.0",
330 | "@aws-sdk/types": "3.55.0",
331 | "tslib": "^2.3.1"
332 | }
333 | },
334 | "@aws-sdk/credential-provider-node": {
335 | "version": "3.76.0",
336 | "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.76.0.tgz",
337 | "integrity": "sha512-PCBB4sj/t5oatxuqogfB/TANMJWjE8zIAwJJagJdXgyo4vMZ8IsSjnkpMwXdUoyPq+rUx6zFq8XagJF+WW0PBw==",
338 | "dev": true,
339 | "requires": {
340 | "@aws-sdk/credential-provider-env": "3.55.0",
341 | "@aws-sdk/credential-provider-imds": "3.75.0",
342 | "@aws-sdk/credential-provider-ini": "3.76.0",
343 | "@aws-sdk/credential-provider-process": "3.75.0",
344 | "@aws-sdk/credential-provider-sso": "3.76.0",
345 | "@aws-sdk/credential-provider-web-identity": "3.55.0",
346 | "@aws-sdk/property-provider": "3.55.0",
347 | "@aws-sdk/shared-ini-file-loader": "3.75.0",
348 | "@aws-sdk/types": "3.55.0",
349 | "tslib": "^2.3.1"
350 | }
351 | },
352 | "@aws-sdk/credential-provider-process": {
353 | "version": "3.75.0",
354 | "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.75.0.tgz",
355 | "integrity": "sha512-G5dvX37AvS+oLGpka2JXv9wS6uViYQnspJ/56RDmXQElE7ChHBRz89GB4lOOowVQMROzpP96LARr8XNJ4iFq/w==",
356 | "dev": true,
357 | "requires": {
358 | "@aws-sdk/property-provider": "3.55.0",
359 | "@aws-sdk/shared-ini-file-loader": "3.75.0",
360 | "@aws-sdk/types": "3.55.0",
361 | "tslib": "^2.3.1"
362 | }
363 | },
364 | "@aws-sdk/credential-provider-sso": {
365 | "version": "3.76.0",
366 | "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.76.0.tgz",
367 | "integrity": "sha512-i2vD1nrq72dNOhfsNI2iRvmI+eaxZeXQCkE5WUqURT8nHCloEkKDPchWWY2obUCVAnL1EPEoSKHyAETl1uSYew==",
368 | "dev": true,
369 | "requires": {
370 | "@aws-sdk/client-sso": "3.76.0",
371 | "@aws-sdk/property-provider": "3.55.0",
372 | "@aws-sdk/shared-ini-file-loader": "3.75.0",
373 | "@aws-sdk/types": "3.55.0",
374 | "tslib": "^2.3.1"
375 | }
376 | },
377 | "@aws-sdk/credential-provider-web-identity": {
378 | "version": "3.55.0",
379 | "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.55.0.tgz",
380 | "integrity": "sha512-aKnXfZNGohTuF9rCGYLg4JEIOvWIZ/sb66XMq7bOUrx13KRPDwL/eUQL8quS5jGRLpjXVNvrS17AFf65GbdUBg==",
381 | "dev": true,
382 | "requires": {
383 | "@aws-sdk/property-provider": "3.55.0",
384 | "@aws-sdk/types": "3.55.0",
385 | "tslib": "^2.3.1"
386 | }
387 | },
388 | "@aws-sdk/endpoint-cache": {
389 | "version": "3.55.0",
390 | "resolved": "https://registry.npmjs.org/@aws-sdk/endpoint-cache/-/endpoint-cache-3.55.0.tgz",
391 | "integrity": "sha512-kxDoHFDuQwZEEUZRp+ZLOg68EXuKPzUN86DcpIZantDVcmu7MSPTbbQp9DZd8MnKVEKCP7Sop5f7zCqOPl3LXw==",
392 | "dev": true,
393 | "requires": {
394 | "mnemonist": "0.38.3",
395 | "tslib": "^2.3.1"
396 | }
397 | },
398 | "@aws-sdk/fetch-http-handler": {
399 | "version": "3.58.0",
400 | "resolved": "https://registry.npmjs.org/@aws-sdk/fetch-http-handler/-/fetch-http-handler-3.58.0.tgz",
401 | "integrity": "sha512-timF3FjPV5Bd+Kgph83LIKVlPCFObVYzious1a6doeLAT6YFwZpRrWbfP/HzS+DCoYiwUsH69oVJ91BoV66oyA==",
402 | "dev": true,
403 | "requires": {
404 | "@aws-sdk/protocol-http": "3.58.0",
405 | "@aws-sdk/querystring-builder": "3.55.0",
406 | "@aws-sdk/types": "3.55.0",
407 | "@aws-sdk/util-base64-browser": "3.58.0",
408 | "tslib": "^2.3.1"
409 | }
410 | },
411 | "@aws-sdk/hash-node": {
412 | "version": "3.55.0",
413 | "resolved": "https://registry.npmjs.org/@aws-sdk/hash-node/-/hash-node-3.55.0.tgz",
414 | "integrity": "sha512-2UdYwY/++AlzWEAFaK9wOed2QSxbzV527vmqKjReLHpPKPrSIlooUxlTH3LU6Y6WVDAzDRtLK43KUVXTLgGK1A==",
415 | "dev": true,
416 | "requires": {
417 | "@aws-sdk/types": "3.55.0",
418 | "@aws-sdk/util-buffer-from": "3.55.0",
419 | "tslib": "^2.3.1"
420 | }
421 | },
422 | "@aws-sdk/invalid-dependency": {
423 | "version": "3.55.0",
424 | "resolved": "https://registry.npmjs.org/@aws-sdk/invalid-dependency/-/invalid-dependency-3.55.0.tgz",
425 | "integrity": "sha512-delH0lV+78fdD/8MXIt9kTLS6IwHvdhqq9dw/ow5VjTUw+xBwUlfPfZplaai+3hKTKWh6a2WZCeDasNItBv9aA==",
426 | "dev": true,
427 | "requires": {
428 | "@aws-sdk/types": "3.55.0",
429 | "tslib": "^2.3.1"
430 | }
431 | },
432 | "@aws-sdk/is-array-buffer": {
433 | "version": "3.55.0",
434 | "resolved": "https://registry.npmjs.org/@aws-sdk/is-array-buffer/-/is-array-buffer-3.55.0.tgz",
435 | "integrity": "sha512-NbiPHVYuPxdqdFd6FxzzN3H1BQn/iWA3ri3Ry7AyLeP/tGs1yzEWMwf8BN8TSMALI0GXT6Sh0GDWy3Ok5xB6DA==",
436 | "dev": true,
437 | "requires": {
438 | "tslib": "^2.3.1"
439 | }
440 | },
441 | "@aws-sdk/lib-dynamodb": {
442 | "version": "3.76.0",
443 | "resolved": "https://registry.npmjs.org/@aws-sdk/lib-dynamodb/-/lib-dynamodb-3.76.0.tgz",
444 | "integrity": "sha512-ok8uFiRir5NsZGChYzYkjT+SB59l0WBGns4iEmSD2rZ/yXpdC240nnOFQJv+DkYVVCA0ZlkGVyJeZ+AoVE6jcA==",
445 | "dev": true,
446 | "requires": {
447 | "@aws-sdk/util-dynamodb": "3.76.0",
448 | "tslib": "^2.3.1"
449 | }
450 | },
451 | "@aws-sdk/middleware-content-length": {
452 | "version": "3.58.0",
453 | "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-content-length/-/middleware-content-length-3.58.0.tgz",
454 | "integrity": "sha512-h/BypPkhjv2CpCUbXA8Fa2s7V2GPiz9l11XhYK+sKSuQvQ7Lbq6VhaKaLqfeD3gLVZHgJZSLGl2btdHV1qHNNA==",
455 | "dev": true,
456 | "requires": {
457 | "@aws-sdk/protocol-http": "3.58.0",
458 | "@aws-sdk/types": "3.55.0",
459 | "tslib": "^2.3.1"
460 | }
461 | },
462 | "@aws-sdk/middleware-endpoint-discovery": {
463 | "version": "3.75.0",
464 | "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-endpoint-discovery/-/middleware-endpoint-discovery-3.75.0.tgz",
465 | "integrity": "sha512-6dJUWlAy/j8fRh1vbROCdK9AA2B/jxMIqTe1zxuBw+sPdlMcMvsuZLb7fK9K+WdgU34pGbef5UTkWSGBK0DuIQ==",
466 | "dev": true,
467 | "requires": {
468 | "@aws-sdk/config-resolver": "3.75.0",
469 | "@aws-sdk/endpoint-cache": "3.55.0",
470 | "@aws-sdk/protocol-http": "3.58.0",
471 | "@aws-sdk/types": "3.55.0",
472 | "tslib": "^2.3.1"
473 | }
474 | },
475 | "@aws-sdk/middleware-host-header": {
476 | "version": "3.58.0",
477 | "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.58.0.tgz",
478 | "integrity": "sha512-q/UKGcanm9e6DBRNN6UKhVqLvpRRdZWbmmPCeDNr4HqhCmgT6i1OvWdhAMOnT++hvCX8DpTsIXzNSlY6zWAxBg==",
479 | "dev": true,
480 | "requires": {
481 | "@aws-sdk/protocol-http": "3.58.0",
482 | "@aws-sdk/types": "3.55.0",
483 | "tslib": "^2.3.1"
484 | }
485 | },
486 | "@aws-sdk/middleware-logger": {
487 | "version": "3.55.0",
488 | "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.55.0.tgz",
489 | "integrity": "sha512-PtRbVrxEzDmeV9prBIP4/9or7R5Dj66mjbFSvNRGZ0n+UBfBFfVRfNrhQPNzQpfV9A3KVl9YyWCVXDSW+/rk9Q==",
490 | "dev": true,
491 | "requires": {
492 | "@aws-sdk/types": "3.55.0",
493 | "tslib": "^2.3.1"
494 | }
495 | },
496 | "@aws-sdk/middleware-retry": {
497 | "version": "3.75.0",
498 | "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-retry/-/middleware-retry-3.75.0.tgz",
499 | "integrity": "sha512-6aQqeasv31d3Iu9t5YyrbbG5m8VKvjTJ+Aeio976ImhZZEEHeh6Hl2i6yX1DvOALIZmFjjMFNHwJkNOVuxXrXg==",
500 | "dev": true,
501 | "requires": {
502 | "@aws-sdk/protocol-http": "3.58.0",
503 | "@aws-sdk/service-error-classification": "3.55.0",
504 | "@aws-sdk/types": "3.55.0",
505 | "@aws-sdk/util-middleware": "3.55.0",
506 | "tslib": "^2.3.1",
507 | "uuid": "^8.3.2"
508 | }
509 | },
510 | "@aws-sdk/middleware-sdk-sts": {
511 | "version": "3.58.0",
512 | "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-sts/-/middleware-sdk-sts-3.58.0.tgz",
513 | "integrity": "sha512-HUz7MhcsSDDTGygOwL61l4voc0pZco06J3z06JjTX19D5XxcQ7hSCtkHHHz0oMb9M1himVSiEon2tjhjsnB99g==",
514 | "dev": true,
515 | "requires": {
516 | "@aws-sdk/middleware-signing": "3.58.0",
517 | "@aws-sdk/property-provider": "3.55.0",
518 | "@aws-sdk/protocol-http": "3.58.0",
519 | "@aws-sdk/signature-v4": "3.58.0",
520 | "@aws-sdk/types": "3.55.0",
521 | "tslib": "^2.3.1"
522 | }
523 | },
524 | "@aws-sdk/middleware-serde": {
525 | "version": "3.55.0",
526 | "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-serde/-/middleware-serde-3.55.0.tgz",
527 | "integrity": "sha512-NkEbTDrSZcC2NhuvfjXHKJEl0xgI2B5tMAwi/rMOq/TEnARwVUL9qAy+5lgeiPCqebiNllWatARrFgAaYf0VeA==",
528 | "dev": true,
529 | "requires": {
530 | "@aws-sdk/types": "3.55.0",
531 | "tslib": "^2.3.1"
532 | }
533 | },
534 | "@aws-sdk/middleware-signing": {
535 | "version": "3.58.0",
536 | "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-signing/-/middleware-signing-3.58.0.tgz",
537 | "integrity": "sha512-4FXubHB66GbhyZUlo6YPQoWpYfED15GNbEmHbJLSONzrVzZR3IkViSPLasDngVm1a050JqKuqNkFYGJBP4No/Q==",
538 | "dev": true,
539 | "requires": {
540 | "@aws-sdk/property-provider": "3.55.0",
541 | "@aws-sdk/protocol-http": "3.58.0",
542 | "@aws-sdk/signature-v4": "3.58.0",
543 | "@aws-sdk/types": "3.55.0",
544 | "tslib": "^2.3.1"
545 | }
546 | },
547 | "@aws-sdk/middleware-stack": {
548 | "version": "3.55.0",
549 | "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-stack/-/middleware-stack-3.55.0.tgz",
550 | "integrity": "sha512-ouD+wFz8W2R0ZQ8HrbhgN8tg1jyINEg9lPEEXY79w1Q5sf94LJ90XKAMVk02rw3dJalUWjLHf0OQe1/qxZfHyA==",
551 | "dev": true,
552 | "requires": {
553 | "tslib": "^2.3.1"
554 | }
555 | },
556 | "@aws-sdk/middleware-user-agent": {
557 | "version": "3.58.0",
558 | "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.58.0.tgz",
559 | "integrity": "sha512-1c69bIWM63JwXijXvb9IWwcwQ/gViKMZ1lhxv52NvdG5VSxWXXsFJ2jETEXZoAypwT97Hmf3xo9SYuaHcKoq+g==",
560 | "dev": true,
561 | "requires": {
562 | "@aws-sdk/protocol-http": "3.58.0",
563 | "@aws-sdk/types": "3.55.0",
564 | "tslib": "^2.3.1"
565 | }
566 | },
567 | "@aws-sdk/node-config-provider": {
568 | "version": "3.75.0",
569 | "resolved": "https://registry.npmjs.org/@aws-sdk/node-config-provider/-/node-config-provider-3.75.0.tgz",
570 | "integrity": "sha512-eSR0HtqBwRp71d7Cp9fWzC+jtM5sDBcnp4vIQDIBPnHVzvMFwo2YPG0eF5SoYUgboHasHW8VGx9dUsKJ/qTcOg==",
571 | "dev": true,
572 | "requires": {
573 | "@aws-sdk/property-provider": "3.55.0",
574 | "@aws-sdk/shared-ini-file-loader": "3.75.0",
575 | "@aws-sdk/types": "3.55.0",
576 | "tslib": "^2.3.1"
577 | }
578 | },
579 | "@aws-sdk/node-http-handler": {
580 | "version": "3.76.0",
581 | "resolved": "https://registry.npmjs.org/@aws-sdk/node-http-handler/-/node-http-handler-3.76.0.tgz",
582 | "integrity": "sha512-zPWPoaFC5n71efREtpSF1seijZ2E+Wsxz56EK3G55BY7WcSlLgdPXtOS1GXCFtq9Ce6gNALhYvaIryITrbtWsw==",
583 | "dev": true,
584 | "requires": {
585 | "@aws-sdk/abort-controller": "3.55.0",
586 | "@aws-sdk/protocol-http": "3.58.0",
587 | "@aws-sdk/querystring-builder": "3.55.0",
588 | "@aws-sdk/types": "3.55.0",
589 | "tslib": "^2.3.1"
590 | }
591 | },
592 | "@aws-sdk/property-provider": {
593 | "version": "3.55.0",
594 | "resolved": "https://registry.npmjs.org/@aws-sdk/property-provider/-/property-provider-3.55.0.tgz",
595 | "integrity": "sha512-o7cKFJSHq5WOhwPsspYrzNto35oKKZvESZuWDtLxaZKSI6l7zpA366BI4kDG6Tc9i2+teV553MbxyZ9eya5A8g==",
596 | "dev": true,
597 | "requires": {
598 | "@aws-sdk/types": "3.55.0",
599 | "tslib": "^2.3.1"
600 | }
601 | },
602 | "@aws-sdk/protocol-http": {
603 | "version": "3.58.0",
604 | "resolved": "https://registry.npmjs.org/@aws-sdk/protocol-http/-/protocol-http-3.58.0.tgz",
605 | "integrity": "sha512-0yFFRPbR+CCa9eOQBBQ2qtrIDLYqSMN0y7G4iqVM8wQdIw7n3QK1PsTI3RNPGJ3Oi2krFTw5uUKqQQZPZEBuVQ==",
606 | "dev": true,
607 | "requires": {
608 | "@aws-sdk/types": "3.55.0",
609 | "tslib": "^2.3.1"
610 | }
611 | },
612 | "@aws-sdk/querystring-builder": {
613 | "version": "3.55.0",
614 | "resolved": "https://registry.npmjs.org/@aws-sdk/querystring-builder/-/querystring-builder-3.55.0.tgz",
615 | "integrity": "sha512-/ZAXNipt9nRR8k+eowwukE/YjXnQ49p5w/MkaQxsBk3IuIf7MAcgVg8glHr0igH84GfUQ7ZVP8v+G2S3tKUG+Q==",
616 | "dev": true,
617 | "requires": {
618 | "@aws-sdk/types": "3.55.0",
619 | "@aws-sdk/util-uri-escape": "3.55.0",
620 | "tslib": "^2.3.1"
621 | }
622 | },
623 | "@aws-sdk/querystring-parser": {
624 | "version": "3.55.0",
625 | "resolved": "https://registry.npmjs.org/@aws-sdk/querystring-parser/-/querystring-parser-3.55.0.tgz",
626 | "integrity": "sha512-e+2FLgo+eDx7oh7ap5HngN9XSVMxredAVztLHxCcSN0lFHHHzMa8b2SpXbaowUxQHh7ziymSqvOrPYFQ71Filg==",
627 | "dev": true,
628 | "requires": {
629 | "@aws-sdk/types": "3.55.0",
630 | "tslib": "^2.3.1"
631 | }
632 | },
633 | "@aws-sdk/service-error-classification": {
634 | "version": "3.55.0",
635 | "resolved": "https://registry.npmjs.org/@aws-sdk/service-error-classification/-/service-error-classification-3.55.0.tgz",
636 | "integrity": "sha512-HdjnDyarsa1Avq1MJurkLyEe9c3eRa76dPmK4TmRGgwJ+tInEzGHL0rBW7V8xBK+PDF+fJQ71hvm8jPYmzvBwQ==",
637 | "dev": true
638 | },
639 | "@aws-sdk/shared-ini-file-loader": {
640 | "version": "3.75.0",
641 | "resolved": "https://registry.npmjs.org/@aws-sdk/shared-ini-file-loader/-/shared-ini-file-loader-3.75.0.tgz",
642 | "integrity": "sha512-xNeBKoEqBWTdlSNhd0oA0ToA915zvKuAYHppOqJlAHpXQhjZN+Jtz31Rlor/EKZbHSMmZX7YzYMHhYWtY8aeCA==",
643 | "dev": true,
644 | "requires": {
645 | "tslib": "^2.3.1"
646 | }
647 | },
648 | "@aws-sdk/signature-v4": {
649 | "version": "3.58.0",
650 | "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4/-/signature-v4-3.58.0.tgz",
651 | "integrity": "sha512-flEo8p3XkzWoBDqnIUQre4jLuT5aLnmfQNI8c2uSjyJ3OBxpJ0iS1cDu3E++d1/pN6Q8o0KOmr2ypHeiyBOujw==",
652 | "dev": true,
653 | "requires": {
654 | "@aws-sdk/is-array-buffer": "3.55.0",
655 | "@aws-sdk/types": "3.55.0",
656 | "@aws-sdk/util-hex-encoding": "3.58.0",
657 | "@aws-sdk/util-middleware": "3.55.0",
658 | "@aws-sdk/util-uri-escape": "3.55.0",
659 | "tslib": "^2.3.1"
660 | }
661 | },
662 | "@aws-sdk/smithy-client": {
663 | "version": "3.72.0",
664 | "resolved": "https://registry.npmjs.org/@aws-sdk/smithy-client/-/smithy-client-3.72.0.tgz",
665 | "integrity": "sha512-eQ2pEzxtS1Vz1XyNKzG4Z+mtfwRzcAs4FUQP0wrrYVJMsIdI0X4vvro8gYGoBbQtOz65uY3XqQdLuXX/SabTQg==",
666 | "dev": true,
667 | "requires": {
668 | "@aws-sdk/middleware-stack": "3.55.0",
669 | "@aws-sdk/types": "3.55.0",
670 | "tslib": "^2.3.1"
671 | }
672 | },
673 | "@aws-sdk/types": {
674 | "version": "3.55.0",
675 | "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.55.0.tgz",
676 | "integrity": "sha512-wrDZjuy1CVAYxDCbm3bWQIKMGfNs7XXmG0eG4858Ixgqmq2avsIn5TORy8ynBxcXn9aekV/+tGEQ7BBSYzIVNQ==",
677 | "dev": true
678 | },
679 | "@aws-sdk/url-parser": {
680 | "version": "3.55.0",
681 | "resolved": "https://registry.npmjs.org/@aws-sdk/url-parser/-/url-parser-3.55.0.tgz",
682 | "integrity": "sha512-qrTwN5xIgTLreqLnZ+x3cAudjNKfxi6srW1H/px2mk4lb2U9B4fpGjZ6VU+XV8U2kR+YlT8J6Jo5iwuVGfC91A==",
683 | "dev": true,
684 | "requires": {
685 | "@aws-sdk/querystring-parser": "3.55.0",
686 | "@aws-sdk/types": "3.55.0",
687 | "tslib": "^2.3.1"
688 | }
689 | },
690 | "@aws-sdk/util-base64-browser": {
691 | "version": "3.58.0",
692 | "resolved": "https://registry.npmjs.org/@aws-sdk/util-base64-browser/-/util-base64-browser-3.58.0.tgz",
693 | "integrity": "sha512-0ebsXIZNpu/fup9OgsFPnRKfCFbuuI9PPRzvP6twzLxUB0c/aix6Co7LGHFKcRKHZdaykoJMXArf8eHj2Nzv1Q==",
694 | "dev": true,
695 | "requires": {
696 | "tslib": "^2.3.1"
697 | }
698 | },
699 | "@aws-sdk/util-base64-node": {
700 | "version": "3.55.0",
701 | "resolved": "https://registry.npmjs.org/@aws-sdk/util-base64-node/-/util-base64-node-3.55.0.tgz",
702 | "integrity": "sha512-UQ/ZuNoAc8CFMpSiRYmevaTsuRKzLwulZTnM8LNlIt9Wx1tpNvqp80cfvVj7yySKROtEi20wq29h31dZf1eYNQ==",
703 | "dev": true,
704 | "requires": {
705 | "@aws-sdk/util-buffer-from": "3.55.0",
706 | "tslib": "^2.3.1"
707 | }
708 | },
709 | "@aws-sdk/util-body-length-browser": {
710 | "version": "3.55.0",
711 | "resolved": "https://registry.npmjs.org/@aws-sdk/util-body-length-browser/-/util-body-length-browser-3.55.0.tgz",
712 | "integrity": "sha512-Ei2OCzXQw5N6ZkTMZbamUzc1z+z1R1Ja5tMEagz5BxuX4vWdBObT+uGlSzL8yvTbjoPjnxWA2aXyEqaUP3JS8Q==",
713 | "dev": true,
714 | "requires": {
715 | "tslib": "^2.3.1"
716 | }
717 | },
718 | "@aws-sdk/util-body-length-node": {
719 | "version": "3.55.0",
720 | "resolved": "https://registry.npmjs.org/@aws-sdk/util-body-length-node/-/util-body-length-node-3.55.0.tgz",
721 | "integrity": "sha512-lU1d4I+9wJwydduXs0SxSfd+mHKjxeyd39VwOv6i2KSwWkPbji9UQqpflKLKw+r45jL7+xU/zfeTUg5Tt/3Gew==",
722 | "dev": true,
723 | "requires": {
724 | "tslib": "^2.3.1"
725 | }
726 | },
727 | "@aws-sdk/util-buffer-from": {
728 | "version": "3.55.0",
729 | "resolved": "https://registry.npmjs.org/@aws-sdk/util-buffer-from/-/util-buffer-from-3.55.0.tgz",
730 | "integrity": "sha512-uVzKG1UgvnV7XX2FPTylBujYMKBPBaq/qFBxfl0LVNfrty7YjpfieQxAe6yRLD+T0Kir/WDQwGvYC+tOYG3IGA==",
731 | "dev": true,
732 | "requires": {
733 | "@aws-sdk/is-array-buffer": "3.55.0",
734 | "tslib": "^2.3.1"
735 | }
736 | },
737 | "@aws-sdk/util-config-provider": {
738 | "version": "3.55.0",
739 | "resolved": "https://registry.npmjs.org/@aws-sdk/util-config-provider/-/util-config-provider-3.55.0.tgz",
740 | "integrity": "sha512-30dzofQQfx6tp1jVZkZ0DGRsT0wwC15nEysKRiAcjncM64A0Cm6sra77d0os3vbKiKoPCI/lMsFr4o3533+qvQ==",
741 | "dev": true,
742 | "requires": {
743 | "tslib": "^2.3.1"
744 | }
745 | },
746 | "@aws-sdk/util-defaults-mode-browser": {
747 | "version": "3.72.0",
748 | "resolved": "https://registry.npmjs.org/@aws-sdk/util-defaults-mode-browser/-/util-defaults-mode-browser-3.72.0.tgz",
749 | "integrity": "sha512-xeoh4jdq+tpZWDwGeXeoAQI+rZaCBEicjumBcqfzkRFE3DyaeyPHn3hiKGSR13R+P6Uf86aqaRNmWAeZZjeE0w==",
750 | "dev": true,
751 | "requires": {
752 | "@aws-sdk/property-provider": "3.55.0",
753 | "@aws-sdk/types": "3.55.0",
754 | "bowser": "^2.11.0",
755 | "tslib": "^2.3.1"
756 | }
757 | },
758 | "@aws-sdk/util-defaults-mode-node": {
759 | "version": "3.75.0",
760 | "resolved": "https://registry.npmjs.org/@aws-sdk/util-defaults-mode-node/-/util-defaults-mode-node-3.75.0.tgz",
761 | "integrity": "sha512-zR53YinMCSVcdXumxBMdnZANl5ld0riuEoDwgKIivag/5xOAp/r+PziYvaMDbIvdqtkwwMBXf+WAc9jb0/D7sg==",
762 | "dev": true,
763 | "requires": {
764 | "@aws-sdk/config-resolver": "3.75.0",
765 | "@aws-sdk/credential-provider-imds": "3.75.0",
766 | "@aws-sdk/node-config-provider": "3.75.0",
767 | "@aws-sdk/property-provider": "3.55.0",
768 | "@aws-sdk/types": "3.55.0",
769 | "tslib": "^2.3.1"
770 | }
771 | },
772 | "@aws-sdk/util-dynamodb": {
773 | "version": "3.76.0",
774 | "resolved": "https://registry.npmjs.org/@aws-sdk/util-dynamodb/-/util-dynamodb-3.76.0.tgz",
775 | "integrity": "sha512-cFgfkCHE2C4iOHxb65plqzXyGaP3yjgATxrJvHMiG1vTj/KNoV+hkO/DGSEvr9Z67JlP9yAvHO2LmM048zSVEw==",
776 | "dev": true,
777 | "requires": {
778 | "tslib": "^2.3.1"
779 | }
780 | },
781 | "@aws-sdk/util-hex-encoding": {
782 | "version": "3.58.0",
783 | "resolved": "https://registry.npmjs.org/@aws-sdk/util-hex-encoding/-/util-hex-encoding-3.58.0.tgz",
784 | "integrity": "sha512-Rl+jXUzk/FJkOLYfUVYPhKa2aUmTpeobRP31l8IatQltSzDgLyRHO35f6UEs7Ztn5s1jbu/POatLAZ2WjbgVyg==",
785 | "dev": true,
786 | "requires": {
787 | "tslib": "^2.3.1"
788 | }
789 | },
790 | "@aws-sdk/util-locate-window": {
791 | "version": "3.55.0",
792 | "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.55.0.tgz",
793 | "integrity": "sha512-0sPmK2JaJE2BbTcnvybzob/VrFKCXKfN4CUKcvn0yGg/me7Bz+vtzQRB3Xp+YSx+7OtWxzv63wsvHoAnXvgxgg==",
794 | "dev": true,
795 | "requires": {
796 | "tslib": "^2.3.1"
797 | }
798 | },
799 | "@aws-sdk/util-middleware": {
800 | "version": "3.55.0",
801 | "resolved": "https://registry.npmjs.org/@aws-sdk/util-middleware/-/util-middleware-3.55.0.tgz",
802 | "integrity": "sha512-82fW2XV+rUalv8lkd4VlhpPp6xnXO5n9sckMp1N+TrQ+p8eqxqT0+o8n1/6s9Qsnkw64Y3m6+EfCdc8/uFOY2g==",
803 | "dev": true,
804 | "requires": {
805 | "tslib": "^2.3.1"
806 | }
807 | },
808 | "@aws-sdk/util-uri-escape": {
809 | "version": "3.55.0",
810 | "resolved": "https://registry.npmjs.org/@aws-sdk/util-uri-escape/-/util-uri-escape-3.55.0.tgz",
811 | "integrity": "sha512-mmdDLUpFCN2nkfwlLdOM54lTD528GiGSPN1qb8XtGLgZsJUmg3uJSFIN2lPeSbEwJB3NFjVas/rnQC48i7mV8w==",
812 | "dev": true,
813 | "requires": {
814 | "tslib": "^2.3.1"
815 | }
816 | },
817 | "@aws-sdk/util-user-agent-browser": {
818 | "version": "3.58.0",
819 | "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.58.0.tgz",
820 | "integrity": "sha512-aJpqCvT09giJRg5xFTBDBRAVF0k0yq3OEf6UTuiOVf5azlL2MGp6PJ/xkJp9Z06PuQQkwBJ/2nIQZemo02a5Sw==",
821 | "dev": true,
822 | "requires": {
823 | "@aws-sdk/types": "3.55.0",
824 | "bowser": "^2.11.0",
825 | "tslib": "^2.3.1"
826 | }
827 | },
828 | "@aws-sdk/util-user-agent-node": {
829 | "version": "3.75.0",
830 | "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.75.0.tgz",
831 | "integrity": "sha512-tUKI/WIhPjGwIxFZIApWz64/JwJwwzt55Rxp8kv0cP/rYVjfCZafokUKLRwJaOBWi79luvNKV7V6lXY7RjT61A==",
832 | "dev": true,
833 | "requires": {
834 | "@aws-sdk/node-config-provider": "3.75.0",
835 | "@aws-sdk/types": "3.55.0",
836 | "tslib": "^2.3.1"
837 | }
838 | },
839 | "@aws-sdk/util-utf8-browser": {
840 | "version": "3.55.0",
841 | "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8-browser/-/util-utf8-browser-3.55.0.tgz",
842 | "integrity": "sha512-ljzqJcyjfJpEVSIAxwtIS8xMRUly84BdjlBXyp6cu4G8TUufgjNS31LWdhyGhgmW5vYBNr+LTz0Kwf6J+ou7Ug==",
843 | "dev": true,
844 | "requires": {
845 | "tslib": "^2.3.1"
846 | }
847 | },
848 | "@aws-sdk/util-utf8-node": {
849 | "version": "3.55.0",
850 | "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8-node/-/util-utf8-node-3.55.0.tgz",
851 | "integrity": "sha512-FsFm7GFaC7j0tlPEm/ri8bU2QCwFW5WKjxUg8lm1oWaxplCpKGUsmcfPJ4sw58GIoyoGu4QXBK60oCWosZYYdQ==",
852 | "dev": true,
853 | "requires": {
854 | "@aws-sdk/util-buffer-from": "3.55.0",
855 | "tslib": "^2.3.1"
856 | }
857 | },
858 | "@aws-sdk/util-waiter": {
859 | "version": "3.55.0",
860 | "resolved": "https://registry.npmjs.org/@aws-sdk/util-waiter/-/util-waiter-3.55.0.tgz",
861 | "integrity": "sha512-Do34MKPFSC/+zVN6vY+FZ+0WN61hzga4nPoAC590AOjs8rW6/H6sDN6Gz1KAZbPnuQUZfvsIJjMxN7lblXHJkQ==",
862 | "dev": true,
863 | "requires": {
864 | "@aws-sdk/abort-controller": "3.55.0",
865 | "@aws-sdk/types": "3.55.0",
866 | "tslib": "^2.3.1"
867 | }
868 | },
869 | "@datadog/datadog-api-client": {
870 | "version": "1.0.0-beta.9",
871 | "resolved": "https://registry.npmjs.org/@datadog/datadog-api-client/-/datadog-api-client-1.0.0-beta.9.tgz",
872 | "integrity": "sha512-yCjrLVZvuxpsZhfriyFZb3q7s0IY/aK7AmyovKwGa/7LZoaaQE8ry6C/9cmWFaUfqie5t3oWr50eRKVhe+Dm6A==",
873 | "requires": {
874 | "@types/buffer-from": "^1.1.0",
875 | "@types/node": "*",
876 | "@types/pako": "^1.0.3",
877 | "btoa": "^1.2.1",
878 | "buffer-from": "^1.1.2",
879 | "cross-fetch": "^3.1.5",
880 | "durations": "^3.4.2",
881 | "es6-promise": "^4.2.4",
882 | "form-data": "^3.0.0",
883 | "loglevel": "^1.7.1",
884 | "pako": "^2.0.4",
885 | "url-parse": "^1.4.3"
886 | }
887 | },
888 | "@types/aws-lambda": {
889 | "version": "8.10.95",
890 | "resolved": "https://registry.npmjs.org/@types/aws-lambda/-/aws-lambda-8.10.95.tgz",
891 | "integrity": "sha512-wGtzLbd04EmqhFjTZmXgLzvmhDdyVU7AMo/JkiPmA2VUdBFQfUBQFCEzaVVK+f1PP5aWx1ejnb7K/8MXYI/frQ==",
892 | "dev": true
893 | },
894 | "@types/buffer-from": {
895 | "version": "1.1.0",
896 | "resolved": "https://registry.npmjs.org/@types/buffer-from/-/buffer-from-1.1.0.tgz",
897 | "integrity": "sha512-BLFpLBcN+RPKUsFxqRkMiwqTOOdi+TrKr5OpLJ9qCnUdSxS6S80+QRX/mIhfR66u0Ykc4QTkReaejOM2ILh+9Q==",
898 | "requires": {
899 | "@types/node": "*"
900 | }
901 | },
902 | "@types/node": {
903 | "version": "17.0.25",
904 | "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.25.tgz",
905 | "integrity": "sha512-wANk6fBrUwdpY4isjWrKTufkrXdu1D2YHCot2fD/DfWxF5sMrVSA+KN7ydckvaTCh0HiqX9IVl0L5/ZoXg5M7w=="
906 | },
907 | "@types/pako": {
908 | "version": "1.0.3",
909 | "resolved": "https://registry.npmjs.org/@types/pako/-/pako-1.0.3.tgz",
910 | "integrity": "sha512-EDxOsHAD5dqjbjEUM1xwa7rpKPFb8ECBE5irONTQU7/OsO3thI5YrNEWSPNMvYmvFM0l/OLQJ6Mgw7PEdXSjhg=="
911 | },
912 | "asynckit": {
913 | "version": "0.4.0",
914 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
915 | "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
916 | },
917 | "bowser": {
918 | "version": "2.11.0",
919 | "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz",
920 | "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==",
921 | "dev": true
922 | },
923 | "btoa": {
924 | "version": "1.2.1",
925 | "resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz",
926 | "integrity": "sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g=="
927 | },
928 | "buffer-from": {
929 | "version": "1.1.2",
930 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
931 | "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="
932 | },
933 | "combined-stream": {
934 | "version": "1.0.8",
935 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
936 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
937 | "requires": {
938 | "delayed-stream": "~1.0.0"
939 | }
940 | },
941 | "cross-fetch": {
942 | "version": "3.1.5",
943 | "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz",
944 | "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==",
945 | "requires": {
946 | "node-fetch": "2.6.7"
947 | }
948 | },
949 | "delayed-stream": {
950 | "version": "1.0.0",
951 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
952 | "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
953 | },
954 | "durations": {
955 | "version": "3.4.2",
956 | "resolved": "https://registry.npmjs.org/durations/-/durations-3.4.2.tgz",
957 | "integrity": "sha512-V/lf7y33dGaypZZetVI1eu7BmvkbC4dItq12OElLRpKuaU5JxQstV2zHwLv8P7cNbQ+KL1WD80zMCTx5dNC4dg=="
958 | },
959 | "entities": {
960 | "version": "2.2.0",
961 | "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz",
962 | "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==",
963 | "dev": true
964 | },
965 | "es6-promise": {
966 | "version": "4.2.8",
967 | "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz",
968 | "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w=="
969 | },
970 | "fast-xml-parser": {
971 | "version": "3.19.0",
972 | "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-3.19.0.tgz",
973 | "integrity": "sha512-4pXwmBplsCPv8FOY1WRakF970TjNGnGnfbOnLqjlYvMiF1SR3yOHyxMR/YCXpPTOspNF5gwudqktIP4VsWkvBg==",
974 | "dev": true
975 | },
976 | "form-data": {
977 | "version": "3.0.1",
978 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz",
979 | "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==",
980 | "requires": {
981 | "asynckit": "^0.4.0",
982 | "combined-stream": "^1.0.8",
983 | "mime-types": "^2.1.12"
984 | }
985 | },
986 | "loglevel": {
987 | "version": "1.8.0",
988 | "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.8.0.tgz",
989 | "integrity": "sha512-G6A/nJLRgWOuuwdNuA6koovfEV1YpqqAG4pRUlFaz3jj2QNZ8M4vBqnVA+HBTmU/AMNUtlOsMmSpF6NyOjztbA=="
990 | },
991 | "mime-db": {
992 | "version": "1.52.0",
993 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
994 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="
995 | },
996 | "mime-types": {
997 | "version": "2.1.35",
998 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
999 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
1000 | "requires": {
1001 | "mime-db": "1.52.0"
1002 | }
1003 | },
1004 | "mnemonist": {
1005 | "version": "0.38.3",
1006 | "resolved": "https://registry.npmjs.org/mnemonist/-/mnemonist-0.38.3.tgz",
1007 | "integrity": "sha512-2K9QYubXx/NAjv4VLq1d1Ly8pWNC5L3BrixtdkyTegXWJIqY+zLNDhhX/A+ZwWt70tB1S8H4BE8FLYEFyNoOBw==",
1008 | "dev": true,
1009 | "requires": {
1010 | "obliterator": "^1.6.1"
1011 | }
1012 | },
1013 | "node-fetch": {
1014 | "version": "2.6.7",
1015 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
1016 | "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==",
1017 | "requires": {
1018 | "whatwg-url": "^5.0.0"
1019 | }
1020 | },
1021 | "obliterator": {
1022 | "version": "1.6.1",
1023 | "resolved": "https://registry.npmjs.org/obliterator/-/obliterator-1.6.1.tgz",
1024 | "integrity": "sha512-9WXswnqINnnhOG/5SLimUlzuU1hFJUc8zkwyD59Sd+dPOMf05PmnYG/d6Q7HZ+KmgkZJa1PxRso6QdM3sTNHig==",
1025 | "dev": true
1026 | },
1027 | "pako": {
1028 | "version": "2.0.4",
1029 | "resolved": "https://registry.npmjs.org/pako/-/pako-2.0.4.tgz",
1030 | "integrity": "sha512-v8tweI900AUkZN6heMU/4Uy4cXRc2AYNRggVmTR+dEncawDJgCdLMximOVA2p4qO57WMynangsfGRb5WD6L1Bg=="
1031 | },
1032 | "querystringify": {
1033 | "version": "2.2.0",
1034 | "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
1035 | "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ=="
1036 | },
1037 | "requires-port": {
1038 | "version": "1.0.0",
1039 | "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
1040 | "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8="
1041 | },
1042 | "tr46": {
1043 | "version": "0.0.3",
1044 | "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
1045 | "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o="
1046 | },
1047 | "tslib": {
1048 | "version": "2.4.0",
1049 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
1050 | "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==",
1051 | "dev": true
1052 | },
1053 | "typescript": {
1054 | "version": "4.6.3",
1055 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.3.tgz",
1056 | "integrity": "sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw==",
1057 | "dev": true
1058 | },
1059 | "url-parse": {
1060 | "version": "1.5.10",
1061 | "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
1062 | "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
1063 | "requires": {
1064 | "querystringify": "^2.1.1",
1065 | "requires-port": "^1.0.0"
1066 | }
1067 | },
1068 | "uuid": {
1069 | "version": "8.3.2",
1070 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
1071 | "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
1072 | "dev": true
1073 | },
1074 | "webidl-conversions": {
1075 | "version": "3.0.1",
1076 | "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
1077 | "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE="
1078 | },
1079 | "whatwg-url": {
1080 | "version": "5.0.0",
1081 | "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
1082 | "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=",
1083 | "requires": {
1084 | "tr46": "~0.0.3",
1085 | "webidl-conversions": "^3.0.0"
1086 | }
1087 | }
1088 | }
1089 | }
1090 |
--------------------------------------------------------------------------------
/sources/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "boilerplate-lambda-typescript",
3 | "version": "1.0.0",
4 | "description": "AWS Lambda Typescript Boilerplate",
5 | "main": "dist/index.js",
6 | "types": "dist/index.d.ts",
7 | "files": [
8 | "dist"
9 | ],
10 | "scripts": {
11 | "build": "tsc || true",
12 | "prepare": "npm run build"
13 | },
14 | "author": "Andi Ashari",
15 | "license": "ISC",
16 | "bugs": {
17 | "url": "https://github.com/aashari/boilerplate-lambda-typescript/issues"
18 | },
19 | "homepage": "https://github.com/aashari/boilerplate-lambda-typescript#readme",
20 | "dependencies": {
21 | "@datadog/datadog-api-client": "^1.0.0-beta.9"
22 | },
23 | "devDependencies": {
24 | "@aws-sdk/client-dynamodb": "^3.76.0",
25 | "@aws-sdk/client-ssm": "^3.75.0",
26 | "@aws-sdk/lib-dynamodb": "^3.76.0",
27 | "@types/aws-lambda": "^8.10.95",
28 | "@types/node": "^17.0.25",
29 | "typescript": "^4.6.3"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/sources/src/decorators/statistic.decorator.ts:
--------------------------------------------------------------------------------
1 | import { inspect } from "util";
2 | import { DatadogLibrary } from "../libraries/datadog.library";
3 |
4 | /**
5 | * Statistic decorator to record the execution time of a function/method, additionally it can deliver the metrics to Datadog.
6 | *
7 | * If Datadog delivery is enabled, then there will be 2 metrics + 1 event sent to Datadog:
8 | * 1. The metrics with metric name: .statistic.method_execution_duration which is the execution time of the function/method
9 | * 2. The metrics with metric name: .statistic.method_execution_count which is the number of times the function/method has been executed
10 | * 3. The event with event title: "Method execution failure: ."
11 | *
12 | * All of the metrics and events will be sent to Datadog with the additional default tags:
13 | * 1. class_name:
14 | * 2. method_name:
15 | * 3. status:success|failure
16 | *
17 | * If Datadog delivery is disabled, then the statistic decorator will only record the execution time of the function/method and log the execution time to the console.
18 | * @param isLogToDatadog enable streaming the statistic to datadog
19 | * @returns void
20 | */
21 | export function statistic(isLogToDatadog: boolean = false) {
22 | // return the decorator function which receives the target function
23 | return function statistic(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
24 | // call the _statistic function with the target function, propertyKey, descriptor and isLogToDatadog
25 | return _statistic(target, propertyKey, descriptor, isLogToDatadog);
26 | }
27 | }
28 |
29 | function _statistic(target: any, propertyKey: string, descriptor: PropertyDescriptor, isLogToDatadog: boolean = false) {
30 | // get the original function and store it in a variable
31 | const originalMethod = descriptor.value;
32 | // override the original function with a new function
33 | descriptor.value = function (...args: any[]) {
34 | // get the start time of the function execution
35 | const start = new Date().getTime();
36 | // get the class name of the target function
37 | // if the target.constructor.name is equal to "Function", then it means the target is a function/static method
38 | const className = (target.constructor.name != 'Function' ? target.constructor.name : target.name);
39 | // generate default Datadog tags
40 | const datadogTags = [`class_name:${className}`, `method_name:${propertyKey}`]
41 | // run the original function and calculate the execution time
42 | return originalMethod.apply(this, args).then((response: any) => {
43 | // record the execution time
44 | const executionDuration = new Date().getTime() - start;
45 | // if Datadog delivery is not enabled, then log the execution time to the console
46 | if (!isLogToDatadog) {
47 | console.info(`statistic.method-execution-duration:${className}.${propertyKey} ${executionDuration}ms`);
48 | // return the original function response
49 | return response;
50 | }
51 | // if Datadog delivery is enabled, then send the execution time and count to Datadog
52 | DatadogLibrary.queueMetric(`statistic.method-execution-duration`, executionDuration, `gauge`, [...datadogTags, `status:success`]);
53 | DatadogLibrary.queueMetric(`statistic.method-execution-count`, 1, `count`, [...datadogTags, `status:success`]);
54 | // return the original function response
55 | return response;
56 | }).catch((error: any) => {
57 |
58 | // record the execution time
59 | const executionDuration = new Date().getTime() - start;
60 |
61 | // if Datadog delivery is not enabled, then log the execution time to the console
62 | if (!isLogToDatadog) {
63 | console.error(`statistic.method-execution-duration:${className}.${propertyKey} ${executionDuration}ms`);
64 | // throw the original function error
65 | throw error;
66 | }
67 |
68 | // if Datadog delivery is enabled, then send the execution time and count to Datadog
69 | DatadogLibrary.queueMetric(`statistic.method-execution-duration`, executionDuration, `gauge`, [...datadogTags, `status:failure`]);
70 | DatadogLibrary.queueMetric(`statistic.method-execution-count`, 1, `count`, [...datadogTags, `status:failure`]);
71 |
72 | // log the errror message to cloudwatch logs
73 | console.error(`Method ${className}.${propertyKey} execution error`, error);
74 |
75 | // stream the error to datadog event
76 | DatadogLibrary.queueEvent(`Method ${className}.${propertyKey} execution error`, [
77 | `Class name: ${className}`,
78 | `Method name: ${propertyKey}`,
79 | `Error: ${error}`,
80 | `Error Details: ${inspect(error)}`
81 | ].join(`\n`), "error", [...datadogTags, `status:failure`]);
82 |
83 | // throw the original function error
84 | throw error;
85 |
86 | });
87 | };
88 | }
89 |
--------------------------------------------------------------------------------
/sources/src/functions/booking-create.function.ts:
--------------------------------------------------------------------------------
1 | import { LambdaFunction } from "..";
2 | import { statistic } from "../decorators/statistic.decorator";
3 | import { BookingModel } from "../models/booking.model";
4 |
5 | export class BookingCreateFunction extends LambdaFunction {
6 |
7 | @statistic()
8 | public async handler(event: any, context: any, callback: any) {
9 | console.log('BookingCreateFunction.handler()');
10 |
11 | let myBookingList: BookingModel[] = []
12 |
13 | for (let i = 0; i < 100; i++) {
14 | let myBooking = new BookingModel();
15 | myBooking.id = i.toString();
16 | myBooking.save();
17 | myBookingList.push(myBooking);
18 | }
19 |
20 | callback(null, {
21 | statusCode: 200,
22 | body: JSON.stringify(myBookingList),
23 | });
24 |
25 | }
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/sources/src/functions/booking-search.function.ts:
--------------------------------------------------------------------------------
1 | import { LambdaFunction } from "..";
2 | import { statistic } from "../decorators/statistic.decorator";
3 | import { BookingModel } from "../models/booking.model";
4 |
5 | export class BookingSearchFunction extends LambdaFunction {
6 |
7 | @statistic()
8 | public async handler(event: any, context: any, callback: any) {
9 |
10 | console.log('BookingSearchFunction.handler()');
11 | let bookingList = await BookingModel.scan();
12 |
13 | callback(null, {
14 | statusCode: 200,
15 | body: JSON.stringify(bookingList),
16 | });
17 |
18 | }
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/sources/src/functions/flight-search.function.ts:
--------------------------------------------------------------------------------
1 | import { LambdaFunction } from "..";
2 | import { statistic } from "../decorators/statistic.decorator";
3 |
4 | export class FlightSearchFunction extends LambdaFunction {
5 |
6 | @statistic()
7 | public async handler(event: any, context: any, callback: any) {
8 | console.log('FlightSearchFunction.handler()');
9 | callback(null, {
10 | statusCode: 200,
11 | body: JSON.stringify({
12 | message: 'Hello World!',
13 | }),
14 | });
15 | }
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/sources/src/helpers/chunk.helper.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Chunk the given array into chunks of the given size.
3 | * @param array The array to chunk
4 | * @param size The size of the chunk
5 | * @returns The array of chunks
6 | */
7 | export function chunk(array: any[], size: number): any[][] {
8 | const chunked_arr: any[] = [];
9 | let index = 0;
10 | while (index < array.length) {
11 | chunked_arr.push(array.slice(index, size + index));
12 | index += size;
13 | }
14 | return chunked_arr;
15 | }
16 |
--------------------------------------------------------------------------------
/sources/src/helpers/parameter-store.helper.ts:
--------------------------------------------------------------------------------
1 | import { GetParametersByPathCommand, Parameter, SSMClient } from "@aws-sdk/client-ssm";
2 |
3 | const SSM_PREFIX = `/service/${process.env.SERVICE_DOMAIN}/${process.env.SERVICE_NAME}/${process.env.SERVICE_ENVIRONMENT}/`;
4 | const SSM_CLIENT = new SSMClient({
5 | region: process.env.AWS_REGION || 'ap-southeast-1'
6 | });
7 |
8 | /**
9 | * Parse the parameter store by path and define the environment variables
10 | * @returns Promise<{ [key: string]: string }> The environment variables map
11 | */
12 | export async function populateEnvironmentVariables(ssmPrefix: string | undefined = undefined, environmentVariablePrefix: string | undefined = undefined) {
13 |
14 | let parameterList: Parameter[] = [];
15 | if (environmentVariablePrefix) environmentVariablePrefix = environmentVariablePrefix.toUpperCase().replace(/[^a-zA-Z0-9]/g, '_');
16 |
17 | // get all parameter based on the prefix
18 | let nexToken: string | undefined;
19 | while (true) {
20 | let parameterListResponse = await SSM_CLIENT.send(new GetParametersByPathCommand({
21 | Path: ssmPrefix ? ssmPrefix : SSM_PREFIX,
22 | WithDecryption: true,
23 | NextToken: nexToken
24 | })).catch(e => {
25 | console.error(`[ParameterStoreHelper][populateEnvironmentVariables] failed to get parameters from parameter store with path ${SSM_PREFIX}`, e);
26 | return null;
27 | });
28 | if (parameterListResponse?.Parameters) {
29 | parameterList = parameterList.concat(parameterListResponse.Parameters);
30 | }
31 | if (!parameterListResponse?.NextToken) break;
32 | nexToken = parameterListResponse.NextToken;
33 | }
34 |
35 | // parse the parameters and set the environment variables
36 | for (let parameter of parameterList) {
37 | if (!parameter?.Name) continue;
38 | // parse the name of the parameter
39 | // this will convert the parameter name to upper case and replace the '-' with '_'
40 | let key = (environmentVariablePrefix ? environmentVariablePrefix + '_' : '') + parameter.Name.replace(ssmPrefix ? ssmPrefix : SSM_PREFIX, '').toUpperCase().replace(/[^a-zA-Z0-9]/g, '_');
41 | if (key.startsWith(`_`)) key = key.substring(1);
42 | // get the value of the parameter
43 | let value = parameter.Value;
44 | if (!value) continue;
45 | // set the environment variable
46 | console.info(`[ParameterStoreHelper][populateEnvironmentVariables] setting environment variable ${key}`);
47 | process.env[key] = value;
48 | }
49 |
50 | // return all the env
51 | return process.env
52 |
53 | }
--------------------------------------------------------------------------------
/sources/src/index.ts:
--------------------------------------------------------------------------------
1 | import { inspect } from "util";
2 | import { populateEnvironmentVariables } from "./helpers/parameter-store.helper";
3 | import { DynamoDBLibrary } from "./libraries/dynamodb.library";
4 |
5 | export interface LambdaFunctionInterface {
6 | handler: (event: any, context: any, callback: any) => Promise;
7 | }
8 |
9 | export class LambdaFunction implements LambdaFunctionInterface {
10 | public handler(event: any, context: any, callback: any) {
11 | console.log('LambdaFunction.handler()');
12 | return Promise.resolve(event);
13 | }
14 | }
15 |
16 | async function startLambda(event: any, context: any, callback: any) {
17 |
18 | // store the timestamp to calculate the duration
19 | const start = new Date().getTime();
20 |
21 | // parse the naming based on function name component
22 | let functionServiceName = process.env.SERVICE_NAME ?? `-`;
23 | let functionName = process.env.FUNCTION_NAME ?? `-`;
24 |
25 | // --------------------------------------------------
26 | // load the helpers here
27 | // --------------------------------------------------
28 | await populateEnvironmentVariables();
29 |
30 | // --------------------------------------------------
31 | // load the library instances here
32 | // --------------------------------------------------
33 | await DynamoDBLibrary.instance();
34 | // await DatadogLibrary.instance(); <-- un comment this line to enable datadog library
35 |
36 | // generate class name based on function name
37 | let className = functionName.split(`-`).map((x: string) => x.charAt(0).toUpperCase() + x.slice(1)).join(``) + 'Function';
38 |
39 | // import the class based on function name and class name, and create an instance of the class
40 | let LambdaFunctionClass = require(`./functions/${functionName}.function`)[className];
41 |
42 | // create the object of the class
43 | let lambdaFunction = new LambdaFunctionClass();
44 |
45 | // errror response object
46 | let errorResponse: any | undefined;
47 |
48 | // call the handler function of the object
49 | return lambdaFunction.handler(event, context, callback).catch((error: any) => {
50 | // catching unhandled promise rejections
51 | errorResponse = error;
52 | return;
53 | }).finally(() => {
54 |
55 | console.info(`---------------------------------------------`);
56 | console.info(`lambda function name: ${functionName}`);
57 | console.info(`lambda function service name: ${functionServiceName}`);
58 | console.info(`lambda function duration: ${new Date().getTime() - start} ms`);
59 | console.info(`lambda function left time: ${context.getRemainingTimeInMillis()} ms`);
60 | console.info(`---------------------------------------------`);
61 |
62 | // un comment this line to send the metric to datadog
63 | // let datadogTags = [
64 | // `name:${functionName}`,
65 | // `status:${errorResponse ? 'failure' : 'success'}`
66 | // ];
67 |
68 | // // stream the metric to datadog
69 | // DatadogLibrary.queueMetric(`lambda.execution-count`, 1, "count", datadogTags);
70 | // DatadogLibrary.queueMetric(`lambda.execution-duration`, new Date().getTime() - start, "gauge", datadogTags);
71 | // DatadogLibrary.queueMetric(`lambda.execution-left-time`, context.getRemainingTimeInMillis(), "gauge", datadogTags);
72 |
73 | if (!errorResponse) return;
74 |
75 | // log the errror message to cloudwatch logs
76 | console.error(`Lambda ${functionName} execution error`, errorResponse);
77 |
78 | // un comment this line to send the metric to datadog
79 | // // stream the error to datadog event
80 | // DatadogLibrary.queueEvent(`Lambda ${functionName} execution error`, [
81 | // `Function Name: ${functionName}`,
82 | // `Error: ${errorResponse}`,
83 | // `Error Details: ${inspect(errorResponse)}`
84 | // ].join(`\n`), `error`, datadogTags);
85 |
86 | // throw the original error
87 | callback(null, {
88 | statusCode: 500,
89 | body: JSON.stringify({
90 | error: errorResponse.message
91 | })
92 | });
93 |
94 | });
95 | }
96 |
97 | export function handler(event: any, context: any, callback: any) {
98 | startLambda(event, context, callback).catch((error: any) => {
99 | console.error(`Lambda execution error`, error);
100 | return Promise.reject(error);
101 | });
102 | }
103 |
--------------------------------------------------------------------------------
/sources/src/libraries/datadog.library.ts:
--------------------------------------------------------------------------------
1 | import { v1 as DatadogAPIClient } from '@datadog/datadog-api-client';
2 |
3 | export class DatadogLibrary {
4 |
5 | private datadogConfiguration: DatadogAPIClient.Configuration;
6 | private datadogMetricsAPI: DatadogAPIClient.MetricsApi;
7 | private datadogEventsAPI: DatadogAPIClient.EventsApi;
8 |
9 | private static datadogLibraryInstance: DatadogLibrary;
10 |
11 | private metricSeries: DatadogAPIClient.Series[] = [];
12 | private metricWaitingHandle: NodeJS.Timeout | undefined;
13 |
14 | private eventSeries: DatadogAPIClient.Event[] = [];
15 | private eventWaitingHandle: NodeJS.Timeout | undefined;
16 |
17 | private defaultDatadogTags = [
18 | `service:${process.env.SERVICE_NAME}`,
19 | `version:${process.env.SERVICE_VERSION}`,
20 | `function_name:${process.env.FUNCTION_NAME}`,
21 | ];
22 |
23 | constructor() { }
24 |
25 | public async initiateDatadogConfiguration() {
26 | this.datadogConfiguration = DatadogAPIClient.createConfiguration();
27 | this.datadogMetricsAPI = new DatadogAPIClient.MetricsApi(this.datadogConfiguration);
28 | this.datadogEventsAPI = new DatadogAPIClient.EventsApi(this.datadogConfiguration);
29 | }
30 |
31 | public static async instance(): Promise {
32 | if (!DatadogLibrary.datadogLibraryInstance) DatadogLibrary.datadogLibraryInstance = new DatadogLibrary();
33 | await DatadogLibrary.datadogLibraryInstance.initiateDatadogConfiguration();
34 | return DatadogLibrary.datadogLibraryInstance;
35 | }
36 |
37 | /**
38 | * Queue a metric to be published to Datadog
39 | * @param metricName The name of the metric
40 | * @param metricValue The value of the metric
41 | * @param type The type of the metric (gauge or count)
42 | * @param additionalTags Additional tags to be added to the metric
43 | * @returns boolean indicating whether the metric was queued successfully
44 | */
45 | public static queueMetric(metricName: string, metricValue: number = 1, type: string = "count", additionalTags: string[] = []) {
46 |
47 | // check whether DD_API_KEY and DD_APP_KEY are set
48 | // if not set please make sure in the main.tf add dd-api-key and dd-app-key in the parameter_store_list
49 | // and make sure change the value in the web console or api console to the value in parameter_store
50 | if (!process.env.DD_APP_KEY || !process.env.DD_APP_KEY || process.env.DD_APP_KEY == 'placeholder' || process.env.DD_APP_KEY == 'placeholder') {
51 | return false;
52 | }
53 |
54 | // generate current metric tag
55 | let currentMetricTag = [...new Set([...this.datadogLibraryInstance.defaultDatadogTags, ...additionalTags])].sort();
56 |
57 | // metricName replace non alphanumeric and non dot characters
58 | metricName = metricName.replace(/[^a-zA-Z0-9.]/g, '_').toLowerCase();
59 |
60 | // add current metric to series
61 | this.datadogLibraryInstance.metricSeries.push({
62 | metric: `${process.env.SERVICE_NAME}.${metricName}`.toLowerCase(),
63 | points: [[Math.round((new Date().getTime() / 1000)), metricValue]],
64 | host: process.env?.AWS_LAMBDA_FUNCTION_NAME ?? "",
65 | type: type,
66 | tags: currentMetricTag.map((tag: string) => tag.toLowerCase())
67 | });
68 |
69 | // if there's existing waiting to publish, cancel it
70 | if (this.datadogLibraryInstance.metricWaitingHandle) {
71 | clearTimeout(this.datadogLibraryInstance.metricWaitingHandle);
72 | }
73 |
74 | // if there's no metrics queued for 300ms second, publish them
75 | this.datadogLibraryInstance.metricWaitingHandle = setTimeout(() => {
76 | let currentSeries = JSON.parse(JSON.stringify(this.datadogLibraryInstance.metricSeries)) as DatadogAPIClient.Series[];
77 | this.datadogLibraryInstance.metricSeries = [];
78 | this.publishMetrics(currentSeries);
79 | }, 300);
80 |
81 | return true;
82 |
83 | }
84 |
85 | /**
86 | * Publish queued metrics to Datadog
87 | * @param currentSeries The series to be published
88 | * @returns Promise indicating whether the metrics were published successfully
89 | */
90 | private static async publishMetrics(currentSeries: DatadogAPIClient.Series[]) {
91 | if (currentSeries.length == 0) return false;
92 | return this.datadogLibraryInstance.datadogMetricsAPI.submitMetrics({
93 | body: { series: currentSeries }
94 | }).then((result: any) => {
95 | console.info(`[DatadogLibrary][publishMetrics] successfully published ${currentSeries.length} metrics`, result);
96 | return true;
97 | }).catch((error: any) => {
98 | console.error(`[DatadogLibrary][publishMetrics] failed to publish ${currentSeries.length} metrics with error: ${error}`, error);
99 | return false;
100 | });
101 | }
102 |
103 | /**
104 | * Queue an event to be published to Datadog
105 | * @param eventName The name of the event
106 | * @param eventText The content/text of the event
107 | * @param additionalTags Additional tags to be added to the event
108 | * @returns boolean indicating whether the event was queued successfully
109 | */
110 | public static queueEvent(eventName: string, eventText: string, eventType: DatadogAPIClient.EventAlertType = "info", additionalTags: string[] = []) {
111 | // check whether DD_API_KEY and DD_APP_KEY are set
112 | // if not set please make sure in the main.tf add dd-api-key and dd-app-key in the parameter_store_list
113 | // and make sure change the value in the web console or api console to the value in parameter_store
114 | if (!process.env.DD_APP_KEY || !process.env.DD_APP_KEY || process.env.DD_APP_KEY == 'placeholder' || process.env.DD_APP_KEY == 'placeholder') {
115 | return false;
116 | }
117 |
118 | // generate current event tag
119 | let currentEventTag = [...new Set([...this.datadogLibraryInstance.defaultDatadogTags, ...additionalTags])].sort();
120 |
121 | // add current event to series
122 | this.datadogLibraryInstance.eventSeries.push({
123 | title: eventName,
124 | text: eventText,
125 | tags: currentEventTag.map((tag: string) => tag.toLowerCase()),
126 | alertType: eventType,
127 | });
128 |
129 | // if there's existing waiting to publish, cancel it
130 | if (this.datadogLibraryInstance.eventWaitingHandle) {
131 | clearTimeout(this.datadogLibraryInstance.eventWaitingHandle);
132 | }
133 |
134 | // if there's no events queued for 300ms, publish them
135 | this.datadogLibraryInstance.eventWaitingHandle = setTimeout(() => {
136 | let currentSeries = JSON.parse(JSON.stringify(this.datadogLibraryInstance.eventSeries)) as DatadogAPIClient.Event[];
137 | this.datadogLibraryInstance.eventSeries = [];
138 | this.publishEvents(currentSeries);
139 | }, 300);
140 |
141 | return true;
142 |
143 | }
144 |
145 | /**
146 | * Publish queued events to Datadog
147 | * @param currentSeries The series to be published
148 | * @returns Promise indicating whether the events were published successfully
149 | */
150 | private static async publishEvents(currentSeries: DatadogAPIClient.Event[]) {
151 | if (currentSeries.length == 0) return false;
152 | let createEventPromises = currentSeries.map((event: DatadogAPIClient.Event) => {
153 | return this.datadogLibraryInstance.datadogEventsAPI.createEvent({
154 | body: {
155 | title: event.title ?? "",
156 | text: event.text ?? "",
157 | tags: event.tags ?? [],
158 | alertType: event.alertType ?? "info",
159 | }
160 | });
161 | });
162 | return Promise.all(createEventPromises).then((result: any) => {
163 | console.info(`[DatadogLibrary][publishEvents] successfully published ${currentSeries.length} events`, result);
164 | return true;
165 | }).catch((error: any) => {
166 | console.error(`[DatadogLibrary][publishEvents] failed to publish ${currentSeries.length} events with error: ${error}`, error);
167 | return false;
168 | });
169 | }
170 |
171 | }
172 |
--------------------------------------------------------------------------------
/sources/src/libraries/dynamodb.library.ts:
--------------------------------------------------------------------------------
1 | import { AttributeValue, DescribeTableCommand, DynamoDBClient, ScanCommandInput, WriteRequest } from '@aws-sdk/client-dynamodb';
2 | import { DynamoDBDocument } from '@aws-sdk/lib-dynamodb';
3 | import { inspect } from 'util';
4 | import { statistic } from '../decorators/statistic.decorator';
5 | import { chunk } from '../helpers/chunk.helper';
6 | import { Model } from '../models/model';
7 | import { DatadogLibrary } from './datadog.library';
8 |
9 | interface BATCH_COMMAND {
10 | [key: string]: (Omit & {
11 | PutRequest?: any | undefined;
12 | DeleteRequest?: any | undefined;
13 | })[];
14 | }
15 |
16 | /**
17 | * DynamoDB library to interact with DynamoDB service, it can be used to create, update, delete, scan and query DynamoDB tables.
18 | * By default, the put and delete methods will be queued and executed in batches, this is to avoid the DynamoDB service from being overloaded.
19 | */
20 | export class DynamoDBLibrary {
21 |
22 | private static dynamoDBLibrary: DynamoDBLibrary;
23 |
24 | private dynamoDBClient: DynamoDBClient;
25 | private documentClient: DynamoDBDocument;
26 | private dynamodbTableKey: { [tableName: string]: string[] } = {};
27 |
28 | private putCommandList: BATCH_COMMAND = {};
29 | private putCommandWaitingHandle: NodeJS.Timeout | undefined;
30 |
31 | private deleteCommandList: BATCH_COMMAND = {};
32 | private deleteCommandWaitingHandle: NodeJS.Timeout | undefined;
33 |
34 | constructor() { }
35 |
36 | public async initiateDatadogConfiguration() {
37 | this.dynamoDBClient = new DynamoDBClient({ region: process.env.AWS_REGION ?? 'ap-southeast-1' });
38 | this.documentClient = DynamoDBDocument.from(this.dynamoDBClient);
39 | }
40 |
41 | public static async instance(): Promise {
42 | if (!DynamoDBLibrary.dynamoDBLibrary) DynamoDBLibrary.dynamoDBLibrary = new DynamoDBLibrary();
43 | await DynamoDBLibrary.dynamoDBLibrary.initiateDatadogConfiguration();
44 | console.info('[DynamoDBLibrary][instance] DynamoDBLibrary initialized and ready to use');
45 | return DynamoDBLibrary.dynamoDBLibrary;
46 | }
47 |
48 | private statisticHandler(tableName: string, method: string, data: any | undefined, errorResponse: any | undefined) {
49 |
50 | DatadogLibrary.queueMetric(`dynamodb.${method}`, 1, `count`, [
51 | `table:${tableName}`,
52 | `class:DynamoDBLibrary`,
53 | `method:${method}`,
54 | `status:${errorResponse ? `failure` : `success`}`
55 | ]);
56 |
57 | if (!errorResponse) return;
58 |
59 | // log the errror message to cloudwatch logs
60 | console.error(`[DynamoDBLibrary][${method}] DynamoDB ${method}.${tableName} execution error`, { errorResponse, data });
61 |
62 | // stream the error to datadog event
63 | DatadogLibrary.queueEvent(`DynamoDB ${method}.${tableName} execution error`, [
64 | `Table Name: ${tableName}`,
65 | `Table Data: ${JSON.stringify(data)}`,
66 | `Error: ${errorResponse}`,
67 | `Error Details: ${inspect(errorResponse)}`,
68 | ].join('\n'), "error", [`table:${tableName}`, `class:DynamoDBLibrary`, `method:${method}`]);
69 |
70 | }
71 |
72 | /**
73 | * Get the table key configuration for the table which should contains the primary key and the sort key
74 | * @param tableName the DynamoDB table name
75 | * @returns Promise the list of string contains the pre defined DynamoDB table key attribute name
76 | */
77 | @statistic()
78 | private static async getTableKey(tableName: string) {
79 | // check if the table key is already cached
80 | if (this.dynamoDBLibrary.dynamodbTableKey[tableName]) return this.dynamoDBLibrary.dynamodbTableKey[tableName];
81 | // generate empty string list
82 | let tableKey: string[] = [];
83 | // get the table description
84 | let tableDescription = await this.dynamoDBLibrary.dynamoDBClient.send(new DescribeTableCommand({ TableName: tableName }));
85 | // get all of the pre defined key attribute name
86 | tableDescription.Table?.KeySchema?.forEach((key) => {
87 | if (key.AttributeName) tableKey.push(key.AttributeName);
88 | });
89 | // cache the table key attribute name list
90 | this.dynamoDBLibrary.dynamodbTableKey[tableName] = tableKey;
91 | // return the table key attribute name list
92 | return tableKey;
93 | }
94 |
95 | /**
96 | * Get the item from the DynamoDB table by the primary key and the sort key (if available)
97 | * @param tableName the DynamoDB table name
98 | * @param key the primary key and the sort key (if available)
99 | * @returns Promise the item from the DynamoDB table
100 | */
101 | @statistic()
102 | public static async get(tableName: string, key: { [key: string]: string }): Promise {
103 | // check whether the item is still on the processing put queue
104 | // if exist, return the item from the queue
105 | if (this.dynamoDBLibrary.putCommandList[tableName]) {
106 | let currentPutList: BATCH_COMMAND[string] = this.dynamoDBLibrary.putCommandList[tableName];
107 | for (let keyName in key) currentPutList = currentPutList.filter((item) => item.PutRequest.Item[keyName] === key[keyName]);
108 | if (currentPutList.length > 0) return currentPutList[0].PutRequest.Item;
109 | }
110 |
111 | // error response record
112 | let errorResponse: any | undefined = undefined;
113 |
114 | // if not exist, get the item from the table, and return the item
115 | return this.dynamoDBLibrary.documentClient.get({
116 | TableName: tableName, Key: key,
117 | }).then((result) => {
118 | return result.Item ? result.Item as any : null;
119 | }).catch(err => {
120 | errorResponse = err;
121 | return null;
122 | }).finally(() => {
123 | this.dynamoDBLibrary.statisticHandler(tableName, 'get', key, errorResponse);
124 | })
125 | }
126 |
127 | /**
128 | * Put the item into the DynamoDB table by the primary key and the sort key (if available)
129 | * @param tableName the DynamoDB table name
130 | * @param data the item to be put into the DynamoDB table
131 | * @param isQueue whether the put operation should be queued
132 | * @returns Promise whether the put operation is successful
133 | */
134 | @statistic()
135 | public static async put(tableName: string, data: Model, isQueue: boolean = true): Promise {
136 |
137 | if (!isQueue) {
138 | // error response record
139 | let errorResponse: any | undefined = undefined;
140 | // put the item into the table, and return the result
141 | return this.dynamoDBLibrary.documentClient.put({
142 | TableName: tableName, Item: data,
143 | }).then((result) => {
144 | return result.$metadata.httpStatusCode === 200 ? true : false;
145 | }).catch(err => {
146 | errorResponse = err;
147 | return false;
148 | }).finally(() => {
149 | this.dynamoDBLibrary.statisticHandler(tableName, 'put', data, errorResponse);
150 | });
151 | }
152 |
153 | // generate queue for put command
154 | if (!this.dynamoDBLibrary.putCommandList[tableName]) this.dynamoDBLibrary.putCommandList[tableName] = [];
155 | this.dynamoDBLibrary.putCommandList[tableName].push({ PutRequest: { Item: data } });
156 |
157 | // if there's existing waiting to publish, cancel it
158 | if (this.dynamoDBLibrary.putCommandWaitingHandle) {
159 | clearTimeout(this.dynamoDBLibrary.putCommandWaitingHandle);
160 | }
161 |
162 | // if there's no metrics queued for 300ms, publish them
163 | this.dynamoDBLibrary.putCommandWaitingHandle = setTimeout(() => {
164 | if (!tableName) return;
165 | let currentPutList: BATCH_COMMAND[string] = JSON.parse(JSON.stringify(this.dynamoDBLibrary.putCommandList[tableName]));
166 | this.dynamoDBLibrary.putCommandList[tableName] = [];
167 | let chunkedList = chunk(currentPutList, 25) as BATCH_COMMAND[string][];
168 | for (let currentChunk of chunkedList) {
169 | this.batchPutQueueByTable(tableName, currentChunk);
170 | }
171 | }, 300);
172 |
173 | // successfully queued write command
174 | return true;
175 |
176 | }
177 |
178 | /**
179 | * Delete the item from the DynamoDB table by the primary key and the sort key (if available)
180 | * @param tableName the DynamoDB table name
181 | * @param data the item to be deleted from the DynamoDB table
182 | * @param isQueue whether the delete operation should be queued
183 | * @returns Promise whether the delete operation is successful
184 | */
185 | @statistic()
186 | public static async delete(tableName: string, data: Model | { [key: string]: string }, isQueue: boolean = true): Promise {
187 |
188 | let key = {};
189 | let tableKey = await this.getTableKey(tableName);
190 | tableKey.forEach((keyName) => key[keyName] = data[keyName]);
191 |
192 | if (!isQueue) {
193 | // error response record
194 | let errorResponse: any | undefined = undefined;
195 | // delete the item from the table, and return the result
196 | return this.dynamoDBLibrary.documentClient.delete({
197 | TableName: tableName, Key: key,
198 | }).then((result) => {
199 | return result.$metadata.httpStatusCode === 200 ? true : false;
200 | }).catch(err => {
201 | errorResponse = err;
202 | return false;
203 | }).finally(() => {
204 | this.dynamoDBLibrary.statisticHandler(tableName, 'delete', key, errorResponse);
205 | });
206 | }
207 |
208 | // generate queue for delete command
209 | if (!this.dynamoDBLibrary.deleteCommandList[tableName]) this.dynamoDBLibrary.deleteCommandList[tableName] = [];
210 | this.dynamoDBLibrary.deleteCommandList[tableName].push({ DeleteRequest: { Key: key } });
211 |
212 | // if there's existing waiting to publish, cancel it
213 | if (this.dynamoDBLibrary.deleteCommandWaitingHandle) {
214 | clearTimeout(this.dynamoDBLibrary.deleteCommandWaitingHandle);
215 | }
216 |
217 | // if there's no metrics queued for 300ms, publish them
218 | this.dynamoDBLibrary.deleteCommandWaitingHandle = setTimeout(() => {
219 | if (!tableName) return;
220 | let currentDeleteList: BATCH_COMMAND[string] = JSON.parse(JSON.stringify(this.dynamoDBLibrary.deleteCommandList[tableName]));
221 | this.dynamoDBLibrary.deleteCommandList[tableName] = [];
222 | let chunkedList = chunk(currentDeleteList, 25) as BATCH_COMMAND[string][];
223 | for (let currentChunk of chunkedList) {
224 | this.batchDeleteQueueByTable(tableName, currentChunk);
225 | }
226 | }, 300);
227 |
228 | // successfully queued write command
229 | return true;
230 |
231 | }
232 |
233 | /**
234 | * Scan the items from the DynamoDB table
235 | * @param tableName the DynamoDB table name
236 | * @param filter the filter to be applied to the scan operation
237 | * @param operand the operand to be applied to the filter
238 | * @returns Promise the items from the DynamoDB table
239 | */
240 | @statistic()
241 | public static async scan(tableName: string, filter: { [key: string]: string } | undefined = undefined, operand: string = 'AND'): Promise {
242 | let resultData: Model[] = [];
243 | let lastEvaluatedKey: { [key: string]: AttributeValue } | undefined;
244 | while (true) {
245 | let params: ScanCommandInput = {
246 | TableName: tableName,
247 | ExclusiveStartKey: lastEvaluatedKey,
248 | }
249 | if (filter) {
250 | params.FilterExpression = Object.keys(filter).map((key) => `contains(#${key}, :${key})`).join(` ${operand} `);
251 | params.ExpressionAttributeNames = Object.keys(filter).reduce((acc, key) => {
252 | acc[`#${key}`] = key;
253 | return acc;
254 | }, {});
255 | params.ExpressionAttributeValues = Object.keys(filter).reduce((acc, key) => {
256 | acc[`:${key}`] = filter[key];
257 | return acc;
258 | }, {})
259 | }
260 | // error response record
261 | let errorResponse: any | undefined = undefined;
262 | // scan the items from the table, and return the result
263 | let isSuccess = await this.dynamoDBLibrary.documentClient.scan(params).then((result) => {
264 | lastEvaluatedKey = result.LastEvaluatedKey;
265 | resultData = resultData.concat(result.Items as Model[]);
266 | return result.$metadata.httpStatusCode === 200 ? true : false;
267 | }).catch(err => {
268 | errorResponse = err;
269 | return false;
270 | }).finally(() => {
271 | this.dynamoDBLibrary.statisticHandler(tableName, 'scan', { filter, params }, errorResponse);
272 | });
273 | if (!isSuccess) break;
274 | if (!lastEvaluatedKey) break;
275 | }
276 | return resultData;
277 | }
278 |
279 | /**
280 | * Batch put the items into the DynamoDB table
281 | * @param tableName the DynamoDB table name
282 | * @param currentPutList the list of items to be put into the DynamoDB table
283 | * @returns Promise whether the put operation is successful
284 | */
285 | @statistic()
286 | private static async batchPutQueueByTable(tableName: string, currentPutList: BATCH_COMMAND[string]) {
287 |
288 | let tableKey = await this.getTableKey(tableName);
289 | let uniquePutList: BATCH_COMMAND[string] = [];
290 |
291 | currentPutList = currentPutList.reverse();
292 | currentPutList.forEach((item) => {
293 | let isUnique: boolean[] = [];
294 | tableKey.forEach((keyName) => {
295 | if (uniquePutList.find((uniqueItem) => uniqueItem.PutRequest.Item[keyName] === item.PutRequest.Item[keyName])) isUnique.push(false);
296 | });
297 | if (isUnique.length != tableKey.length) uniquePutList.push(item);
298 | });
299 |
300 | if (uniquePutList.length === 0) return false;
301 | // error response record
302 | let errorResponse: any | undefined = undefined;
303 | // batch put the items into the table, and return the result
304 | return this.dynamoDBLibrary.documentClient.batchWrite({
305 | RequestItems: { [tableName]: uniquePutList },
306 | }).then((result) => {
307 | console.info(`[dynamodblibrary][batchPutQueueByTable] successfully put ${uniquePutList.length} items to table ${tableName}`);
308 | return result.$metadata.httpStatusCode === 200 ? true : false;
309 | }).catch(err => {
310 | errorResponse = err;
311 | return false;
312 | }).finally(() => {
313 | this.dynamoDBLibrary.statisticHandler(tableName, 'batchPut', uniquePutList, errorResponse);
314 | });
315 | }
316 |
317 | /**
318 | * Batch delete the items from the DynamoDB table
319 | * @param tableName the DynamoDB table name
320 | * @param currentDeleteList the list of items to be deleted from the DynamoDB table
321 | * @returns Promise whether the delete operation is successful
322 | */
323 | @statistic()
324 | private static async batchDeleteQueueByTable(tableName: string, currentDeleteList: BATCH_COMMAND[string]) {
325 |
326 | let tableKey = await this.getTableKey(tableName);
327 | let uniqueDeleteList: BATCH_COMMAND[string] = [];
328 |
329 | currentDeleteList = currentDeleteList.reverse();
330 | currentDeleteList.forEach((item) => {
331 | let isUnique: boolean[] = [];
332 | tableKey.forEach((keyName) => {
333 | if (uniqueDeleteList.find((uniqueItem) => uniqueItem.DeleteRequest.Key[keyName] === item.DeleteRequest.Key[keyName])) isUnique.push(false);
334 | });
335 | if (isUnique.length != tableKey.length) uniqueDeleteList.push(item);
336 | });
337 |
338 | if (uniqueDeleteList.length === 0) return false;
339 | // error response record
340 | let errorResponse: any | undefined = undefined;
341 | // batch delete the items from the table, and return the result
342 | return this.dynamoDBLibrary.documentClient.batchWrite({
343 | RequestItems: { [tableName]: uniqueDeleteList },
344 | }).then((result) => {
345 | console.info(`[dynamodblibrary][batchDeleteQueueByTable] successfully delete ${uniqueDeleteList.length} data from table ${tableName}`);
346 | return result.$metadata.httpStatusCode === 200 ? true : false;
347 | }).catch(err => {
348 | errorResponse = err;
349 | return false;
350 | }).then(() => {
351 | this.dynamoDBLibrary.statisticHandler(tableName, 'batchDelete', uniqueDeleteList, errorResponse);
352 | });
353 |
354 | }
355 |
356 | }
357 |
--------------------------------------------------------------------------------
/sources/src/models/booking.model.ts:
--------------------------------------------------------------------------------
1 | import { Model } from './model';
2 |
3 | export class BookingModel extends Model {
4 | public id: string;
5 | // insert your model properties here
6 | // e.g. public name: string;
7 | }
8 |
--------------------------------------------------------------------------------
/sources/src/models/flight.model.ts:
--------------------------------------------------------------------------------
1 | import { Model } from './model';
2 |
3 | export class FlightModel extends Model {
4 | public id: string;
5 | // insert your model properties here
6 | // e.g. public name: string;
7 | }
8 |
--------------------------------------------------------------------------------
/sources/src/models/model.ts:
--------------------------------------------------------------------------------
1 | import { DynamoDBLibrary } from "../libraries/dynamodb.library";
2 |
3 | export class Model {
4 |
5 | public created_at?: number;
6 | public updated_at?: number;
7 |
8 | constructor(data?: Model) {
9 | if (!data) return this;
10 | for (let key in data) this[key] = data[key];
11 | return this;
12 | }
13 |
14 | /**
15 | * Generate the actual DynamoDB table name for the given Model class name.
16 | * @param className The name of the Model class.
17 | * @returns string of the actual DynamoDB table name.
18 | */
19 | static getTableName(className: string | undefined = undefined): string {
20 | let environmentName = `DYNAMODB_TABLE_${(className ? className : this.name).replace(/([a-z])([A-Z])/g, '$1_$2').toUpperCase()}`;
21 | let environmentNameComponent = environmentName.split('_'); environmentNameComponent.pop();
22 | let tableName = process.env[environmentNameComponent.join('_').toUpperCase()];
23 | if (!tableName) throw new Error(`[Model][getTableName] table name not found for ${environmentName}`);
24 | return tableName;
25 | }
26 |
27 | /**
28 | * Compare the given object with the current object, and update the current object with the given object.
29 | * @param newData The new data to update the current object with.
30 | * @returns string[] The list of keys that are updated.
31 | */
32 | public compareAndSave(newData: Model) {
33 | let updatedFields: string[] = []
34 | let newDataObject = JSON.parse(JSON.stringify(newData));
35 | for (let key in newDataObject) if (JSON.stringify(newDataObject[key]) !== JSON.stringify(this[key])) {
36 | this[key] = newDataObject[key];
37 | updatedFields.push(key);
38 | }
39 | return updatedFields;
40 | }
41 |
42 | /**
43 | * Save the current object to the DynamoDB table.
44 | * @returns Promise Whether the save operation is successful.
45 | */
46 | public async save() {
47 | return Model.put(this, this.constructor.name);
48 | }
49 |
50 | /**
51 | * Delete the current object from the DynamoDB table.
52 | * @returns Promise Whether the delete operation is successful.
53 | */
54 | public async delete() {
55 | return Model.delete(this, this.constructor.name);
56 | }
57 |
58 | /**
59 | * Static method to get the object by the given key.
60 | * @param key The key to get the object by.
61 | * @returns Promise The object if found, otherwise null.
62 | */
63 | public static async get(key: { [key: string]: string }): Promise {
64 | let tableName = this.getTableName();
65 | return DynamoDBLibrary.get(tableName, key);
66 | }
67 |
68 | /**
69 | * Static method to put the object to the DynamoDB table.
70 | * @param data The object to put to the DynamoDB table.
71 | * @param className The name of the Model class.
72 | * @returns Promise Whether the put operation is successful.
73 | */
74 | public static async put(data: Model, className: string | undefined = undefined): Promise {
75 | let tableName = this.getTableName(className);
76 | if (!data.created_at) data.created_at = Date.now();
77 | data.updated_at = Date.now();
78 | return DynamoDBLibrary.put(tableName, data);
79 | }
80 |
81 | /**
82 | * Static method to scan the DynamoDB table by the given filter and return the list of objects.
83 | * @param query The filter to scan the DynamoDB table by.
84 | * @returns Promise The list of objects.
85 | */
86 | public static async scan(query: { [key: string]: string } | undefined = undefined): Promise {
87 | let tableName = this.getTableName();
88 | return DynamoDBLibrary.scan(tableName, query);
89 | }
90 |
91 | /**
92 | * Static method to delete the object by the given key.
93 | * @param data The key to delete the object by.
94 | * @param className The name of the Model class.
95 | * @returns Promise Whether the delete operation is successful.
96 | */
97 | public static async delete(data: Model | { [key: string]: string }, className: string | undefined = undefined): Promise {
98 | let tableName = this.getTableName(className);
99 | return DynamoDBLibrary.delete(tableName, data);
100 | }
101 |
102 | }
103 |
--------------------------------------------------------------------------------
/sources/src/models/transaction.model.ts:
--------------------------------------------------------------------------------
1 | import { Model } from './model';
2 |
3 | export class FlightModel extends Model {
4 | public booking_id: string;
5 | public flight_id: string;
6 | // insert your model properties here
7 | // e.g. public name: string;
8 | }
9 |
--------------------------------------------------------------------------------
/sources/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "declaration": true,
4 | "strictNullChecks": true,
5 | "experimentalDecorators": true,
6 | "target": "es6",
7 | "outDir": "dist",
8 | "module": "commonjs",
9 | "moduleResolution": "node",
10 | "sourceMap": true,
11 | "esModuleInterop": true,
12 | "lib": ["es2016", "dom"],
13 | "rootDir": "src"
14 | },
15 | "include": ["src"],
16 | "exclude": ["node_modules", "dist"]
17 | }
18 |
--------------------------------------------------------------------------------
/terraform.tfvars.example:
--------------------------------------------------------------------------------
1 | # service version (e.g. v1.0.0)
2 | service_version = "v1.0.0"
3 |
4 | # service domain is the level 1 logical stacks grouping
5 | service_domain = "flight"
6 |
7 | # service name is the level 2 logical stacks grouping, this will be used as the prefix for the stack name
8 | service_name = "booking"
9 |
10 | # service environment is the environment of the service
11 | service_environment = "dev"
12 |
13 | # following configuration is an optional configuration
14 | # generate SSM Parameter Store which will be loaded into the Lambda Function environment
15 | parameter_store_list = [
16 | "dd-api-key", // this will be loaded into the Lambda Function environment as DD_API_KEY
17 | "dd-app-key", // this will be loaded into the Lambda Function environment as DD_APP_KEY
18 | ]
19 |
20 | # following configuration is an optional configuration
21 | # this will create a DynamoDB Table and SSM Parameter Store to store the table name
22 | # the parameterstore will be loaded into the Lambda Function environment as DYNAMO_DB_TABLE_${each.name}
23 | dynamodb_table_list = [
24 | {
25 | name : "booking",
26 | key : "id",
27 | },
28 | {
29 | name : "flight",
30 | key : "id",
31 | },
32 | {
33 | name : "transaction",
34 | key : "booking_id",
35 | range_key : "flight_id",
36 | }
37 | ]
38 |
39 | # following configuration is an optional configuration
40 | # leave this as empty object if you don't want to use Lambda Function custom configuration
41 | lambda_function_configuration = {
42 | booking-create : {
43 | lambda_memory_size : "128",
44 | lambda_timeout : "60",
45 | schedule_expression : "rate(1 minute)", // this will trigger the Lambda Function every minute
46 | }
47 | }
48 |
49 | # define the default tags for the resources
50 | default_tags = {}
51 |
--------------------------------------------------------------------------------
/variables.tf:
--------------------------------------------------------------------------------
1 | variable "service_version" {
2 | default = "v1.0.0"
3 | type = string
4 | description = "The version of the service"
5 | }
6 |
7 | variable "service_domain" {
8 | type = string
9 | description = "The 1st level of logical grouping of the service, e.g. 'api', 'web', 'db', etc."
10 | }
11 |
12 | variable "service_name" {
13 | type = string
14 | description = "The 2nd level of logical grouping of the service, e.g. 'my-api', 'my-web', 'my-db', etc."
15 | }
16 |
17 | variable "service_environment" {
18 | type = string
19 | description = "The 3rd level of logical grouping of the service, e.g. 'dev', 'test', 'prod', etc."
20 | }
21 |
22 | variable "parameter_store_list" {
23 | type = list(string)
24 | default = []
25 | description = <[
"datadog-api-key",
"datadog-app-key",
"sentry-dsn",
"sentry-environment"
]
28 | EOF
29 | }
30 |
31 | variable "dynamodb_table_list" {
32 | type = list(object({
33 | name = string,
34 | key = string,
35 | range_key = optional(string),
36 | }))
37 | default = []
38 | description = <[
{
"name": "booking",
"key": "id"
},
{
"name": "flight",
"key": "id"
},
{
"name": "transaction",
"key": "booking_id",
"range_key": "flight_id"
}
]
41 | EOF
42 | }
43 |
44 | variable "lambda_function_configuration" {
45 | type = map(object({
46 | lambda_memory_size = optional(number),
47 | lambda_timeout = optional(number),
48 | schedule_expression = optional(string),
49 | }))
50 | default = {}
51 | description = <{
"booking-create": {
"lambda_memory_size": 1024,
"lambda_timeout": 300
}
}
54 | EOF
55 | }
56 |
57 | variable "default_tags" {
58 | type = map(string)
59 | default = {}
60 | description = "The default tags for the service"
61 | }
62 |
--------------------------------------------------------------------------------