├── .eslintrc.json ├── .gitattributes ├── .github ├── pull_request_template.md └── workflows │ ├── auto-approve.yml │ ├── build.yml │ ├── pull-request-lint.yml │ ├── release.yml │ └── upgrade-main.yml ├── .gitignore ├── .mergify.yml ├── .npmignore ├── .projen ├── deps.json ├── files.json └── tasks.json ├── .projenrc.js ├── LICENSE ├── README.md ├── docs ├── constructs │ ├── .nojekyll │ ├── assets │ │ ├── hierarchy.js │ │ ├── highlight.css │ │ ├── icons.js │ │ ├── icons.svg │ │ ├── main.js │ │ ├── navigation.js │ │ ├── search.js │ │ └── style.css │ ├── classes │ │ ├── AssetCdn.html │ │ ├── BaseApi.html │ │ ├── CognitoAuthentication.html │ │ ├── GraphQlApi.html │ │ ├── LambdaFunction.html │ │ ├── RestApi.html │ │ ├── SingleTableDatastore.html │ │ └── Workflow.html │ ├── hierarchy.html │ ├── index.html │ ├── interfaces │ │ ├── AssetCdnProps.html │ │ ├── BaseApiProps.html │ │ ├── CognitoAuthenticationProps.html │ │ ├── GraphQlApiProps.html │ │ ├── ICognitoAuthentication.html │ │ ├── IJwtAuthentication.html │ │ ├── JsResolverOptions.html │ │ ├── LambdaFunctionProps.html │ │ ├── LambdaTracingOptions.html │ │ ├── RestApiProps.html │ │ ├── SingleTableDatastoreProps.html │ │ ├── SingleTableDesign.html │ │ ├── VtlResolverOptions.html │ │ └── WorkflowProps.html │ ├── modules.html │ └── types │ │ └── LambdaOptions.html ├── lambda │ ├── .nojekyll │ ├── assets │ │ ├── hierarchy.js │ │ ├── highlight.css │ │ ├── icons.js │ │ ├── icons.svg │ │ ├── main.js │ │ ├── navigation.js │ │ ├── search.js │ │ └── style.css │ ├── classes │ │ ├── auth.ApiGatewayv1CognitoAuthorizer.html │ │ ├── auth.ApiGatewayv1JwtAuthorizer.html │ │ ├── auth.ApiGatewayv2CognitoAuthorizer.html │ │ ├── auth.ApiGatewayv2JwtAuthorizer.html │ │ ├── auth.AppSyncCognitoAuthorizer.html │ │ ├── auth.CognitoAuthorizer.html │ │ ├── auth.JwtAuthorizer.html │ │ ├── errors.BadRequestError.html │ │ ├── errors.ForbiddenError.html │ │ ├── errors.HttpError.html │ │ ├── errors.NotFoundError.html │ │ └── errors.UnauthenticatedError.html │ ├── functions │ │ ├── api.createAppSyncHandler.html │ │ ├── api.createHttpHandler.html │ │ ├── api.createOpenApiHandler.html │ │ ├── api.createOpenApiHandlerWithRequestBody.html │ │ └── api.createOpenApiHandlerWithRequestBodyNoResponse.html │ ├── hierarchy.html │ ├── index.html │ ├── interfaces │ │ ├── api.AppSyncHandlerContext.html │ │ ├── api.HttpHandlerContext.html │ │ ├── api.HttpResponseContext.html │ │ ├── api.Operation.html │ │ └── api.OperationWithRequestBody.html │ ├── modules.html │ ├── modules │ │ ├── api.html │ │ ├── auth.html │ │ └── errors.html │ └── types │ │ ├── api.APIGatewayv1Handler.html │ │ ├── api.AppSyncHandler.html │ │ └── api.HttpHandler.html └── projen │ ├── .nojekyll │ ├── assets │ ├── hierarchy.js │ ├── highlight.css │ ├── icons.js │ ├── icons.svg │ ├── main.js │ ├── navigation.js │ ├── search.js │ └── style.css │ ├── classes │ ├── Datastore.html │ ├── GraphQlApi.html │ ├── RestApi.html │ ├── ServerlessProject.html │ └── Workflow.html │ ├── index.html │ ├── interfaces │ ├── DatastoreOptions.html │ ├── GraphQlApiOptions.html │ ├── RestApiOptions.html │ ├── ServerlessProjectOptions.html │ └── WorkflowOptions.html │ ├── modules.html │ └── variables │ └── PACKAGE_NAME.html ├── files └── aws.graphql ├── llm.md ├── package-lock.json ├── package.json ├── src ├── constructs │ ├── asset-cdn.ts │ ├── authentication.ts │ ├── base-api.ts │ ├── func.ts │ ├── graphql.ts │ ├── index.ts │ ├── rest-api.ts │ ├── table.ts │ └── workflow.ts ├── lambda │ ├── auth.ts │ ├── errors.ts │ ├── handler.ts │ └── index.ts ├── projen │ ├── core.ts │ ├── datastore.ts │ ├── graphql.ts │ ├── index.ts │ ├── lazy-sampledir.ts │ ├── lazy-textfile.ts │ ├── rest-api.ts │ └── workflow.ts ├── shared │ └── outputs.ts └── tests │ ├── index.ts │ ├── integ-test-util.ts │ └── lambda-test-utils.ts ├── test ├── hello.test.ts ├── projen │ ├── datastore.test.ts │ ├── graphql.test.ts │ ├── rest-api.test.ts │ └── workflow.test.ts └── util │ └── synth.ts ├── tsconfig.dev.json └── tsconfig.json /.eslintrc.json: -------------------------------------------------------------------------------- 1 | // ~~ Generated by projen. To modify, edit .projenrc.js and run "npx projen". 2 | { 3 | "env": { 4 | "jest": true, 5 | "node": true 6 | }, 7 | "root": true, 8 | "plugins": [ 9 | "@typescript-eslint", 10 | "import", 11 | "@stylistic" 12 | ], 13 | "parser": "@typescript-eslint/parser", 14 | "parserOptions": { 15 | "ecmaVersion": 2018, 16 | "sourceType": "module", 17 | "project": "./tsconfig.dev.json" 18 | }, 19 | "extends": [ 20 | "plugin:import/typescript" 21 | ], 22 | "settings": { 23 | "import/parsers": { 24 | "@typescript-eslint/parser": [ 25 | ".ts", 26 | ".tsx" 27 | ] 28 | }, 29 | "import/resolver": { 30 | "node": {}, 31 | "typescript": { 32 | "project": "./tsconfig.dev.json", 33 | "alwaysTryTypes": true 34 | } 35 | } 36 | }, 37 | "ignorePatterns": [ 38 | "*.js", 39 | "*.d.ts", 40 | "node_modules/", 41 | "*.generated.ts", 42 | "coverage", 43 | "!.projenrc.js" 44 | ], 45 | "rules": { 46 | "indent": [ 47 | "off" 48 | ], 49 | "@stylistic/indent": [ 50 | "error", 51 | 2 52 | ], 53 | "quotes": [ 54 | "error", 55 | "single", 56 | { 57 | "avoidEscape": true 58 | } 59 | ], 60 | "comma-dangle": [ 61 | "error", 62 | "always-multiline" 63 | ], 64 | "comma-spacing": [ 65 | "error", 66 | { 67 | "before": false, 68 | "after": true 69 | } 70 | ], 71 | "no-multi-spaces": [ 72 | "error", 73 | { 74 | "ignoreEOLComments": false 75 | } 76 | ], 77 | "array-bracket-spacing": [ 78 | "error", 79 | "never" 80 | ], 81 | "array-bracket-newline": [ 82 | "error", 83 | "consistent" 84 | ], 85 | "object-curly-spacing": [ 86 | "error", 87 | "always" 88 | ], 89 | "object-curly-newline": [ 90 | "error", 91 | { 92 | "multiline": true, 93 | "consistent": true 94 | } 95 | ], 96 | "object-property-newline": [ 97 | "error", 98 | { 99 | "allowAllPropertiesOnSameLine": true 100 | } 101 | ], 102 | "keyword-spacing": [ 103 | "error" 104 | ], 105 | "brace-style": [ 106 | "error", 107 | "1tbs", 108 | { 109 | "allowSingleLine": true 110 | } 111 | ], 112 | "space-before-blocks": [ 113 | "error" 114 | ], 115 | "curly": [ 116 | "error", 117 | "multi-line", 118 | "consistent" 119 | ], 120 | "@stylistic/member-delimiter-style": [ 121 | "error" 122 | ], 123 | "semi": [ 124 | "error", 125 | "always" 126 | ], 127 | "max-len": [ 128 | "error", 129 | { 130 | "code": 150, 131 | "ignoreUrls": true, 132 | "ignoreStrings": true, 133 | "ignoreTemplateLiterals": true, 134 | "ignoreComments": true, 135 | "ignoreRegExpLiterals": true 136 | } 137 | ], 138 | "quote-props": [ 139 | "error", 140 | "consistent-as-needed" 141 | ], 142 | "@typescript-eslint/no-require-imports": [ 143 | "error" 144 | ], 145 | "import/no-extraneous-dependencies": [ 146 | "error", 147 | { 148 | "devDependencies": [ 149 | "**/test/**", 150 | "**/build-tools/**" 151 | ], 152 | "optionalDependencies": false, 153 | "peerDependencies": true 154 | } 155 | ], 156 | "import/no-unresolved": [ 157 | "error" 158 | ], 159 | "import/order": [ 160 | "warn", 161 | { 162 | "groups": [ 163 | "builtin", 164 | "external" 165 | ], 166 | "alphabetize": { 167 | "order": "asc", 168 | "caseInsensitive": true 169 | } 170 | } 171 | ], 172 | "import/no-duplicates": [ 173 | "error" 174 | ], 175 | "no-shadow": [ 176 | "off" 177 | ], 178 | "@typescript-eslint/no-shadow": [ 179 | "error" 180 | ], 181 | "key-spacing": [ 182 | "error" 183 | ], 184 | "no-multiple-empty-lines": [ 185 | "error" 186 | ], 187 | "@typescript-eslint/no-floating-promises": [ 188 | "error" 189 | ], 190 | "no-return-await": [ 191 | "off" 192 | ], 193 | "@typescript-eslint/return-await": [ 194 | "error" 195 | ], 196 | "no-trailing-spaces": [ 197 | "error" 198 | ], 199 | "dot-notation": [ 200 | "error" 201 | ], 202 | "no-bitwise": [ 203 | "error" 204 | ], 205 | "@typescript-eslint/member-ordering": [ 206 | "error", 207 | { 208 | "default": [ 209 | "public-static-field", 210 | "public-static-method", 211 | "protected-static-field", 212 | "protected-static-method", 213 | "private-static-field", 214 | "private-static-method", 215 | "field", 216 | "constructor", 217 | "method" 218 | ] 219 | } 220 | ] 221 | }, 222 | "overrides": [ 223 | { 224 | "files": [ 225 | ".projenrc.js" 226 | ], 227 | "rules": { 228 | "@typescript-eslint/no-require-imports": "off", 229 | "import/no-extraneous-dependencies": "off" 230 | } 231 | } 232 | ] 233 | } 234 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.js and run "npx projen". 2 | 3 | * text=auto eol=lf 4 | *.snap linguist-generated 5 | /.eslintrc.json linguist-generated 6 | /.gitattributes linguist-generated 7 | /.github/pull_request_template.md linguist-generated 8 | /.github/workflows/auto-approve.yml linguist-generated 9 | /.github/workflows/build.yml linguist-generated 10 | /.github/workflows/pull-request-lint.yml linguist-generated 11 | /.github/workflows/release.yml linguist-generated 12 | /.github/workflows/upgrade-main.yml linguist-generated 13 | /.gitignore linguist-generated 14 | /.gitpod.yml linguist-generated 15 | /.mergify.yml linguist-generated 16 | /.npmignore linguist-generated 17 | /.projen/** linguist-generated 18 | /.projen/deps.json linguist-generated 19 | /.projen/files.json linguist-generated 20 | /.projen/tasks.json linguist-generated 21 | /LICENSE linguist-generated 22 | /package-lock.json linguist-generated 23 | /package.json linguist-generated 24 | /tsconfig.dev.json linguist-generated 25 | /tsconfig.json linguist-generated -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | * **Please check if the PR fulfills these requirements** 2 | - [ ] The commit message describes your change 3 | - [ ] Tests for the changes have been added if possible (for bug fixes / features) 4 | - [ ] Docs have been added / updated (for bug fixes / features) 5 | 6 | 7 | * **What kind of change does this PR introduce?** (Bug fix, feature, docs update, ...) 8 | 9 | 10 | 11 | * **What is the current behavior?** (You can also link to an open issue here) 12 | 13 | 14 | 15 | * **What is the new behavior (if this is a feature change)?** 16 | 17 | 18 | 19 | * **Does this PR introduce a breaking change?** (What changes might users need to make in their setup due to this PR?) 20 | 21 | 22 | 23 | * **Other information**: -------------------------------------------------------------------------------- /.github/workflows/auto-approve.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.js and run "npx projen". 2 | 3 | name: auto-approve 4 | on: 5 | pull_request_target: 6 | types: 7 | - labeled 8 | - opened 9 | - synchronize 10 | - reopened 11 | - ready_for_review 12 | jobs: 13 | approve: 14 | runs-on: ubuntu-latest 15 | permissions: 16 | pull-requests: write 17 | if: contains(github.event.pull_request.labels.*.name, 'auto-approve') && (github.event.pull_request.user.login == 'hoegertn' || github.event.pull_request.user.login == 'open-constructs-projen[bot]') 18 | steps: 19 | - uses: hmarr/auto-approve-action@v2.2.1 20 | with: 21 | github-token: ${{ secrets.GITHUB_TOKEN }} 22 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.js and run "npx projen". 2 | 3 | name: build 4 | on: 5 | pull_request: {} 6 | workflow_dispatch: {} 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | permissions: 11 | contents: write 12 | outputs: 13 | self_mutation_happened: ${{ steps.self_mutation.outputs.self_mutation_happened }} 14 | env: 15 | CI: "true" 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v4 19 | with: 20 | ref: ${{ github.event.pull_request.head.ref }} 21 | repository: ${{ github.event.pull_request.head.repo.full_name }} 22 | - name: Install dependencies 23 | run: npm install 24 | - name: build 25 | run: npx projen build 26 | - name: Find mutations 27 | id: self_mutation 28 | run: |- 29 | git add . 30 | git diff --staged --patch --exit-code > repo.patch || echo "self_mutation_happened=true" >> $GITHUB_OUTPUT 31 | working-directory: ./ 32 | - name: Upload patch 33 | if: steps.self_mutation.outputs.self_mutation_happened 34 | uses: actions/upload-artifact@v4.4.0 35 | with: 36 | name: repo.patch 37 | path: repo.patch 38 | overwrite: true 39 | - name: Fail build on mutation 40 | if: steps.self_mutation.outputs.self_mutation_happened 41 | run: |- 42 | echo "::error::Files were changed during build (see build log). If this was triggered from a fork, you will need to update your branch." 43 | cat repo.patch 44 | exit 1 45 | self-mutation: 46 | needs: build 47 | runs-on: ubuntu-latest 48 | permissions: 49 | contents: write 50 | if: always() && needs.build.outputs.self_mutation_happened && !(github.event.pull_request.head.repo.full_name != github.repository) 51 | steps: 52 | - name: Generate token 53 | id: generate_token 54 | uses: tibdex/github-app-token@3beb63f4bd073e61482598c45c71c1019b59b73a 55 | with: 56 | app_id: ${{ secrets.PROJEN_APP_ID }} 57 | private_key: ${{ secrets.PROJEN_APP_PRIVATE_KEY }} 58 | - name: Checkout 59 | uses: actions/checkout@v4 60 | with: 61 | token: ${{ steps.generate_token.outputs.token }} 62 | ref: ${{ github.event.pull_request.head.ref }} 63 | repository: ${{ github.event.pull_request.head.repo.full_name }} 64 | - name: Download patch 65 | uses: actions/download-artifact@v4 66 | with: 67 | name: repo.patch 68 | path: ${{ runner.temp }} 69 | - name: Apply patch 70 | run: '[ -s ${{ runner.temp }}/repo.patch ] && git apply ${{ runner.temp }}/repo.patch || echo "Empty patch. Skipping."' 71 | - name: Set git identity 72 | run: |- 73 | git config user.name "github-actions" 74 | git config user.email "github-actions@github.com" 75 | - name: Push changes 76 | env: 77 | PULL_REQUEST_REF: ${{ github.event.pull_request.head.ref }} 78 | run: |- 79 | git add . 80 | git commit -s -m "chore: self mutation" 81 | git push origin HEAD:$PULL_REQUEST_REF 82 | -------------------------------------------------------------------------------- /.github/workflows/pull-request-lint.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.js and run "npx projen". 2 | 3 | name: pull-request-lint 4 | on: 5 | pull_request_target: 6 | types: 7 | - labeled 8 | - opened 9 | - synchronize 10 | - reopened 11 | - ready_for_review 12 | - edited 13 | merge_group: {} 14 | jobs: 15 | validate: 16 | name: Validate PR title 17 | runs-on: ubuntu-latest 18 | permissions: 19 | pull-requests: write 20 | if: (github.event_name == 'pull_request' || github.event_name == 'pull_request_target') 21 | steps: 22 | - uses: amannn/action-semantic-pull-request@v5.4.0 23 | env: 24 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 25 | with: 26 | types: |- 27 | feat 28 | fix 29 | chore 30 | ci 31 | docs 32 | style 33 | refactor 34 | test 35 | revert 36 | Revert 37 | requireScope: false 38 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.js and run "npx projen". 2 | 3 | name: release 4 | on: 5 | push: 6 | branches: 7 | - main 8 | workflow_dispatch: {} 9 | concurrency: 10 | group: ${{ github.workflow }} 11 | cancel-in-progress: false 12 | jobs: 13 | release: 14 | runs-on: ubuntu-latest 15 | permissions: 16 | contents: write 17 | outputs: 18 | latest_commit: ${{ steps.git_remote.outputs.latest_commit }} 19 | tag_exists: ${{ steps.check_tag_exists.outputs.exists }} 20 | env: 21 | CI: "true" 22 | steps: 23 | - name: Checkout 24 | uses: actions/checkout@v4 25 | with: 26 | fetch-depth: 0 27 | - name: Set git identity 28 | run: |- 29 | git config user.name "github-actions" 30 | git config user.email "github-actions@github.com" 31 | - name: Install dependencies 32 | run: npm ci 33 | - name: release 34 | run: npx projen release 35 | - name: Check if version has already been tagged 36 | id: check_tag_exists 37 | run: |- 38 | TAG=$(cat dist/releasetag.txt) 39 | ([ ! -z "$TAG" ] && git ls-remote -q --exit-code --tags origin $TAG && (echo "exists=true" >> $GITHUB_OUTPUT)) || (echo "exists=false" >> $GITHUB_OUTPUT) 40 | cat $GITHUB_OUTPUT 41 | - name: Check for new commits 42 | id: git_remote 43 | run: |- 44 | echo "latest_commit=$(git ls-remote origin -h ${{ github.ref }} | cut -f1)" >> $GITHUB_OUTPUT 45 | cat $GITHUB_OUTPUT 46 | - name: Backup artifact permissions 47 | if: ${{ steps.git_remote.outputs.latest_commit == github.sha }} 48 | run: cd dist && getfacl -R . > permissions-backup.acl 49 | continue-on-error: true 50 | - name: Upload artifact 51 | if: ${{ steps.git_remote.outputs.latest_commit == github.sha }} 52 | uses: actions/upload-artifact@v4.4.0 53 | with: 54 | name: build-artifact 55 | path: dist 56 | overwrite: true 57 | release_github: 58 | name: Publish to GitHub Releases 59 | needs: 60 | - release 61 | - release_npm 62 | runs-on: ubuntu-latest 63 | permissions: 64 | contents: write 65 | if: needs.release.outputs.tag_exists != 'true' && needs.release.outputs.latest_commit == github.sha 66 | steps: 67 | - uses: actions/setup-node@v4 68 | with: 69 | node-version: lts/* 70 | - name: Download build artifacts 71 | uses: actions/download-artifact@v4 72 | with: 73 | name: build-artifact 74 | path: dist 75 | - name: Restore build artifact permissions 76 | run: cd dist && setfacl --restore=permissions-backup.acl 77 | continue-on-error: true 78 | - name: Release 79 | env: 80 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 81 | GITHUB_REPOSITORY: ${{ github.repository }} 82 | GITHUB_REF: ${{ github.sha }} 83 | run: errout=$(mktemp); gh release create $(cat dist/releasetag.txt) -R $GITHUB_REPOSITORY -F dist/changelog.md -t $(cat dist/releasetag.txt) --target $GITHUB_REF 2> $errout && true; exitcode=$?; if [ $exitcode -ne 0 ] && ! grep -q "Release.tag_name already exists" $errout; then cat $errout; exit $exitcode; fi 84 | release_npm: 85 | name: Publish to npm 86 | needs: release 87 | runs-on: ubuntu-latest 88 | permissions: 89 | id-token: write 90 | contents: read 91 | if: needs.release.outputs.tag_exists != 'true' && needs.release.outputs.latest_commit == github.sha 92 | steps: 93 | - uses: actions/setup-node@v4 94 | with: 95 | node-version: lts/* 96 | - name: Download build artifacts 97 | uses: actions/download-artifact@v4 98 | with: 99 | name: build-artifact 100 | path: dist 101 | - name: Restore build artifact permissions 102 | run: cd dist && setfacl --restore=permissions-backup.acl 103 | continue-on-error: true 104 | - name: Release 105 | env: 106 | NPM_DIST_TAG: latest 107 | NPM_REGISTRY: registry.npmjs.org 108 | NPM_CONFIG_PROVENANCE: "true" 109 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 110 | run: npx -p publib@latest publib-npm 111 | -------------------------------------------------------------------------------- /.github/workflows/upgrade-main.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.js and run "npx projen". 2 | 3 | name: upgrade-main 4 | on: 5 | workflow_dispatch: {} 6 | schedule: 7 | - cron: 0 0 * * 1 8 | jobs: 9 | upgrade: 10 | name: Upgrade 11 | runs-on: ubuntu-latest 12 | permissions: 13 | contents: read 14 | outputs: 15 | patch_created: ${{ steps.create_patch.outputs.patch_created }} 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v4 19 | with: 20 | ref: main 21 | - name: Install dependencies 22 | run: npm ci 23 | - name: Upgrade dependencies 24 | run: npx projen upgrade 25 | - name: Find mutations 26 | id: create_patch 27 | run: |- 28 | git add . 29 | git diff --staged --patch --exit-code > repo.patch || echo "patch_created=true" >> $GITHUB_OUTPUT 30 | working-directory: ./ 31 | - name: Upload patch 32 | if: steps.create_patch.outputs.patch_created 33 | uses: actions/upload-artifact@v4.4.0 34 | with: 35 | name: repo.patch 36 | path: repo.patch 37 | overwrite: true 38 | pr: 39 | name: Create Pull Request 40 | needs: upgrade 41 | runs-on: ubuntu-latest 42 | permissions: 43 | contents: read 44 | if: ${{ needs.upgrade.outputs.patch_created }} 45 | steps: 46 | - name: Generate token 47 | id: generate_token 48 | uses: tibdex/github-app-token@3beb63f4bd073e61482598c45c71c1019b59b73a 49 | with: 50 | app_id: ${{ secrets.PROJEN_APP_ID }} 51 | private_key: ${{ secrets.PROJEN_APP_PRIVATE_KEY }} 52 | - name: Checkout 53 | uses: actions/checkout@v4 54 | with: 55 | ref: main 56 | - name: Download patch 57 | uses: actions/download-artifact@v4 58 | with: 59 | name: repo.patch 60 | path: ${{ runner.temp }} 61 | - name: Apply patch 62 | run: '[ -s ${{ runner.temp }}/repo.patch ] && git apply ${{ runner.temp }}/repo.patch || echo "Empty patch. Skipping."' 63 | - name: Set git identity 64 | run: |- 65 | git config user.name "github-actions" 66 | git config user.email "github-actions@github.com" 67 | - name: Create Pull Request 68 | id: create-pr 69 | uses: peter-evans/create-pull-request@v6 70 | with: 71 | token: ${{ steps.generate_token.outputs.token }} 72 | commit-message: |- 73 | chore(deps): upgrade dependencies 74 | 75 | Upgrades project dependencies. See details in [workflow run]. 76 | 77 | [Workflow Run]: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} 78 | 79 | ------ 80 | 81 | *Automatically created by projen via the "upgrade-main" workflow* 82 | branch: github-actions/upgrade-main 83 | title: "chore(deps): upgrade dependencies" 84 | labels: auto-approve 85 | body: |- 86 | Upgrades project dependencies. See details in [workflow run]. 87 | 88 | [Workflow Run]: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} 89 | 90 | ------ 91 | 92 | *Automatically created by projen via the "upgrade-main" workflow* 93 | author: github-actions 94 | committer: github-actions 95 | signoff: true 96 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.js and run "npx projen". 2 | !/.gitattributes 3 | !/.projen/tasks.json 4 | !/.projen/deps.json 5 | !/.projen/files.json 6 | !/.github/workflows/pull-request-lint.yml 7 | !/.gitpod.yml 8 | !/.github/workflows/auto-approve.yml 9 | !/package.json 10 | !/LICENSE 11 | !/.npmignore 12 | logs 13 | *.log 14 | npm-debug.log* 15 | yarn-debug.log* 16 | yarn-error.log* 17 | lerna-debug.log* 18 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 19 | pids 20 | *.pid 21 | *.seed 22 | *.pid.lock 23 | lib-cov 24 | coverage 25 | *.lcov 26 | .nyc_output 27 | build/Release 28 | node_modules/ 29 | jspm_packages/ 30 | *.tsbuildinfo 31 | .eslintcache 32 | *.tgz 33 | .yarn-integrity 34 | .cache 35 | /test-reports/ 36 | junit.xml 37 | /coverage/ 38 | !/.github/workflows/build.yml 39 | /dist/changelog.md 40 | /dist/version.txt 41 | !/.github/workflows/release.yml 42 | !/.mergify.yml 43 | !/.github/workflows/upgrade-main.yml 44 | !/.github/pull_request_template.md 45 | !/test/ 46 | !/tsconfig.json 47 | !/tsconfig.dev.json 48 | !/src/ 49 | /lib 50 | /dist/ 51 | !/.eslintrc.json 52 | !/.projenrc.js 53 | -------------------------------------------------------------------------------- /.mergify.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.js and run "npx projen". 2 | 3 | queue_rules: 4 | - name: default 5 | update_method: merge 6 | conditions: 7 | - "#approved-reviews-by>=1" 8 | - -label~=(do-not-merge) 9 | - status-success=build 10 | merge_method: squash 11 | commit_message_template: |- 12 | {{ title }} (#{{ number }}) 13 | 14 | {{ body }} 15 | pull_request_rules: 16 | - name: Automatic merge on approval and successful build 17 | actions: 18 | delete_head_branch: {} 19 | queue: 20 | name: default 21 | conditions: 22 | - "#approved-reviews-by>=1" 23 | - -label~=(do-not-merge) 24 | - status-success=build 25 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.js and run "npx projen". 2 | /.projen/ 3 | /test-reports/ 4 | junit.xml 5 | /coverage/ 6 | permissions-backup.acl 7 | /dist/changelog.md 8 | /dist/version.txt 9 | /.mergify.yml 10 | /test/ 11 | /tsconfig.dev.json 12 | /src/ 13 | !/lib/ 14 | !/lib/**/*.js 15 | !/lib/**/*.d.ts 16 | dist 17 | /tsconfig.json 18 | /.github/ 19 | /.vscode/ 20 | /.idea/ 21 | /.projenrc.js 22 | tsconfig.tsbuildinfo 23 | /.eslintrc.json 24 | /.gitattributes 25 | -------------------------------------------------------------------------------- /.projen/deps.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": [ 3 | { 4 | "name": "@aws-sdk/client-cognito-identity-provider", 5 | "type": "build" 6 | }, 7 | { 8 | "name": "@aws-sdk/client-dynamodb", 9 | "type": "build" 10 | }, 11 | { 12 | "name": "@aws-sdk/client-s3", 13 | "type": "build" 14 | }, 15 | { 16 | "name": "@aws-sdk/lib-dynamodb", 17 | "type": "build" 18 | }, 19 | { 20 | "name": "@hapi/boom", 21 | "type": "build" 22 | }, 23 | { 24 | "name": "@stylistic/eslint-plugin", 25 | "version": "^2", 26 | "type": "build" 27 | }, 28 | { 29 | "name": "@types/jest", 30 | "type": "build" 31 | }, 32 | { 33 | "name": "@types/js-yaml", 34 | "type": "build" 35 | }, 36 | { 37 | "name": "@types/jsonwebtoken", 38 | "type": "build" 39 | }, 40 | { 41 | "name": "@types/jwk-to-pem", 42 | "type": "build" 43 | }, 44 | { 45 | "name": "@types/lambda-log", 46 | "type": "build" 47 | }, 48 | { 49 | "name": "@types/node", 50 | "type": "build" 51 | }, 52 | { 53 | "name": "@types/uuid", 54 | "type": "build" 55 | }, 56 | { 57 | "name": "@typescript-eslint/eslint-plugin", 58 | "version": "^8", 59 | "type": "build" 60 | }, 61 | { 62 | "name": "@typescript-eslint/parser", 63 | "version": "^8", 64 | "type": "build" 65 | }, 66 | { 67 | "name": "commit-and-tag-version", 68 | "version": "^12", 69 | "type": "build" 70 | }, 71 | { 72 | "name": "constructs", 73 | "version": "^10.0.0", 74 | "type": "build" 75 | }, 76 | { 77 | "name": "eslint-import-resolver-typescript", 78 | "type": "build" 79 | }, 80 | { 81 | "name": "eslint-plugin-import", 82 | "type": "build" 83 | }, 84 | { 85 | "name": "eslint", 86 | "version": "^9", 87 | "type": "build" 88 | }, 89 | { 90 | "name": "jest", 91 | "type": "build" 92 | }, 93 | { 94 | "name": "jest-junit", 95 | "version": "^16", 96 | "type": "build" 97 | }, 98 | { 99 | "name": "projen", 100 | "type": "build" 101 | }, 102 | { 103 | "name": "ts-jest", 104 | "type": "build" 105 | }, 106 | { 107 | "name": "ts-node", 108 | "type": "build" 109 | }, 110 | { 111 | "name": "typedoc", 112 | "version": "0.27.6", 113 | "type": "build" 114 | }, 115 | { 116 | "name": "typescript", 117 | "type": "build" 118 | }, 119 | { 120 | "name": "@aws-sdk/client-cognito-identity-provider", 121 | "type": "peer" 122 | }, 123 | { 124 | "name": "@aws-sdk/client-dynamodb", 125 | "type": "peer" 126 | }, 127 | { 128 | "name": "@aws-sdk/client-s3", 129 | "type": "peer" 130 | }, 131 | { 132 | "name": "@aws-sdk/lib-dynamodb", 133 | "type": "peer" 134 | }, 135 | { 136 | "name": "aws-cdk-lib", 137 | "version": ">=2.187.0 <3.0.0", 138 | "type": "peer" 139 | }, 140 | { 141 | "name": "dynamodb-onetable", 142 | "version": "2.7.5", 143 | "type": "peer" 144 | }, 145 | { 146 | "name": "openapi-typescript", 147 | "type": "peer" 148 | }, 149 | { 150 | "name": "projen", 151 | "version": ">=0.91.6 <1.0.0", 152 | "type": "peer" 153 | }, 154 | { 155 | "name": "@types/aws-lambda", 156 | "type": "runtime" 157 | }, 158 | { 159 | "name": "axios", 160 | "type": "runtime" 161 | }, 162 | { 163 | "name": "constructs", 164 | "type": "runtime" 165 | }, 166 | { 167 | "name": "date-fns", 168 | "type": "runtime" 169 | }, 170 | { 171 | "name": "js-yaml", 172 | "type": "runtime" 173 | }, 174 | { 175 | "name": "jsonwebtoken", 176 | "type": "runtime" 177 | }, 178 | { 179 | "name": "jwk-to-pem", 180 | "type": "runtime" 181 | }, 182 | { 183 | "name": "lambda-log", 184 | "type": "runtime" 185 | }, 186 | { 187 | "name": "uuid", 188 | "type": "runtime" 189 | } 190 | ], 191 | "//": "~~ Generated by projen. To modify, edit .projenrc.js and run \"npx projen\"." 192 | } 193 | -------------------------------------------------------------------------------- /.projen/files.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | ".eslintrc.json", 4 | ".gitattributes", 5 | ".github/pull_request_template.md", 6 | ".github/workflows/auto-approve.yml", 7 | ".github/workflows/build.yml", 8 | ".github/workflows/pull-request-lint.yml", 9 | ".github/workflows/release.yml", 10 | ".github/workflows/upgrade-main.yml", 11 | ".gitignore", 12 | ".gitpod.yml", 13 | ".mergify.yml", 14 | ".npmignore", 15 | ".projen/deps.json", 16 | ".projen/files.json", 17 | ".projen/tasks.json", 18 | "LICENSE", 19 | "tsconfig.dev.json", 20 | "tsconfig.json" 21 | ], 22 | "//": "~~ Generated by projen. To modify, edit .projenrc.js and run \"npx projen\"." 23 | } 24 | -------------------------------------------------------------------------------- /.projenrc.js: -------------------------------------------------------------------------------- 1 | const { typescript, javascript, github, ReleasableCommits } = require('projen'); 2 | 3 | const project = new typescript.TypeScriptProject({ 4 | authorName: 'Taimos GmbH', 5 | authorEmail: 'info@taimos.de', 6 | authorOrganization: true, 7 | authorUrl: 'https://taimos.de', 8 | copyrightOwner: 'Taimos GmbH', 9 | copyrightPeriod: '2024', 10 | license: 'Apache-2.0', 11 | licensed: true, 12 | stability: 'experimental', 13 | name: 'cdk-serverless', 14 | deps: [ 15 | '@types/aws-lambda', 16 | 'date-fns', 17 | 'js-yaml', 18 | 'jsonwebtoken', 19 | 'jwk-to-pem', 20 | 'axios', 21 | 'uuid', 22 | 'lambda-log', 23 | 'constructs', 24 | ], 25 | defaultReleaseBranch: 'main', 26 | packageManager: javascript.NodePackageManager.NPM, 27 | minMajorVersion: '2', 28 | docgen: false, 29 | devDeps: [ 30 | 'ts-node', 31 | '@types/js-yaml', 32 | '@types/lambda-log', 33 | '@types/jsonwebtoken', 34 | '@types/jwk-to-pem', 35 | '@types/uuid', 36 | '@hapi/boom', 37 | 'typedoc@0.27.6', 38 | '@aws-sdk/client-cognito-identity-provider', 39 | '@aws-sdk/client-s3', 40 | '@aws-sdk/client-dynamodb', 41 | '@aws-sdk/lib-dynamodb', 42 | ], 43 | peerDeps: [ 44 | 'openapi-typescript', 45 | 'dynamodb-onetable@2.7.5', 46 | 'projen@>=0.91.6 <1.0.0', 47 | 'aws-cdk-lib@>=2.187.0 <3.0.0', 48 | '@aws-sdk/client-cognito-identity-provider', 49 | '@aws-sdk/client-s3', 50 | '@aws-sdk/client-dynamodb', 51 | '@aws-sdk/lib-dynamodb', 52 | ], 53 | keywords: [ 54 | 'aws', 55 | 'cdk', 56 | 'serverless', 57 | 'lambda', 58 | 'dynamodb', 59 | ], 60 | repository: 'https://github.com/open-constructs/cdk-serverless', 61 | tsconfig: { 62 | compilerOptions: { 63 | lib: [ 64 | 'es2019', 65 | 'dom', 66 | ], 67 | skipLibCheck: true, 68 | }, 69 | }, 70 | releaseToNpm: true, 71 | npmAccess: javascript.NpmAccess.PUBLIC, 72 | gitpod: true, 73 | autoApproveUpgrades: true, 74 | autoApproveOptions: { allowedUsernames: ['hoegertn', 'open-constructs-projen[bot]'], secret: 'GITHUB_TOKEN' }, 75 | depsUpgradeOptions: { workflowOptions: { schedule: javascript.UpgradeDependenciesSchedule.WEEKLY } }, 76 | releasableCommits: ReleasableCommits.ofType(['feat', 'fix', 'revert', 'Revert']), 77 | githubOptions: { 78 | projenCredentials: github.GithubCredentials.fromApp(), 79 | pullRequestLintOptions: { 80 | semanticTitleOptions: { 81 | types: ['feat', 'fix', 'chore', 'ci', 'docs', 'style', 'refactor', 'test', 'revert', 'Revert'], 82 | }, 83 | }, 84 | }, 85 | pullRequestTemplateContents: [`* **Please check if the PR fulfills these requirements** 86 | - [ ] The commit message describes your change 87 | - [ ] Tests for the changes have been added if possible (for bug fixes / features) 88 | - [ ] Docs have been added / updated (for bug fixes / features) 89 | 90 | 91 | * **What kind of change does this PR introduce?** (Bug fix, feature, docs update, ...) 92 | 93 | 94 | 95 | * **What is the current behavior?** (You can also link to an open issue here) 96 | 97 | 98 | 99 | * **What is the new behavior (if this is a feature change)?** 100 | 101 | 102 | 103 | * **Does this PR introduce a breaking change?** (What changes might users need to make in their setup due to this PR?) 104 | 105 | 106 | 107 | * **Other information**:`], 108 | 109 | }); 110 | 111 | const docgen = project.addTask('docgen', { 112 | description: 'Generate TypeScript API reference', 113 | exec: `typedoc ${project.srcdir}/constructs --disableSources --out ${project.docsDirectory}constructs/`, 114 | }); 115 | docgen.exec(`typedoc ${project.srcdir}/projen --disableSources --out ${project.docsDirectory}projen/`); 116 | docgen.exec(`typedoc ${project.srcdir}/lambda --disableSources --out ${project.docsDirectory}lambda/`); 117 | project.postCompileTask.spawn(docgen); 118 | 119 | project.synth(); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CDK Serverless 2 | 3 | [![npm version](https://badge.fury.io/js/cdk-serverless.svg)](https://badge.fury.io/js/cdk-serverless) 4 | 5 | CDK Serverless is a powerful toolkit designed to simplify serverless application development using the AWS Cloud Development Kit (CDK). It offers project management features, higher-level (L3) constructs, and utility libraries to streamline the creation and management of serverless architectures. Additionally, it leverages utility libraries to write Lambda functions and do live updates to Lambda function code during development. 6 | 7 | Video introduction: https://www.youtube.com/watch?v=xhNJ0cXG3O8 8 | 9 | ### Features 10 | 11 | * Projen helper classes for easy project configuration 12 | * AWS CDK L3-constructs for RestApi, GraphQlApi, and more 13 | * Zero-config Lambda function and VTL template generation 14 | * Automatic DynamoDB single-table infrastructure setup 15 | * Built-in monitoring for Lambda functions and APIs 16 | * Full compatibility with CDK for custom implementations 17 | * Type-safe auto-completion for routes, resolvers, etc. 18 | * Support for Cognito authentication and authorization 19 | * Automated generation of CloudFormation outputs for testing 20 | 21 | ## Quick Start 22 | 23 | To begin a new project with CDK Serverless: 24 | 25 | Create a new CDK TypeScript app using projen: 26 | 27 | ```bash 28 | $ npx projen new awscdk-app-ts 29 | ``` 30 | 31 | Adding CDK Serverless is a two step process: 32 | 33 | 1. Add 'cdk-serverless' as a dependency to your project 34 | 2. Run `npx projen` to install it 35 | 36 | Now you can use the project type `ServerlessProject` for your app. 37 | 38 | ### Adding projen constructs 39 | 40 | First you need to add the desired construct to your projen configuration: (e.g. RestApi) 41 | 42 | ```typescript 43 | import { RestApi } from 'cdk-serverless/projen'; 44 | 45 | new RestApi(project, { 46 | apiName: 'TestApi', // logical name of your API 47 | definitionFile: 'testapi.yaml', // path to your OpenAPI spec 48 | }); 49 | ``` 50 | 51 | Then run projen to generate construct files and models for the API. 52 | 53 | ### Using the CDK serverless L3 constructs 54 | 55 | In your stack you can then reference the generated L3s to create the API: 56 | 57 | ```typescript 58 | import { TestApiRestApi } from './generated/rest.testapi-api.generated'; 59 | 60 | 61 | const api = new TestApiRestApi(this, 'Api', { 62 | stageName: props.stageName, 63 | domainName: props.domainName, 64 | apiHostname: 'api', 65 | singleTableDatastore, 66 | cors: true, 67 | additionalEnv: { 68 | DOMAIN_NAME: props.domainName, 69 | }, 70 | }); 71 | ``` 72 | 73 | This will also create Lambda functions for all operations defined in your spec and wire them accordingly. 74 | 75 | ## Testing Utilities 76 | 77 | CDK Serverless provides two powerful test utilities to help you write comprehensive tests for your serverless applications. 78 | 79 | ### LambdaTestUtil 80 | 81 | The `LambdaTestUtil` provides classes for testing both REST and GraphQL Lambda functions in isolation. It's perfect for unit testing your Lambda handlers. 82 | 83 | #### REST API Testing 84 | 85 | ```typescript 86 | import { LambdaRestUnitTest } from 'cdk-serverless/tests/lambda-test-utils'; 87 | 88 | const test = new LambdaRestUnitTest(handler, { 89 | // Optional default headers for all requests 90 | headers: { 91 | 'Content-Type': 'application/json', 92 | }, 93 | // Optional default Cognito user for all requests 94 | cognito: { 95 | username: 'test-user', 96 | email: 'test@example.com', 97 | groups: ['admin'], 98 | }, 99 | }); 100 | 101 | // Test a GET request 102 | const result = await test.call({ 103 | path: '/items', 104 | method: 'GET', 105 | }); 106 | 107 | // Test a POST request with body 108 | const result = await test.call({ 109 | path: '/items', 110 | method: 'POST', 111 | body: JSON.stringify({ name: 'test' }), 112 | }); 113 | ``` 114 | 115 | #### GraphQL Testing 116 | 117 | ```typescript 118 | import { LambdaGraphQLTest } from 'cdk-serverless/tests/lambda-test-utils'; 119 | 120 | const test = new LambdaGraphQLTest(handler, { 121 | // Optional default Cognito user for all requests 122 | cognito: { 123 | username: 'test-user', 124 | email: 'test@example.com', 125 | groups: ['admin'], 126 | }, 127 | }); 128 | 129 | // Test a GraphQL query 130 | const result = await test.call({ 131 | fieldName: 'getItem', 132 | arguments: { id: '123' }, 133 | }); 134 | ``` 135 | 136 | ### IntegTestUtil 137 | 138 | The `IntegTestUtil` provides a comprehensive set of tools for integration testing your deployed serverless applications. It handles authentication, data cleanup, and API testing. 139 | 140 | ```typescript 141 | import { IntegTestUtil } from 'cdk-serverless/tests/integ-test-util'; 142 | 143 | // Initialize with your stack outputs 144 | const test = new IntegTestUtil({ 145 | region: 'us-east-1', 146 | apiOptions: { 147 | baseURL: 'https://api.example.com', 148 | }, 149 | authOptions: { 150 | userPoolId: 'us-east-1_xxxxx', 151 | userPoolClientId: 'xxxxxxxx', 152 | identityPoolId: 'us-east-1:xxxxxxxx', 153 | }, 154 | datastoreOptions: { 155 | tableName: 'MyTable', 156 | }, 157 | }); 158 | 159 | // Create and authenticate a test user 160 | await test.createUser('test@example.com', { 161 | 'custom:attribute': 'value', 162 | }, ['admin']); 163 | 164 | // Get an authenticated API client 165 | const client = await test.getAuthenticatedClient('test@example.com'); 166 | 167 | // Make API calls 168 | const response = await client.get('/items'); 169 | 170 | // Clean up test data 171 | await test.cleanupItems(); 172 | await test.removeUser('test@example.com'); 173 | ``` 174 | 175 | ## Contribute 176 | 177 | ### How to contribute to CDK Serverless 178 | 179 | #### **Did you find a bug?** 180 | 181 | * **Ensure the bug was not already reported** by searching on GitHub under [Issues](https://github.com/taimos/cdk-serverless/issues). 182 | 183 | * If you're unable to find an open issue addressing the problem, [open a new one](https://github.com/taimos/cdk-serverless/issues/new). Be sure to include a **title and clear description**, as much relevant information as possible, and a **code sample** or an **executable test case** demonstrating the expected behavior that is not occurring. 184 | 185 | #### **Did you write a patch that fixes a bug?** 186 | 187 | * Open a new GitHub pull request with the patch. 188 | 189 | * Ensure the PR description clearly describes the problem and solution. Include the relevant issue number if applicable. 190 | 191 | #### **Did you fix whitespace, format code, or make a purely cosmetic patch?** 192 | 193 | Changes that are cosmetic in nature and do not add anything substantial to the stability, functionality, or testability will normally not be accepted. 194 | 195 | #### **Do you intend to add a new feature or change an existing one?** 196 | 197 | * Suggest your change under [Issues](https://github.com/taimos/cdk-serverless/issues). 198 | 199 | * Do not open a pull request on GitHub until you have collected positive feedback about the change. 200 | 201 | #### **Do you want to contribute to the CDK Serverless documentation?** 202 | 203 | * Just file a PR with your recommended changes 204 | 205 | ## Authors 206 | 207 | Brought to you by [Taimos](https://taimos.de) -------------------------------------------------------------------------------- /docs/constructs/.nojekyll: -------------------------------------------------------------------------------- 1 | TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false. -------------------------------------------------------------------------------- /docs/constructs/assets/hierarchy.js: -------------------------------------------------------------------------------- 1 | window.hierarchyData = "eJyVkrFuwyAURf/lzaSRA8aYLe1QdWu7RhmQ/VKjELCATJH/vSJNW9pEtrMwwH2cewQn8M7FAHIjKOEVKeotAY87g03UzgaQJyjqtFp1QJDw8uQ+rI5ufYwd2qgblXJAYK9tC3JVcgJHb0CCthH9TjUYlreHHrp4MECgMSoEkBBDu0i3LH4m02GnTevRgtwwvh0IMJ61GS9TrMR3mTMDw/K+Il8bAwFeZdBHFXDd61fv+jAhnkfv1aW0JKw6Owt6jR+zvEQmvP7SOCOsFolGaZnhnr3quzczU/hfeoZzAnJ2Ezim+Juafj1W5X/mHUOcaZNH56mwWlyjxjwukSmJYfgEhuw1OA==" -------------------------------------------------------------------------------- /docs/constructs/assets/highlight.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --light-hl-0: #795E26; 3 | --dark-hl-0: #DCDCAA; 4 | --light-hl-1: #000000; 5 | --dark-hl-1: #D4D4D4; 6 | --light-hl-2: #A31515; 7 | --dark-hl-2: #CE9178; 8 | --light-hl-3: #AF00DB; 9 | --dark-hl-3: #C586C0; 10 | --light-hl-4: #001080; 11 | --dark-hl-4: #9CDCFE; 12 | --light-hl-5: #0000FF; 13 | --dark-hl-5: #569CD6; 14 | --light-hl-6: #008000; 15 | --dark-hl-6: #6A9955; 16 | --light-hl-7: #0070C1; 17 | --dark-hl-7: #4FC1FF; 18 | --light-hl-8: #098658; 19 | --dark-hl-8: #B5CEA8; 20 | --light-hl-9: #267F99; 21 | --dark-hl-9: #4EC9B0; 22 | --light-code-background: #FFFFFF; 23 | --dark-code-background: #1E1E1E; 24 | } 25 | 26 | @media (prefers-color-scheme: light) { :root { 27 | --hl-0: var(--light-hl-0); 28 | --hl-1: var(--light-hl-1); 29 | --hl-2: var(--light-hl-2); 30 | --hl-3: var(--light-hl-3); 31 | --hl-4: var(--light-hl-4); 32 | --hl-5: var(--light-hl-5); 33 | --hl-6: var(--light-hl-6); 34 | --hl-7: var(--light-hl-7); 35 | --hl-8: var(--light-hl-8); 36 | --hl-9: var(--light-hl-9); 37 | --code-background: var(--light-code-background); 38 | } } 39 | 40 | @media (prefers-color-scheme: dark) { :root { 41 | --hl-0: var(--dark-hl-0); 42 | --hl-1: var(--dark-hl-1); 43 | --hl-2: var(--dark-hl-2); 44 | --hl-3: var(--dark-hl-3); 45 | --hl-4: var(--dark-hl-4); 46 | --hl-5: var(--dark-hl-5); 47 | --hl-6: var(--dark-hl-6); 48 | --hl-7: var(--dark-hl-7); 49 | --hl-8: var(--dark-hl-8); 50 | --hl-9: var(--dark-hl-9); 51 | --code-background: var(--dark-code-background); 52 | } } 53 | 54 | :root[data-theme='light'] { 55 | --hl-0: var(--light-hl-0); 56 | --hl-1: var(--light-hl-1); 57 | --hl-2: var(--light-hl-2); 58 | --hl-3: var(--light-hl-3); 59 | --hl-4: var(--light-hl-4); 60 | --hl-5: var(--light-hl-5); 61 | --hl-6: var(--light-hl-6); 62 | --hl-7: var(--light-hl-7); 63 | --hl-8: var(--light-hl-8); 64 | --hl-9: var(--light-hl-9); 65 | --code-background: var(--light-code-background); 66 | } 67 | 68 | :root[data-theme='dark'] { 69 | --hl-0: var(--dark-hl-0); 70 | --hl-1: var(--dark-hl-1); 71 | --hl-2: var(--dark-hl-2); 72 | --hl-3: var(--dark-hl-3); 73 | --hl-4: var(--dark-hl-4); 74 | --hl-5: var(--dark-hl-5); 75 | --hl-6: var(--dark-hl-6); 76 | --hl-7: var(--dark-hl-7); 77 | --hl-8: var(--dark-hl-8); 78 | --hl-9: var(--dark-hl-9); 79 | --code-background: var(--dark-code-background); 80 | } 81 | 82 | .hl-0 { color: var(--hl-0); } 83 | .hl-1 { color: var(--hl-1); } 84 | .hl-2 { color: var(--hl-2); } 85 | .hl-3 { color: var(--hl-3); } 86 | .hl-4 { color: var(--hl-4); } 87 | .hl-5 { color: var(--hl-5); } 88 | .hl-6 { color: var(--hl-6); } 89 | .hl-7 { color: var(--hl-7); } 90 | .hl-8 { color: var(--hl-8); } 91 | .hl-9 { color: var(--hl-9); } 92 | pre, code { background: var(--code-background); } 93 | -------------------------------------------------------------------------------- /docs/constructs/assets/navigation.js: -------------------------------------------------------------------------------- 1 | window.navigationData = "eJyNlF1PwjAUhv9Lr4kKCX5whxiNxERFoheGi7IdtobSLu1BNIb/bjQbbPb0bNfnOc/edW/3/i0QPlGMxNh7wElqRE8UEnMxEomW3oM/rSYnOW606Im1MqkY9QeX+95h+1p6GBcqXC4H3O7EZkahHW8xB4MqkagsEYPEOO+dk0X+rMlYxxlneJCbZSpvtyahIzXnnGkGHskg5YDbfVEm0zCXSw03EqVH6yAUURRnfbNuvdJ2F5qqCbdddeLJ2cIfFcoguJVMaq35I5qqwfA8rE7cVAc4EVmQuDaOcw85Fidu/sdwuvuW8tesNMrKpzvsIg4wTjr1M/BWf4B7LH5Z+ggCilM2r1H8XAmuXTt3MlEm48JSICcu72w8aB3gRNSdjVujdNdHgFcZXYGA4pSvqLtUIMQ4afXTib9+g2j/7kE0/CoO35oOdHZ10R8O9osf1Rx0Wg==" -------------------------------------------------------------------------------- /docs/constructs/assets/search.js: -------------------------------------------------------------------------------- 1 | window.searchData = "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"; -------------------------------------------------------------------------------- /docs/constructs/hierarchy.html: -------------------------------------------------------------------------------- 1 | cdk-serverless
2 | -------------------------------------------------------------------------------- /docs/constructs/interfaces/ICognitoAuthentication.html: -------------------------------------------------------------------------------- 1 | ICognitoAuthentication | cdk-serverless

Interface ICognitoAuthentication

interface ICognitoAuthentication {
    userpool: IUserPool;
}

Implemented by

Properties

Properties

userpool: IUserPool

The Cognito user pool that holds user information

3 |
4 | -------------------------------------------------------------------------------- /docs/constructs/interfaces/LambdaTracingOptions.html: -------------------------------------------------------------------------------- 1 | LambdaTracingOptions | cdk-serverless

Interface LambdaTracingOptions

interface LambdaTracingOptions {
    xRayTracing?: Tracing;
}

Properties

Properties

xRayTracing?: Tracing

Activate tracing with X-Ray

3 |
lambda.Tracing.DISABLED
4 | 
5 | 6 |
7 | -------------------------------------------------------------------------------- /docs/constructs/types/LambdaOptions.html: -------------------------------------------------------------------------------- 1 | LambdaOptions | cdk-serverless

Type Alias LambdaOptions

LambdaOptions: Omit<
    lambdaNodejs.NodejsFunctionProps,
    "entry"
    | "handler"
    | "description",
>
2 | -------------------------------------------------------------------------------- /docs/lambda/.nojekyll: -------------------------------------------------------------------------------- 1 | TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false. -------------------------------------------------------------------------------- /docs/lambda/assets/hierarchy.js: -------------------------------------------------------------------------------- 1 | window.hierarchyData = "eJylk11PgzAUhv/Lue4mLbABd5txGi800Rgvll1U6KSRtVgOLtPsv5s6XWBfwLzhgrxP3qenp19gtMYCoqnrU8IIC4MZASPmmYhRalVA9AXMfhRfCIjgdomjElNt5KcwQOBNqgQiygICpckggjjjRSGKC15i2q+l+ykuMiCbBESARdKzeG/zg0CcyiwxQkE09RgZBoTScLYm4FUFLvWrkqg7SOwRXUSY+6MwDCoKo1xecxRLvvqg3eZxlOyiRP0fJUrDw07sbCd2vlO4cfKPONHu13aSbnCzKuGx8fxLZZ9uVmFuXSV/XKn4HIvDYAuBsLq+97kw3D7ubSPzB3+NUqEwcx7b0lz2t9mjJVugvhCu49mFcB3vUPOzxPRBvJeiwLFOVl1EdtAWXlbDpxWNG8T8yhh9cuTCBor+NtvlNbhDh7hBQDxnQDy2GcTQqRiMefJ7hrYeO0TznbtB9c6flF0hoVDGHEXStvUQ1lztOYNK9USbF5kkQrUtrQMt6lh1x+40TnSpWh+xlm8qW6+/AeQIba0=" -------------------------------------------------------------------------------- /docs/lambda/assets/highlight.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --light-hl-0: #795E26; 3 | --dark-hl-0: #DCDCAA; 4 | --light-hl-1: #000000; 5 | --dark-hl-1: #D4D4D4; 6 | --light-hl-2: #A31515; 7 | --dark-hl-2: #CE9178; 8 | --light-hl-3: #AF00DB; 9 | --dark-hl-3: #C586C0; 10 | --light-hl-4: #001080; 11 | --dark-hl-4: #9CDCFE; 12 | --light-hl-5: #0000FF; 13 | --dark-hl-5: #569CD6; 14 | --light-hl-6: #008000; 15 | --dark-hl-6: #6A9955; 16 | --light-hl-7: #0070C1; 17 | --dark-hl-7: #4FC1FF; 18 | --light-code-background: #FFFFFF; 19 | --dark-code-background: #1E1E1E; 20 | } 21 | 22 | @media (prefers-color-scheme: light) { :root { 23 | --hl-0: var(--light-hl-0); 24 | --hl-1: var(--light-hl-1); 25 | --hl-2: var(--light-hl-2); 26 | --hl-3: var(--light-hl-3); 27 | --hl-4: var(--light-hl-4); 28 | --hl-5: var(--light-hl-5); 29 | --hl-6: var(--light-hl-6); 30 | --hl-7: var(--light-hl-7); 31 | --code-background: var(--light-code-background); 32 | } } 33 | 34 | @media (prefers-color-scheme: dark) { :root { 35 | --hl-0: var(--dark-hl-0); 36 | --hl-1: var(--dark-hl-1); 37 | --hl-2: var(--dark-hl-2); 38 | --hl-3: var(--dark-hl-3); 39 | --hl-4: var(--dark-hl-4); 40 | --hl-5: var(--dark-hl-5); 41 | --hl-6: var(--dark-hl-6); 42 | --hl-7: var(--dark-hl-7); 43 | --code-background: var(--dark-code-background); 44 | } } 45 | 46 | :root[data-theme='light'] { 47 | --hl-0: var(--light-hl-0); 48 | --hl-1: var(--light-hl-1); 49 | --hl-2: var(--light-hl-2); 50 | --hl-3: var(--light-hl-3); 51 | --hl-4: var(--light-hl-4); 52 | --hl-5: var(--light-hl-5); 53 | --hl-6: var(--light-hl-6); 54 | --hl-7: var(--light-hl-7); 55 | --code-background: var(--light-code-background); 56 | } 57 | 58 | :root[data-theme='dark'] { 59 | --hl-0: var(--dark-hl-0); 60 | --hl-1: var(--dark-hl-1); 61 | --hl-2: var(--dark-hl-2); 62 | --hl-3: var(--dark-hl-3); 63 | --hl-4: var(--dark-hl-4); 64 | --hl-5: var(--dark-hl-5); 65 | --hl-6: var(--dark-hl-6); 66 | --hl-7: var(--dark-hl-7); 67 | --code-background: var(--dark-code-background); 68 | } 69 | 70 | .hl-0 { color: var(--hl-0); } 71 | .hl-1 { color: var(--hl-1); } 72 | .hl-2 { color: var(--hl-2); } 73 | .hl-3 { color: var(--hl-3); } 74 | .hl-4 { color: var(--hl-4); } 75 | .hl-5 { color: var(--hl-5); } 76 | .hl-6 { color: var(--hl-6); } 77 | .hl-7 { color: var(--hl-7); } 78 | pre, code { background: var(--code-background); } 79 | -------------------------------------------------------------------------------- /docs/lambda/assets/navigation.js: -------------------------------------------------------------------------------- 1 | window.navigationData = "eJyllMtSwjAUht8la0alA6jsCiOiC3BwHBcMi9gcbMaSxPRURMd3d7iVhoY01W37n+8/10y/CcInki6hipMGURRj0iULybIE0nOq+FmMi4Q0yBsXjHRbDRLFPGEaBOlO8+BQqceViIZUsAR0X4rN9xzHBYKe02hHtKpNn6Dd+Wnk+CGi8mSXpVXgCaRKihR8yEdaF3qsQFPkUpwE5govzDPHeALvGaTYk2xVTT0KcJmED3e3FGFJVx/NXfMOfFyp/dzKsiPqxfVlsx0UycaorVBDUcUrjNcGK/yuIkUaKMKp/OaZiNZN3GJtWpPfaZXQ1lRt3JNJW6BjBSJU3ItramuiT65btZNz8eoaj+T+5v6RwgFSSmZWSIdmGFvewAxjz0eQ5+fRl6+CowwzjKXmX8VRRQlN0z3XGWO6NoMr47AOgfdLrGVk6D1Ngj9UVI7xNatZUVCjos0N+xdjl7ssfNm1oD4NqWhCcdNBa6nT8q5vv3tte4+y3YXdrIPKOe1YRzpXkQOpXzhjINxEU+YCrl9WNytXuDAjiQOZCeZGGSoX7kmsxwUCeUQRKqg2sW22s18AdXFg" -------------------------------------------------------------------------------- /docs/lambda/assets/search.js: -------------------------------------------------------------------------------- 1 | window.searchData = "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"; -------------------------------------------------------------------------------- /docs/lambda/functions/api.createHttpHandler.html: -------------------------------------------------------------------------------- 1 | createHttpHandler | cdk-serverless

Function createHttpHandler

A factory function that creates an HTTP request handler.

2 |
3 | -------------------------------------------------------------------------------- /docs/lambda/hierarchy.html: -------------------------------------------------------------------------------- 1 | cdk-serverless
2 | -------------------------------------------------------------------------------- /docs/lambda/modules.html: -------------------------------------------------------------------------------- 1 | cdk-serverless

cdk-serverless

Namespaces

api
auth
errors
2 | -------------------------------------------------------------------------------- /docs/lambda/types/api.APIGatewayv1Handler.html: -------------------------------------------------------------------------------- 1 | APIGatewayv1Handler | cdk-serverless

Type Alias APIGatewayv1Handler

APIGatewayv1Handler: AWSLambda.Handler<
    AWSLambda.APIGatewayProxyWithCognitoAuthorizerEvent,
    AWSLambda.APIGatewayProxyResult
    | undefined,
>

A type definition for an API Gateway v1 request handler.

2 |
3 | -------------------------------------------------------------------------------- /docs/lambda/types/api.AppSyncHandler.html: -------------------------------------------------------------------------------- 1 | AppSyncHandler | cdk-serverless

Type Alias AppSyncHandler<T, R>

AppSyncHandler: (context: AppSyncHandlerContext<T>) => Promise<R>

A function that handles an AppSync resolver event. 2 | Receives the context object and returns the response.

3 |

Type Parameters

  • T
  • R

Type declaration

4 | -------------------------------------------------------------------------------- /docs/lambda/types/api.HttpHandler.html: -------------------------------------------------------------------------------- 1 | HttpHandler | cdk-serverless

Type Alias HttpHandler<T, R>

HttpHandler: (context: HttpHandlerContext, body: T) => Promise<R>

A type definition for an HTTP handler.

2 |

Type Parameters

  • T
  • R

Type declaration

3 | -------------------------------------------------------------------------------- /docs/projen/.nojekyll: -------------------------------------------------------------------------------- 1 | TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false. -------------------------------------------------------------------------------- /docs/projen/assets/hierarchy.js: -------------------------------------------------------------------------------- 1 | window.hierarchyData = "eJyrVirKzy8pVrKKjtVRKkpNy0lNLsnMzytWsqqurQUAmx4Kpg==" -------------------------------------------------------------------------------- /docs/projen/assets/highlight.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --light-hl-0: #795E26; 3 | --dark-hl-0: #DCDCAA; 4 | --light-hl-1: #000000; 5 | --dark-hl-1: #D4D4D4; 6 | --light-hl-2: #A31515; 7 | --dark-hl-2: #CE9178; 8 | --light-hl-3: #AF00DB; 9 | --dark-hl-3: #C586C0; 10 | --light-hl-4: #001080; 11 | --dark-hl-4: #9CDCFE; 12 | --light-hl-5: #0000FF; 13 | --dark-hl-5: #569CD6; 14 | --light-hl-6: #008000; 15 | --dark-hl-6: #6A9955; 16 | --light-hl-7: #0070C1; 17 | --dark-hl-7: #4FC1FF; 18 | --light-hl-8: #098658; 19 | --dark-hl-8: #B5CEA8; 20 | --light-hl-9: #CD3131; 21 | --dark-hl-9: #F44747; 22 | --light-hl-10: #000000; 23 | --dark-hl-10: #C8C8C8; 24 | --light-code-background: #FFFFFF; 25 | --dark-code-background: #1E1E1E; 26 | } 27 | 28 | @media (prefers-color-scheme: light) { :root { 29 | --hl-0: var(--light-hl-0); 30 | --hl-1: var(--light-hl-1); 31 | --hl-2: var(--light-hl-2); 32 | --hl-3: var(--light-hl-3); 33 | --hl-4: var(--light-hl-4); 34 | --hl-5: var(--light-hl-5); 35 | --hl-6: var(--light-hl-6); 36 | --hl-7: var(--light-hl-7); 37 | --hl-8: var(--light-hl-8); 38 | --hl-9: var(--light-hl-9); 39 | --hl-10: var(--light-hl-10); 40 | --code-background: var(--light-code-background); 41 | } } 42 | 43 | @media (prefers-color-scheme: dark) { :root { 44 | --hl-0: var(--dark-hl-0); 45 | --hl-1: var(--dark-hl-1); 46 | --hl-2: var(--dark-hl-2); 47 | --hl-3: var(--dark-hl-3); 48 | --hl-4: var(--dark-hl-4); 49 | --hl-5: var(--dark-hl-5); 50 | --hl-6: var(--dark-hl-6); 51 | --hl-7: var(--dark-hl-7); 52 | --hl-8: var(--dark-hl-8); 53 | --hl-9: var(--dark-hl-9); 54 | --hl-10: var(--dark-hl-10); 55 | --code-background: var(--dark-code-background); 56 | } } 57 | 58 | :root[data-theme='light'] { 59 | --hl-0: var(--light-hl-0); 60 | --hl-1: var(--light-hl-1); 61 | --hl-2: var(--light-hl-2); 62 | --hl-3: var(--light-hl-3); 63 | --hl-4: var(--light-hl-4); 64 | --hl-5: var(--light-hl-5); 65 | --hl-6: var(--light-hl-6); 66 | --hl-7: var(--light-hl-7); 67 | --hl-8: var(--light-hl-8); 68 | --hl-9: var(--light-hl-9); 69 | --hl-10: var(--light-hl-10); 70 | --code-background: var(--light-code-background); 71 | } 72 | 73 | :root[data-theme='dark'] { 74 | --hl-0: var(--dark-hl-0); 75 | --hl-1: var(--dark-hl-1); 76 | --hl-2: var(--dark-hl-2); 77 | --hl-3: var(--dark-hl-3); 78 | --hl-4: var(--dark-hl-4); 79 | --hl-5: var(--dark-hl-5); 80 | --hl-6: var(--dark-hl-6); 81 | --hl-7: var(--dark-hl-7); 82 | --hl-8: var(--dark-hl-8); 83 | --hl-9: var(--dark-hl-9); 84 | --hl-10: var(--dark-hl-10); 85 | --code-background: var(--dark-code-background); 86 | } 87 | 88 | .hl-0 { color: var(--hl-0); } 89 | .hl-1 { color: var(--hl-1); } 90 | .hl-2 { color: var(--hl-2); } 91 | .hl-3 { color: var(--hl-3); } 92 | .hl-4 { color: var(--hl-4); } 93 | .hl-5 { color: var(--hl-5); } 94 | .hl-6 { color: var(--hl-6); } 95 | .hl-7 { color: var(--hl-7); } 96 | .hl-8 { color: var(--hl-8); } 97 | .hl-9 { color: var(--hl-9); } 98 | .hl-10 { color: var(--hl-10); } 99 | pre, code { background: var(--code-background); } 100 | -------------------------------------------------------------------------------- /docs/projen/assets/navigation.js: -------------------------------------------------------------------------------- 1 | window.navigationData = "eJx90E8PwUAQBfDvMueGqBDprUEcxP+Dg4isGumyus3OBIn47i7VqmXO781vkrd5AOOdIYKBYkVsHUIAueIUIkiMIkJqllEj5YuBAM46O0DUCnvPoLwfOZWnCxPn2geqTBKWSPzzvAik2xW6KzqDRHNnT5iwr3gVyVtbdz4ae/OZdyJdl4PNctY2o0rRGaM7quRz1aJUB8NO9+e4kui1JLIYVfLqFQnztpXYf2XpwXt1yf3qSNw87o/j0XA3jSfDyroqp9XeIDU/87rTDp/bF6mPGZQ=" -------------------------------------------------------------------------------- /docs/projen/assets/search.js: -------------------------------------------------------------------------------- 1 | window.searchData = "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"; -------------------------------------------------------------------------------- /docs/projen/interfaces/RestApiOptions.html: -------------------------------------------------------------------------------- 1 | RestApiOptions | cdk-serverless

Interface RestApiOptions

interface RestApiOptions {
    apiName: string;
    definitionFile: string;
}

Properties

Properties

apiName: string
definitionFile: string
4 | -------------------------------------------------------------------------------- /docs/projen/variables/PACKAGE_NAME.html: -------------------------------------------------------------------------------- 1 | PACKAGE_NAME | cdk-serverless

Variable PACKAGE_NAMEConst

PACKAGE_NAME: "cdk-serverless" = 'cdk-serverless'
2 | -------------------------------------------------------------------------------- /files/aws.graphql: -------------------------------------------------------------------------------- 1 | scalar AWSTime 2 | scalar AWSDate 3 | scalar AWSDateTime 4 | scalar AWSTimestamp 5 | scalar AWSEmail 6 | scalar AWSJSON 7 | scalar AWSURL 8 | scalar AWSPhone 9 | scalar AWSIPAddress 10 | scalar BigInt 11 | scalar Double 12 | 13 | directive @aws_subscribe(mutations: [String!]!) on FIELD_DEFINITION 14 | 15 | # Allows transformer libraries to deprecate directive arguments. 16 | directive @deprecated(reason: String!) on INPUT_FIELD_DEFINITION | ENUM 17 | 18 | directive @aws_auth(cognito_groups: [String!]!) on FIELD_DEFINITION 19 | directive @aws_api_key on FIELD_DEFINITION | OBJECT 20 | directive @aws_iam on FIELD_DEFINITION | OBJECT 21 | directive @aws_oidc on FIELD_DEFINITION | OBJECT 22 | directive @aws_cognito_user_pools( 23 | cognito_groups: [String!] 24 | ) on FIELD_DEFINITION | OBJECT 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cdk-serverless", 3 | "repository": { 4 | "type": "git", 5 | "url": "https://github.com/open-constructs/cdk-serverless" 6 | }, 7 | "scripts": { 8 | "build": "npx projen build", 9 | "bump": "npx projen bump", 10 | "clobber": "npx projen clobber", 11 | "compile": "npx projen compile", 12 | "default": "npx projen default", 13 | "docgen": "npx projen docgen", 14 | "eject": "npx projen eject", 15 | "eslint": "npx projen eslint", 16 | "package": "npx projen package", 17 | "post-compile": "npx projen post-compile", 18 | "post-upgrade": "npx projen post-upgrade", 19 | "pre-compile": "npx projen pre-compile", 20 | "release": "npx projen release", 21 | "test": "npx projen test", 22 | "test:watch": "npx projen test:watch", 23 | "unbump": "npx projen unbump", 24 | "upgrade": "npx projen upgrade", 25 | "watch": "npx projen watch", 26 | "projen": "npx projen" 27 | }, 28 | "author": { 29 | "name": "Taimos GmbH", 30 | "email": "info@taimos.de", 31 | "url": "https://taimos.de", 32 | "organization": true 33 | }, 34 | "devDependencies": { 35 | "@aws-sdk/client-cognito-identity-provider": "3.726.0", 36 | "@aws-sdk/client-dynamodb": "3.726.0", 37 | "@aws-sdk/client-s3": "3.726.0", 38 | "@aws-sdk/lib-dynamodb": "3.726.0", 39 | "@hapi/boom": "^10.0.1", 40 | "@stylistic/eslint-plugin": "^2", 41 | "@types/jest": "^29.5.14", 42 | "@types/js-yaml": "^4.0.9", 43 | "@types/jsonwebtoken": "^9.0.9", 44 | "@types/jwk-to-pem": "^2.0.3", 45 | "@types/lambda-log": "^2.2.1", 46 | "@types/node": "^18", 47 | "@types/uuid": "^9.0.8", 48 | "@typescript-eslint/eslint-plugin": "^8", 49 | "@typescript-eslint/parser": "^8", 50 | "aws-cdk-lib": "2.187.0", 51 | "commit-and-tag-version": "^12", 52 | "constructs": "^10.0.0", 53 | "dynamodb-onetable": "2.7.5", 54 | "eslint": "^9", 55 | "eslint-import-resolver-typescript": "^3.10.0", 56 | "eslint-plugin-import": "^2.31.0", 57 | "jest": "^29.7.0", 58 | "jest-junit": "^16", 59 | "openapi-typescript": "7.6.1", 60 | "projen": "0.91.6", 61 | "ts-jest": "^29.3.1", 62 | "ts-node": "^10.9.2", 63 | "typedoc": "0.27.6", 64 | "typescript": "^5.7.3" 65 | }, 66 | "peerDependencies": { 67 | "@aws-sdk/client-cognito-identity-provider": "^3.777.0", 68 | "@aws-sdk/client-dynamodb": "^3.777.0", 69 | "@aws-sdk/client-s3": "^3.779.0", 70 | "@aws-sdk/lib-dynamodb": "^3.778.0", 71 | "aws-cdk-lib": ">=2.187.0 <3.0.0", 72 | "dynamodb-onetable": "2.7.5", 73 | "openapi-typescript": "^7.6.1", 74 | "projen": ">=0.91.6 <1.0.0" 75 | }, 76 | "dependencies": { 77 | "@types/aws-lambda": "^8.10.148", 78 | "axios": "^1.8.4", 79 | "constructs": "^10.3.0", 80 | "date-fns": "^4.1.0", 81 | "js-yaml": "^4.1.0", 82 | "jsonwebtoken": "^9.0.2", 83 | "jwk-to-pem": "^2.0.7", 84 | "lambda-log": "^3.1.0", 85 | "uuid": "^11.1.0" 86 | }, 87 | "keywords": [ 88 | "aws", 89 | "cdk", 90 | "dynamodb", 91 | "lambda", 92 | "serverless" 93 | ], 94 | "main": "lib/index.js", 95 | "license": "Apache-2.0", 96 | "publishConfig": { 97 | "access": "public" 98 | }, 99 | "version": "0.0.0", 100 | "jest": { 101 | "coverageProvider": "v8", 102 | "testMatch": [ 103 | "/@(src|test)/**/*(*.)@(spec|test).ts?(x)", 104 | "/@(src|test)/**/__tests__/**/*.ts?(x)" 105 | ], 106 | "clearMocks": true, 107 | "collectCoverage": true, 108 | "coverageReporters": [ 109 | "json", 110 | "lcov", 111 | "clover", 112 | "cobertura", 113 | "text" 114 | ], 115 | "coverageDirectory": "coverage", 116 | "coveragePathIgnorePatterns": [ 117 | "/node_modules/" 118 | ], 119 | "testPathIgnorePatterns": [ 120 | "/node_modules/" 121 | ], 122 | "watchPathIgnorePatterns": [ 123 | "/node_modules/" 124 | ], 125 | "reporters": [ 126 | "default", 127 | [ 128 | "jest-junit", 129 | { 130 | "outputDirectory": "test-reports" 131 | } 132 | ] 133 | ], 134 | "transform": { 135 | "^.+\\.[t]sx?$": [ 136 | "ts-jest", 137 | { 138 | "tsconfig": "tsconfig.dev.json" 139 | } 140 | ] 141 | } 142 | }, 143 | "types": "lib/index.d.ts", 144 | "//": "~~ Generated by projen. To modify, edit .projenrc.js and run \"npx projen\"." 145 | } 146 | -------------------------------------------------------------------------------- /src/constructs/asset-cdn.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CfnOutput, 3 | aws_certificatemanager as certificatemanager, 4 | aws_cloudfront as cloudfront, 5 | aws_route53 as route53, 6 | aws_route53_targets as route53Targets, 7 | aws_s3 as s3, 8 | } from 'aws-cdk-lib'; 9 | import { Construct } from 'constructs'; 10 | import { CFN_OUTPUT_SUFFIX_ASSETCDN_BUCKETNAME, CFN_OUTPUT_SUFFIX_ASSETCDN_DOMAINNAME, CFN_OUTPUT_SUFFIX_ASSETCDN_URL } from '../shared/outputs'; 11 | 12 | /** 13 | * Properties for AssetCdn L3 construct 14 | */ 15 | export interface AssetCdnProps { 16 | /** 17 | * Domain name of the asset content delivery network (e.g. example.com) 18 | */ 19 | domainName: string; 20 | 21 | /** 22 | * Hostname of the asset content delivery network (e.g. cdn) 23 | */ 24 | hostName: string; 25 | } 26 | 27 | /** 28 | * The AssetCdn construct is responsible for setting up an S3 bucket for asset storage 29 | * and a CloudFront distribution to serve the assets securely with HTTPS. It also 30 | * configures a Route 53 DNS record to point to the CloudFront distribution. 31 | */ 32 | export class AssetCdn extends Construct { 33 | 34 | /** 35 | * The Route 53 hosted zone for the asset domain. 36 | */ 37 | public readonly zone: route53.IHostedZone; 38 | 39 | /** 40 | * The S3 bucket used to store the assets. 41 | */ 42 | public readonly assetBucket: s3.Bucket; 43 | 44 | /** 45 | * The domain name used for accessing the assets. 46 | */ 47 | public readonly assetDomainName: string; 48 | 49 | /** 50 | * Creates an instance of AssetCdn. 51 | * 52 | * @param scope - The scope in which this construct is defined. 53 | * @param id - The scoped construct ID. 54 | * @param props - The properties of the AssetCdn construct. 55 | */ 56 | constructor(scope: Construct, id: string, props: AssetCdnProps) { 57 | super(scope, id); 58 | 59 | this.zone = route53.HostedZone.fromLookup(this, 'HostedZone', { 60 | domainName: props.domainName, 61 | }); 62 | 63 | this.assetBucket = new s3.Bucket(this, 'AssetBucket', { 64 | blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, 65 | }); 66 | this.assetBucket.addCorsRule({ 67 | allowedHeaders: ['*'], 68 | allowedMethods: [s3.HttpMethods.PUT], 69 | allowedOrigins: ['*'], 70 | }); 71 | 72 | this.assetDomainName = `${props.hostName}.${props.domainName}`; 73 | 74 | 75 | new CfnOutput(this, CFN_OUTPUT_SUFFIX_ASSETCDN_BUCKETNAME, { 76 | key: CFN_OUTPUT_SUFFIX_ASSETCDN_BUCKETNAME, 77 | value: this.assetBucket.bucketName, 78 | }); 79 | new CfnOutput(this, CFN_OUTPUT_SUFFIX_ASSETCDN_DOMAINNAME, { 80 | key: CFN_OUTPUT_SUFFIX_ASSETCDN_DOMAINNAME, 81 | value: this.assetDomainName, 82 | }); 83 | new CfnOutput(this, CFN_OUTPUT_SUFFIX_ASSETCDN_URL, { 84 | key: CFN_OUTPUT_SUFFIX_ASSETCDN_URL, 85 | value: 'https://' + this.assetDomainName, 86 | }); 87 | 88 | // TODO this will be deprecated soon; find other solution 89 | const cert = new certificatemanager.DnsValidatedCertificate(this, 'Certificate', { 90 | hostedZone: this.zone, 91 | domainName: this.assetDomainName, 92 | region: 'us-east-1', 93 | }); 94 | 95 | const originAccessIdentity = new cloudfront.OriginAccessIdentity(this, 'OAI', { comment: `S3 Frontend ${this.assetDomainName}` }); 96 | this.assetBucket.grantRead(originAccessIdentity); 97 | 98 | const distribution = new cloudfront.CloudFrontWebDistribution(this, 'Distribution', { 99 | originConfigs: [ 100 | { 101 | behaviors: [{ isDefaultBehavior: true }], 102 | s3OriginSource: { 103 | s3BucketSource: this.assetBucket, 104 | originAccessIdentity, 105 | }, 106 | }, 107 | ], 108 | viewerCertificate: cloudfront.ViewerCertificate.fromAcmCertificate(cert, { 109 | aliases: [this.assetDomainName], 110 | }), 111 | comment: `Asset endpoint for ${this.assetDomainName}`, 112 | priceClass: cloudfront.PriceClass.PRICE_CLASS_ALL, 113 | viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, 114 | }); 115 | 116 | new route53.ARecord(this, 'AliasRecord', { 117 | recordName: this.assetDomainName, 118 | zone: this.zone, 119 | target: route53.RecordTarget.fromAlias(new route53Targets.CloudFrontTarget(distribution)), 120 | }); 121 | 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/constructs/base-api.ts: -------------------------------------------------------------------------------- 1 | import { aws_route53 } from 'aws-cdk-lib'; 2 | import { Construct } from 'constructs'; 3 | import { AssetCdn } from './asset-cdn'; 4 | import { ICognitoAuthentication, IJwtAuthentication } from './authentication'; 5 | import { LambdaOptions, LambdaTracingOptions } from './func'; 6 | import { SingleTableDatastore } from './table'; 7 | 8 | export interface BaseApiProps { 9 | 10 | /** 11 | * Name of the API 12 | */ 13 | apiName: string; 14 | 15 | /** 16 | * Deployment stage (e.g. dev) 17 | */ 18 | stageName: string; 19 | 20 | /** 21 | * Configure CloudWatch Dashboard for the API and the Lambda functions 22 | * 23 | * @default true 24 | */ 25 | monitoring?: boolean; 26 | 27 | /** 28 | * @default none 29 | */ 30 | singleTableDatastore?: SingleTableDatastore; 31 | 32 | /** 33 | * Use a Cognito user pool for authorization. 34 | * Alternatively supply a JWT issuer and audience 35 | * to use any other JWT-based authorization service. 36 | * 37 | * @default none 38 | */ 39 | authentication?: IJwtAuthentication | ICognitoAuthentication; 40 | 41 | /** 42 | * Configure a content delivery network for static assets 43 | * 44 | * @default none 45 | */ 46 | assetCdn?: AssetCdn; 47 | 48 | /** 49 | * Additional environment variables of all Lambda functions 50 | */ 51 | additionalEnv?: { 52 | [key: string]: string; 53 | }; 54 | 55 | /** 56 | * additional options for the underlying Lambda function construct 57 | */ 58 | lambdaOptions?: LambdaOptions; 59 | 60 | /** 61 | * Tracing config for the generated Lambda functions 62 | */ 63 | lambdaTracing?: LambdaTracingOptions; 64 | 65 | /** 66 | * Domain name of the API (e.g. example.com) 67 | * 68 | * only one of hostedZone and domainName can be specified 69 | * 70 | * @default - No custom domain is configured 71 | */ 72 | domainName?: string; 73 | /** 74 | * Hosted Zone of the API (e.g. example.com) 75 | * 76 | * only one of hostedZone and domainName can be specified 77 | * 78 | * @default - No custom domain is configured 79 | */ 80 | hostedZone?: aws_route53.IHostedZone; 81 | 82 | /** 83 | * Hostname of the API if a domain name is specified 84 | * 85 | * @default api 86 | */ 87 | apiHostname?: string; 88 | } 89 | 90 | /** 91 | * Base class for different types of APIs 92 | */ 93 | export abstract class BaseApi extends Construct { 94 | 95 | protected readonly hostedZone?: aws_route53.IHostedZone; 96 | protected readonly apiHostName?: string; 97 | protected readonly apiDomainName?: string; 98 | protected readonly apiFQDN?: string; 99 | 100 | constructor(scope: Construct, id: string, props: BaseApiProps) { 101 | super(scope, id); 102 | 103 | if (props.hostedZone && props.domainName) { 104 | throw new Error('Cannot specify hostedZone and domainName at the same time'); 105 | } 106 | 107 | this.hostedZone = props.hostedZone; 108 | if (props.domainName) { 109 | this.hostedZone = aws_route53.HostedZone.fromLookup(this, 'Zone', { domainName: props.domainName }); 110 | } 111 | this.apiDomainName = this.hostedZone?.zoneName; 112 | this.apiHostName = props.apiHostname ?? 'api'; 113 | if (this.apiDomainName && this.apiHostName) { 114 | this.apiFQDN = `${this.apiHostName}.${this.apiDomainName}`; 115 | } 116 | 117 | } 118 | 119 | } 120 | -------------------------------------------------------------------------------- /src/constructs/index.ts: -------------------------------------------------------------------------------- 1 | export * from './asset-cdn'; 2 | export * from './authentication'; 3 | export * from './base-api'; 4 | export * from './func'; 5 | export * from './graphql'; 6 | export * from './rest-api'; 7 | export * from './table'; 8 | export * from './workflow'; -------------------------------------------------------------------------------- /src/constructs/table.ts: -------------------------------------------------------------------------------- 1 | import { aws_dynamodb as dynamodb, aws_kms as kms } from 'aws-cdk-lib'; 2 | import { GlobalSecondaryIndexProps, LocalSecondaryIndexProps } from 'aws-cdk-lib/aws-dynamodb'; 3 | import { Construct } from 'constructs'; 4 | 5 | export interface SingleTableDesign { 6 | 7 | /** 8 | * structure of the primary key 9 | */ 10 | primaryKey: { 11 | partitionKey: string; 12 | sortKey?: string; 13 | }; 14 | 15 | /** 16 | * The name of TTL attribute. 17 | * 18 | * @default - TTL is disabled 19 | */ 20 | timeToLiveAttribute?: string; 21 | 22 | /** 23 | * global secondary indexes 24 | */ 25 | globalIndexes?: GlobalSecondaryIndexProps[]; 26 | 27 | /** 28 | * local secondary indexes 29 | */ 30 | localIndexes?: LocalSecondaryIndexProps[]; 31 | } 32 | 33 | export interface SingleTableDatastoreProps { 34 | 35 | /** 36 | * Table design 37 | */ 38 | readonly design: SingleTableDesign; 39 | 40 | /** 41 | * Whether server-side encryption with an AWS managed customer master key is enabled. 42 | * 43 | * @default - server-side encryption is enabled with an AWS owned customer master key 44 | * @stability stable 45 | */ 46 | readonly encryption?: dynamodb.TableEncryption; 47 | 48 | /** 49 | * External KMS key to use for table encryption. 50 | * 51 | * This property can only be set if `encryption` is set to `TableEncryption.CUSTOMER_MANAGED`. 52 | * 53 | * @default - If `encryption` is set to `TableEncryption.CUSTOMER_MANAGED` and this 54 | * property is undefined, a new KMS key will be created and associated with this table. 55 | * @stability stable 56 | */ 57 | readonly encryptionKey?: kms.IKey; 58 | } 59 | 60 | /** 61 | * The SingleTableDatastore construct sets up an AWS DynamoDB table with a single-table design. 62 | * This construct facilitates the creation of a DynamoDB table with various configurations, including primary key, sort key, global secondary indexes, local secondary indexes, and encryption. 63 | * It also supports point-in-time recovery and time-to-live attributes. 64 | * 65 | * @example 66 | * const datastore = new SingleTableDatastore(this, 'MyDatastore', { 67 | * design: { 68 | * primaryKey: { 69 | * partitionKey: 'PK', 70 | * sortKey: 'SK', 71 | * }, 72 | * globalIndexes: [ 73 | * { 74 | * indexName: 'GSI1', 75 | * partitionKey: { name: 'GSI1PK', type: dynamodb.AttributeType.STRING }, 76 | * sortKey: { name: 'GSI1SK', type: dynamodb.AttributeType.STRING }, 77 | * }, 78 | * ], 79 | * localIndexes: [ 80 | * { 81 | * indexName: 'LSI1', 82 | * sortKey: { name: 'LSI1SK', type: dynamodb.AttributeType.STRING }, 83 | * }, 84 | * ], 85 | * timeToLiveAttribute: 'TTL', 86 | * }, 87 | * encryption: dynamodb.TableEncryption.AWS_MANAGED, 88 | * }); 89 | */ 90 | export class SingleTableDatastore extends Construct { 91 | 92 | /** 93 | * The DynamoDB table instance. 94 | */ 95 | public readonly table: dynamodb.Table; 96 | 97 | /** 98 | * Creates an instance of SingleTableDatastore. 99 | * 100 | * @param scope - The scope in which this construct is defined. 101 | * @param id - The scoped construct ID. 102 | * @param props - The properties of the SingleTableDatastore construct. 103 | */ 104 | constructor(scope: Construct, id: string, props: SingleTableDatastoreProps) { 105 | super(scope, id); 106 | 107 | this.table = new dynamodb.Table(this, 'Resource', { 108 | billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, 109 | partitionKey: { 110 | type: dynamodb.AttributeType.STRING, 111 | name: props.design.primaryKey.partitionKey, 112 | }, 113 | ...props.design.primaryKey.sortKey && { 114 | sortKey: { 115 | type: dynamodb.AttributeType.STRING, 116 | name: props.design.primaryKey.sortKey, 117 | }, 118 | }, 119 | pointInTimeRecovery: true, 120 | encryption: props.encryption, 121 | encryptionKey: props.encryptionKey, 122 | timeToLiveAttribute: props.design.timeToLiveAttribute, 123 | }); 124 | 125 | for (const index of props.design.globalIndexes ?? []) { 126 | this.table.addGlobalSecondaryIndex(index); 127 | } 128 | for (const index of props.design.localIndexes ?? []) { 129 | this.table.addLocalSecondaryIndex(index); 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/constructs/workflow.ts: -------------------------------------------------------------------------------- 1 | import * as iam from 'aws-cdk-lib/aws-iam'; 2 | import { Asset } from 'aws-cdk-lib/aws-s3-assets'; 3 | import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; 4 | import * as constructs from 'constructs'; 5 | 6 | 7 | export interface WorkflowProps { 8 | readonly stateMachineType?: sfn.StateMachineType; 9 | readonly loggingConfiguration?: sfn.CfnStateMachine.LoggingConfigurationProperty; 10 | readonly tracingConfiguration?: sfn.CfnStateMachine.TracingConfigurationProperty; 11 | 12 | readonly definitionFileName: string; 13 | readonly definitionSubstitutions?: { 14 | [key: string]: (string); 15 | }; 16 | } 17 | 18 | /** 19 | * The Workflow construct sets up an AWS Step Functions state machine with a specified definition file and IAM role. 20 | * This construct facilitates the creation of a Step Functions workflow, including the definition from an S3 location and 21 | * various configurations for logging and tracing. It implements the IGrantable interface for granting permissions. 22 | * 23 | * @example 24 | * const workflow = new Workflow(this, 'MyWorkflow', { 25 | * definitionFileName: 'path/to/definition.asl.json', 26 | * stateMachineType: sfn.StateMachineType.STANDARD, 27 | * loggingConfiguration: { 28 | * level: sfn.LogLevel.ALL, 29 | * includeExecutionData: true, 30 | * destinations: [new logs.LogGroup(this, 'LogGroup')], 31 | * }, 32 | * tracingConfiguration: { 33 | * enabled: true, 34 | * }, 35 | * definitionSubstitutions: { 36 | * '${MyVariable}': 'MyValue', 37 | * }, 38 | * }); 39 | * 40 | * const lambdaFunction = new lambda.Function(this, 'MyFunction', { 41 | * runtime: lambda.Runtime.NODEJS_20_X, 42 | * handler: 'index.handler', 43 | * code: lambda.Code.fromAsset('lambda'), 44 | * }); 45 | * 46 | * workflow.grantPrincipal.grantInvoke(lambdaFunction); 47 | */ 48 | export class Workflow extends constructs.Construct implements iam.IGrantable { 49 | 50 | /** 51 | * The IAM role assumed by the state machine. 52 | */ 53 | public readonly role: iam.IRole; 54 | 55 | /** 56 | * The ARN of the created state machine. 57 | */ 58 | public readonly workflowArn: string; 59 | 60 | /** 61 | * The principal to which permissions can be granted. 62 | */ 63 | public readonly grantPrincipal: iam.IPrincipal; 64 | 65 | /** 66 | * Creates an instance of Workflow. 67 | * 68 | * @param scope - The scope in which this construct is defined. 69 | * @param id - The scoped construct ID. 70 | * @param props - The properties of the Workflow construct. 71 | */ 72 | constructor(scope: constructs.Construct, id: string, props: WorkflowProps) { 73 | super(scope, id); 74 | 75 | this.role = new iam.Role(this, 'Role', { 76 | assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), 77 | }); 78 | this.grantPrincipal = this.role; 79 | 80 | const definitionAsset = new Asset(this, 'DefinitionAsset', { 81 | path: props.definitionFileName, 82 | }); 83 | 84 | const resource = new sfn.CfnStateMachine(this, 'Resource', { 85 | stateMachineType: props.stateMachineType ?? sfn.StateMachineType.STANDARD, 86 | roleArn: this.role.roleArn, 87 | definitionS3Location: { 88 | bucket: definitionAsset.s3BucketName, 89 | key: definitionAsset.s3ObjectKey, 90 | }, 91 | definitionSubstitutions: props.definitionSubstitutions, 92 | loggingConfiguration: props.loggingConfiguration, 93 | tracingConfiguration: props.tracingConfiguration, 94 | }); 95 | resource.node.addDependency(this.role); 96 | this.workflowArn = resource.attrArn; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/lambda/errors.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * A base class for HTTP errors. 3 | */ 4 | export class HttpError extends Error { 5 | /** 6 | * Creates an instance of HttpError. 7 | * 8 | * @param statusCode - The HTTP status code for the error. 9 | * @param [message] - The error message. 10 | */ 11 | constructor(public readonly statusCode: number, message?: string) { 12 | super(message); 13 | } 14 | } 15 | 16 | /** 17 | * Represents a 400 Bad Request error. 18 | */ 19 | export class BadRequestError extends HttpError { 20 | /** 21 | * Creates an instance of BadRequestError. 22 | * 23 | * @param [message] - The error message. 24 | */ 25 | constructor(message?: string) { 26 | super(400, message); 27 | } 28 | } 29 | 30 | /** 31 | * Represents a 401 Unauthenticated error. 32 | */ 33 | export class UnauthenticatedError extends HttpError { 34 | /** 35 | * Creates an instance of UnauthenticatedError. 36 | * 37 | * @param [message] - The error message. 38 | */ 39 | constructor(message?: string) { 40 | super(401, message); 41 | } 42 | } 43 | 44 | /** 45 | * Represents a 403 Forbidden error. 46 | */ 47 | export class ForbiddenError extends HttpError { 48 | /** 49 | * Creates an instance of ForbiddenError. 50 | * 51 | * @param [message] - The error message. 52 | */ 53 | constructor(message?: string) { 54 | super(403, message); 55 | } 56 | } 57 | 58 | /** 59 | * Represents a 404 Not Found error. 60 | */ 61 | export class NotFoundError extends HttpError { 62 | /** 63 | * Creates an instance of NotFoundError. 64 | * 65 | * @param [message] - The error message. 66 | */ 67 | constructor(message?: string) { 68 | super(404, message); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/lambda/index.ts: -------------------------------------------------------------------------------- 1 | export * as auth from './auth'; 2 | export * as api from './handler'; 3 | export * as errors from './errors'; -------------------------------------------------------------------------------- /src/projen/core.ts: -------------------------------------------------------------------------------- 1 | import * as pj from 'projen'; 2 | 3 | export const PACKAGE_NAME = 'cdk-serverless'; 4 | 5 | export interface ServerlessProjectOptions extends pj.awscdk.AwsCdkTypeScriptAppOptions { 6 | // Additional options can be specified here 7 | } 8 | 9 | /** 10 | * The ServerlessProject construct sets up an AWS CDK TypeScript project with serverless application dependencies and configurations. 11 | * This construct extends the projen AwsCdkTypeScriptApp to include additional dependencies and development dependencies required 12 | * for serverless applications. It also enables TypeScript projen configuration (projenrcTs). 13 | * 14 | * @example 15 | * const project = new ServerlessProject({ 16 | * name: 'MyServerlessApp', 17 | * defaultReleaseBranch: 'main', 18 | * cdkVersion: '2.140.0', 19 | * deps: ['additional-dependency'], 20 | * devDeps: ['additional-dev-dependency'], 21 | * }); 22 | */ 23 | export class ServerlessProject extends pj.awscdk.AwsCdkTypeScriptApp { 24 | 25 | /** 26 | * Creates an instance of ServerlessProject. 27 | * 28 | * @param {ServerlessProjectOptions} options - The options for configuring the ServerlessProject. 29 | */ 30 | constructor(options: ServerlessProjectOptions) { 31 | super({ 32 | ...options, 33 | projenrcTs: true, 34 | deps: [ 35 | ...options.deps ?? [], 36 | 'uuid', 37 | 'esbuild', 38 | 'js-yaml', 39 | '@aws-sdk/client-s3', 40 | ], 41 | devDeps: [ 42 | ...options.devDeps ?? [], 43 | '@types/aws-lambda', 44 | '@types/uuid', 45 | '@types/lambda-log', 46 | '@types/js-yaml', 47 | ], 48 | }); 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/projen/datastore.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | import { join } from 'path'; 3 | import { OneSchema } from 'dynamodb-onetable'; 4 | import * as pj from 'projen'; 5 | import { PACKAGE_NAME } from './core'; 6 | import { LazyTextFile } from './lazy-textfile'; 7 | import { CFN_OUTPUT_SUFFIX_DATASTORE_TABLENAME } from '../shared/outputs'; 8 | 9 | export interface DatastoreOptions { 10 | readonly modelName: string; 11 | readonly definitionFile: string; 12 | } 13 | 14 | /** 15 | * The Datastore construct sets up a DynamoDB single-table design for a serverless project using projen. 16 | * This construct extends the projen Component to include dependencies and development dependencies required for DynamoDB and provides 17 | * methods to generate model and construct files based on a schema definition file. 18 | * 19 | * @example 20 | * const datastore = new Datastore(app, { 21 | * modelName: 'User', 22 | * definitionFile: 'src/models/user.schema.json', 23 | * }); 24 | */ 25 | export class Datastore extends pj.Component { 26 | 27 | constructor(app: pj.awscdk.AwsCdkTypeScriptApp, protected options: DatastoreOptions) { 28 | super(app); 29 | 30 | app.addDeps( 31 | 'dynamodb-onetable', 32 | '@aws-sdk/client-dynamodb', 33 | '@aws-sdk/lib-dynamodb', 34 | 'uuid', 35 | ); 36 | app.addDevDeps( 37 | '@types/uuid', 38 | ); 39 | 40 | new pj.SampleFile(this.project, this.options.definitionFile, { 41 | contents: JSON.stringify({ 42 | format: 'onetable:1.0.0', 43 | version: '0.1.0', 44 | indexes: { 45 | primary: { 46 | hash: 'PK', 47 | sort: 'SK', 48 | }, 49 | }, 50 | models: { 51 | User: { 52 | PK: { 53 | type: 'string', 54 | required: true, 55 | value: 'User#${name}', 56 | }, 57 | SK: { 58 | type: 'string', 59 | required: true, 60 | value: 'User#${name}', 61 | }, 62 | name: { 63 | type: 'string', 64 | required: true, 65 | }, 66 | }, 67 | }, 68 | }), 69 | }); 70 | 71 | new LazyTextFile(this.project, `src/generated/datastore.${this.options.modelName.toLowerCase()}-model.generated.ts`, { content: this.createModelFile.bind(this) }); 72 | new LazyTextFile(this.project, `src/generated/datastore.${this.options.modelName.toLowerCase()}-construct.generated.ts`, { content: this.createConstructFile.bind(this) }); 73 | } 74 | 75 | protected createModelFile(file: pj.FileBase): string { 76 | const model = JSON.parse(fs.readFileSync(join(this.project.outdir, this.options.definitionFile)).toString()) as OneSchema; 77 | 78 | return `// ${file.marker} 79 | /* eslint-disable */ 80 | import { Model, Table, Entity } from 'dynamodb-onetable'; 81 | import { env } from 'process'; 82 | import { Dynamo } from 'dynamodb-onetable/Dynamo'; 83 | import { DynamoDBClient } from '@aws-sdk/client-dynamodb'; 84 | 85 | export const dynamoClient = new Dynamo({ client: new DynamoDBClient({}) }) 86 | export const TABLE_NAME: string = env.TABLE ?? 'dummy-table'; 87 | 88 | ${Object.entries(model.indexes).map(([idx, data]) => `export const Index_${idx}_Name = '${idx}'; 89 | export const Index_${idx}_HashKey = '${data.hash}'; 90 | export const Index_${idx}_SortKey = '${data.sort}'; 91 | `).join('\n')} 92 | 93 | export const Schema = ${this.stringifyModel(model)}; 94 | 95 | export const table = new Table({ 96 | client: dynamoClient, 97 | name: TABLE_NAME, 98 | schema: Schema, 99 | isoDates: true, 100 | // logger: true, 101 | hidden: false, 102 | }); 103 | 104 | ${Object.entries(model.models).map(([typeName, _]) => `export type ${typeName}Type = Entity; 105 | export const ${typeName}: Model<${typeName}Type> = table.getModel<${typeName}Type>('${typeName}'); 106 | `).join('\n')} 107 | 108 | `; 109 | } 110 | 111 | private stringifyModel(model: OneSchema) { 112 | // array, binary, boolean, date, number, object, set, string 113 | // Array, Binary, Boolean, Buffer, Date, Number, Object, Set, String 114 | 115 | return JSON.stringify(model, null, 2) 116 | .replace(/"type": "array"/g, '"type": Array') 117 | .replace(/"type": "binary"/g, '"type": Binary') 118 | .replace(/"type": "boolean"/g, '"type": Boolean') 119 | .replace(/"type": "date"/g, '"type": Date') 120 | .replace(/"type": "number"/g, '"type": Number') 121 | .replace(/"type": "object"/g, '"type": Object') 122 | .replace(/"type": "set"/g, '"type": Set') 123 | .replace(/"type": "string"/g, '"type": String'); 124 | } 125 | 126 | protected createConstructFile(file: pj.FileBase): string { 127 | const model = JSON.parse(fs.readFileSync(join(this.project.outdir, this.options.definitionFile)).toString()) as OneSchema; 128 | 129 | return `// ${file.marker} 130 | /* eslint-disable */ 131 | import { AttributeType, ProjectionType } from 'aws-cdk-lib/aws-dynamodb'; 132 | import { CfnOutput } from 'aws-cdk-lib'; 133 | import { Construct } from 'constructs'; 134 | import { SingleTableDatastore, SingleTableDatastoreProps } from '${PACKAGE_NAME}/lib/constructs'; 135 | 136 | export interface ${this.options.modelName}DatastoreProps extends Omit { 137 | // 138 | } 139 | 140 | export class ${this.options.modelName}Datastore extends SingleTableDatastore { 141 | 142 | constructor(scope: Construct, id: string, props: ${this.options.modelName}DatastoreProps = {}) { 143 | super(scope, id, { 144 | ...props, 145 | design: { 146 | primaryKey: { 147 | partitionKey: '${model.indexes.primary.hash}', 148 | ${model.indexes.primary.sort ? `sortKey: '${model.indexes.primary.sort}',` : ''} 149 | }, 150 | // timeToLiveAttribute: 'TODO', 151 | globalIndexes: [ 152 | ${Object.entries(model.indexes).filter(([idx, data]) => idx !== 'primary' && data.type !== 'local').map(([idx, data]) => `{ 153 | indexName: '${idx}', 154 | partitionKey: { 155 | name: '${data.hash}', 156 | type: AttributeType.STRING, 157 | }, 158 | ${data.sort ? `sortKey: { 159 | name: '${data.sort}', 160 | type: AttributeType.STRING, 161 | },` : ''} 162 | projectionType: ProjectionType.${typeof data.project === 'string' ? data.project.toUpperCase() : 'INCLUDE'}, 163 | ${Array.isArray(data.project) ? `nonKeyAttributes: [${data.project.map(e => `'${e}'`).join(',')}],` : ''} 164 | }`).join(',\n')} 165 | ], 166 | localIndexes: [ 167 | ${Object.entries(model.indexes).filter(([_, data]) => data.type === 'local').map(([idx, data]) => `{ 168 | indexName: '${idx}', 169 | sortKey: { 170 | name: '${data.sort}', 171 | type: AttributeType.STRING, 172 | }, 173 | projectionType: ProjectionType.${typeof data.project === 'string' ? data.project : 'INCLUDE'}, 174 | ${Array.isArray(data.project) ? `nonKeyAttributes: [${data.project.map(e => `'${e}'`).join(',')}],` : ''} 175 | }`).join(',\n')} 176 | ], 177 | } 178 | }); 179 | new CfnOutput(this, '${this.options.modelName}${CFN_OUTPUT_SUFFIX_DATASTORE_TABLENAME}', { 180 | key: '${this.options.modelName}${CFN_OUTPUT_SUFFIX_DATASTORE_TABLENAME}', 181 | value: this.table.tableName, 182 | }); 183 | } 184 | 185 | }`; 186 | } 187 | 188 | } -------------------------------------------------------------------------------- /src/projen/graphql.ts: -------------------------------------------------------------------------------- 1 | import * as pj from 'projen'; 2 | import { PACKAGE_NAME } from './core'; 3 | 4 | export interface GraphQlApiOptions { 5 | readonly apiName: string; 6 | readonly definitionFile: string; 7 | } 8 | 9 | /** 10 | * The GraphQlApi construct sets up a GraphQL API for a serverless project using projen. 11 | * This construct extends the projen Component to include dependencies and development dependencies required for GraphQL 12 | * and provides methods to generate TypeScript types and resolvers from a GraphQL schema definition file. 13 | * 14 | * @example 15 | * const graphQlApi = new GraphQlApi(app, { 16 | * apiName: 'MyGraphQlApi', 17 | * definitionFile: 'src/graphql/schema.graphql', 18 | * }); 19 | */ 20 | export class GraphQlApi extends pj.Component { 21 | 22 | public readonly codegenConfigFileName: string; 23 | public readonly codegenConfigFile: pj.YamlFile; 24 | 25 | constructor(app: pj.awscdk.AwsCdkTypeScriptApp, protected options: GraphQlApiOptions) { 26 | super(app); 27 | 28 | app.addDevDeps( 29 | '@graphql-codegen/cli', 30 | '@graphql-codegen/typescript', 31 | '@graphql-codegen/typescript-resolvers', 32 | '@types/aws-lambda', 33 | '@types/lambda-log', 34 | ); 35 | app.addDeps( 36 | 'graphql', 37 | '@aws-appsync/utils', 38 | 'esbuild', 39 | ); 40 | 41 | this.codegenConfigFileName = `graphql-codegen.${options.apiName.toLowerCase()}.yml`; 42 | const generateTask = app.addTask(`generate:api:${options.apiName.toLowerCase()}`, { 43 | exec: `graphql-codegen -c ${this.codegenConfigFileName}`, 44 | description: 'Generate Types from the GraphQL specification', 45 | }); 46 | app.preCompileTask.prependSpawn(generateTask); 47 | 48 | const codegenConfig = { 49 | schema: [ 50 | this.options.definitionFile, 51 | 'node_modules/cdk-serverless/files/aws.graphql', 52 | ], 53 | config: { 54 | scalars: { 55 | AWSDate: 'string', 56 | AWSURL: 'string', 57 | AWSEmail: 'string', 58 | AWSTime: 'string', 59 | AWSDateTime: 'string', 60 | AWSTimestamp: 'string', 61 | AWSPhone: 'string', 62 | AWSIPAddress: 'string', 63 | AWSJSON: 'string', 64 | }, 65 | maybeValue: 'T | undefined', 66 | }, 67 | generates: { 68 | [`./src/generated/graphql.${this.options.apiName.toLowerCase()}-model.generated.ts`]: { 69 | plugins: ['typescript', 'typescript-resolvers'], 70 | }, 71 | }, 72 | }; 73 | 74 | this.codegenConfigFile = new pj.YamlFile(this.project, this.codegenConfigFileName, { 75 | obj: codegenConfig, 76 | }); 77 | 78 | const constructFile = new pj.TextFile(this.project, `src/generated/graphql.${this.options.apiName.toLowerCase()}-api.generated.ts`); 79 | constructFile.addLine(`// ${constructFile.marker} 80 | /* eslint-disable */ 81 | import { Construct } from 'constructs'; 82 | import { GraphQlApi, GraphQlApiProps } from '${PACKAGE_NAME}/lib/constructs'; 83 | import { Resolvers } from './graphql.${this.options.apiName.toLowerCase()}-model.generated'; 84 | 85 | export interface ${this.options.apiName}GraphQlApiProps extends Omit { 86 | // 87 | } 88 | 89 | export class ${this.options.apiName}GraphQlApi extends GraphQlApi { 90 | 91 | constructor(scope: Construct, id: string, props: ${this.options.apiName}GraphQlApiProps) { 92 | super(scope, id, { 93 | ...props, 94 | apiName: '${this.options.apiName}', 95 | definitionFileName: '${this.options.definitionFile}', 96 | }); 97 | } 98 | 99 | }`); 100 | 101 | new pj.SampleFile(this.project, this.options.definitionFile, { 102 | contents: `type Query { 103 | users: [User] 104 | } 105 | 106 | type User { 107 | id: ID! 108 | name: String 109 | }`, 110 | }); 111 | } 112 | 113 | } 114 | -------------------------------------------------------------------------------- /src/projen/index.ts: -------------------------------------------------------------------------------- 1 | export * from './core'; 2 | export * from './rest-api'; 3 | export * from './datastore'; 4 | export * from './workflow'; 5 | export * from './graphql'; -------------------------------------------------------------------------------- /src/projen/lazy-sampledir.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | import { Component, Project } from 'projen'; 4 | 5 | /** 6 | * SampleDir options 7 | */ 8 | export interface LazySampleDirOptions { 9 | /** 10 | * The files to render into the directory. 11 | */ 12 | readonly fileGenerator: () => { 13 | [fileName: string]: () => string; 14 | }; 15 | } 16 | 17 | /** 18 | * Renders the given files into the directory if the files do not exist. Use this to create sample code files 19 | */ 20 | export class LazySampleDir extends Component { 21 | private readonly dir: string; 22 | private readonly options: LazySampleDirOptions; 23 | 24 | /** 25 | * Create sample files in the given directory 26 | * @param project Parent project to add files to. 27 | * @param dir directory to add files to. 28 | * @param options options for which files to create. 29 | */ 30 | constructor(project: Project, dir: string, options: LazySampleDirOptions) { 31 | super(project); 32 | this.dir = dir; 33 | this.options = options; 34 | } 35 | 36 | public synthesize() { 37 | const fullOutdir = path.join(this.project.outdir, this.dir); 38 | 39 | // previously creating the directory to allow empty dirs to be created 40 | fs.mkdirSync(fullOutdir, { recursive: true }); 41 | 42 | const files = this.options.fileGenerator(); 43 | for (const filename in files) { 44 | writeFile(path.join(fullOutdir, filename), files[filename]); 45 | } 46 | } 47 | } 48 | 49 | function writeFile(filePath: string, contentGenerator: (() => string)) { 50 | if (fs.existsSync(filePath)) { 51 | return; 52 | } 53 | 54 | fs.mkdirSync(path.dirname(filePath), { recursive: true }); 55 | fs.writeFileSync(filePath, contentGenerator()); 56 | } -------------------------------------------------------------------------------- /src/projen/lazy-textfile.ts: -------------------------------------------------------------------------------- 1 | import { IConstruct } from 'constructs'; 2 | import { FileBase, FileBaseOptions, IResolver, TextFile } from 'projen'; 3 | 4 | export interface LazyTextFileOptions extends FileBaseOptions { 5 | /** 6 | * The content provider for the file. 7 | */ 8 | readonly content: (file: FileBase) => string; 9 | } 10 | 11 | export class LazyTextFile extends TextFile { 12 | 13 | private readonly content: (file: FileBase) => string; 14 | 15 | /** 16 | * Defines a text file. 17 | * 18 | * @param project The project 19 | * @param filePath File path 20 | * @param options Options 21 | */ 22 | constructor(scope: IConstruct, filePath: string, options?: LazyTextFileOptions) { 23 | super(scope, filePath, options); 24 | this.content = options?.content ?? (() => ''); 25 | } 26 | 27 | protected synthesizeContent(_: IResolver): string | undefined { 28 | return this.content(this); 29 | } 30 | } -------------------------------------------------------------------------------- /src/projen/workflow.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | import { join } from 'path'; 3 | import * as pj from 'projen'; 4 | import { PACKAGE_NAME } from './core'; 5 | import { LazyTextFile } from './lazy-textfile'; 6 | 7 | export interface WorkflowOptions { 8 | readonly workflowName: string; 9 | readonly definitionFile: string; 10 | } 11 | 12 | interface VariableDefinition { 13 | readonly fullName: string; 14 | readonly name: string; 15 | readonly type: string; 16 | } 17 | 18 | /** 19 | * The Workflow construct sets up an AWS Step Functions workflow for a serverless project using projen. 20 | * This construct extends the projen Component to generate a sample state machine definition file and corresponding TypeScript construct file. 21 | * 22 | * @example 23 | * const workflow = new Workflow(app, { 24 | * workflowName: 'MyWorkflow', 25 | * definitionFile: 'statemachine/definition.json', 26 | * }); 27 | */ 28 | export class Workflow extends pj.Component { 29 | 30 | /** 31 | * Creates an instance of Workflow. 32 | * 33 | * @param app - The AWS CDK TypeScript app. 34 | * @param options - The options for configuring the Workflow. 35 | */ 36 | constructor(app: pj.awscdk.AwsCdkTypeScriptApp, protected options: WorkflowOptions) { 37 | super(app); 38 | 39 | new pj.SampleFile(this.project, this.options.definitionFile, { 40 | contents: JSON.stringify({ 41 | StartAt: 'Hello World', 42 | States: { 43 | 'Hello World': { 44 | Type: 'Pass', 45 | Result: { 46 | Hello: '${world}', 47 | }, 48 | End: true, 49 | }, 50 | }, 51 | }, undefined, 2), 52 | }); 53 | 54 | new LazyTextFile(this.project, `src/generated/workflow.${this.options.workflowName.toLowerCase()}.generated.ts`, { content: this.createConstructFile.bind(this) }); 55 | } 56 | 57 | /** 58 | * Creates the construct file content for the generated Workflow. 59 | * 60 | * @param file - The file object to create content for. 61 | * @returns The content of the construct file. 62 | */ 63 | protected createConstructFile(file: pj.FileBase): string { 64 | const workflowDefinition = fs.readFileSync(join(this.project.outdir, this.options.definitionFile)).toString(); 65 | const matches = workflowDefinition.match(/\$\{[a-zA-Z0-9#]*\}/g)?.map(match => match.substring(2, match.length - 1)); 66 | const matchedVariables: VariableDefinition[] = (matches ?? []).map(varName => { 67 | if (varName.indexOf('#') < 0) { 68 | return { name: varName, fullName: varName, type: 'string' }; 69 | } 70 | const [name, type] = varName.split('#'); 71 | return { name, type, fullName: varName }; 72 | }); 73 | 74 | return `// ${file.marker} 75 | /* eslint-disable */ 76 | import * as constructs from 'constructs'; 77 | import { ITable } from 'aws-cdk-lib/aws-dynamodb'; 78 | import { IFunction } from 'aws-cdk-lib/aws-lambda'; 79 | import * as sls from '${PACKAGE_NAME}/lib/constructs'; 80 | 81 | export interface ${this.options.workflowName}WorkflowProps extends Omit { 82 | readonly stateConfig: { 83 | ${matchedVariables.map(v => this.renderStateConfigDefinition(v)).join('\n')} 84 | }; 85 | } 86 | 87 | export class ${this.options.workflowName}Workflow extends sls.Workflow { 88 | 89 | constructor(scope: constructs.Construct, id: string, props: ${this.options.workflowName}WorkflowProps) { 90 | super(scope, id, { 91 | ...props, 92 | definitionFileName: '${this.options.definitionFile}', 93 | definitionSubstitutions: { 94 | ${matchedVariables.map(v => this.renderDefinitionSubstitution(v)).join('\n')} 95 | } 96 | }); 97 | } 98 | 99 | }`; 100 | } 101 | 102 | /** 103 | * Renders the state configuration definition for a given variable. 104 | * 105 | * @param def - The variable definition. 106 | * @returns The state configuration definition string. 107 | */ 108 | protected renderStateConfigDefinition(def: VariableDefinition): string { 109 | switch (def.type) { 110 | case 'DynamoDBTable': 111 | return `readonly ${def.name}: ITable;`; 112 | case 'LambdaFunction': 113 | return `readonly ${def.name}: IFunction;`; 114 | case 'string': 115 | default: 116 | return `readonly ${def.name}: string;`; 117 | } 118 | } 119 | 120 | /** 121 | * Renders the definition substitution for a given variable. 122 | * 123 | * @param def - The variable definition. 124 | * @returns The definition substitution string. 125 | */ 126 | protected renderDefinitionSubstitution(def: VariableDefinition): string { 127 | switch (def.type) { 128 | case 'DynamoDBTable': 129 | return `'${def.fullName}': props.stateConfig.${def.name}.tableName,`; 130 | case 'LambdaFunction': 131 | return `'${def.fullName}': props.stateConfig.${def.name}.functionArn,`; 132 | case 'string': 133 | default: 134 | return `'${def.fullName}': props.stateConfig.${def.name},`; 135 | } 136 | } 137 | 138 | } 139 | -------------------------------------------------------------------------------- /src/shared/outputs.ts: -------------------------------------------------------------------------------- 1 | 2 | export const CFN_OUTPUT_SUFFIX_RESTAPI_DOMAINNAME = 'RestApiDomainName'; 3 | export const CFN_OUTPUT_SUFFIX_RESTAPI_URL = 'RestApiUrl'; 4 | 5 | export const CFN_OUTPUT_SUFFIX_GRAPHQL_DOMAINNAME = 'GraphQlDomainName'; 6 | 7 | export const CFN_OUTPUT_SUFFIX_DATASTORE_TABLENAME = 'TableName'; 8 | 9 | export const CFN_OUTPUT_SUFFIX_ASSETCDN_BUCKETNAME = 'AssetCdnBucketName'; 10 | export const CFN_OUTPUT_SUFFIX_ASSETCDN_DOMAINNAME = 'AssetCdnDomainName'; 11 | export const CFN_OUTPUT_SUFFIX_ASSETCDN_URL = 'AssetCdnUrl'; 12 | 13 | export const CFN_OUTPUT_SUFFIX_AUTH_USERPOOLID = 'UserPoolId'; 14 | export const CFN_OUTPUT_SUFFIX_AUTH_USERPOOL_CLIENTID = 'UserPoolClientId'; 15 | export const CFN_OUTPUT_SUFFIX_AUTH_IDENTITYPOOL_ID = 'IdentityPoolId'; 16 | export const CFN_OUTPUT_SUFFIX_AUTH_IDENTITYPOOL_AUTH_ROLEARN = 'IdentityPoolAuthRoleArn'; 17 | export const CFN_OUTPUT_SUFFIX_AUTH_IDENTITYPOOL_UNAUTH_ROLEARN = 'IdentityPoolUnAuthRoleArn'; -------------------------------------------------------------------------------- /src/tests/index.ts: -------------------------------------------------------------------------------- 1 | export * from './integ-test-util'; 2 | export * from './lambda-test-utils'; -------------------------------------------------------------------------------- /src/tests/integ-test-util.ts: -------------------------------------------------------------------------------- 1 | import { AdminAddUserToGroupCommand, AdminCreateUserCommand, AdminDeleteUserCommand, AdminSetUserPasswordCommand, CognitoIdentityProviderClient, MessageActionType } from '@aws-sdk/client-cognito-identity-provider'; 2 | import { DynamoDBClient } from '@aws-sdk/client-dynamodb'; 3 | import { DeleteCommand, DynamoDBDocumentClient, NativeAttributeValue } from '@aws-sdk/lib-dynamodb'; 4 | import { Axios, AxiosRequestConfig, HttpStatusCode } from 'axios'; 5 | import { decode, JwtPayload } from 'jsonwebtoken'; 6 | import { CFN_OUTPUT_SUFFIX_AUTH_IDENTITYPOOL_ID, CFN_OUTPUT_SUFFIX_AUTH_USERPOOLID, CFN_OUTPUT_SUFFIX_AUTH_USERPOOL_CLIENTID, CFN_OUTPUT_SUFFIX_DATASTORE_TABLENAME, CFN_OUTPUT_SUFFIX_RESTAPI_URL } from '../shared/outputs'; 7 | 8 | export interface IntegTestUtilOptions { 9 | 10 | readonly region: string; 11 | 12 | readonly apiOptions?: { 13 | readonly baseURL?: string; 14 | }; 15 | 16 | readonly authOptions?: { 17 | readonly userPoolId?: string; 18 | 19 | readonly userPoolClientId?: string; 20 | readonly userPoolClientSecret?: string; 21 | 22 | readonly identityPoolId?: string; 23 | }; 24 | 25 | readonly datastoreOptions?: { 26 | readonly tableName?: string; 27 | }; 28 | 29 | } 30 | 31 | export interface CfnOutputConfig { 32 | readonly region: string; 33 | readonly apiName: string; 34 | readonly datastoreName: string; 35 | } 36 | 37 | export function parseCfnOutputs(output: any, stackName: string, config: CfnOutputConfig): IntegTestUtilOptions { 38 | const outputs = output[stackName]; 39 | return { 40 | region: config.region, 41 | apiOptions: { 42 | baseURL: outputs[`${config.apiName}${CFN_OUTPUT_SUFFIX_RESTAPI_URL}`], 43 | }, 44 | authOptions: { 45 | userPoolId: outputs[CFN_OUTPUT_SUFFIX_AUTH_USERPOOLID], 46 | userPoolClientId: outputs[CFN_OUTPUT_SUFFIX_AUTH_USERPOOL_CLIENTID], 47 | identityPoolId: outputs[CFN_OUTPUT_SUFFIX_AUTH_IDENTITYPOOL_ID], 48 | }, 49 | datastoreOptions: { 50 | tableName: `${config.datastoreName}${outputs[CFN_OUTPUT_SUFFIX_DATASTORE_TABLENAME]}`, 51 | }, 52 | }; 53 | } 54 | 55 | interface TokenInfo { 56 | readonly token: string; 57 | readonly payload: JwtPayload; 58 | } 59 | 60 | export class IntegTestUtil { 61 | 62 | public readonly tableName?: string; 63 | 64 | private apiTokens: { [email: string]: TokenInfo } = {}; 65 | private itemsToDelete: Record[] = []; 66 | 67 | constructor(protected options: IntegTestUtilOptions) { 68 | process.env.AWS_REGION = options.region; 69 | process.env.AWS_DEFAULT_REGION = options.region; 70 | if (options.datastoreOptions?.tableName) { 71 | process.env.TABLE = options.datastoreOptions.tableName; 72 | this.tableName = options.datastoreOptions.tableName; 73 | } 74 | } 75 | 76 | // AUTH 77 | 78 | public getClient(config?: AxiosRequestConfig) { 79 | return new Axios({ 80 | baseURL: this.options.apiOptions?.baseURL, 81 | transformResponse: (data) => { 82 | try { 83 | return JSON.parse(data); 84 | } catch (error) { 85 | return data; 86 | } 87 | }, 88 | transformRequest: (data) => { 89 | try { 90 | return JSON.stringify(data); 91 | } catch (error) { 92 | return data; 93 | } 94 | }, 95 | ...config, 96 | }); 97 | } 98 | 99 | public async getAuthenticatedClient(email: string, password?: string, config?: AxiosRequestConfig) { 100 | if (!this.apiTokens[email]) { 101 | if (!password) { 102 | throw new Error('No password provided; You can only leave password blank for users created by this utility'); 103 | } 104 | await this.loginUser(email, password); 105 | } 106 | return this.getClient({ 107 | headers: { 108 | Authorization: `Bearer ${this.apiTokens[email].token}`, 109 | ...(config?.headers ?? {}), 110 | }, 111 | ...config, 112 | }); 113 | } 114 | 115 | // DATASTORE 116 | 117 | public initializeItemsToCleanup() { 118 | this.itemsToDelete = []; 119 | } 120 | 121 | public addItemToDeleteAfterTest(key: Record) { 122 | this.itemsToDelete.push(key); 123 | } 124 | 125 | public async cleanupItems() { 126 | const ddb = DynamoDBDocumentClient.from(new DynamoDBClient({ region: this.options.region })); 127 | for (const item of this.itemsToDelete) { 128 | try { 129 | await ddb.send(new DeleteCommand({ TableName: this.tableName, Key: item })); 130 | } catch (error) { 131 | console.log(error); 132 | } 133 | } 134 | } 135 | 136 | // AUTH 137 | 138 | public getTokenPayload(email: string): JwtPayload | undefined { 139 | return this.apiTokens[email]?.payload; 140 | } 141 | 142 | public async createUser(email: string, attributes: { [key: string]: string }, groups: string[]) { 143 | if (!this.options.authOptions?.userPoolId) { 144 | throw new Error('No userPoolId configured'); 145 | } 146 | 147 | const randomPassword: string = Math.random().toString(36).slice(-16); 148 | 149 | const cognitoClient: CognitoIdentityProviderClient = new CognitoIdentityProviderClient({ region: this.options.region }); 150 | // Create a new user in Cognito 151 | await cognitoClient.send(new AdminCreateUserCommand({ 152 | UserPoolId: this.options.authOptions?.userPoolId, 153 | Username: email, 154 | MessageAction: MessageActionType.SUPPRESS, 155 | TemporaryPassword: randomPassword, 156 | UserAttributes: [{ 157 | Name: 'email', 158 | Value: email, 159 | }, { 160 | Name: 'email_verified', 161 | Value: 'true', 162 | }, 163 | ...Object.entries(attributes).map((attr) => ({ 164 | Name: attr[0], 165 | Value: attr[1], 166 | }))], 167 | })); 168 | // Make password permanent to get user out of FORCE_CHANGE_PASSWORD 169 | await cognitoClient.send(new AdminSetUserPasswordCommand({ 170 | UserPoolId: this.options.authOptions?.userPoolId, 171 | Username: email, 172 | Password: randomPassword, 173 | Permanent: true, 174 | })); 175 | // Add user to groups 176 | for (const group of groups) { 177 | await cognitoClient.send(new AdminAddUserToGroupCommand({ 178 | UserPoolId: this.options.authOptions?.userPoolId, 179 | Username: email, 180 | GroupName: group, 181 | })); 182 | } 183 | 184 | await this.loginUser(email, randomPassword); 185 | } 186 | 187 | public async removeUser(email: string) { 188 | if (!this.options.authOptions?.userPoolId) { 189 | throw new Error('No userPoolId configured'); 190 | } 191 | const cognitoClient: CognitoIdentityProviderClient = new CognitoIdentityProviderClient({ region: this.options.region }); 192 | await cognitoClient.send(new AdminDeleteUserCommand({ 193 | UserPoolId: this.options.authOptions?.userPoolId, 194 | Username: email, 195 | })); 196 | delete this.apiTokens[email]; 197 | } 198 | 199 | 200 | protected async loginUser(email: string, password: string) { 201 | if (!this.options.authOptions?.userPoolClientId) { 202 | throw new Error('No userPoolClientId configured'); 203 | } 204 | const cognitoClient = new Axios({ 205 | baseURL: `https://cognito-idp.${this.options.region}.amazonaws.com/`, 206 | }); 207 | const auth = await cognitoClient.post('/', JSON.stringify({ 208 | AuthParameters: { 209 | USERNAME: email, 210 | PASSWORD: password, 211 | }, 212 | AuthFlow: 'USER_PASSWORD_AUTH', 213 | ClientId: this.options.authOptions.userPoolClientId, 214 | }), { 215 | headers: { 216 | 'Content-Type': 'application/x-amz-json-1.1', 217 | 'X-Amz-Target': 'AWSCognitoIdentityProviderService.InitiateAuth', 218 | }, 219 | }); 220 | if (auth.status !== HttpStatusCode.Ok) { 221 | throw new Error(`Failed to authenticate user ${email}`); 222 | } 223 | const token = JSON.parse(auth.data).AuthenticationResult.IdToken; 224 | this.apiTokens[email] = { 225 | token, 226 | payload: decode(this.apiTokens[email].token) as JwtPayload, 227 | }; 228 | } 229 | } -------------------------------------------------------------------------------- /src/tests/lambda-test-utils.ts: -------------------------------------------------------------------------------- 1 | import { APIGatewayProxyEventMultiValueQueryStringParameters, APIGatewayProxyEventPathParameters, APIGatewayProxyResult, APIGatewayProxyWithCognitoAuthorizerEvent, Context } from 'aws-lambda'; 2 | import { APIGatewayv1Handler } from '../lambda/handler'; 3 | 4 | 5 | export interface CognitoUser { 6 | email: string; 7 | username: string; 8 | groups: string[]; 9 | } 10 | 11 | export interface LambdaRestTestOptionsBase { 12 | headers?: Record; 13 | cognito?: CognitoUser; 14 | } 15 | 16 | export interface LambdaRestTestOptions extends LambdaRestTestOptionsBase { 17 | path?: string; 18 | method?: string; 19 | body?: string; 20 | pathParameters?: APIGatewayProxyEventPathParameters; 21 | queryStringParameters?: APIGatewayProxyEventMultiValueQueryStringParameters; 22 | } 23 | 24 | export function createContext(): Context { 25 | return { 26 | callbackWaitsForEmptyEventLoop: false, 27 | functionName: 'test', 28 | functionVersion: '1', 29 | invokedFunctionArn: 'arn:aws:lambda:us-east-1:123456789012:function:test', 30 | memoryLimitInMB: '1024', 31 | awsRequestId: 'requestId', 32 | logGroupName: 'logGroupName', 33 | logStreamName: 'logStreamName', 34 | getRemainingTimeInMillis: () => 1000, 35 | done: jest.fn(), 36 | fail: jest.fn(), 37 | succeed: jest.fn(), 38 | identity: undefined, 39 | clientContext: undefined, 40 | }; 41 | } 42 | 43 | export class LambdaRestUnitTest { 44 | 45 | constructor( 46 | private readonly handler: APIGatewayv1Handler, 47 | private readonly defaults?: LambdaRestTestOptionsBase, 48 | ) { } 49 | 50 | private createRestEvent(options: LambdaRestTestOptions = {}): APIGatewayProxyWithCognitoAuthorizerEvent { 51 | const { 52 | path = '/', 53 | method = 'GET', 54 | body = '', 55 | } = options; 56 | 57 | const headers = { 58 | ...this.defaults?.headers, 59 | ...options.headers, 60 | }; 61 | const cognitoClaims = { 62 | username: 'DummyUser', 63 | email: 'dummy@example.com', 64 | groups: [], 65 | ...this.defaults?.cognito, 66 | ...options.cognito, 67 | }; 68 | 69 | return { 70 | body, 71 | headers, 72 | multiValueHeaders: Object.fromEntries(Object.entries(headers).map(([key, value]) => [key, value ? [value] : []])), 73 | httpMethod: method, 74 | isBase64Encoded: false, 75 | path, 76 | pathParameters: options.pathParameters ?? null, 77 | queryStringParameters: options.queryStringParameters ? Object.fromEntries(Object.entries(options.queryStringParameters).map(([key, value]) => [key, value?.join(',')])) : null, 78 | multiValueQueryStringParameters: options.queryStringParameters ?? null, 79 | stageVariables: null, 80 | requestContext: { 81 | accountId: '123456789012', 82 | apiId: 'appId', 83 | authorizer: { 84 | claims: { 85 | 'sub': cognitoClaims.username, 86 | 'cognito:username': cognitoClaims.username, 87 | 'cognito:groups': (cognitoClaims.groups) as unknown as string, 88 | 'email': cognitoClaims.email, 89 | }, 90 | }, 91 | protocol: 'HTTP/1.1', 92 | httpMethod: method, 93 | identity: { 94 | accessKey: null, 95 | accountId: '123456789012', 96 | apiKey: null, 97 | apiKeyId: null, 98 | caller: null, 99 | clientCert: null, 100 | cognitoAuthenticationProvider: null, 101 | cognitoAuthenticationType: null, 102 | cognitoIdentityId: null, 103 | cognitoIdentityPoolId: null, 104 | principalOrgId: null, 105 | sourceIp: '', 106 | user: null, 107 | userAgent: null, 108 | userArn: null, 109 | }, 110 | path, 111 | stage: 'prod', 112 | requestId: 'requestId', 113 | requestTimeEpoch: 0, 114 | resourceId: '', 115 | resourcePath: '', 116 | }, 117 | resource: '', 118 | }; 119 | } 120 | 121 | async call(options: LambdaRestTestOptions = {}): Promise { 122 | const event = this.createRestEvent(options); 123 | const context = createContext(); 124 | const result = await this.handler(event, context, () => { }); 125 | if (!result) { 126 | throw new Error('No result returned from lambda'); 127 | } 128 | return result; 129 | } 130 | } 131 | 132 | 133 | export interface LambdaGraphQLTestOptionsBase { 134 | cognito?: CognitoUser; 135 | stash?: Record; 136 | } 137 | 138 | export interface LambdaGraphQLTestOptions | null> extends LambdaGraphQLTestOptionsBase { 139 | fieldName?: string; 140 | arguments?: TArgs; 141 | source?: TSource; 142 | parentTypeName?: string; 143 | prevResult?: Record; 144 | } 145 | 146 | export class LambdaGraphQLTest | null> { 147 | 148 | constructor( 149 | private readonly handler: AWSLambda.AppSyncResolverHandler, 150 | private readonly defaults?: LambdaGraphQLTestOptionsBase, 151 | ) { } 152 | 153 | private createGraphQLEvent(options: LambdaGraphQLTestOptions): AWSLambda.AppSyncResolverEvent { 154 | const { 155 | fieldName = 'testField', 156 | arguments: args = {}, 157 | source = null, 158 | parentTypeName = 'Query', 159 | prevResult = {}, 160 | stash = {}, 161 | } = options; 162 | 163 | const cognitoClaims = { 164 | username: 'DummyUser', 165 | email: 'dummy@example.com', 166 | groups: [], 167 | ...this.defaults?.cognito, 168 | ...options.cognito, 169 | }; 170 | 171 | return { 172 | arguments: args, 173 | source, 174 | info: { 175 | fieldName, 176 | parentTypeName, 177 | selectionSetGraphQL: '', 178 | selectionSetList: [], 179 | variables: {}, 180 | }, 181 | request: { 182 | domainName: null, 183 | headers: {}, 184 | }, 185 | identity: { 186 | claims: { 187 | 'sub': cognitoClaims.username, 188 | 'cognito:username': cognitoClaims.username, 189 | 'cognito:groups': cognitoClaims.groups as unknown as string, 190 | 'email': cognitoClaims.email, 191 | }, 192 | groups: cognitoClaims.groups, 193 | sub: cognitoClaims.username, 194 | accountId: '123456789012', 195 | cognitoIdentityAuthProvider: null, 196 | cognitoIdentityAuthType: null, 197 | cognitoIdentityId: null, 198 | cognitoIdentityPoolId: null, 199 | resolverContext: null, 200 | userArn: null, 201 | defaultAuthStrategy: 'ALLOW', 202 | issuer: 'https://cognito-idp.region.amazonaws.com/userPoolId', 203 | sourceIp: ['127.0.0.1'], 204 | username: cognitoClaims.username, 205 | }, 206 | prev: { 207 | result: prevResult, 208 | }, 209 | stash: { 210 | ...this.defaults?.stash, 211 | ...stash, 212 | }, 213 | } as AWSLambda.AppSyncResolverEvent; 214 | } 215 | 216 | async call(options: LambdaGraphQLTestOptions): Promise { 217 | const event = this.createGraphQLEvent(options); 218 | const context = createContext(); 219 | const result = await this.handler(event, context, () => { }); 220 | if (!result) { 221 | throw new Error('No result returned from lambda'); 222 | } 223 | return result; 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /test/hello.test.ts: -------------------------------------------------------------------------------- 1 | 2 | test('hello', () => { 3 | // 4 | }); -------------------------------------------------------------------------------- /test/projen/datastore.test.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import { AwsCdkTypeScriptApp } from 'projen/lib/awscdk'; 3 | import { synthSnapshot } from 'projen/lib/util/synth'; 4 | import { Datastore } from '../../src/projen'; 5 | 6 | describe('A Datastore projen component instance', () => { 7 | describe('when created on a project', () => { 8 | test('should synthesize its files in one go with its project from an empty base directory', () => { 9 | const project = new AwsCdkTypeScriptApp({ 10 | name: 'TestProject', 11 | cdkVersion: '2.1.0', 12 | defaultReleaseBranch: 'main', 13 | }); 14 | 15 | new Datastore(project, { 16 | modelName: 'TestModel', 17 | definitionFile: 'testmodel.json', 18 | }); 19 | 20 | const snap = synthSnapshot(project); 21 | 22 | expect(Object.keys(snap)).toContain('src/generated/datastore.testmodel-model.generated.ts'); 23 | expect(Object.keys(snap)).toContain('src/generated/datastore.testmodel-construct.generated.ts'); 24 | }); 25 | 26 | test('should not overwrite an existing Onetable schema definition', () => { 27 | const project = new AwsCdkTypeScriptApp({ 28 | name: 'TestProject', 29 | cdkVersion: '2.1.0', 30 | defaultReleaseBranch: 'main', 31 | }); 32 | 33 | new Datastore(project, { 34 | modelName: 'TestModel', 35 | definitionFile: 'existingmodel.json', 36 | }); 37 | 38 | const existingOneTableSchema = { 39 | format: 'onetable:1.0.0', 40 | version: '0.1.0', 41 | indexes: { 42 | primary: { 43 | hash: 'PK', 44 | sort: 'SK', 45 | }, 46 | }, 47 | models: { 48 | ExistingUser: { 49 | PK: { 50 | type: 'string', 51 | required: true, 52 | value: 'ExistingUser#${name}', 53 | }, 54 | SK: { 55 | type: 'string', 56 | required: true, 57 | value: 'ExistingUser#${name}', 58 | }, 59 | name: { 60 | type: 'string', 61 | required: true, 62 | }, 63 | }, 64 | }, 65 | }; 66 | fs.writeFileSync(`${project.outdir}/existingmodel.json`, JSON.stringify(existingOneTableSchema)); 67 | 68 | const snap = synthSnapshot(project); 69 | 70 | expect(Object.keys(snap)).toContain('existingmodel.json'); 71 | expect(snap['existingmodel.json']).toEqual(existingOneTableSchema); 72 | }); 73 | }); 74 | }); -------------------------------------------------------------------------------- /test/projen/graphql.test.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | import { AwsCdkTypeScriptApp } from 'projen/lib/awscdk'; 3 | import { synthSnapshot } from 'projen/lib/util/synth'; 4 | import { GraphQlApi } from '../../src/projen'; 5 | 6 | describe('A GraphQL projen component instance', () => { 7 | describe('when created on a project', () => { 8 | test('should synthesize its files in one go with its project from an empty base directory', () => { 9 | const project = new AwsCdkTypeScriptApp({ 10 | name: 'TestProject', 11 | cdkVersion: '2.1.0', 12 | defaultReleaseBranch: 'main', 13 | }); 14 | 15 | new GraphQlApi(project, { 16 | apiName: 'TestGraphQL', 17 | definitionFile: 'testapi.graphql', 18 | }); 19 | 20 | const snap = synthSnapshot(project); 21 | 22 | expect(Object.keys(snap)).toContain('testapi.graphql'); 23 | expect(Object.keys(snap)).toContain('graphql-codegen.testgraphql.yml'); 24 | expect(Object.keys(snap)).toContain('src/generated/graphql.testgraphql-api.generated.ts'); 25 | }); 26 | 27 | test('should not overwrite an existing GraphQL schema definition', () => { 28 | const project = new AwsCdkTypeScriptApp({ 29 | name: 'TestProject', 30 | cdkVersion: '2.1.0', 31 | defaultReleaseBranch: 'main', 32 | }); 33 | 34 | new GraphQlApi(project, { 35 | apiName: 'ExistingGraphQl', 36 | definitionFile: 'existingapi.graphql', 37 | }); 38 | 39 | const existingGraphQl = `type Query { 40 | existing: [ExistingUser] 41 | } 42 | 43 | type ExistingUser { 44 | id: ID! 45 | name: String 46 | }`; 47 | fs.writeFileSync(`${project.outdir}/existingapi.graphql`, existingGraphQl); 48 | 49 | const snap = synthSnapshot(project); 50 | 51 | expect(Object.keys(snap)).toContain('existingapi.graphql'); 52 | expect(Object.keys(snap)).toContain('graphql-codegen.existinggraphql.yml'); 53 | expect(Object.keys(snap)).toContain('src/generated/graphql.existinggraphql-api.generated.ts'); 54 | 55 | expect(snap['existingapi.graphql']).toEqual(existingGraphQl); 56 | }); 57 | }); 58 | }); -------------------------------------------------------------------------------- /test/projen/workflow.test.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | import { AwsCdkTypeScriptApp } from 'projen/lib/awscdk'; 3 | import { synthSnapshot } from 'projen/lib/util/synth'; 4 | import { Workflow } from '../../src/projen'; 5 | 6 | describe('A Step Function Workflow projen component instance', () => { 7 | describe('when created on a project', () => { 8 | test('should synthesize its files in one go with its project from an empty base directory', () => { 9 | const project = new AwsCdkTypeScriptApp({ 10 | name: 'TestProject', 11 | cdkVersion: '2.1.0', 12 | defaultReleaseBranch: 'main', 13 | }); 14 | 15 | new Workflow(project, { 16 | workflowName: 'TestWorkflow', 17 | definitionFile: 'testmachine.json', 18 | }); 19 | 20 | const snap = synthSnapshot(project); 21 | 22 | expect(Object.keys(snap)).toContain('testmachine.json'); 23 | expect(Object.keys(snap)).toContain('src/generated/workflow.testworkflow.generated.ts'); 24 | 25 | }); 26 | 27 | test('should not overwrite an existing ASL JSON file', () => { 28 | const project = new AwsCdkTypeScriptApp({ 29 | name: 'TestProject', 30 | cdkVersion: '2.1.0', 31 | defaultReleaseBranch: 'main', 32 | }); 33 | 34 | new Workflow(project, { 35 | workflowName: 'ExistingWorkflow', 36 | definitionFile: 'existingworkflow.json', 37 | }); 38 | 39 | const asl = { 40 | StartAt: 'Already here', 41 | States: { 42 | 'Already here': { 43 | Type: 'Pass', 44 | Result: { 45 | Exists: true, 46 | Value: '${someVariable}', 47 | }, 48 | End: true, 49 | }, 50 | }, 51 | }; 52 | 53 | fs.writeFileSync(`${project.outdir}/existingworkflow.json`, JSON.stringify(asl)); 54 | 55 | const snap = synthSnapshot(project); 56 | 57 | expect(Object.keys(snap)).toContain('existingworkflow.json'); 58 | expect(Object.keys(snap)).toContain('src/generated/workflow.existingworkflow.generated.ts'); 59 | expect(snap['existingworkflow.json']).toEqual(asl); 60 | }); 61 | }); 62 | }); -------------------------------------------------------------------------------- /test/util/synth.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | import * as path from 'node:path'; 3 | import * as yaml from 'js-yaml'; 4 | import { Project } from 'projen'; 5 | 6 | interface OpenApiDefinitionSnapshot { 7 | definitionFile: string; 8 | content: string; 9 | } 10 | 11 | export function createOpenApiDefinitionFile(project: Project, obj: any, filename?: string): OpenApiDefinitionSnapshot { 12 | const spec = yaml.dump(obj); 13 | const outputFileName = filename ?? 'openapi.yaml'; 14 | fs.writeFileSync(path.join(project.outdir, outputFileName), spec); 15 | return { definitionFile: outputFileName, content: spec }; 16 | } -------------------------------------------------------------------------------- /tsconfig.dev.json: -------------------------------------------------------------------------------- 1 | // ~~ Generated by projen. To modify, edit .projenrc.js and run "npx projen". 2 | { 3 | "compilerOptions": { 4 | "alwaysStrict": true, 5 | "declaration": true, 6 | "esModuleInterop": true, 7 | "experimentalDecorators": true, 8 | "inlineSourceMap": true, 9 | "inlineSources": true, 10 | "lib": [ 11 | "es2019", 12 | "dom" 13 | ], 14 | "module": "CommonJS", 15 | "noEmitOnError": false, 16 | "noFallthroughCasesInSwitch": true, 17 | "noImplicitAny": true, 18 | "noImplicitReturns": true, 19 | "noImplicitThis": true, 20 | "noUnusedLocals": true, 21 | "noUnusedParameters": true, 22 | "resolveJsonModule": true, 23 | "strict": true, 24 | "strictNullChecks": true, 25 | "strictPropertyInitialization": true, 26 | "stripInternal": true, 27 | "target": "ES2019", 28 | "skipLibCheck": true 29 | }, 30 | "include": [ 31 | "src/**/*.ts", 32 | "test/**/*.ts", 33 | ".projenrc.js" 34 | ], 35 | "exclude": [ 36 | "node_modules" 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | // ~~ Generated by projen. To modify, edit .projenrc.js and run "npx projen". 2 | { 3 | "compilerOptions": { 4 | "rootDir": "src", 5 | "outDir": "lib", 6 | "alwaysStrict": true, 7 | "declaration": true, 8 | "esModuleInterop": true, 9 | "experimentalDecorators": true, 10 | "inlineSourceMap": true, 11 | "inlineSources": true, 12 | "lib": [ 13 | "es2019", 14 | "dom" 15 | ], 16 | "module": "CommonJS", 17 | "noEmitOnError": false, 18 | "noFallthroughCasesInSwitch": true, 19 | "noImplicitAny": true, 20 | "noImplicitReturns": true, 21 | "noImplicitThis": true, 22 | "noUnusedLocals": true, 23 | "noUnusedParameters": true, 24 | "resolveJsonModule": true, 25 | "strict": true, 26 | "strictNullChecks": true, 27 | "strictPropertyInitialization": true, 28 | "stripInternal": true, 29 | "target": "ES2019", 30 | "skipLibCheck": true 31 | }, 32 | "include": [ 33 | "src/**/*.ts" 34 | ], 35 | "exclude": [] 36 | } 37 | --------------------------------------------------------------------------------