├── .gitignore ├── .prettierrc ├── .vscode └── settings.json ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── appsync-logo-600.png ├── examples ├── cdk │ ├── aoss-search-app │ │ ├── .eslintignore │ │ ├── .eslintrc.json │ │ ├── .gitignore │ │ ├── .npmignore │ │ ├── README.md │ │ ├── bin │ │ │ └── aoss-search-app.ts │ │ ├── cdk.json │ │ ├── jest.config.js │ │ ├── lib │ │ │ ├── aoss-search-app-stack.ts │ │ │ └── appsync │ │ │ │ ├── .graphqlconfig.yml │ │ │ │ ├── codegen │ │ │ │ └── index.ts │ │ │ │ ├── resolvers │ │ │ │ ├── Mutation.createIndex.[aoss].ts │ │ │ │ ├── Mutation.indexTodo.[aoss].ts │ │ │ │ ├── Query.search.[aoss].ts │ │ │ │ └── utils.ts │ │ │ │ └── schema.graphql │ │ ├── package-lock.json │ │ ├── package.json │ │ ├── test │ │ │ └── aos-ddb-search-app.test.ts │ │ └── tsconfig.json │ ├── constructs │ │ └── appsync-helper │ │ │ ├── .gitignore │ │ │ ├── .npmignore │ │ │ ├── README.md │ │ │ ├── jest.config.js │ │ │ ├── lib │ │ │ └── index.ts │ │ │ ├── package-lock.json │ │ │ ├── package.json │ │ │ ├── test │ │ │ └── appsync-helper.test.ts │ │ │ └── tsconfig.json │ ├── dynamodb-todo-app │ │ ├── .eslintignore │ │ ├── .eslintrc.json │ │ ├── .gitignore │ │ ├── .npmignore │ │ ├── README.md │ │ ├── bin │ │ │ └── dynamodb-todo-app.ts │ │ ├── cdk.json │ │ ├── jest.config.js │ │ ├── lib │ │ │ ├── appsync │ │ │ │ ├── .graphqlconfig.yml │ │ │ │ ├── codegen │ │ │ │ │ └── index.ts │ │ │ │ ├── resolvers │ │ │ │ │ ├── Mutation.createTodo.[todos].ts │ │ │ │ │ ├── Mutation.deleteTodo.[todos].ts │ │ │ │ │ ├── Mutation.updateTodo.[todos].ts │ │ │ │ │ ├── Query.getTodo.[todos].ts │ │ │ │ │ ├── Query.listTodos.[todos].ts │ │ │ │ │ └── utils.ts │ │ │ │ └── schema.graphql │ │ │ └── dynamodb-todo-app-stack.ts │ │ ├── package-lock.json │ │ ├── package.json │ │ ├── test │ │ │ └── dynamodb-todo-app.test.ts │ │ └── tsconfig.json │ └── pub-sub-app │ │ ├── .eslintignore │ │ ├── .eslintrc.json │ │ ├── .gitignore │ │ ├── .npmignore │ │ ├── README.md │ │ ├── bin │ │ └── pub-sub-app.ts │ │ ├── cdk.json │ │ ├── jest.config.js │ │ ├── lib │ │ ├── appsync │ │ │ ├── .graphqlconfig.yml │ │ │ ├── codegen │ │ │ │ └── index.ts │ │ │ ├── resolvers │ │ │ │ ├── Mutation.publish.[NONE].ts │ │ │ │ ├── Query.whoami.[NONE].ts │ │ │ │ └── Subscription.onPublish.[NONE].ts │ │ │ └── schema.graphql │ │ └── pub-sub-app-stack.ts │ │ ├── package-lock.json │ │ ├── package.json │ │ ├── test │ │ └── pub-sub-app.test.ts │ │ └── tsconfig.json ├── cloudformation │ ├── todo-api-cfn │ │ ├── README.md │ │ ├── resolvers │ │ │ ├── createTodo.js │ │ │ ├── deleteTodo.js │ │ │ ├── getTodo.js │ │ │ ├── listTodos.js │ │ │ ├── queryTodosByOwner.js │ │ │ └── updateTodo.js │ │ ├── schema.graphql │ │ └── template.yaml │ └── todo-api-pipeline-cfn │ │ ├── README.md │ │ ├── functions │ │ ├── createItem.js │ │ ├── deleteItem.js │ │ ├── getItem.js │ │ ├── listItems.js │ │ ├── out.js │ │ ├── queryItems.js │ │ ├── scratch.js │ │ └── udpateItem.js │ │ ├── resolvers │ │ └── default.js │ │ ├── schema.graphql │ │ └── template.yaml ├── serverless │ └── lambda-http-ddb-datasources │ │ ├── README.md │ │ ├── lambda │ │ └── index.js │ │ ├── package.json │ │ ├── resolvers │ │ ├── ddb │ │ │ ├── addTodo.js │ │ │ ├── getTodo.js │ │ │ └── listTodos.js │ │ ├── http │ │ │ ├── getUser.js │ │ │ └── listUsers.js │ │ └── lambda │ │ │ ├── getPost.js │ │ │ └── listPosts.js │ │ ├── schema.graphql │ │ └── serverless.yml └── terraform │ ├── .terraform.lock.hcl │ ├── main.tf │ ├── provider.tf │ ├── resolvers │ ├── getTodo.js │ └── listTodos.js │ └── schema.graphql ├── package-lock.json ├── package.json ├── samples ├── NONE │ ├── enhancedSubscription.js │ └── localPublish.js ├── dynamodb │ ├── batch │ │ ├── batchDeleteItems.js │ │ ├── batchGetItems.js │ │ └── batchPutItems.js │ ├── general │ │ ├── deleteItem.js │ │ ├── getItem.js │ │ ├── listItems.js │ │ ├── putItem.js │ │ ├── updateIncrementCount.js │ │ └── updateItem.js │ └── queries │ │ ├── all-items-today.js │ │ ├── pagination.js │ │ ├── simple-query.js │ │ ├── with-contains-expression.js │ │ ├── with-filter-on-index.js │ │ └── with-greater-than.js ├── eventbridge │ └── simple.js ├── http │ ├── forward.js │ ├── getToApiGW.js │ ├── publishToSNS.js │ ├── putToApiGW.js │ └── translate.js ├── lambda │ └── invoke.js ├── opensearch │ ├── geo.js │ ├── getDocumentByID.js │ ├── paginate.js │ └── simpleTermQuery.js ├── package-lock.json ├── package.json ├── pipeline │ └── default.js └── rds │ ├── README.md │ └── queries │ ├── invoice.js │ ├── invoices.js │ ├── playlist_count.js │ ├── sales.js │ ├── select.js │ ├── sql.js │ └── subquery.js └── scripts ├── evaluate.sh └── evaluate ├── index.mjs ├── package-lock.json └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.toptal.com/developers/gitignore/api/node 2 | # Edit at https://www.toptal.com/developers/gitignore?templates=node 3 | 4 | ### Node ### 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | yarn-debug.log* 10 | yarn-error.log* 11 | lerna-debug.log* 12 | .pnpm-debug.log* 13 | 14 | # Diagnostic reports (https://nodejs.org/api/report.html) 15 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 16 | 17 | # Runtime data 18 | pids 19 | *.pid 20 | *.seed 21 | *.pid.lock 22 | 23 | # Directory for instrumented libs generated by jscoverage/JSCover 24 | lib-cov 25 | 26 | # Coverage directory used by tools like istanbul 27 | coverage 28 | *.lcov 29 | 30 | # nyc test coverage 31 | .nyc_output 32 | 33 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 34 | .grunt 35 | 36 | # Bower dependency directory (https://bower.io/) 37 | bower_components 38 | 39 | # node-waf configuration 40 | .lock-wscript 41 | 42 | # Compiled binary addons (https://nodejs.org/api/addons.html) 43 | build/Release 44 | 45 | # Dependency directories 46 | node_modules/ 47 | jspm_packages/ 48 | 49 | # Snowpack dependency directory (https://snowpack.dev/) 50 | web_modules/ 51 | 52 | # TypeScript cache 53 | *.tsbuildinfo 54 | 55 | # Optional npm cache directory 56 | .npm 57 | 58 | # Optional eslint cache 59 | .eslintcache 60 | 61 | # Optional stylelint cache 62 | .stylelintcache 63 | 64 | # Microbundle cache 65 | .rpt2_cache/ 66 | .rts2_cache_cjs/ 67 | .rts2_cache_es/ 68 | .rts2_cache_umd/ 69 | 70 | # Optional REPL history 71 | .node_repl_history 72 | 73 | # Output of 'npm pack' 74 | *.tgz 75 | 76 | # Yarn Integrity file 77 | .yarn-integrity 78 | 79 | # dotenv environment variable files 80 | .env 81 | .env.development.local 82 | .env.test.local 83 | .env.production.local 84 | .env.local 85 | 86 | # parcel-bundler cache (https://parceljs.org/) 87 | .cache 88 | .parcel-cache 89 | 90 | # Next.js build output 91 | .next 92 | out 93 | 94 | # Nuxt.js build / generate output 95 | .nuxt 96 | dist 97 | 98 | # Gatsby files 99 | .cache/ 100 | # Comment in the public line in if your project uses Gatsby and not Next.js 101 | # https://nextjs.org/blog/next-9-1#public-directory-support 102 | # public 103 | 104 | # vuepress build output 105 | .vuepress/dist 106 | 107 | # vuepress v2.x temp and cache directory 108 | .temp 109 | 110 | # Docusaurus cache and generated files 111 | .docusaurus 112 | 113 | # Serverless directories 114 | .serverless/ 115 | 116 | # FuseBox cache 117 | .fusebox/ 118 | 119 | # DynamoDB Local files 120 | .dynamodb/ 121 | 122 | # TernJS port file 123 | .tern-port 124 | 125 | # Stores VSCode versions used for testing VSCode extensions 126 | .vscode-test 127 | 128 | # yarn v2 129 | .yarn/cache 130 | .yarn/unplugged 131 | .yarn/build-state.yml 132 | .yarn/install-state.gz 133 | .pnp.* 134 | 135 | ### Node Patch ### 136 | # Serverless Webpack directories 137 | .webpack/ 138 | 139 | # Optional stylelint cache 140 | 141 | # SvelteKit build / generate output 142 | .svelte-kit 143 | 144 | # End of https://www.toptal.com/developers/gitignore/api/node 145 | .DS_Store 146 | scripts/response.json 147 | 148 | # CDK asset staging directory 149 | .cdk.staging 150 | cdk.out 151 | 152 | ## Terraform 153 | 154 | # Local .terraform directories 155 | **/.terraform/* 156 | 157 | # .tfstate files 158 | *.tfstate 159 | *.tfstate.* 160 | 161 | # Crash log files 162 | crash.log 163 | crash.*.log 164 | 165 | # Exclude all .tfvars files, which are likely to contain sensitive data, such as 166 | # password, private keys, and other secrets. These should not be part of version 167 | # control as they are data points which are potentially sensitive and subject 168 | # to change depending on the environment. 169 | *.tfvars 170 | *.tfvars.json 171 | 172 | # Ignore override files as they are usually used to override resources locally and so 173 | # are not checked in 174 | override.tf 175 | override.tf.json 176 | *_override.tf 177 | *_override.tf.json 178 | 179 | # Ignore transient lock info files created by terraform apply 180 | .terraform.tfstate.lock.info 181 | 182 | # Include override files you do wish to add to version control using negated pattern 183 | # !example_override.tf 184 | 185 | # Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan 186 | # example: *tfplan* 187 | 188 | # Ignore CLI configuration files 189 | .terraformrc 190 | terraform.rc -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "tabWidth": 2, 4 | "semi": true, 5 | "useTabs": true, 6 | "singleQuote": true, 7 | "printWidth": 100 8 | } 9 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "eslint.workingDirectories": [ 3 | { 4 | "mode": "auto" 5 | } 6 | ], 7 | "cSpell.words": ["aoss", "codegen", "todos"] 8 | } 9 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | ## Reporting Bugs/Feature Requests 10 | 11 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 12 | 13 | When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already 14 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 15 | 16 | * A reproducible test case or series of steps 17 | * The version of our code being used 18 | * Any modifications you've made relevant to the bug 19 | * Anything unusual about your environment or deployment 20 | 21 | ## Contributing via Pull Requests 22 | 23 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 24 | 25 | 1. You are working against the latest source on the *main* branch. 26 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 27 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 28 | 29 | To send us a pull request, please: 30 | 31 | 1. Fork the repository. 32 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 33 | 3. Ensure local tests pass. 34 | 4. Commit to your fork using clear commit messages. 35 | 5. Send us a pull request, answering any default questions in the pull request interface. 36 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 37 | 38 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 39 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 40 | 41 | ## Finding contributions to work on 42 | 43 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start. 44 | 45 | ## Code of Conduct 46 | 47 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 48 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 49 | opensource-codeofconduct@amazon.com with any additional questions or comments. 50 | 51 | ## Security issue notifications 52 | 53 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 54 | 55 | ## Licensing 56 | 57 | See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 58 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 10 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 11 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 12 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 13 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 14 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 15 | 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
AWS AppSync GraphQL Samples
5 |
6 |
116 | ```
117 |
118 | In the tab where you sent the mutation, log in as the `tester` user. Then send this message to yourself:
119 |
120 | ```graphql
121 | mutation Pub {
122 | publish(input: {kind: DIRECT, text: "hello", to: "tester"}) {
123 | createdAt
124 | from
125 | id
126 | kind
127 | text
128 | to
129 | }
130 | }
131 | ```
132 |
133 | In the subscription tab, you do not see the message. The subscription uses enhanced filtering and only allows anonymous users (those using the API KEY) to receive message sent to ALL. Stop your subscription and change authorization mode to Cognito User Pools. Sign in if needed, and restart the subscription. Send yourself another message, which you then receive.
134 |
135 | Try this again by creating other users and sending DIRECT or ALL messages to the various users so see that DIRECT messages are only received by the users they are addressed to.
136 |
137 | ## Connect to an app
138 |
139 | You can use the info in [./output.json](./output.json) to configure your Amplify client in your app. See [Building a client application](https://docs.aws.amazon.com/appsync/latest/devguide/building-a-client-app.html).
140 |
141 | ## Destroy the stack
142 |
143 | To destroy the stack and all the created resources:
144 |
145 | ```sh
146 | npm run cdk destroy
147 | ```
148 |
--------------------------------------------------------------------------------
/examples/cdk/pub-sub-app/bin/pub-sub-app.ts:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | import 'source-map-support/register';
3 | import * as cdk from 'aws-cdk-lib';
4 | import { PubSubAppStack } from '../lib/pub-sub-app-stack';
5 |
6 | const app = new cdk.App();
7 | new PubSubAppStack(app, 'PubSubAppStack', {
8 | /* If you don't specify 'env', this stack will be environment-agnostic.
9 | * Account/Region-dependent features and context lookups will not work,
10 | * but a single synthesized template can be deployed anywhere. */
11 |
12 | /* Uncomment the next line to specialize this stack for the AWS Account
13 | * and Region that are implied by the current CLI configuration. */
14 | // env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION },
15 |
16 | /* Uncomment the next line if you know exactly what Account and Region you
17 | * want to deploy the stack to. */
18 | // env: { account: '123456789012', region: 'us-east-1' },
19 |
20 | /* For more information, see https://docs.aws.amazon.com/cdk/latest/guide/environments.html */
21 | });
--------------------------------------------------------------------------------
/examples/cdk/pub-sub-app/cdk.json:
--------------------------------------------------------------------------------
1 | {
2 | "app": "npx ts-node --prefer-ts-exts bin/pub-sub-app.ts",
3 | "watch": {
4 | "include": [
5 | "**"
6 | ],
7 | "exclude": [
8 | "README.md",
9 | "cdk*.json",
10 | "**/*.d.ts",
11 | "**/*.js",
12 | "tsconfig.json",
13 | "package*.json",
14 | "yarn.lock",
15 | "node_modules",
16 | "test"
17 | ]
18 | },
19 | "context": {
20 | "@aws-cdk/aws-lambda:recognizeLayerVersion": true,
21 | "@aws-cdk/core:checkSecretUsage": true,
22 | "@aws-cdk/core:target-partitions": [
23 | "aws",
24 | "aws-cn"
25 | ],
26 | "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true,
27 | "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true,
28 | "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true,
29 | "@aws-cdk/aws-iam:minimizePolicies": true,
30 | "@aws-cdk/core:validateSnapshotRemovalPolicy": true,
31 | "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true,
32 | "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true,
33 | "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true,
34 | "@aws-cdk/aws-apigateway:disableCloudWatchRole": true,
35 | "@aws-cdk/core:enablePartitionLiterals": true,
36 | "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true,
37 | "@aws-cdk/aws-iam:standardizedServicePrincipals": true,
38 | "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true,
39 | "@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true,
40 | "@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true,
41 | "@aws-cdk/aws-route53-patters:useCertificate": true,
42 | "@aws-cdk/customresources:installLatestAwsSdkDefault": false,
43 | "@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true,
44 | "@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true,
45 | "@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true,
46 | "@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true,
47 | "@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true,
48 | "@aws-cdk/aws-redshift:columnId": true,
49 | "@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true,
50 | "@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true,
51 | "@aws-cdk/aws-apigateway:requestValidatorUniqueId": true,
52 | "@aws-cdk/aws-kms:aliasNameRef": true,
53 | "@aws-cdk/core:includePrefixInUniqueNameGeneration": true
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/examples/cdk/pub-sub-app/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | testEnvironment: 'node',
3 | roots: ['/test'],
4 | testMatch: ['**/*.test.ts'],
5 | transform: {
6 | '^.+\\.tsx?$': 'ts-jest'
7 | }
8 | };
9 |
--------------------------------------------------------------------------------
/examples/cdk/pub-sub-app/lib/appsync/.graphqlconfig.yml:
--------------------------------------------------------------------------------
1 | projects:
2 | Codegen Project:
3 | schemaPath: schema.graphql
4 | includes:
5 | - codegen/graphql/**/*.ts
6 | excludes:
7 | - ./amplify/**
8 | extensions:
9 | amplify:
10 | codeGenTarget: typescript
11 | generatedFileName: codegen/API.ts
12 | docsFilePath: codegen/graphql
13 | region: us-east-1
14 | apiId: null
15 | frontend: javascript
16 | framework: none
17 | maxDepth: 2
18 | extensions:
19 | amplify:
20 | version: 3
21 |
--------------------------------------------------------------------------------
/examples/cdk/pub-sub-app/lib/appsync/codegen/index.ts:
--------------------------------------------------------------------------------
1 | export * from './API';
2 | export type Result = Omit;
3 |
--------------------------------------------------------------------------------
/examples/cdk/pub-sub-app/lib/appsync/resolvers/Mutation.publish.[NONE].ts:
--------------------------------------------------------------------------------
1 | import { util, Context, NONERequest, AppSyncIdentityCognito } from '@aws-appsync/utils';
2 | import { MSG_KIND, Message, PublishMutationVariables, Result } from '../codegen';
3 |
4 | export function request(ctx: Context): NONERequest {
5 | const { text, kind, to } = ctx.args.input;
6 | let from = 'anonymous';
7 |
8 | if (util.authType() === 'User Pool Authorization') {
9 | from = (ctx.identity as AppSyncIdentityCognito).username;
10 | } else {
11 | if (kind === MSG_KIND.DIRECT) {
12 | util.error('Anonymous user cannot send direct messages', 'ApplicationError');
13 | }
14 | }
15 |
16 | if (kind === MSG_KIND.ALL && to) {
17 | util.error(`Cannot specify 'to' in an 'ALL' message`, 'ApplicationError');
18 | }
19 | const msg: Result = {
20 | id: util.autoId(),
21 | createdAt: util.time.nowISO8601(),
22 | kind,
23 | text,
24 | from,
25 | to,
26 | };
27 |
28 | return { payload: msg };
29 | }
30 |
31 | export const response = (ctx: Context) => ctx.result;
32 |
--------------------------------------------------------------------------------
/examples/cdk/pub-sub-app/lib/appsync/resolvers/Query.whoami.[NONE].ts:
--------------------------------------------------------------------------------
1 | import { util, Context, NONERequest, AppSyncIdentityCognito } from '@aws-appsync/utils';
2 |
3 | export function request(ctx: Context): NONERequest {
4 | let me = 'anonymous';
5 |
6 | if (util.authType() === 'User Pool Authorization') {
7 | me = (ctx.identity as AppSyncIdentityCognito).username;
8 | }
9 |
10 | return { payload: me };
11 | }
12 |
13 | export const response = (ctx: Context) => ctx.result;
14 |
--------------------------------------------------------------------------------
/examples/cdk/pub-sub-app/lib/appsync/resolvers/Subscription.onPublish.[NONE].ts:
--------------------------------------------------------------------------------
1 | import { util, Context, extensions, AppSyncIdentityCognito, NONERequest } from '@aws-appsync/utils';
2 | import { MSG_KIND, Message } from '../codegen';
3 |
4 | export function request(ctx: Context): NONERequest {
5 | if (util.authType() === 'User Pool Authorization') {
6 | const identity = ctx.identity as AppSyncIdentityCognito;
7 | const filter = util.transform.toSubscriptionFilter({
8 | or: [{ to: { eq: identity.username } }, { kind: { eq: MSG_KIND.ALL } }],
9 | });
10 | extensions.setSubscriptionFilter(filter);
11 | } else {
12 | const filter = util.transform.toSubscriptionFilter({
13 | kind: { eq: MSG_KIND.ALL },
14 | });
15 | extensions.setSubscriptionFilter(filter);
16 | }
17 | return { payload: null };
18 | }
19 |
20 | export const response = (ctx: Context) => ctx.result;
21 |
--------------------------------------------------------------------------------
/examples/cdk/pub-sub-app/lib/appsync/schema.graphql:
--------------------------------------------------------------------------------
1 | type Message @aws_api_key @aws_cognito_user_pools {
2 | id: ID!
3 | text: String!
4 | from: String!
5 | kind: MSG_KIND!
6 | to: String
7 | createdAt: AWSDateTime
8 | }
9 |
10 | enum MSG_KIND {
11 | ALL
12 | DIRECT
13 | }
14 |
15 | type Mutation @aws_api_key @aws_cognito_user_pools {
16 | publish(input: PublishMessageInput!): Message
17 | }
18 |
19 | type Query @aws_api_key @aws_cognito_user_pools {
20 | whoami: String
21 | }
22 |
23 | type Subscription @aws_api_key @aws_cognito_user_pools {
24 | onPublish: Message @aws_subscribe(mutations: ["publish"])
25 | }
26 |
27 | input PublishMessageInput {
28 | text: String!
29 | kind: MSG_KIND!
30 | to: String
31 | }
32 |
--------------------------------------------------------------------------------
/examples/cdk/pub-sub-app/lib/pub-sub-app-stack.ts:
--------------------------------------------------------------------------------
1 | import * as cdk from 'aws-cdk-lib';
2 | import * as appsync from 'aws-cdk-lib/aws-appsync';
3 | import * as cognito from 'aws-cdk-lib/aws-cognito';
4 | import { Construct } from 'constructs';
5 | import path = require('path');
6 | import { AppSyncHelper } from 'appsync-helper';
7 |
8 | export class PubSubAppStack extends cdk.Stack {
9 | constructor(scope: Construct, id: string, props?: cdk.StackProps) {
10 | super(scope, id, props);
11 |
12 | const userPool = new cognito.UserPool(this, 'UserPool', {
13 | userPoolName: 'PubSubAppUserPool',
14 | selfSignUpEnabled: true,
15 | autoVerify: { email: true },
16 | standardAttributes: { email: { required: true } },
17 | removalPolicy: cdk.RemovalPolicy.DESTROY,
18 | });
19 |
20 | const client = userPool.addClient('customer-app-client-web', {
21 | preventUserExistenceErrors: true,
22 | authFlows: { userPassword: true, userSrp: true },
23 | });
24 |
25 | const appSyncApp = new AppSyncHelper(this, 'LocalMessageAPI', {
26 | basedir: path.join(__dirname, 'appsync'),
27 | authorizationConfig: {
28 | defaultAuthorization: {
29 | authorizationType: appsync.AuthorizationType.API_KEY,
30 | apiKeyConfig: { name: 'default', description: 'default auth mode' },
31 | },
32 | additionalAuthorizationModes: [
33 | {
34 | authorizationType: appsync.AuthorizationType.USER_POOL,
35 | userPoolConfig: { userPool },
36 | },
37 | ],
38 | },
39 | logConfig: {
40 | fieldLogLevel: appsync.FieldLogLevel.ALL,
41 | excludeVerboseContent: false,
42 | retention: cdk.aws_logs.RetentionDays.ONE_WEEK,
43 | },
44 | xrayEnabled: true,
45 | });
46 | appSyncApp.addNoneDataSource('NONE');
47 | appSyncApp.bind();
48 |
49 | new cdk.CfnOutput(this, 'GRAPHQLENDPOINT', { value: appSyncApp.api.graphqlUrl });
50 | new cdk.CfnOutput(this, 'REGION', { value: cdk.Stack.of(this).region });
51 | new cdk.CfnOutput(this, 'AUTHTYPE', { value: 'API_KEY' });
52 | new cdk.CfnOutput(this, 'APIKEY', { value: appSyncApp.api.apiKey! });
53 | new cdk.CfnOutput(this, 'USERPOOLSID', { value: userPool.userPoolId });
54 | new cdk.CfnOutput(this, 'USERPOOLSWEBCLIENTID', { value: client.userPoolClientId });
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/examples/cdk/pub-sub-app/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "pub-sub-app",
3 | "version": "0.1.0",
4 | "bin": {
5 | "pub-sub-app": "bin/pub-sub-app.js"
6 | },
7 | "scripts": {
8 | "build": "tsc",
9 | "watch": "tsc -w",
10 | "test": "jest",
11 | "cdk": "cdk",
12 | "prebuild": "$npm_execpath run codegen",
13 | "codegen": "cd lib/appsync && npx --package=@aws-amplify/cli amplify codegen"
14 | },
15 | "devDependencies": {
16 | "@aws-appsync/eslint-plugin": "^1.2.6",
17 | "@aws-appsync/utils": "1.2.6",
18 | "@graphql-tools/schema": "^10.0.0",
19 | "@types/jest": "^29.5.1",
20 | "@types/node": "20.1.7",
21 | "@typescript-eslint/eslint-plugin": "^6.3.0",
22 | "@typescript-eslint/parser": "^6.3.0",
23 | "appsync-helper": "file:../constructs/appsync-helper",
24 | "aws-cdk": "2.87.0",
25 | "esbuild": "^0.18.17",
26 | "eslint": "^8.46.0",
27 | "graphql": "^16.7.1",
28 | "jest": "^29.5.0",
29 | "ts-jest": "^29.1.0",
30 | "ts-node": "^10.9.1",
31 | "typescript": "~5.1.3"
32 | },
33 | "dependencies": {
34 | "aws-cdk-lib": "2.87.0",
35 | "constructs": "^10.0.0",
36 | "source-map-support": "^0.5.21"
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/examples/cdk/pub-sub-app/test/pub-sub-app.test.ts:
--------------------------------------------------------------------------------
1 | // import * as cdk from 'aws-cdk-lib';
2 | // import { Template } from 'aws-cdk-lib/assertions';
3 | // import * as PubSubApp from '../lib/pub-sub-app-stack';
4 |
5 | // example test. To run these tests, uncomment this file along with the
6 | // example resource in lib/pub-sub-app-stack.ts
7 | test('SQS Queue Created', () => {
8 | // const app = new cdk.App();
9 | // // WHEN
10 | // const stack = new PubSubApp.PubSubAppStack(app, 'MyTestStack');
11 | // // THEN
12 | // const template = Template.fromStack(stack);
13 |
14 | // template.hasResourceProperties('AWS::SQS::Queue', {
15 | // VisibilityTimeout: 300
16 | // });
17 | });
18 |
--------------------------------------------------------------------------------
/examples/cdk/pub-sub-app/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2020",
4 | "module": "commonjs",
5 | "lib": ["es2020", "dom"],
6 | "declaration": true,
7 | "strict": true,
8 | "noImplicitAny": true,
9 | "strictNullChecks": true,
10 | "noImplicitThis": true,
11 | "alwaysStrict": true,
12 | "noUnusedLocals": false,
13 | "noUnusedParameters": false,
14 | "noImplicitReturns": true,
15 | "noFallthroughCasesInSwitch": false,
16 | "inlineSourceMap": true,
17 | "inlineSources": true,
18 | "experimentalDecorators": true,
19 | "strictPropertyInitialization": false,
20 | "typeRoots": ["./node_modules/@types"]
21 | },
22 | "exclude": ["node_modules", "cdk.out"]
23 | }
24 |
--------------------------------------------------------------------------------
/examples/cloudformation/todo-api-cfn/README.md:
--------------------------------------------------------------------------------
1 | # Todo API
2 |
3 | This folder provides the implementation for a Todo API implemented with AppSync JavaScript resolvers.
4 | The API allows you to interact with a Todo type:
5 |
6 | ```graphql
7 | type Todo {
8 | id: ID!
9 | title: String
10 | description: String
11 | owner: String
12 | }
13 | ```
14 |
15 | The [resolvers](./resolvers/) folder contains the code for the resolvers.
16 |
17 | ## Deploy the stack
18 |
19 | Deploy this stack by using [template.yaml](./template.yaml) from the Cloudformation console or using the AWS CLI.
20 |
21 | With the AWS CLI, from this folder:
22 |
23 | ```sh
24 | aws cloudformation deploy --template-file ./template.yaml --stack-name simple-todo-api-app --capabilities CAPABILITY_IAM
25 | ```
26 |
27 | Once deployed, you can find your API **SimpleTodoAPI** in the AWS console.
28 |
29 | ## Delete the stack
30 |
31 | To delete your resources: visit the Cloudformation console and delete the stack.
32 |
33 | With the AWS CLI:
34 |
35 | ```sh
36 | aws cloudformation delete-stack --stack-name simple-todo-api-app
37 | ```
38 |
39 | Note: The DynamoDB table deployed by this template is retained when the stack is deleted.
40 |
--------------------------------------------------------------------------------
/examples/cloudformation/todo-api-cfn/resolvers/createTodo.js:
--------------------------------------------------------------------------------
1 | import { util } from '@aws-appsync/utils';
2 |
3 | export function request(ctx) {
4 | const { input: values } = ctx.arguments;
5 | const key = { id: util.autoId() };
6 | const condition = { id: { attributeExists: false } };
7 | console.log('--> create todo with requested values: ', values);
8 | return dynamodbPutRequest({ key, values, condition });
9 | }
10 |
11 | export function response(ctx) {
12 | const { error, result } = ctx;
13 | if (error) {
14 | return util.appendError(error.message, error.type, result);
15 | }
16 | return ctx.result;
17 | }
18 |
19 | /**
20 | * Helper function to create a new item
21 | * @returns a PutItem request
22 | */
23 | function dynamodbPutRequest({ key, values, condition: inCondObj }) {
24 | const condition = JSON.parse(util.transform.toDynamoDBConditionExpression(inCondObj));
25 | if (condition.expressionValues && !Object.keys(condition.expressionValues).length) {
26 | delete condition.expressionValues;
27 | }
28 | return {
29 | operation: 'PutItem',
30 | key: util.dynamodb.toMapValues(key),
31 | attributeValues: util.dynamodb.toMapValues(values),
32 | condition,
33 | };
34 | }
35 |
--------------------------------------------------------------------------------
/examples/cloudformation/todo-api-cfn/resolvers/deleteTodo.js:
--------------------------------------------------------------------------------
1 | import { util } from '@aws-appsync/utils';
2 |
3 | export function request(ctx) {
4 | return {
5 | operation: 'DeleteItem',
6 | key: util.dynamodb.toMapValues({ id: ctx.args.id }),
7 | };
8 | }
9 |
10 | export function response(ctx) {
11 | const { error, result } = ctx;
12 | if (error) {
13 | return util.appendError(error.message, error.type, result);
14 | }
15 | return ctx.result;
16 | }
17 |
--------------------------------------------------------------------------------
/examples/cloudformation/todo-api-cfn/resolvers/getTodo.js:
--------------------------------------------------------------------------------
1 | import { util } from '@aws-appsync/utils';
2 |
3 | export function request(ctx) {
4 | return {
5 | operation: 'GetItem',
6 | key: util.dynamodb.toMapValues({ id: ctx.args.id }),
7 | };
8 | }
9 |
10 | export function response(ctx) {
11 | const { error, result } = ctx;
12 | if (error) {
13 | return util.appendError(error.message, error.type, result);
14 | }
15 | return ctx.result;
16 | }
17 |
--------------------------------------------------------------------------------
/examples/cloudformation/todo-api-cfn/resolvers/listTodos.js:
--------------------------------------------------------------------------------
1 | import { util } from '@aws-appsync/utils';
2 |
3 | export function request(ctx) {
4 | const { filter: f, limit = 20, nextToken } = ctx.args;
5 | const filter = f ? JSON.parse(util.transform.toDynamoDBFilterExpression(f)) : null;
6 | return { operation: 'Scan', filter, limit, nextToken };
7 | }
8 |
9 | export function response(ctx) {
10 | const { error, result } = ctx;
11 | if (error) {
12 | return util.appendError(error.message, error.type, result);
13 | }
14 | const { items = [], nextToken } = result;
15 | return { items, nextToken };
16 | }
17 |
--------------------------------------------------------------------------------
/examples/cloudformation/todo-api-cfn/resolvers/queryTodosByOwner.js:
--------------------------------------------------------------------------------
1 | import { util } from '@aws-appsync/utils';
2 |
3 | export function request(ctx) {
4 | const { owner, first = 20, after } = ctx.args;
5 | return dynamodbQueryRequest('owner', owner, 'owner-index', first, after);
6 | }
7 |
8 | export function response(ctx) {
9 | const { error, result } = ctx;
10 | if (error) {
11 | return util.appendError(error.message, error.type, result);
12 | }
13 | return result;
14 | }
15 |
16 | function dynamodbQueryRequest(key, value, index, limit, nextToken) {
17 | const expression = `#key = :key`;
18 | const expressionNames = { '#key': key };
19 | const expressionValues = util.dynamodb.toMapValues({ ':key': value });
20 | return {
21 | operation: 'Query',
22 | query: { expression, expressionNames, expressionValues },
23 | index,
24 | limit,
25 | nextToken,
26 | scanIndexForward: true,
27 | select: 'ALL_ATTRIBUTES',
28 | };
29 | }
30 |
--------------------------------------------------------------------------------
/examples/cloudformation/todo-api-cfn/resolvers/updateTodo.js:
--------------------------------------------------------------------------------
1 | import { util } from '@aws-appsync/utils';
2 |
3 | export function request(ctx) {
4 | const {
5 | input: { id, ...values },
6 | } = ctx.args;
7 | const condition = { id: { attributeExists: true } };
8 | return dynamodbUpdateRequest({ key: { id }, values, condition });
9 | }
10 |
11 | export function response(ctx) {
12 | const { error, result } = ctx;
13 | if (error) {
14 | return util.appendError(error.message, error.type, result);
15 | }
16 | return result;
17 | }
18 |
19 | function dynamodbUpdateRequest({ key, values, condition: inCondObj }) {
20 | const sets = [];
21 | const removes = [];
22 | const expressionNames = {};
23 | const expValues = {};
24 |
25 | for (const [k, v] of Object.entries(values)) {
26 | expressionNames[`#${k}`] = k;
27 | if (v) {
28 | sets.push(`#${k} = :${k}`);
29 | expValues[`:${k}`] = v;
30 | } else {
31 | removes.push(`#${k}`);
32 | }
33 | }
34 |
35 | console.log(`SET: ${sets.length}, REMOVE: ${removes.length}`);
36 |
37 | let expression = sets.length ? `SET ${sets.join(', ')}` : '';
38 | expression += removes.length ? ` REMOVE ${removes.join(', ')}` : '';
39 |
40 | console.log('update expression', expression);
41 |
42 | const condition = JSON.parse(util.transform.toDynamoDBConditionExpression(inCondObj));
43 |
44 | if (condition.expressionValues && !Object.keys(condition.expressionValues).length) {
45 | delete condition.expressionValues;
46 | }
47 |
48 | return {
49 | operation: 'UpdateItem',
50 | key: util.dynamodb.toMapValues(key),
51 | condition,
52 | update: {
53 | expression,
54 | expressionNames,
55 | expressionValues: util.dynamodb.toMapValues(expValues),
56 | },
57 | };
58 | }
59 |
--------------------------------------------------------------------------------
/examples/cloudformation/todo-api-cfn/schema.graphql:
--------------------------------------------------------------------------------
1 | input CreateTodoInput {
2 | title: String
3 | description: String
4 | owner: String
5 | }
6 |
7 | input DeleteTodoInput {
8 | id: ID!
9 | }
10 |
11 | type Mutation {
12 | createTodo(input: CreateTodoInput!): Todo
13 | updateTodo(input: UpdateTodoInput!): Todo
14 | deleteTodo(input: DeleteTodoInput!): Todo
15 | }
16 |
17 | type Query {
18 | getTodo(id: ID!): Todo
19 | listTodos(
20 | filter: TableTodoFilterInput
21 | limit: Int
22 | nextToken: String
23 | ): TodoConnection
24 | queryTodosByOwnerIndex(
25 | owner: String!
26 | first: Int
27 | after: String
28 | ): TodoConnection
29 | }
30 |
31 | type Subscription {
32 | onCreateTodo(id: ID, title: String, description: String, owner: String): Todo
33 | @aws_subscribe(mutations: ["createTodo"])
34 | onUpdateTodo(id: ID, title: String, description: String, owner: String): Todo
35 | @aws_subscribe(mutations: ["updateTodo"])
36 | onDeleteTodo(id: ID, title: String, description: String, owner: String): Todo
37 | @aws_subscribe(mutations: ["deleteTodo"])
38 | }
39 |
40 | input TableBooleanFilterInput {
41 | ne: Boolean
42 | eq: Boolean
43 | }
44 |
45 | input TableFloatFilterInput {
46 | ne: Float
47 | eq: Float
48 | le: Float
49 | lt: Float
50 | ge: Float
51 | gt: Float
52 | contains: Float
53 | notContains: Float
54 | between: [Float]
55 | }
56 |
57 | input TableIDFilterInput {
58 | ne: ID
59 | eq: ID
60 | le: ID
61 | lt: ID
62 | ge: ID
63 | gt: ID
64 | contains: ID
65 | notContains: ID
66 | between: [ID]
67 | beginsWith: ID
68 | }
69 |
70 | input TableIntFilterInput {
71 | ne: Int
72 | eq: Int
73 | le: Int
74 | lt: Int
75 | ge: Int
76 | gt: Int
77 | contains: Int
78 | notContains: Int
79 | between: [Int]
80 | }
81 |
82 | input TableStringFilterInput {
83 | ne: String
84 | eq: String
85 | le: String
86 | lt: String
87 | ge: String
88 | gt: String
89 | contains: String
90 | notContains: String
91 | between: [String]
92 | beginsWith: String
93 | }
94 |
95 | input TableTodoFilterInput {
96 | id: TableIDFilterInput
97 | title: TableStringFilterInput
98 | description: TableStringFilterInput
99 | owner: TableStringFilterInput
100 | }
101 |
102 | type Todo {
103 | id: ID!
104 | title: String
105 | description: String
106 | owner: String
107 | }
108 |
109 | type TodoConnection {
110 | items: [Todo]
111 | nextToken: String
112 | }
113 |
114 | input UpdateTodoInput {
115 | id: ID!
116 | title: String
117 | description: String
118 | owner: String
119 | }
120 |
--------------------------------------------------------------------------------
/examples/cloudformation/todo-api-cfn/template.yaml:
--------------------------------------------------------------------------------
1 | AWSTemplateFormatVersion: 2010-09-09
2 | Description: An AppSync API with JavaScript resolvers with a DynamoDB data source
3 | Resources:
4 | TodoAPI:
5 | Type: AWS::AppSync::GraphQLApi
6 | Properties:
7 | AuthenticationType: API_KEY
8 | Name: SimpleTodoAPI
9 | TodoAPISchema:
10 | Type: AWS::AppSync::GraphQLSchema
11 | Properties:
12 | ApiId: !GetAtt TodoAPI.ApiId
13 | Definition: |
14 | input CreateTodoInput {
15 | title: String
16 | description: String
17 | owner: String
18 | }
19 |
20 | input DeleteTodoInput {
21 | id: ID!
22 | }
23 |
24 | type Mutation {
25 | createTodo(input: CreateTodoInput!): Todo
26 | updateTodo(input: UpdateTodoInput!): Todo
27 | deleteTodo(input: DeleteTodoInput!): Todo
28 | }
29 |
30 | type Query {
31 | getTodo(id: ID!): Todo
32 | listTodos(filter: TableTodoFilterInput, limit: Int, nextToken: String): TodoConnection
33 | queryTodosByOwnerIndex(owner: String!, first: Int, after: String): TodoConnection
34 | }
35 |
36 | type Subscription {
37 | onCreateTodo(id: ID, title: String, description: String, owner: String): Todo @aws_subscribe(mutations: ["createTodo"])
38 | onUpdateTodo(id: ID, title: String, description: String, owner: String): Todo @aws_subscribe(mutations: ["updateTodo"])
39 | onDeleteTodo(id: ID, title: String, description: String, owner: String): Todo @aws_subscribe(mutations: ["deleteTodo"])
40 | }
41 |
42 | input TableBooleanFilterInput {
43 | ne: Boolean
44 | eq: Boolean
45 | }
46 |
47 | input TableFloatFilterInput {
48 | ne: Float
49 | eq: Float
50 | le: Float
51 | lt: Float
52 | ge: Float
53 | gt: Float
54 | contains: Float
55 | notContains: Float
56 | between: [Float]
57 | }
58 |
59 | input TableIDFilterInput {
60 | ne: ID
61 | eq: ID
62 | le: ID
63 | lt: ID
64 | ge: ID
65 | gt: ID
66 | contains: ID
67 | notContains: ID
68 | between: [ID]
69 | beginsWith: ID
70 | }
71 |
72 | input TableIntFilterInput {
73 | ne: Int
74 | eq: Int
75 | le: Int
76 | lt: Int
77 | ge: Int
78 | gt: Int
79 | contains: Int
80 | notContains: Int
81 | between: [Int]
82 | }
83 |
84 | input TableStringFilterInput {
85 | ne: String
86 | eq: String
87 | le: String
88 | lt: String
89 | ge: String
90 | gt: String
91 | contains: String
92 | notContains: String
93 | between: [String]
94 | beginsWith: String
95 | }
96 |
97 | input TableTodoFilterInput {
98 | id: TableIDFilterInput
99 | title: TableStringFilterInput
100 | description: TableStringFilterInput
101 | owner: TableStringFilterInput
102 | }
103 |
104 | type Todo {
105 | id: ID!
106 | title: String
107 | description: String
108 | owner: String
109 | }
110 |
111 | type TodoConnection {
112 | items: [Todo]
113 | nextToken: String
114 | }
115 |
116 | input UpdateTodoInput {
117 | id: ID!
118 | title: String
119 | description: String
120 | owner: String
121 | }
122 | DefaultAPIKey:
123 | Type: AWS::AppSync::ApiKey
124 | Properties:
125 | ApiId: !GetAtt TodoAPI.ApiId
126 | DependsOn:
127 | - TodoAPISchema
128 | DataSourceServiceRole:
129 | Type: AWS::IAM::Role
130 | Properties:
131 | AssumeRolePolicyDocument:
132 | Statement:
133 | - Action: sts:AssumeRole
134 | Effect: Allow
135 | Principal:
136 | Service: appsync.amazonaws.com
137 | Version: '2012-10-17'
138 | ServiceRoleDefaultPolicy:
139 | Type: AWS::IAM::Policy
140 | Properties:
141 | PolicyDocument:
142 | Statement:
143 | - Action:
144 | - dynamodb:BatchGetItem
145 | - dynamodb:BatchWriteItem
146 | - dynamodb:ConditionCheckItem
147 | - dynamodb:DeleteItem
148 | - dynamodb:DescribeTable
149 | - dynamodb:GetItem
150 | - dynamodb:GetRecords
151 | - dynamodb:GetShardIterator
152 | - dynamodb:PutItem
153 | - dynamodb:Query
154 | - dynamodb:Scan
155 | - dynamodb:UpdateItem
156 | Effect: Allow
157 | Resource:
158 | - !GetAtt TodoTable.Arn
159 | - Fn::Join:
160 | - ''
161 | - - !GetAtt TodoTable.Arn
162 | - /index/*
163 | Version: '2012-10-17'
164 | PolicyName: ServiceRoleDefaultPolicy
165 | Roles:
166 | - !Ref DataSourceServiceRole
167 | TodoTableDataSource:
168 | Type: AWS::AppSync::DataSource
169 | Properties:
170 | ApiId: !GetAtt TodoAPI.ApiId
171 | Name: table
172 | Type: AMAZON_DYNAMODB
173 | DynamoDBConfig:
174 | AwsRegion: !Ref AWS::Region
175 | TableName: !Ref TodoTable
176 | ServiceRoleArn: !GetAtt DataSourceServiceRole.Arn
177 | CreateTodoResolver:
178 | Type: AWS::AppSync::Resolver
179 | Properties:
180 | ApiId: !GetAtt TodoAPI.ApiId
181 | FieldName: createTodo
182 | TypeName: Mutation
183 | DataSourceName: table
184 | Runtime:
185 | Name: 'APPSYNC_JS'
186 | RuntimeVersion: '1.0.0'
187 | Code: |
188 | import { util } from '@aws-appsync/utils';
189 |
190 | export function request(ctx) {
191 | const { input: values } = ctx.arguments;
192 | const key = { id: util.autoId() };
193 | const condition = { and: [{ id: { attributeExists: false } }] };
194 | console.log('--> create todo with requested values: ', values);
195 | return dynamodbPutRequest({ key, values, condition });
196 | }
197 |
198 | export function response(ctx) {
199 | const { error, result } = ctx;
200 | if (error) {
201 | return util.error(error.message, error.type, result);
202 | }
203 | return ctx.result;
204 | }
205 |
206 | /**
207 | * Helper function to create a new item
208 | * @returns a PutItem request
209 | */
210 | function dynamodbPutRequest({ key, values, condition: inCondObj }) {
211 | const condition = JSON.parse(util.transform.toDynamoDBConditionExpression(inCondObj));
212 | if (condition.expressionValues && !Object.keys(condition.expressionValues).length) {
213 | delete condition.expressionValues;
214 | }
215 | return {
216 | operation: 'PutItem',
217 | key: util.dynamodb.toMapValues(key),
218 | attributeValues: util.dynamodb.toMapValues(values),
219 | condition,
220 | };
221 | }
222 | DependsOn:
223 | - TodoAPISchema
224 | - TodoTableDataSource
225 | UpdateTodoResolver:
226 | Type: AWS::AppSync::Resolver
227 | Properties:
228 | ApiId: !GetAtt TodoAPI.ApiId
229 | FieldName: updateTodo
230 | TypeName: Mutation
231 | DataSourceName: table
232 | Runtime:
233 | Name: 'APPSYNC_JS'
234 | RuntimeVersion: '1.0.0'
235 | Code: |
236 | import { util } from '@aws-appsync/utils';
237 |
238 | export function request(ctx) {
239 | const {
240 | input: { id, ...values },
241 | } = ctx.args;
242 | const condition = { id: { attributeExists: true } };
243 | return dynamodbUpdateRequest({ key: { id }, values, condition });
244 | }
245 |
246 | export function response(ctx) {
247 | const { error, result } = ctx;
248 | if (error) {
249 | return util.error(error.message, error.type, result);
250 | }
251 | return result;
252 | }
253 |
254 | function dynamodbUpdateRequest({ key, values, condition: inCondObj }) {
255 | const sets = [];
256 | const removes = [];
257 | const expressionNames = {};
258 | const expValues = {};
259 |
260 | for (const [k, v] of Object.entries(values)) {
261 | expressionNames[`#${k}`] = k;
262 | if (v) {
263 | sets.push(`#${k} = :${k}`);
264 | expValues[`:${k}`] = v;
265 | } else {
266 | removes.push(`#${k}`);
267 | }
268 | }
269 |
270 | console.log(`SET: ${sets.length}, REMOVE: ${removes.length}`);
271 |
272 | let expression = sets.length ? `SET ${sets.join(', ')}` : '';
273 | expression += removes.length ? ` REMOVE ${removes.join(', ')}` : '';
274 |
275 | console.log('update expression', expression);
276 |
277 | const condition = JSON.parse(util.transform.toDynamoDBConditionExpression(inCondObj));
278 |
279 | if (condition.expressionValues && !Object.keys(condition.expressionValues).length) {
280 | delete condition.expressionValues;
281 | }
282 |
283 | return {
284 | operation: 'UpdateItem',
285 | key: util.dynamodb.toMapValues(key),
286 | condition,
287 | update: {
288 | expression,
289 | expressionNames,
290 | expressionValues: util.dynamodb.toMapValues(expValues),
291 | },
292 | };
293 | }
294 | DependsOn:
295 | - TodoAPISchema
296 | - TodoTableDataSource
297 | DeleteTodoResolver:
298 | Type: AWS::AppSync::Resolver
299 | Properties:
300 | ApiId: !GetAtt TodoAPI.ApiId
301 | FieldName: deleteTodo
302 | TypeName: Mutation
303 | DataSourceName: table
304 | Runtime:
305 | Name: 'APPSYNC_JS'
306 | RuntimeVersion: '1.0.0'
307 | Code: |
308 | import { util } from '@aws-appsync/utils';
309 |
310 | export function request(ctx) {
311 | return {
312 | operation: 'DeleteItem',
313 | key: util.dynamodb.toMapValues({id: ctx.args.input.id}),
314 | };
315 | }
316 |
317 | export function response(ctx) {
318 | const { error, result } = ctx;
319 | if (error) {
320 | return util.error(error.message, error.type, result);
321 | }
322 | return ctx.result;
323 | }
324 | DependsOn:
325 | - TodoAPISchema
326 | - TodoTableDataSource
327 | GetTodoResolver:
328 | Type: AWS::AppSync::Resolver
329 | Properties:
330 | ApiId: !GetAtt TodoAPI.ApiId
331 | FieldName: getTodo
332 | TypeName: Query
333 | DataSourceName: table
334 | Runtime:
335 | Name: 'APPSYNC_JS'
336 | RuntimeVersion: '1.0.0'
337 | Code: |
338 | import { util } from '@aws-appsync/utils';
339 |
340 | export function request(ctx) {
341 | return {
342 | operation: 'GetItem',
343 | key: util.dynamodb.toMapValues({ id: ctx.args.id}),
344 | };
345 | }
346 |
347 | export function response(ctx) {
348 | const { error, result } = ctx;
349 | if (error) {
350 | return util.error(error.message, error.type, result);
351 | }
352 | return ctx.result;
353 | }
354 | DependsOn:
355 | - TodoAPISchema
356 | - TodoTableDataSource
357 | ListTodosResolver:
358 | Type: AWS::AppSync::Resolver
359 | Properties:
360 | ApiId: !GetAtt TodoAPI.ApiId
361 | FieldName: listTodos
362 | TypeName: Query
363 | DataSourceName: table
364 | Runtime:
365 | Name: 'APPSYNC_JS'
366 | RuntimeVersion: '1.0.0'
367 | Code: |
368 | import { util } from '@aws-appsync/utils';
369 |
370 | export function request(ctx) {
371 | const { filter: f, limit = 20, nextToken } = ctx.args;
372 | const filter = f ? JSON.parse(util.transform.toDynamoDBFilterExpression(f)) : null;
373 | return { operation: 'Scan', filter, limit, nextToken };
374 | }
375 |
376 | export function response(ctx) {
377 | const { error, result } = ctx;
378 | if (error) {
379 | return util.error(error.message, error.type, result);
380 | }
381 | const { items = [], nextToken } = result;
382 | return { items, nextToken };
383 | }
384 | DependsOn:
385 | - TodoAPISchema
386 | - TodoTableDataSource
387 | QueryTodosResolver:
388 | Type: AWS::AppSync::Resolver
389 | Properties:
390 | ApiId: !GetAtt TodoAPI.ApiId
391 | FieldName: queryTodosByOwnerIndex
392 | TypeName: Query
393 | DataSourceName: table
394 | Runtime:
395 | Name: 'APPSYNC_JS'
396 | RuntimeVersion: '1.0.0'
397 | Code: |
398 | import { util } from '@aws-appsync/utils';
399 |
400 | export function request(ctx) {
401 | const { owner, first = 20, after } = ctx.args;
402 | return dynamodbQueryRequest('owner', owner, 'owner-index', first, after);
403 | }
404 |
405 | export function response(ctx) {
406 | const { error, result } = ctx;
407 | if (error) {
408 | return util.error(error.message, error.type, result);
409 | }
410 | return result;
411 | }
412 |
413 | function dynamodbQueryRequest(key, value, index, limit, nextToken) {
414 | const expression = `#key = :key`;
415 | const expressionNames = { '#key': key };
416 | const expressionValues = util.dynamodb.toMapValues({ ':key': value });
417 | return {
418 | operation: 'Query',
419 | query: { expression, expressionNames, expressionValues },
420 | index,
421 | limit,
422 | nextToken,
423 | scanIndexForward: true,
424 | select: 'ALL_ATTRIBUTES',
425 | };
426 | }
427 | DependsOn:
428 | - TodoAPISchema
429 | - TodoTableDataSource
430 | TodoTable:
431 | Type: AWS::DynamoDB::Table
432 | Properties:
433 | KeySchema:
434 | - AttributeName: id
435 | KeyType: HASH
436 | AttributeDefinitions:
437 | - AttributeName: id
438 | AttributeType: S
439 | - AttributeName: owner
440 | AttributeType: S
441 | GlobalSecondaryIndexes:
442 | - IndexName: owner-index
443 | KeySchema:
444 | - AttributeName: owner
445 | KeyType: HASH
446 | Projection:
447 | ProjectionType: ALL
448 | ProvisionedThroughput:
449 | ReadCapacityUnits: 5
450 | WriteCapacityUnits: 5
451 | ProvisionedThroughput:
452 | ReadCapacityUnits: 5
453 | WriteCapacityUnits: 5
454 | UpdateReplacePolicy: Retain
455 | DeletionPolicy: Retain
456 |
--------------------------------------------------------------------------------
/examples/cloudformation/todo-api-pipeline-cfn/README.md:
--------------------------------------------------------------------------------
1 | # Todo API
2 |
3 | This folder provides the implementation for a Todo API implemented with AppSync JavaScript resolvers.
4 | The API allows you to interact with a Todo type:
5 |
6 | ```graphql
7 | type Todo {
8 | id: ID!
9 | title: String
10 | description: String
11 | owner: String
12 | }
13 | ```
14 |
15 | The [functions](./functions/) folder contains the code for the AppSync functions, while the [resolvers](./resolvers/) folder contains the code for the pipeline resolvers. The resolvers in this API do not implement any before or after business logic, and the same code is use for all resolvers.
16 |
17 | ## Deploy the stack
18 |
19 | Deploy this stack by using [template.yaml](./template.yaml) from the Cloudformation console or using the AWS CLI.
20 |
21 | With the AWS CLI, from this folder:
22 |
23 | ```sh
24 | aws cloudformation deploy --template-file ./template.yaml --stack-name demo-todo-api-js --capabilities CAPABILITY_IAM
25 | ```
26 |
27 | Once deployed, you can find your API **TodoAPIwithJs** in the AWS console.
28 |
29 | ## Delete the stack
30 |
31 | To delete your resources: visit the Cloudformation console and delete the stack.
32 |
33 | With the AWS CLI:
34 |
35 | ```sh
36 | aws cloudformation delete-stack --stack-name demo-todo-api-js
37 | ```
38 |
39 | Note: The DynamoDB table deployed by this template is retained when the stack is deleted.
40 |
--------------------------------------------------------------------------------
/examples/cloudformation/todo-api-pipeline-cfn/functions/createItem.js:
--------------------------------------------------------------------------------
1 | /**
2 | * AppSync function: creates a new item in a DynamoDB table.
3 | * Find more samples and templates at https://github.com/aws-samples/aws-appsync-resolver-samples
4 | */
5 |
6 | import { util } from '@aws-appsync/utils';
7 |
8 | /**
9 | * Creates a new item in a DynamoDB table
10 | * @param ctx contextual information about the request
11 | */
12 | export function request(ctx) {
13 | const { input: values } = ctx.arguments;
14 | const key = { id: util.autoId() };
15 | const condition = { id: { attributeExists: false } };
16 | console.log('--> create todo with requested values: ', values);
17 | return dynamodbPutRequest({ key, values, condition });
18 | }
19 |
20 | /**
21 | * Returns the result
22 | * @param ctx contextual information about the request
23 | */
24 | export function response(ctx) {
25 | const { error, result } = ctx;
26 | if (error) {
27 | return util.appendError(error.message, error.type, result);
28 | }
29 | return ctx.result;
30 | }
31 |
32 | /**
33 | * Helper function to create a new item
34 | * @returns a PutItem request
35 | */
36 | function dynamodbPutRequest({ key, values, condition: inCondObj }) {
37 | const condition = JSON.parse(util.transform.toDynamoDBConditionExpression(inCondObj));
38 | if (condition.expressionValues && !Object.keys(condition.expressionValues).length) {
39 | delete condition.expressionValues;
40 | }
41 | return {
42 | operation: 'PutItem',
43 | key: util.dynamodb.toMapValues(key),
44 | attributeValues: util.dynamodb.toMapValues(values),
45 | condition,
46 | };
47 | }
48 |
--------------------------------------------------------------------------------
/examples/cloudformation/todo-api-pipeline-cfn/functions/deleteItem.js:
--------------------------------------------------------------------------------
1 | /**
2 | * AppSync function: deletes an item in a DynamoDB table.
3 | * Find more samples and templates at https://github.com/aws-samples/aws-appsync-resolver-samples
4 | */
5 |
6 | import { util } from '@aws-appsync/utils';
7 |
8 | export function request(ctx) {
9 | const { id } = ctx.args.input;
10 | return dynamodbDeleteRequest({ id });
11 | }
12 |
13 | export function response(ctx) {
14 | const { error, result } = ctx;
15 | if (error) {
16 | return util.appendError(error.message, error.type, result);
17 | }
18 | return ctx.result;
19 | }
20 |
21 | function dynamodbDeleteRequest(key) {
22 | return {
23 | operation: 'DeleteItem',
24 | key: util.dynamodb.toMapValues(key),
25 | };
26 | }
27 |
--------------------------------------------------------------------------------
/examples/cloudformation/todo-api-pipeline-cfn/functions/getItem.js:
--------------------------------------------------------------------------------
1 | /**
2 | * AppSync function: Fetches an item in a DynamoDB table.
3 | * Find more samples and templates at https://github.com/aws-samples/aws-appsync-resolver-samples
4 | */
5 |
6 | import { util } from '@aws-appsync/utils';
7 |
8 | /**
9 | * Request a single item from the attached DynamoDB table datasource
10 | * @param ctx the request context
11 | */
12 | export function request(ctx) {
13 | const { id } = ctx.args;
14 | return dynamoDBGetItemRequest({ id });
15 | }
16 |
17 | /**
18 | * Returns the result
19 | * @param ctx the request context
20 | */
21 | export function response(ctx) {
22 | const { error, result } = ctx;
23 | if (error) {
24 | return util.appendError(error.message, error.type, result);
25 | }
26 | return ctx.result;
27 | }
28 |
29 | /**
30 | * A helper function to get a DynamoDB it
31 | * @param key a set of keys for the item
32 | * @returns a DynamoDB Get request
33 | */
34 | function dynamoDBGetItemRequest(key) {
35 | return {
36 | operation: 'GetItem',
37 | key: util.dynamodb.toMapValues(key),
38 | };
39 | }
40 |
--------------------------------------------------------------------------------
/examples/cloudformation/todo-api-pipeline-cfn/functions/listItems.js:
--------------------------------------------------------------------------------
1 | /**
2 | * AppSync function: lists items in a DynamoDB table.
3 | * Find more samples and templates at https://github.com/aws-samples/aws-appsync-resolver-samples
4 | */
5 |
6 | import { util } from '@aws-appsync/utils';
7 |
8 | export function request(ctx) {
9 | const { filter, limit = 20, nextToken } = ctx.args;
10 | return dynamoDBScanRequest({ filter, limit, nextToken });
11 | }
12 |
13 | export function response(ctx) {
14 | const { error, result } = ctx;
15 | if (error) {
16 | return util.appendError(error.message, error.type, result);
17 | }
18 | const { items = [], nextToken } = result;
19 | return { items, nextToken };
20 | }
21 |
22 | function dynamoDBScanRequest({ filter: f, limit, nextToken }) {
23 | const filter = f ? JSON.parse(util.transform.toDynamoDBFilterExpression(f)) : null;
24 |
25 | return { operation: 'Scan', filter, limit, nextToken };
26 | }
27 |
--------------------------------------------------------------------------------
/examples/cloudformation/todo-api-pipeline-cfn/functions/out.js:
--------------------------------------------------------------------------------
1 | // apis/todo-api/functions/scratch.js
2 | import { util as util3 } from "@aws-appsync/utils";
3 |
4 | // utils/dynamodb/index.js
5 | import { util } from "@aws-appsync/utils";
6 |
7 | // utils/http/index.js
8 | import { util as util2 } from "@aws-appsync/utils";
9 | function publishToSNSRequest(topicArn, values) {
10 | const arn = util2.urlEncode(topicArn);
11 | const message = util2.urlEncode(JSON.stringify(values));
12 | let body = `Action=Publish&Version=2010-03-31&TopicArn=${arn}`;
13 | body += `$&Message=${message}`;
14 | return {
15 | method: "POST",
16 | resourcePath: "/",
17 | params: {
18 | body,
19 | headers: {
20 | "content-type": "application/x-www-form-urlencoded"
21 | }
22 | }
23 | };
24 | }
25 |
26 | // apis/todo-api/functions/scratch.js
27 | function request(ctx) {
28 | const { input: values } = ctx.arguments;
29 | return publishToSNSRequest("TOPIC_ARN", values);
30 | }
31 | function response(ctx) {
32 | const { error, result } = ctx;
33 | if (error) {
34 | return util3.appendError(error.message, error.type, result);
35 | }
36 | return ctx.result;
37 | }
38 | export {
39 | request,
40 | response
41 | };
42 |
--------------------------------------------------------------------------------
/examples/cloudformation/todo-api-pipeline-cfn/functions/queryItems.js:
--------------------------------------------------------------------------------
1 | /**
2 | * AppSync function: queries items on a given index in a DynamoDB table.
3 | * Find more samples and templates at https://github.com/aws-samples/aws-appsync-resolver-samples
4 | */
5 |
6 | import { util } from '@aws-appsync/utils';
7 |
8 | export function request(ctx) {
9 | const { owner, first = 20, after } = ctx.args;
10 | return dynamodbQueryRequest('owner', owner, 'owner-index', first, after);
11 | }
12 |
13 | export function response(ctx) {
14 | const { error, result } = ctx;
15 | if (error) {
16 | return util.appendError(error.message, error.type, result);
17 | }
18 | return result;
19 | }
20 |
21 | function dynamodbQueryRequest(key, value, index, limit, nextToken) {
22 | const expression = `#key = :key`;
23 | const expressionNames = { '#key': key };
24 | const expressionValues = util.dynamodb.toMapValues({ ':key': value });
25 | return {
26 | operation: 'Query',
27 | query: { expression, expressionNames, expressionValues },
28 | index,
29 | limit,
30 | nextToken,
31 | scanIndexForward: true,
32 | select: 'ALL_ATTRIBUTES',
33 | };
34 | }
35 |
--------------------------------------------------------------------------------
/examples/cloudformation/todo-api-pipeline-cfn/functions/scratch.js:
--------------------------------------------------------------------------------
1 | import { util } from '@aws-appsync/utils';
2 | import * as dynamodb from '../../../utils/dynamodb';
3 | import * as http from '../../../utils/http';
4 |
5 | export function request(ctx) {
6 | const { input: values } = ctx.arguments;
7 | // const key = { id: util.autoId() };
8 | // const condition = { id: { attributeExists: false } };
9 | // return dynamodb.update({ key, values, condition });
10 | return http.publishToSNSRequest('TOPIC_ARN', values);
11 | }
12 |
13 | export function response(ctx) {
14 | const { error, result } = ctx;
15 | if (error) {
16 | return util.appendError(error.message, error.type, result);
17 | }
18 | return ctx.result;
19 | }
20 |
--------------------------------------------------------------------------------
/examples/cloudformation/todo-api-pipeline-cfn/functions/udpateItem.js:
--------------------------------------------------------------------------------
1 | /**
2 | * AppSync function: updates a new item in a DynamoDB table.
3 | * Find more samples and templates at https://github.com/aws-samples/aws-appsync-resolver-samples
4 | */
5 |
6 | import { util } from '@aws-appsync/utils';
7 |
8 | export function request(ctx) {
9 | const {
10 | input: { id, ...values },
11 | } = ctx.args;
12 | const condition = { id: { attributeExists: true } };
13 | return dynamodbUpdateRequest({ key: { id }, values, condition });
14 | }
15 |
16 | export function response(ctx) {
17 | const { error, result } = ctx;
18 | if (error) {
19 | return util.appendError(error.message, error.type, result);
20 | }
21 | return result;
22 | }
23 |
24 | function dynamodbUpdateRequest({ key, values, condition: inCondObj }) {
25 | const sets = [];
26 | const removes = [];
27 | const expressionNames = {};
28 | const expValues = {};
29 |
30 | for (const [k, v] of Object.entries(values)) {
31 | expressionNames[`#${k}`] = k;
32 | if (v) {
33 | sets.push(`#${k} = :${k}`);
34 | expValues[`:${k}`] = v;
35 | } else {
36 | removes.push(`#${k}`);
37 | }
38 | }
39 |
40 | console.log(`SET: ${sets.length}, REMOVE: ${removes.length}`);
41 |
42 | let expression = sets.length ? `SET ${sets.join(', ')}` : '';
43 | expression += removes.length ? ` REMOVE ${removes.join(', ')}` : '';
44 |
45 | console.log('update expression', expression);
46 |
47 | const condition = JSON.parse(util.transform.toDynamoDBConditionExpression(inCondObj));
48 |
49 | if (condition.expressionValues && !Object.keys(condition.expressionValues).length) {
50 | delete condition.expressionValues;
51 | }
52 |
53 | return {
54 | operation: 'UpdateItem',
55 | key: util.dynamodb.toMapValues(key),
56 | condition,
57 | update: {
58 | expression,
59 | expressionNames,
60 | expressionValues: util.dynamodb.toMapValues(expValues),
61 | },
62 | };
63 | }
64 |
--------------------------------------------------------------------------------
/examples/cloudformation/todo-api-pipeline-cfn/resolvers/default.js:
--------------------------------------------------------------------------------
1 | /**
2 | * AppSync resolver: implements before and after business logic for your pipeline
3 | * Find more samples and templates at https://github.com/aws-samples/aws-appsync-resolver-samples
4 | */
5 | /**
6 | * Called before the request function of the first AppSync function in the pipeline.
7 | * @param ctx The context object that holds contextual information about the function invocation.
8 | */
9 | export function request(ctx) {
10 | return {};
11 | }
12 | /**
13 | * Called after the response function of the last AppSync function in the pipeline.
14 | * @param ctx The context object that holds contextual information about the function invocation.
15 | */
16 | export function response(ctx) {
17 | return ctx.prev.result;
18 | }
19 |
--------------------------------------------------------------------------------
/examples/cloudformation/todo-api-pipeline-cfn/schema.graphql:
--------------------------------------------------------------------------------
1 | input CreateTodoInput {
2 | title: String
3 | description: String
4 | owner: String
5 | }
6 |
7 | input DeleteTodoInput {
8 | id: ID!
9 | }
10 |
11 | type Mutation {
12 | createTodo(input: CreateTodoInput!): Todo
13 | updateTodo(input: UpdateTodoInput!): Todo
14 | deleteTodo(input: DeleteTodoInput!): Todo
15 | }
16 |
17 | type Query {
18 | getTodo(id: ID!): Todo
19 | listTodos(
20 | filter: TableTodoFilterInput
21 | limit: Int
22 | nextToken: String
23 | ): TodoConnection
24 | queryTodosByOwnerIndex(
25 | owner: String!
26 | first: Int
27 | after: String
28 | ): TodoConnection
29 | }
30 |
31 | type Subscription {
32 | onCreateTodo(id: ID, title: String, description: String, owner: String): Todo
33 | @aws_subscribe(mutations: ["createTodo"])
34 | onUpdateTodo(id: ID, title: String, description: String, owner: String): Todo
35 | @aws_subscribe(mutations: ["updateTodo"])
36 | onDeleteTodo(id: ID, title: String, description: String, owner: String): Todo
37 | @aws_subscribe(mutations: ["deleteTodo"])
38 | }
39 |
40 | input TableBooleanFilterInput {
41 | ne: Boolean
42 | eq: Boolean
43 | }
44 |
45 | input TableFloatFilterInput {
46 | ne: Float
47 | eq: Float
48 | le: Float
49 | lt: Float
50 | ge: Float
51 | gt: Float
52 | contains: Float
53 | notContains: Float
54 | between: [Float]
55 | }
56 |
57 | input TableIDFilterInput {
58 | ne: ID
59 | eq: ID
60 | le: ID
61 | lt: ID
62 | ge: ID
63 | gt: ID
64 | contains: ID
65 | notContains: ID
66 | between: [ID]
67 | beginsWith: ID
68 | }
69 |
70 | input TableIntFilterInput {
71 | ne: Int
72 | eq: Int
73 | le: Int
74 | lt: Int
75 | ge: Int
76 | gt: Int
77 | contains: Int
78 | notContains: Int
79 | between: [Int]
80 | }
81 |
82 | input TableStringFilterInput {
83 | ne: String
84 | eq: String
85 | le: String
86 | lt: String
87 | ge: String
88 | gt: String
89 | contains: String
90 | notContains: String
91 | between: [String]
92 | beginsWith: String
93 | }
94 |
95 | input TableTodoFilterInput {
96 | id: TableIDFilterInput
97 | title: TableStringFilterInput
98 | description: TableStringFilterInput
99 | owner: TableStringFilterInput
100 | }
101 |
102 | type Todo {
103 | id: ID!
104 | title: String
105 | description: String
106 | owner: String
107 | }
108 |
109 | type TodoConnection {
110 | items: [Todo]
111 | nextToken: String
112 | }
113 |
114 | input UpdateTodoInput {
115 | id: ID!
116 | title: String
117 | description: String
118 | owner: String
119 | }
120 |
--------------------------------------------------------------------------------
/examples/serverless/lambda-http-ddb-datasources/README.md:
--------------------------------------------------------------------------------
1 | # Serverless Framework AWS AppSync Example
2 |
3 | Type `Post` is using Lambda data source
4 |
5 | ```graphql
6 | type Post {
7 | id: ID!
8 | title: String
9 | body: String
10 | }
11 | ```
12 |
13 | Type `User` is using HTTP data source
14 |
15 | ```graphql
16 | type User {
17 | id: ID!
18 | firstName: String
19 | lastName: String
20 | }
21 | ```
22 |
23 | Type `Todo` is using DynamoDB data source with util examples in the resolvers
24 |
25 | ```graphql
26 | type Todo {
27 | id: ID!
28 | title: String
29 | completed: Boolean!
30 | }
31 | ```
32 |
33 | ## Deploy the stack
34 |
35 | Deploy this stack by using Serverless Framework as defined in [serverless.yml](./serverless.yml) by running `serverless deploy`.
36 |
37 | ## Delete the resource
38 |
39 | To delete your resources: visit the Cloudformation console and delete the stack.
40 |
41 | With the AWS CLI:
42 |
43 | ```sh
44 | aws cloudformation delete-stack --stack-name sls-appsync-example-dev
45 | ```
46 |
47 | Note: The stack name `sls-appsync-example-dev` above assumes the `service` and `environemnt` attributes in `serverless.yml` were not modified.
48 |
--------------------------------------------------------------------------------
/examples/serverless/lambda-http-ddb-datasources/lambda/index.js:
--------------------------------------------------------------------------------
1 | module.exports.handler = async (event) => {
2 | const posts = [
3 | {
4 | id: '1',
5 | title: 'Post One',
6 | },
7 | {
8 | id: '2',
9 | title: 'Post Two',
10 | },
11 | {
12 | id: '3',
13 | title: 'Post Three',
14 | },
15 | {
16 | id: '4',
17 | title: 'Post Four',
18 | },
19 | {
20 | id: '5',
21 | title: 'Post Five',
22 | },
23 | ];
24 |
25 | if (event.field === 'getPost') {
26 | return posts.find((post) => post.id === event.arguments.id);
27 | } else {
28 | return posts;
29 | }
30 | };
31 |
--------------------------------------------------------------------------------
/examples/serverless/lambda-http-ddb-datasources/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "dependencies": {
3 | "serverless-appsync-plugin": "^2.7.0"
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/examples/serverless/lambda-http-ddb-datasources/resolvers/ddb/addTodo.js:
--------------------------------------------------------------------------------
1 | import { util } from '@aws-appsync/utils';
2 | import * as ddb from '@aws-appsync/utils/dynamodb';
3 |
4 | export function request(ctx) {
5 | return ddb.put({
6 | key: {
7 | id: util.autoId(),
8 | },
9 | item: ctx.arguments.input,
10 | });
11 | }
12 |
13 | export function response(ctx) {
14 | return ctx.result;
15 | }
16 |
--------------------------------------------------------------------------------
/examples/serverless/lambda-http-ddb-datasources/resolvers/ddb/getTodo.js:
--------------------------------------------------------------------------------
1 | import * as ddb from '@aws-appsync/utils/dynamodb';
2 |
3 | export function request(ctx) {
4 | return ddb.get({ key: { id: ctx.arguments.id } });
5 | }
6 |
7 | export function response(ctx) {
8 | return ctx.result;
9 | }
10 |
--------------------------------------------------------------------------------
/examples/serverless/lambda-http-ddb-datasources/resolvers/ddb/listTodos.js:
--------------------------------------------------------------------------------
1 | export function request(ctx) {
2 | return {
3 | operation: 'Scan',
4 | };
5 | }
6 |
7 | export function response(ctx) {
8 | return ctx.result.items;
9 | }
10 |
--------------------------------------------------------------------------------
/examples/serverless/lambda-http-ddb-datasources/resolvers/http/getUser.js:
--------------------------------------------------------------------------------
1 | export function request(ctx) {
2 | return {
3 | method: 'GET',
4 | resourcePath: '/users/' + ctx.args.id,
5 | params: {
6 | headers: {
7 | 'Content-Type': 'application/json',
8 | },
9 | },
10 | };
11 | }
12 |
13 | export function response(ctx) {
14 | return JSON.parse(ctx.result.body);
15 | }
16 |
--------------------------------------------------------------------------------
/examples/serverless/lambda-http-ddb-datasources/resolvers/http/listUsers.js:
--------------------------------------------------------------------------------
1 | export function request() {
2 | return {
3 | method: 'GET',
4 | resourcePath: '/users',
5 | };
6 | }
7 |
8 | export function response(ctx) {
9 | const data = JSON.parse(ctx.result.body);
10 | return data.users;
11 | }
12 |
--------------------------------------------------------------------------------
/examples/serverless/lambda-http-ddb-datasources/resolvers/lambda/getPost.js:
--------------------------------------------------------------------------------
1 | export function request(ctx) {
2 | return {
3 | operation: 'Invoke',
4 | payload: { field: ctx.info.fieldName, arguments: ctx.args },
5 | };
6 | }
7 |
8 | export function response(ctx) {
9 | return ctx.result;
10 | }
11 |
--------------------------------------------------------------------------------
/examples/serverless/lambda-http-ddb-datasources/resolvers/lambda/listPosts.js:
--------------------------------------------------------------------------------
1 | export function request(ctx) {
2 | return {
3 | operation: 'Invoke',
4 | payload: { field: ctx.info.fieldName },
5 | };
6 | }
7 |
8 | export function response(ctx) {
9 | return ctx.result;
10 | }
11 |
--------------------------------------------------------------------------------
/examples/serverless/lambda-http-ddb-datasources/schema.graphql:
--------------------------------------------------------------------------------
1 | schema {
2 | query: Query
3 | mutation: Mutation
4 | }
5 |
6 | type Query {
7 | listPosts: [Post]
8 | listUsers: [User]
9 | listTodos: [Todo]
10 | getPost(id: ID!): Post
11 | getUser(id: ID!): User
12 | getTodo(id: ID!): Todo
13 | }
14 |
15 | type Mutation {
16 | addTodo(input: AddTodoInput): Todo
17 | }
18 |
19 | type Post {
20 | id: ID!
21 | title: String
22 | body: String
23 | }
24 |
25 | type User {
26 | id: ID!
27 | firstName: String
28 | lastName: String
29 | }
30 |
31 | type Todo {
32 | id: ID!
33 | title: String
34 | completed: Boolean!
35 | }
36 |
37 | input AddTodoInput {
38 | title: String
39 | completed: Boolean
40 | }
41 |
--------------------------------------------------------------------------------
/examples/serverless/lambda-http-ddb-datasources/serverless.yml:
--------------------------------------------------------------------------------
1 | service: sls-appsync-example
2 | frameworkVersion: '3'
3 |
4 | provider:
5 | name: aws
6 |
7 | resources:
8 | Resources:
9 | myTodosTable:
10 | Type: AWS::DynamoDB::Table
11 | Properties:
12 | TableName: myTodosTable
13 | AttributeDefinitions:
14 | - AttributeName: id
15 | AttributeType: S
16 | KeySchema:
17 | - AttributeName: id
18 | KeyType: HASH
19 | ProvisionedThroughput:
20 | ReadCapacityUnits: 1
21 | WriteCapacityUnits: 1
22 | plugins:
23 | - serverless-appsync-plugin
24 |
25 | appSync:
26 | name: sls-appsync
27 |
28 | logging:
29 | level: ALL
30 | retentionInDays: 14
31 |
32 | authentication:
33 | type: API_KEY
34 |
35 | apiKeys:
36 | - name: myKey
37 | expiresAfter: 7d
38 |
39 | dataSources:
40 | myTodosTable:
41 | type: AMAZON_DYNAMODB
42 | description: 'My appsync table'
43 | config:
44 | tableName: myTodosTable
45 | myLambda:
46 | type: 'AWS_LAMBDA'
47 | config:
48 | function:
49 | handler: 'lambda/index.handler'
50 | runtime: nodejs18.x
51 | package:
52 | individually: true
53 | patterns:
54 | - '!./node_modules**' # exclude root files
55 | - '!./**'
56 | - './lambda/**'
57 | myEndpoint:
58 | type: 'HTTP'
59 | config:
60 | endpoint: https://dummyjson.com
61 |
62 | resolvers:
63 | Query.listPosts:
64 | dataSource: myLambda
65 | kind: UNIT
66 | code: 'resolvers/lambda/listPosts.js'
67 | Query.listUsers:
68 | dataSource: myEndpoint
69 | kind: UNIT
70 | code: 'resolvers/http/listUsers.js'
71 | Query.listTodos:
72 | dataSource: myTodosTable
73 | kind: UNIT
74 | code: 'resolvers/ddb/listTodos.js'
75 |
76 | Query.getPost:
77 | dataSource: myLambda
78 | kind: UNIT
79 | code: 'resolvers/lambda/getPost.js'
80 | Query.getUser:
81 | dataSource: myEndpoint
82 | kind: UNIT
83 | code: 'resolvers/http/getUser.js'
84 | Query.getTodo:
85 | dataSource: myTodosTable
86 | kind: UNIT
87 | code: 'resolvers/ddb/getTodo.js'
88 |
89 | Mutation.addTodo:
90 | dataSource: myTodosTable
91 | kind: UNIT
92 | code: 'resolvers/ddb/addTodo.js'
93 |
--------------------------------------------------------------------------------
/examples/terraform/.terraform.lock.hcl:
--------------------------------------------------------------------------------
1 | # This file is maintained automatically by "terraform init".
2 | # Manual edits may be lost in future updates.
3 |
4 | provider "registry.terraform.io/hashicorp/aws" {
5 | version = "5.82.2"
6 | constraints = "~> 5.82.2"
7 | hashes = [
8 | "h1:ce6Dw2y4PpuqAPtnQ0dO270dRTmwEARqnfffrE1VYJ8=",
9 | "zh:0262fc96012fb7e173e1b7beadd46dfc25b1dc7eaef95b90e936fc454724f1c8",
10 | "zh:397413613d27f4f54d16efcbf4f0a43c059bd8d827fe34287522ae182a992f9b",
11 | "zh:436c0c5d56e1da4f0a4c13129e12a0b519d12ab116aed52029b183f9806866f3",
12 | "zh:4d942d173a2553d8d532a333a0482a090f4e82a2238acf135578f163b6e68470",
13 | "zh:624aebc549bfbce06cc2ecfd8631932eb874ac7c10eb8466ce5b9a2fbdfdc724",
14 | "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425",
15 | "zh:9e632dee2dfdf01b371cca7854b1ec63ceefa75790e619b0642b34d5514c6733",
16 | "zh:a07567acb115b60a3df8f6048d12735b9b3bcf85ec92a62f77852e13d5a3c096",
17 | "zh:ab7002df1a1be6432ac0eb1b9f6f0dd3db90973cd5b1b0b33d2dae54553dfbd7",
18 | "zh:bc1ff65e2016b018b3e84db7249b2cd0433cb5c81dc81f9f6158f2197d6b9fde",
19 | "zh:bcad84b1d767f87af6e1ba3dc97fdb8f2ad5de9224f192f1412b09aba798c0a8",
20 | "zh:cf917dceaa0f9d55d9ff181b5dcc4d1e10af21b6671811b315ae2a6eda866a2a",
21 | "zh:d8e90ecfb3216f3cc13ccde5a16da64307abb6e22453aed2ac3067bbf689313b",
22 | "zh:d9054e0e40705df729682ad34c20db8695d57f182c65963abd151c6aba1ab0d3",
23 | "zh:ecf3a4f3c57eb7e89f71b8559e2a71e4cdf94eea0118ec4f2cb37e4f4d71a069",
24 | ]
25 | }
26 |
--------------------------------------------------------------------------------
/examples/terraform/main.tf:
--------------------------------------------------------------------------------
1 | // Create AppSync API
2 | resource "aws_appsync_graphql_api" "appsync_api" {
3 | authentication_type = "API_KEY"
4 | name = "AppSync Terraform API"
5 |
6 | schema = file("schema.graphql")
7 | }
8 |
9 | // Create API Key
10 | resource "aws_appsync_api_key" "appsync_api_key" {
11 | api_id = aws_appsync_graphql_api.appsync_api.id
12 |
13 | // Set the expiration 7 days from now - https://developer.hashicorp.com/terraform/language/functions/timeadd
14 | expires = timeadd(timestamp(), "168h")
15 | }
16 |
17 | // Add HTTP Datasource
18 | resource "aws_appsync_datasource" "todo_http_datasource" {
19 | api_id = aws_appsync_graphql_api.appsync_api.id
20 | name = "lambdaPosts"
21 | type = "HTTP"
22 |
23 | http_config {
24 | endpoint = "" // Replace this with actual endpoint. For example, https://jsonplaceholder.typicode.com
25 | }
26 | }
27 |
28 | // Add resolver for Query.listTodos
29 | resource "aws_appsync_resolver" "listTodos" {
30 | api_id = aws_appsync_graphql_api.appsync_api.id
31 | type = "Query"
32 | field = "listTodos"
33 |
34 | data_source = aws_appsync_datasource.todo_http_datasource.name
35 | kind = "UNIT"
36 |
37 | code = file("resolvers/listTodos.js")
38 |
39 | runtime {
40 | name = "APPSYNC_JS"
41 | runtime_version = "1.0.0"
42 | }
43 | }
44 |
45 | // Add resolver for Query.getTodo
46 | resource "aws_appsync_resolver" "getTodo" {
47 | api_id = aws_appsync_graphql_api.appsync_api.id
48 | type = "Query"
49 | field = "getTodo"
50 |
51 | data_source = aws_appsync_datasource.todo_http_datasource.name
52 | kind = "UNIT"
53 |
54 | code = file("resolvers/getTodo.js")
55 |
56 | runtime {
57 | name = "APPSYNC_JS"
58 | runtime_version = "1.0.0"
59 | }
60 | caching_config {
61 | ttl = 3600
62 | }
63 | }
64 |
65 |
66 | // To Enable caching uncomment the following code block
67 | # resource "aws_appsync_api_cache" "cache_config" {
68 | # api_id = aws_appsync_graphql_api.appsync_api.id
69 | # api_caching_behavior = "PER_RESOLVER_CACHING"
70 | # type = "LARGE"
71 | # ttl = 3600
72 | # }
73 |
--------------------------------------------------------------------------------
/examples/terraform/provider.tf:
--------------------------------------------------------------------------------
1 | terraform {
2 | required_providers {
3 | aws = {
4 | source = "hashicorp/aws"
5 | version = "~> 5.82.2"
6 | }
7 | }
8 | }
--------------------------------------------------------------------------------
/examples/terraform/resolvers/getTodo.js:
--------------------------------------------------------------------------------
1 | import { util } from '@aws-appsync/utils';
2 |
3 | export function request(ctx) {
4 | return fetch(`/todos/${ctx.args.id}`);
5 | }
6 |
7 | export function response(ctx) {
8 | const { statusCode, body } = ctx.result;
9 | // if response is 200, return the response
10 | if (statusCode === 200) {
11 | return JSON.parse(body); // this will depend on the response shape of the actual api/datasource
12 | }
13 | // if response is not 200, append the response to error block.
14 | util.appendError(body, statusCode);
15 | }
16 |
17 | function fetch(resourcePath, options) {
18 | const { method = 'GET', headers, body, query } = options;
19 |
20 | return {
21 | resourcePath,
22 | method,
23 | params: { headers, query, body },
24 | };
25 | }
26 |
--------------------------------------------------------------------------------
/examples/terraform/resolvers/listTodos.js:
--------------------------------------------------------------------------------
1 | import { util } from '@aws-appsync/utils';
2 |
3 | export function request(ctx) {
4 | return fetch(`/todos`);
5 | }
6 |
7 | export function response(ctx) {
8 | const { statusCode, body } = ctx.result;
9 | // if response is 200, return the response
10 | if (statusCode === 200) {
11 | return JSON.parse(body); // this will depend on the response shape of the actual api/datasource
12 | }
13 | // if response is not 200, append the response to error block.
14 | util.appendError(body, statusCode);
15 | }
16 |
17 | function fetch(resourcePath, options) {
18 | const { method = 'GET', headers, body, query } = options;
19 |
20 | return {
21 | resourcePath,
22 | method,
23 | params: { headers, query, body },
24 | };
25 | }
26 |
--------------------------------------------------------------------------------
/examples/terraform/schema.graphql:
--------------------------------------------------------------------------------
1 | type Todo {
2 | userId: Int
3 | id: ID
4 | title: String
5 | completed: Boolean
6 | }
7 | type Query {
8 | getTodo(id: ID!): Todo
9 | listTodos: [Todo]
10 | }
11 |
12 | schema {
13 | query: Query
14 | }
15 |
--------------------------------------------------------------------------------
/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "aws-appsync-resolver-samples",
3 | "version": "1.0.0",
4 | "lockfileVersion": 3,
5 | "requires": true,
6 | "packages": {
7 | "": {
8 | "name": "aws-appsync-resolver-samples",
9 | "version": "1.0.0",
10 | "license": "MIT-0"
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "aws-appsync-resolver-samples",
3 | "version": "1.0.0",
4 | "description": "Getting started samples for AppSync JavaScript resolvers",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "git+https://github.com/aws-samples/aws-appsync-resolver-samples.git"
12 | },
13 | "keywords": [
14 | "appsync",
15 | "graphql",
16 | "javascript"
17 | ],
18 | "license": "MIT-0",
19 | "bugs": {
20 | "url": "https://github.com/aws-samples/aws-appsync-resolver-samples/issues"
21 | },
22 | "homepage": "https://github.com/aws-samples/aws-appsync-resolver-samples#readme"
23 | }
24 |
--------------------------------------------------------------------------------
/samples/NONE/enhancedSubscription.js:
--------------------------------------------------------------------------------
1 | // TITLE: Set up an enhanced subscription
2 |
3 | import { util, extensions } from '@aws-appsync/utils';
4 |
5 | /**
6 | * Sends an empty payload as the subscription is established
7 | * @param {*} ctx the context
8 | * @returns {import('@aws-appsync/utils').NONERequest} the request
9 | */
10 | export function request(ctx) {
11 | //noop
12 | return { payload: {} };
13 | }
14 |
15 | /**
16 | * Creates an enhanced subscription
17 | * @param {import('@aws-appsync/utils').Context} ctx the context
18 | * @returns {*} the result
19 | */
20 | export function response(ctx) {
21 | // the logged in user group
22 | const groups = ['admin', 'operators'];
23 | // or use the user's own groups
24 | // const groups = ctx.identity.groups
25 |
26 | // This filter sets up a subscription that is triggered when:
27 | // - a mutation with severity >= 7 and priority high or medium is made
28 | // - or a mtuation with classification "security" is made and the user belongs to the "admin" or "operators" group
29 | const filter = util.transform.toSubscriptionFilter({
30 | or: [
31 | { and: [{ severity: { ge: 7 } }, { priority: { in: ['high', 'medium'] } }] },
32 | { and: [{ classification: { eq: 'security' } }, { group: { in: groups } }] },
33 | ],
34 | });
35 | console.log(filter);
36 | extensions.setSubscriptionFilter(filter);
37 | return null;
38 | }
39 |
--------------------------------------------------------------------------------
/samples/NONE/localPublish.js:
--------------------------------------------------------------------------------
1 | // TITLE: Locally publish a message
2 |
3 | import { util } from '@aws-appsync/utils';
4 |
5 | /**
6 | * Publishes an event localy
7 | * @param {import('@aws-appsync/utils').Context} ctx the context
8 | * @returns {import('@aws-appsync/utils').NONERequest} the request
9 | */
10 | export function request(ctx) {
11 | return {
12 | payload: {
13 | body: ctx.args.body,
14 | to: ctx.args.to,
15 | from: /** @type {import('@aws-appsync/utils').AppSyncIdentityCognito} */ (ctx.identity)
16 | .username,
17 | sentAt: util.time.nowISO8601(),
18 | },
19 | };
20 | }
21 |
22 | /**
23 | * Forward the payload in the `result` object
24 | * @param {import('@aws-appsync/utils').Context} ctx the context
25 | * @returns {*} the result
26 | */
27 | export function response(ctx) {
28 | return ctx.result;
29 | }
30 |
--------------------------------------------------------------------------------
/samples/dynamodb/batch/batchDeleteItems.js:
--------------------------------------------------------------------------------
1 | import { util } from '@aws-appsync/utils';
2 |
3 | /**
4 | * Deletes items from DynamoDB tables in batches with the provided `id` keys
5 | * @param {import('@aws-appsync/utils').Context} ctx the context
6 | * @returns {import('@aws-appsync/utils').DynamoDBBatchDeleteItemRequest} the request
7 | */
8 | export function request(ctx) {
9 | return {
10 | operation: 'BatchDeleteItem',
11 | tables: {
12 | Posts: {
13 | keys: ctx.args.ids.map((id) => util.dynamodb.toMapValues({ id })),
14 | },
15 | },
16 | };
17 | }
18 |
19 | /**
20 | * Returns the BatchDeleteItem table results
21 | * @param {import('@aws-appsync/utils').Context} ctx the context
22 | * @returns {[*]} the items
23 | */
24 | export function response(ctx) {
25 | if (ctx.error) {
26 | util.error(ctx.error.message, ctx.error.type);
27 | }
28 | return ctx.result.data.Posts;
29 | }
30 |
--------------------------------------------------------------------------------
/samples/dynamodb/batch/batchGetItems.js:
--------------------------------------------------------------------------------
1 | import { util } from '@aws-appsync/utils';
2 |
3 | /**
4 | * Gets items from the DynamoDB tables in batches with the provided `id` keys
5 | * @param {import('@aws-appsync/utils').Context} ctx the context
6 | * @returns {import('@aws-appsync/utils').DynamoDBBatchGetItemRequest} the request
7 | */
8 | export function request(ctx) {
9 | return {
10 | operation: 'BatchGetItem',
11 | tables: {
12 | Posts: {
13 | keys: ctx.args.ids.map((id) => util.dynamodb.toMapValues({ id })),
14 | consistentRead: true,
15 | },
16 | },
17 | };
18 | }
19 |
20 | /**
21 | * Returns the BatchGetItem table items
22 | * @param {import('@aws-appsync/utils').Context} ctx the context
23 | * @returns {[*]} the items
24 | */
25 | export function response(ctx) {
26 | if (ctx.error) {
27 | util.error(ctx.error.message, ctx.error.type);
28 | }
29 | return ctx.result.data.Posts;
30 | }
31 |
--------------------------------------------------------------------------------
/samples/dynamodb/batch/batchPutItems.js:
--------------------------------------------------------------------------------
1 | import { util } from '@aws-appsync/utils';
2 |
3 | /**
4 | * Gets items from the DynamoDB tables in batches with the provided `id` keys
5 | * @param {import('@aws-appsync/utils').Context} ctx the context
6 | * @returns {import('@aws-appsync/utils').DynamoDBBatchPutItemRequest} the request
7 | */
8 | export function request(ctx) {
9 | return {
10 | operation: 'BatchPutItem',
11 | tables: {
12 | Posts: ctx.args.posts.map((post) => util.dynamodb.toMapValues(post)),
13 | },
14 | };
15 | }
16 |
17 | /**
18 | * Returns the BatchPutItem table results
19 | * @param {import('@aws-appsync/utils').Context} ctx the context
20 | * @returns {[*]} the items
21 | */
22 | export function response(ctx) {
23 | if (ctx.error) {
24 | util.error(ctx.error.message, ctx.error.type);
25 | }
26 | return ctx.result.data.Posts;
27 | }
28 |
--------------------------------------------------------------------------------
/samples/dynamodb/general/deleteItem.js:
--------------------------------------------------------------------------------
1 | import * as ddb from '@aws-appsync/utils/dynamodb';
2 |
3 | export const request = (ctx) => ddb.remove({ key: { id: ctx.args.id } });
4 | export const response = (ctx) => ctx.result;
5 |
--------------------------------------------------------------------------------
/samples/dynamodb/general/getItem.js:
--------------------------------------------------------------------------------
1 | import * as ddb from '@aws-appsync/utils/dynamodb';
2 |
3 | export const request = (ctx) => ddb.get({ key: { id: ctx.args.id } });
4 | export const response = (ctx) => ctx.result;
5 |
--------------------------------------------------------------------------------
/samples/dynamodb/general/listItems.js:
--------------------------------------------------------------------------------
1 | import * as ddb from '@aws-appsync/utils/dynamodb';
2 |
3 | export function request(ctx) {
4 | const { limit = 10, nextToken } = ctx.args;
5 | return ddb.scan({ limit, nextToken });
6 | }
7 |
8 | export function response(ctx) {
9 | const { items, nextToken } = ctx.result;
10 | return { items: items ?? [], nextToken };
11 | }
12 |
--------------------------------------------------------------------------------
/samples/dynamodb/general/putItem.js:
--------------------------------------------------------------------------------
1 | import * as ddb from '@aws-appsync/utils/dynamodb';
2 |
3 | export function request(ctx) {
4 | const key = { id: util.autoId() };
5 | const item = ctx.args.input;
6 | const condition = { id: { attributeExists: false } };
7 | return ddb.put({ key, item, condition });
8 | }
9 |
10 | export const response = (ctx) => ctx.result;
11 |
--------------------------------------------------------------------------------
/samples/dynamodb/general/updateIncrementCount.js:
--------------------------------------------------------------------------------
1 | import { util } from '@aws-appsync/utils';
2 | import * as ddb from '@aws-appsync/utils/dynamodb';
3 |
4 | export function request(ctx) {
5 | const { id, ...values } = ctx.args.input;
6 | values.version = ddb.operations.increment(1);
7 | const condition = { id: { attributeExists: true } };
8 | return ddb.update({ key: { id }, update: values, condition });
9 | }
10 |
11 | export function response(ctx) {
12 | const { error, result } = ctx;
13 | if (error) {
14 | return util.error(error.message, error.type);
15 | }
16 | return result;
17 | }
18 |
--------------------------------------------------------------------------------
/samples/dynamodb/general/updateItem.js:
--------------------------------------------------------------------------------
1 | import { util } from '@aws-appsync/utils';
2 | import * as ddb from '@aws-appsync/utils/dynamodb';
3 |
4 | export function request(ctx) {
5 | const { id, ...values } = ctx.args.input;
6 | const condition = { id: { attributeExists: true } };
7 | return ddb.update({ key: { id }, update: values, condition });
8 | }
9 |
10 | export function response(ctx) {
11 | const { error, result } = ctx;
12 | if (error) {
13 | return util.error(error.message, error.type);
14 | }
15 | return result;
16 | }
17 |
--------------------------------------------------------------------------------
/samples/dynamodb/queries/all-items-today.js:
--------------------------------------------------------------------------------
1 | import { util } from '@aws-appsync/utils';
2 | import * as ddb from '@aws-appsync/utils/dynamodb';
3 |
4 | /**
5 | * Queries a DynamoDB table and returns items created `today`
6 | * @param {import('@aws-appsync/utils').Context<{id: string}>} ctx the context
7 | * @returns {import('@aws-appsync/utils').DynamoDBQueryRequest} the request
8 | */
9 | export function request(ctx) {
10 | const today = util.time.nowISO8601().substring(0, 10);
11 | return ddb.query({
12 | query: { id: { eq: ctx.args.id }, createdAt: { beginsWith: today } },
13 | });
14 | }
15 |
16 | /**
17 | * Returns the query items
18 | * @param {import('@aws-appsync/utils').Context} ctx the context
19 | * @returns {[*]} a flat list of result items
20 | */
21 | export function response(ctx) {
22 | if (ctx.error) {
23 | util.error(ctx.error.message, ctx.error.type);
24 | }
25 | return ctx.result.items;
26 | }
27 |
--------------------------------------------------------------------------------
/samples/dynamodb/queries/pagination.js:
--------------------------------------------------------------------------------
1 | import { util } from '@aws-appsync/utils';
2 | import * as ddb from '@aws-appsync/utils/dynamodb';
3 |
4 | /**
5 | * Queries a DynamoDB table, limits the number of returned items, and paginates with the provided `nextToken`
6 | * @param {import('@aws-appsync/utils').Context<{id: string; limit?: number; nextToken?:string}>} ctx the context
7 | * @returns {import('@aws-appsync/utils').DynamoDBQueryRequest} the request
8 | */
9 | export function request(ctx) {
10 | const { id, limit = 20, nextToken } = ctx.args;
11 | return ddb.query({ query: { id: { eq: id } }, limit, nextToken });
12 | }
13 |
14 | /**
15 | * Returns the query items
16 | * @param {import('@aws-appsync/utils').Context} ctx the context
17 | * @returns {[*]} a flat list of result items
18 | */
19 | export function response(ctx) {
20 | if (ctx.error) {
21 | util.error(ctx.error.message, ctx.error.type);
22 | }
23 | return ctx.result.items;
24 | }
25 |
--------------------------------------------------------------------------------
/samples/dynamodb/queries/simple-query.js:
--------------------------------------------------------------------------------
1 | import * as ddb from '@aws-appsync/utils/dynamodb';
2 |
3 | export const request = (ctx) => ddb.query({ query: { id: { eq: ctx.args.id } } });
4 | export const response = (ctx) => ctx.result.items;
5 |
--------------------------------------------------------------------------------
/samples/dynamodb/queries/with-contains-expression.js:
--------------------------------------------------------------------------------
1 | import { util } from '@aws-appsync/utils';
2 | import * as ddb from '@aws-appsync/utils/dynamodb';
3 |
4 | /**
5 | * Queries a DynamoDB table for items based on the `id` and that contain the `tag`
6 | * @param {import('@aws-appsync/utils').Context<{id: string; tag:string}>} ctx the context
7 | * @returns {import('@aws-appsync/utils').DynamoDBQueryRequest} the request
8 | */
9 | export function request(ctx) {
10 | return ddb.query({
11 | query: { id: { eq: ctx.args.id } },
12 | filter: { tags: { contains: ctx.args.tag } },
13 | });
14 | }
15 |
16 | /**
17 | * Returns the query items
18 | * @param {import('@aws-appsync/utils').Context} ctx the context
19 | * @returns {[*]} a flat list of result items
20 | */
21 | export function response(ctx) {
22 | if (ctx.error) {
23 | util.error(ctx.error.message, ctx.error.type);
24 | }
25 | return ctx.result.items;
26 | }
27 |
--------------------------------------------------------------------------------
/samples/dynamodb/queries/with-filter-on-index.js:
--------------------------------------------------------------------------------
1 | import { util } from '@aws-appsync/utils';
2 | import * as ddb from '@aws-appsync/utils/dynamodb';
3 |
4 | export function request(ctx) {
5 | return ddb.query({
6 | index: 'name-index',
7 | query: { name: { eq: ctx.args.name } },
8 | filter: { city: { contains: ctx.args.city } },
9 | });
10 | }
11 |
12 | /**
13 | * Returns the query items
14 | * @param {import('@aws-appsync/utils').Context} ctx the context
15 | * @returns {[*]} a flat list of result items
16 | */
17 | export function response(ctx) {
18 | if (ctx.error) {
19 | util.error(ctx.error.message, ctx.error.type);
20 | }
21 | return ctx.result.items;
22 | }
23 |
--------------------------------------------------------------------------------
/samples/dynamodb/queries/with-greater-than.js:
--------------------------------------------------------------------------------
1 | import { util } from '@aws-appsync/utils';
2 | import * as ddb from '@aws-appsync/utils/dynamodb';
3 | /**
4 | * Queries a DynamoDB table for items created after a specified data (`createdAt`)
5 | * @param {import('@aws-appsync/utils').Context<{id: string; createdAt: string}>} ctx the context
6 | * @returns {import('@aws-appsync/utils').DynamoDBQueryRequest} the request
7 | */
8 | export function request(ctx) {
9 | const { id, createdAt } = ctx.args;
10 | return ddb.query({ query: { id: { eq: id } }, createdAt: { gt: createdAt } });
11 | }
12 |
13 | /**
14 | * Returns the query items
15 | * @param {import('@aws-appsync/utils').Context} ctx the context
16 | * @returns {[*]} a flat list of result items
17 | */
18 | export function response(ctx) {
19 | if (ctx.error) {
20 | util.error(ctx.error.message, ctx.error.type);
21 | }
22 | return ctx.result.items;
23 | }
24 |
--------------------------------------------------------------------------------
/samples/eventbridge/simple.js:
--------------------------------------------------------------------------------
1 | import { util } from '@aws-appsync/utils';
2 |
3 | /**
4 | * Sends an event to Event Bridge
5 | * @param {import('@aws-appsync/utils').Context} ctx the context
6 | * @returns {*} the request
7 | */
8 | export function request(ctx) {
9 | return {
10 | operation: 'PutEvents',
11 | events: [
12 | {
13 | source: ctx.source,
14 | detail: {
15 | key1: [1, 2, 3, 4],
16 | key2: 'strval',
17 | },
18 | detailType: 'sampleDetailType',
19 | resources: ['Resouce1', 'Resource2'],
20 | time: util.time.nowISO8601(),
21 | },
22 | ],
23 | };
24 | }
25 |
26 | /**
27 | * Process the response
28 | * @param {import('@aws-appsync/utils').Context} ctx the context
29 | * @returns {*} the EventBridge response
30 | */
31 | export function response(ctx) {
32 | return ctx.result;
33 | }
34 |
--------------------------------------------------------------------------------
/samples/http/forward.js:
--------------------------------------------------------------------------------
1 | import { util } from '@aws-appsync/utils';
2 |
3 | /**
4 | * Sends a GET request to retrieve a user's inforrmation
5 | * @param {import('@aws-appsync/utils').Context} ctx the context
6 | * @returns the request
7 | */
8 | export function request(ctx) {
9 | return fetch(ctx.args.path, {
10 | // headers: util.http.copyHeaders(ctx.request.headers),
11 | body: ctx.args.body, // can include in the body
12 | query: ctx.args.query, // or in the query string
13 | });
14 | }
15 |
16 | /**
17 | * Process the HTTP response
18 | * @param {import('@aws-appsync/utils').Context} ctx the context
19 | * @returns {*} the publish response
20 | */
21 | export function response(ctx) {
22 | const { statusCode, body } = ctx.result;
23 | // if response is 200, return the response
24 | if (statusCode === 200) {
25 | return body;
26 | }
27 | // if response is not 200, append the response to error block.
28 | util.appendError(body, statusCode);
29 | }
30 |
31 | /**
32 | * Sends an HTTP request
33 | * @param {string} resourcePath path of the request
34 | * @param {Object} [options] values to publish
35 | * @param {'PUT' | 'POST' | 'GET' | 'DELETE' | 'PATCH'} [options.method] the request method
36 | * @param {Object.} [options.headers] the request headers
37 | * @param {string | Object.} [options.body] the request body
38 | * @param {Object.} [options.query] Key-value pairs that specify the query string
39 | * @returns {import('@aws-appsync/utils').HTTPRequest} the request
40 | */
41 | function fetch(resourcePath, options) {
42 | const { method = 'GET', headers, body: _body, query = {} } = options;
43 | const [path, params] = resourcePath.split('?');
44 | console.log('params > ', params);
45 | if (params && params.length) {
46 | params.split('&').forEach((param) => {
47 | console.log('param > ', param);
48 | const [key, value] = param.split('=');
49 | query[key] = value;
50 | });
51 | }
52 | const body = typeof _body === 'object' ? JSON.stringify(_body) : _body;
53 | return {
54 | resourcePath: path,
55 | method,
56 | params: { headers, query, body },
57 | };
58 | }
59 |
--------------------------------------------------------------------------------
/samples/http/getToApiGW.js:
--------------------------------------------------------------------------------
1 | import { util } from '@aws-appsync/utils';
2 |
3 | /**
4 | * Sends a GET request to retrieve a user's inforrmation
5 | * @param {import('@aws-appsync/utils').Context} ctx the context
6 | * @returns the request
7 | */
8 | export function request(ctx) {
9 | return fetch(`/v1/users/${ctx.args.id}`, {
10 | headers: {
11 | 'Content-Type': 'application/json',
12 | },
13 | });
14 | }
15 |
16 | /**
17 | * Process the HTTP response
18 | * @param {import('@aws-appsync/utils').Context} ctx the context
19 | * @returns {*} the publish response
20 | */
21 | export function response(ctx) {
22 | const { statusCode, body } = ctx.result;
23 | // if response is 200, return the response
24 | if (statusCode === 200) {
25 | return body;
26 | }
27 | // if response is not 200, append the response to error block.
28 | util.appendError(body, statusCode);
29 | }
30 |
31 | /**
32 | * Sends an HTTP request
33 | * @param {string} resourcePath path of the request
34 | * @param {Object} [options] values to publish
35 | * @param {'PUT' | 'POST' | 'GET' | 'DELETE' | 'PATCH'} [options.method] the request method
36 | * @param {Object.} [options.headers] the request headers
37 | * @param {string | Object.} [options.body] the request body
38 | * @param {Object.} [options.query] Key-value pairs that specify the query string
39 | * @returns {import('@aws-appsync/utils').HTTPRequest} the request
40 | */
41 | function fetch(resourcePath, options) {
42 | const { method = 'GET', headers, body: _body, query } = options;
43 | const body = typeof _body === 'object' ? JSON.stringify(_body) : _body;
44 | return {
45 | resourcePath,
46 | method,
47 | params: { headers, query, body },
48 | };
49 | }
50 |
--------------------------------------------------------------------------------
/samples/http/publishToSNS.js:
--------------------------------------------------------------------------------
1 | import { util } from '@aws-appsync/utils';
2 |
3 | /**
4 | * Sends a publish request to the SNS topic
5 | * @param {import('@aws-appsync/utils').Context} ctx the context
6 | * @returns the request
7 | */
8 | export function request(ctx) {
9 | const TOPIC_ARN = '';
10 | const { input: values } = ctx.args;
11 | return publishToSNSRequest(TOPIC_ARN, values);
12 | }
13 |
14 | /**
15 | * Process the publish response
16 | * @param {import('@aws-appsync/utils').Context} ctx the context
17 | * @returns {string} the publish response
18 | */
19 | export function response(ctx) {
20 | const {
21 | result: { statusCode, body },
22 | } = ctx;
23 | if (statusCode === 200) {
24 | // if response is 200
25 | // parse the xml response
26 | return util.xml.toMap(body).PublishResponse.PublishResult;
27 | }
28 |
29 | // if response is not 200, append the response to error block.
30 | util.appendError(body, `${statusCode}`);
31 | }
32 |
33 | /**
34 | * Sends a publish request
35 | * @param {string} topicArn SNS topic ARN
36 | * @param {*} values values to publish
37 | * @returns {import('@aws-appsync/utils').HTTPRequest} the request
38 | */
39 | function publishToSNSRequest(topicArn, values) {
40 | const arn = util.urlEncode(topicArn);
41 | const tmp = JSON.stringify(values);
42 | const message = util.urlEncode(tmp);
43 | const Body = {
44 | Action: 'Publish',
45 | Version: '2010-03-31',
46 | topicArn: arn,
47 | Message: message,
48 | };
49 | const body = Object.entries(Body)
50 | .map(([k, v]) => `${k}=${v}`)
51 | .join('&');
52 |
53 | return fetch('/', {
54 | method: 'POST',
55 | body,
56 | headers: {
57 | 'content-type': 'application/x-www-form-urlencoded',
58 | },
59 | });
60 | }
61 |
62 | /**
63 | * Sends an HTTP request
64 | * @param {string} resourcePath path of the request
65 | * @param {Object} [options] values to publish
66 | * @param {'PUT' | 'POST' | 'GET' | 'DELETE' | 'PATCH'} [options.method] the request method
67 | * @param {Object.} [options.headers] the request headers
68 | * @param {string | Object.} [options.body] the request body
69 | * @param {Object.} [options.query] Key-value pairs that specify the query string
70 | * @returns {import('@aws-appsync/utils').HTTPRequest} the request
71 | */
72 | function fetch(resourcePath, options) {
73 | const { method = 'GET', headers, body: _body, query } = options;
74 | const body = typeof _body === 'object' ? JSON.stringify(_body) : _body;
75 | return {
76 | resourcePath,
77 | method,
78 | params: { headers, query, body },
79 | };
80 | }
81 |
--------------------------------------------------------------------------------
/samples/http/putToApiGW.js:
--------------------------------------------------------------------------------
1 | import { util } from '@aws-appsync/utils';
2 |
3 | /**
4 | * Sends a POST request to create a new user
5 | * @param {import('@aws-appsync/utils').Context<{input: {id: string; name:string}}>} ctx the context
6 | * @returns the request
7 | */
8 | export function request(ctx) {
9 | return fetch(`/v1/users`, {
10 | method: 'POST',
11 | headers: { 'Content-Type': 'application/json' },
12 | body: ctx.args.input,
13 | });
14 | }
15 |
16 | /**
17 | * Process the HTTP response
18 | * @param {import('@aws-appsync/utils').Context} ctx the context
19 | * @returns {*} the publish response
20 | */
21 | export function response(ctx) {
22 | const { statusCode, body } = ctx.result;
23 | // if response is 200, return the response
24 | if (statusCode === 200) {
25 | return body;
26 | }
27 | // if response is not 200, append the response to error block.
28 | util.appendError(body, statusCode);
29 | }
30 |
31 | /**
32 | * Sends an HTTP request
33 | * @param {string} resourcePath path of the request
34 | * @param {Object} [options] values to publish
35 | * @param {'PUT' | 'POST' | 'GET' | 'DELETE' | 'PATCH'} [options.method] the request method
36 | * @param {Object.} [options.headers] the request headers
37 | * @param {string | Object.} [options.body] the request body
38 | * @param {Object.} [options.query] Key-value pairs that specify the query string
39 | * @returns {import('@aws-appsync/utils').HTTPRequest} the request
40 | */
41 | function fetch(resourcePath, options) {
42 | const { method = 'GET', headers, body: _body, query } = options;
43 | const body = typeof _body === 'object' ? JSON.stringify(_body) : _body;
44 | return {
45 | resourcePath,
46 | method,
47 | params: { headers, query, body },
48 | };
49 | }
50 |
--------------------------------------------------------------------------------
/samples/http/translate.js:
--------------------------------------------------------------------------------
1 | import { util } from '@aws-appsync/utils';
2 |
3 | /**
4 | * Sends a tranlsate request to the Translate service
5 | * @param {import('@aws-appsync/utils').Context} ctx the context
6 | * @returns the request
7 | */
8 | export function request(ctx) {
9 | const { text = 'Hello World!', source = 'EN', target = 'FR' } = ctx.args;
10 | return awsTranslateRequest(text, source, target);
11 | }
12 |
13 | /**
14 | * Process the translate response
15 | * @param {import('@aws-appsync/utils').Context} ctx the context
16 | * @returns {string} the translated text
17 | */
18 | export function response(ctx) {
19 | const { result } = ctx;
20 | if (result.statusCode !== 200) {
21 | return util.appendError(result.body, `${result.statusCode}`);
22 | }
23 | const body = JSON.parse(result.body);
24 | return body.TranslatedText;
25 | }
26 |
27 | /**
28 | * Sends a request to the Translate service
29 | * @param {string} text text to translate
30 | * @param {string} source the source language code
31 | * @param {string} target the target language code
32 | * @returns {import('@aws-appsync/utils').HTTPRequest} the request
33 | */
34 | function awsTranslateRequest(text, source, target) {
35 | return fetch('/', {
36 | method: 'POST',
37 | headers: {
38 | 'content-type': 'application/x-amz-json-1.1',
39 | 'x-amz-target': 'AWSShineFrontendService_20170701.TranslateText',
40 | },
41 | body: { Text: text, SourceLanguageCode: source, TargetLanguageCode: target },
42 | });
43 | }
44 |
45 | /**
46 | * Sends an HTTP request
47 | * @param {string} resourcePath path of the request
48 | * @param {Object} [options] values to publish
49 | * @param {'PUT' | 'POST' | 'GET' | 'DELETE' | 'PATCH'} [options.method] the request method
50 | * @param {Object.} [options.headers] the request headers
51 | * @param {string | Object.} [options.body] the request body
52 | * @param {Object.} [options.query] Key-value pairs that specify the query string
53 | * @returns {import('@aws-appsync/utils').HTTPRequest} the request
54 | */
55 | function fetch(resourcePath, options) {
56 | const { method = 'GET', headers, body: _body, query } = options;
57 | const body = typeof _body === 'object' ? JSON.stringify(_body) : _body;
58 | return {
59 | resourcePath,
60 | method,
61 | params: { headers, query, body },
62 | };
63 | }
64 |
--------------------------------------------------------------------------------
/samples/lambda/invoke.js:
--------------------------------------------------------------------------------
1 | import { util } from '@aws-appsync/utils';
2 |
3 | /**
4 | * Sends a request to a Lambda function. Passes all information about the request from the `info` object.
5 | * @param {import('@aws-appsync/utils').Context} ctx the context
6 | * @returns {import('@aws-appsync/utils').LambdaRequest} the request
7 | */
8 | export function request(ctx) {
9 | const payload = {
10 | arguments: ctx.arguments,
11 | identity: ctx.identity,
12 | source: ctx.source,
13 | request: ctx.request,
14 | info: {
15 | fieldName: ctx.info.fieldName,
16 | parentTypeName: ctx.info.parentTypeName,
17 | variables: ctx.info.variables,
18 | selectionSetList: ctx.info.selectionSetList,
19 | selectionSetGraphQL: ctx.info.selectionSetGraphQL,
20 | },
21 | };
22 | return { operation: 'Invoke', payload };
23 | }
24 |
25 | /**
26 | * Process a Lambda function response
27 | * @param {import('@aws-appsync/utils').Context} ctx the context
28 | * @returns {*} the Lambda function response
29 | */
30 | export function response(ctx) {
31 | const { result, error } = ctx;
32 | if (error) {
33 | util.error(error.message, error.type, result);
34 | }
35 | return result;
36 | }
37 |
--------------------------------------------------------------------------------
/samples/opensearch/geo.js:
--------------------------------------------------------------------------------
1 | // TITLE: Get all documents within a 20 mile radius
2 |
3 | import { util } from '@aws-appsync/utils';
4 |
5 | /**
6 | * Searches for all documents using Geodistance aggregation
7 | * @param {import('@aws-appsync/utils').Context} ctx the context
8 | * @returns {*} the request
9 | */
10 | export function request(ctx) {
11 | // Replace with actual values, e.g.: post
12 | const index = '';
13 | return {
14 | operation: 'GET',
15 | path: `/${index}/_search`,
16 | params: {
17 | body: {
18 | query: {
19 | filtered: {
20 | query: { match_all: {} },
21 | filter: {
22 | geo_distance: {
23 | distance: '20mi',
24 | location: { lat: 47.6205, lon: 122.3493 },
25 | },
26 | },
27 | },
28 | },
29 | },
30 | },
31 | };
32 | }
33 |
34 | /**
35 | * Returns the fetched items
36 | * @param {import('@aws-appsync/utils').Context} ctx the context
37 | * @returns {*} the result
38 | */
39 | export function response(ctx) {
40 | if (ctx.error) {
41 | util.error(ctx.error.message, ctx.error.type);
42 | }
43 | return ctx.result.hits.hits.map((hit) => hit._source);
44 | }
45 |
--------------------------------------------------------------------------------
/samples/opensearch/getDocumentByID.js:
--------------------------------------------------------------------------------
1 | // TITLE: Get document by id
2 |
3 | import { util } from '@aws-appsync/utils';
4 |
5 | /**
6 | * Gets a document by `id`
7 | * @param {import('@aws-appsync/utils').Context} ctx the context
8 | * @returns {*} the request
9 | */
10 | export function request(ctx) {
11 | // Replace with actual values, e.g.: post
12 | const index = '';
13 | return {
14 | operation: 'GET',
15 | path: `/${index}/_doc/${ctx.args.id}`,
16 | };
17 | }
18 |
19 | /**
20 | * Returns the fetched item
21 | * @param {import('@aws-appsync/utils').Context} ctx the context
22 | * @returns {*} the result
23 | */
24 | export function response(ctx) {
25 | if (ctx.error) {
26 | util.error(ctx.error.message, ctx.error.type);
27 | }
28 | return ctx.result['_source'];
29 | }
30 |
--------------------------------------------------------------------------------
/samples/opensearch/paginate.js:
--------------------------------------------------------------------------------
1 | // TITLE: Paginate with fixed-size pages
2 |
3 | import { util } from '@aws-appsync/utils';
4 |
5 | /**
6 | * Paginates through search results using `from` and `size` arguments
7 | * @param {import('@aws-appsync/utils').Context} ctx the context
8 | * @returns {*} the request
9 | */
10 | export function request(ctx) {
11 | // Replace with actual values, e.g.: post
12 | const index = '';
13 | return {
14 | operation: 'GET',
15 | path: `/${index}/_search`,
16 | params: {
17 | body: {
18 | from: ctx.args.from ?? 0,
19 | size: ctx.args.size ?? 50,
20 | },
21 | },
22 | };
23 | }
24 |
25 | /**
26 | * Returns the fetched items
27 | * @param {import('@aws-appsync/utils').Context} ctx the context
28 | * @returns {*} the result
29 | */
30 | export function response(ctx) {
31 | if (ctx.error) {
32 | util.error(ctx.error.message, ctx.error.type);
33 | }
34 | return ctx.result.hits.hits.map((hit) => hit._source);
35 | }
36 |
--------------------------------------------------------------------------------
/samples/opensearch/simpleTermQuery.js:
--------------------------------------------------------------------------------
1 | // TITLE: Simple search
2 | // (default for OS Function)
3 |
4 | import { util } from '@aws-appsync/utils';
5 |
6 | /**
7 | * Searches for documents by using an input term
8 | * @param {import('@aws-appsync/utils').Context} ctx the context
9 | * @returns {*} the request
10 | */
11 | export function request(ctx) {
12 | // Replace with actual values, e.g.: post
13 | const index = '';
14 | return {
15 | operation: 'GET',
16 | path: `/${index}/_search`,
17 | params: {
18 | body: {
19 | from: 0,
20 | size: 50,
21 | query: {
22 | term: {
23 | '': ctx.args.field, // replace with your field
24 | },
25 | },
26 | },
27 | },
28 | };
29 | }
30 |
31 | /**
32 | * Returns the fetched items
33 | * @param {import('@aws-appsync/utils').Context} ctx the context
34 | * @returns {*} the result
35 | */
36 | export function response(ctx) {
37 | if (ctx.error) {
38 | util.error(ctx.error.message, ctx.error.type);
39 | }
40 | return ctx.result.hits.hits.map((hit) => hit._source);
41 | }
42 |
--------------------------------------------------------------------------------
/samples/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "samples",
3 | "version": "1.0.0",
4 | "lockfileVersion": 3,
5 | "requires": true,
6 | "packages": {
7 | "": {
8 | "name": "samples",
9 | "version": "1.0.0",
10 | "license": "ISC",
11 | "devDependencies": {
12 | "@aws-appsync/utils": "^1.3.1"
13 | }
14 | },
15 | "node_modules/@aws-appsync/utils": {
16 | "version": "1.3.1",
17 | "resolved": "https://registry.npmjs.org/@aws-appsync/utils/-/utils-1.3.1.tgz",
18 | "integrity": "sha512-wJdB1de0t5FxU1rnIIcSkbvMrONjuRpRegAcGbReClxoqajiR+U1PP0mylHgp9X3UTMe372wkuDFFABjTXqS0g==",
19 | "dev": true
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/samples/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "samples",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "keywords": [],
10 | "author": "",
11 | "license": "ISC",
12 | "devDependencies": {
13 | "@aws-appsync/utils": "^1.3.1"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/samples/pipeline/default.js:
--------------------------------------------------------------------------------
1 | import { util } from '@aws-appsync/utils';
2 |
3 | /**
4 | * Triggers the pipeline
5 | * @param {import('@aws-appsync/utils').Context} ctx the context
6 | * @returns an object that is send to the first function
7 | */
8 | export function request(ctx) {
9 | return {};
10 | }
11 |
12 | /**
13 | * Simply forwards the result
14 | * @param {import('@aws-appsync/utils').Context} ctx the context
15 | * @returns {*} the result from the last function in the pipeline
16 | */
17 | export const response = (ctx) => ctx.prev.result;
18 |
--------------------------------------------------------------------------------
/samples/rds/README.md:
--------------------------------------------------------------------------------
1 |
2 | # AppSyncJS built-in module for Amazon RDS
3 |
4 |
5 | - [AppSyncJS built-in module for Amazon RDS](#appsyncjs-built-in-module-for-amazon-rds)
6 | - [Functions](#functions)
7 | - [sql](#sql)
8 | - [Select](#select)
9 | - [Basic use](#basic-use)
10 | - [Specifying columns](#specifying-columns)
11 | - [Limits and offsets](#limits-and-offsets)
12 | - [Order By](#order-by)
13 | - [Filters](#filters)
14 | - [Joins](#joins)
15 | - [Aggregates](#aggregates)
16 | - [More on aliases](#more-on-aliases)
17 | - [Subqueries](#subqueries)
18 | - [Insert](#insert)
19 | - [Single item insertions](#single-item-insertions)
20 | - [MySQL use case](#mysql-use-case)
21 | - [Postgres use case](#postgres-use-case)
22 | - [Update](#update)
23 | - [Casting](#casting)
24 |
25 |
26 | AppSync's built-in module for Amazon RDS module provides an enhanced experience for interacting with Amazon Aurora databases configured with the Amazon RDS Data API. The module is imported using `@aws-appsync/utils/rds`:
27 |
28 | ```js
29 | import * as rds from '@aws-appsync/utils/rds';
30 | ```
31 |
32 | Functions can also be imported individually. For instance, the import below uses sql:
33 |
34 | ```js
35 | import { sql } from '@aws-appsync/utils/rds';
36 | ```
37 |
38 | The example in this folder are based on the `Chinook_PostgreSql.sql` database schema that you can find [here](https://github.com/lerocha/chinook-database/blob/master/ChinookDatabase/DataSources/Chinook_PostgreSql.sql). You can load this schema in your database and create an AppSync GraphQL API from the definition from the AppSync console. Learn more about the [introspection feature](https://docs.aws.amazon.com/appsync/latest/devguide/rds-introspection.html#using-introspection-console).
39 |
40 | ## Functions
41 |
42 | You can use the AWS AppSync RDS module's utility helpers to interact with your database.
43 |
44 | ### sql
45 |
46 | The `sql` tag template utility is your go-to tool to write SQL queries directly. Use this utility when the provided utilities below are not enough to create the statements that you need. You can also use the `sql` operator to customize certain fields of the `select`, `insert`, `update`, and `remove` utilities.
47 |
48 | ```js
49 | import { sql, createPgStatement as pg } from '@aws-appsync/utils/rds';
50 | export function request(ctx) {
51 | return pg(sql`select count(*) from album where artist_id = ${ctx.args.artist_id}`)
52 | }
53 | ```
54 |
55 | This will generate a query and automatically map dynamic values to placeholders. This approach is safer than writing queries directly and helps prevent potential SQL Injection vulnerabilities.
56 |
57 | In your logs, you see the following request.
58 |
59 | ```JSON
60 | {
61 | "statements": [
62 | "select count(*) from album where artist_id = :P0"
63 | ],
64 | "variableMap": {
65 | ":P0": 134
66 | },
67 | "variableTypeHintMap": {}
68 | }
69 | ```
70 |
71 | ### Select
72 |
73 | the select utility creates a `select` statement to query your relational database.
74 |
75 | #### Basic use
76 |
77 | In its basic form, you can specify the table you want to query:
78 |
79 | ```js
80 | import { select, createPgStatement as pg } from '@aws-appsync/utils/rds';
81 | export function request(ctx) {
82 | // Generates statement:
83 | // "SELECT * FROM "album"
84 | return pg(select({table: 'album'}));
85 | }
86 | ```
87 |
88 | Note that you can also specify the schema in your table identifier:
89 |
90 | ```js
91 | import { select, createPgStatement as pg } from '@aws-appsync/utils/rds';
92 | export function request(ctx) {
93 | // Generates statement:
94 | // SELECT * FROM "private"."album"
95 | return pg(select({table: 'private.album'}));
96 | }
97 | ```
98 |
99 | And you can specify an alias as well
100 |
101 | ```js
102 | import { select, createPgStatement as pg } from '@aws-appsync/utils/rds';
103 | export function request(ctx) {
104 | // Generates statement:
105 | // SELECT * FROM "private"."album" as "al"
106 | return pg(select({table: {al: 'private.album'}));
107 | }
108 |
109 | ```
110 |
111 | Handling the return
112 |
113 | You can return a list of items using the `toJsonObject` helper:
114 |
115 | ```javascript
116 | import { toJsonObject } from '@aws-appsync/utils/rds'
117 | export function response(ctx) {
118 | const { error, result } = ctx
119 | if (error) {
120 | return util.appendError(error.message, error.type, result)
121 | }
122 | return toJsonObject(result)[0]
123 | }
124 | ```
125 |
126 | To return a specific item, simply select an index from the array:
127 |
128 | ```javascript
129 | import { toJsonObject } from '@aws-appsync/utils/rds'
130 | export function response(ctx) {
131 | const { error, result } = ctx
132 | if (error) {
133 | return util.appendError(error.message, error.type, result)
134 | }
135 | return toJsonObject(result)[0][0]
136 | }
137 | ```
138 |
139 | #### Specifying columns
140 |
141 | You can specify columns with the columns property. If this isn't set to a value, it defaults to `*`:
142 |
143 | ```js
144 | import { select, createPgStatement as pg } from '@aws-appsync/utils/rds';
145 | export function request(ctx) {
146 | // Generates statement:
147 | // SELECT "id", "name"
148 | // FROM "album"
149 | return pg(select({
150 | table: 'album',
151 | columns: ['album_id', 'title']
152 | }));
153 | }
154 | ```
155 |
156 | You can specify a column's table as well:
157 |
158 | ```js
159 | import { select, createPgStatement as pg } from '@aws-appsync/utils/rds';
160 | export function request(ctx) {
161 | // Generates statement:
162 | // SELECT "id", "album"."name"
163 | // FROM "album"
164 | return pg(select({
165 | table: 'album',
166 | columns: ['album_id', 'album.title']
167 | }));
168 | }
169 | ```
170 |
171 | You can use aliases
172 |
173 | ```js
174 | import { select, createPgStatement as pg } from '@aws-appsync/utils/rds';
175 | export function request(ctx) {
176 | // Generates statement:
177 | // SELECT "id", "album"."title" as "name"
178 | // FROM "album"
179 | return pg(select({
180 | table: 'album',
181 | columns: ['album_id', { name: 'album.title' }]
182 | }));
183 | }
184 | ```
185 |
186 | #### Limits and offsets
187 |
188 | You can apply limit and offset to the query:
189 |
190 | ```js
191 | import { select, createPgStatement as pg } from '@aws-appsync/utils/rds';
192 | export function request(ctx) {
193 | // Generates statement:
194 | // SELECT "id", "name"
195 | // FROM "album"
196 | // LIMIT :limit
197 | // OFFSET :offset
198 | return pg(select({ table: 'album', limit: 10, offset: 40 }));
199 | }
200 | ```
201 |
202 | #### Order By
203 |
204 | You can sort your results with the `orderBy` property. Provide an array of objects specifying the column and an optional `dir` property:
205 |
206 | ```js
207 | import { select, createPgStatement as pg } from '@aws-appsync/utils/rds';
208 | export function request(ctx) {
209 | return pg(select({
210 | table: 'album',
211 | columns: ['album_id', 'artist_id', 'title'],
212 | orderBy: [{column: 'artist_id'}, {column: 'title', dir: 'DESC'}]
213 | }));
214 | }
215 | ```
216 |
217 | #### Filters
218 |
219 | You can build filters by using the special condition object:
220 |
221 | ```js
222 | import { select, createPgStatement as pg } from '@aws-appsync/utils/rds'
223 | export function request(ctx) {
224 | return pg(select({
225 | table: 'album',
226 | columns: ['album_id', 'artist_id', 'title'],
227 | where: {title: {beginsWith: 'W'}}
228 | }));
229 | }
230 | ```
231 |
232 | You can also combine filters:
233 |
234 | ```js
235 | import { select, createPgStatement as pg } from '@aws-appsync/utils/rds'
236 | export function request(ctx) {
237 | return pg(select({
238 | table: 'track',
239 | columns: ['track_id', 'album_id', 'milliseconds'],
240 | where: {album_id: {between: [1,2]}, milliseconds: {gt: 100_000}}
241 | }));
242 | }
243 | ```
244 |
245 | You can also create OR statements:
246 |
247 | ```js
248 | import { select, createPgStatement as pg } from '@aws-appsync/utils/rds'
249 | export function request(ctx) {
250 | return pg(select({
251 | table: 'track',
252 | columns: ['track_id', 'name'],
253 | where: { or: [
254 | { unit_price: { lt: 1} },
255 | { composer: { attributeExists: false } }
256 | ]}
257 | }));
258 | }
259 | ```
260 |
261 | You can also negate a condition with not:
262 |
263 | ```js
264 | import { select, createPgStatement as pg } from '@aws-appsync/utils/rds'
265 | export function request(ctx) {
266 | return createPgStatement(select({
267 | table: 'track',
268 | columns: ['track_id', 'name'],
269 | where: { not: [
270 | {or: [
271 | { unit_price: { lt: 1} },
272 | { composer: { attributeExists: false } }
273 | ]}
274 | ]}
275 | }));
276 | }
277 | ```
278 |
279 | You can use the following operators to compare values:
280 |
281 | | Operator | Description | Possible values |
282 | | --------------- | --------------- | --------------- |
283 | | eq | Equal | number, string, boolean |
284 | | ne | Not equal | number, string, boolean |
285 | | le | Less than or equal | number, string |
286 | | lt | Less than | number, string |
287 | | ge | Greater than or equal | number, string |
288 | | gt | Greater than | number, string |
289 | | contains | Like | string |
290 | | notContains | Not like | string |
291 | | beginsWith | Starts with prefix | string |
292 | | between | Between two values | number, string |
293 | | attributeExists | The attribute is not null | number, string, boolean |
294 | | size | checks the length of the element | string |
295 |
296 | You can use the `sql` helper to write custom conditions:
297 |
298 | ```javascript
299 | import { select, createPgStatement as pg, agg } from '@aws-appsync/utils/rds';
300 | export function request(ctx) {
301 | return pg(select({
302 | from: 'album',
303 | where: sql`length(title) > ${ctx.args.size}`
304 | }))
305 | }
306 | ```
307 |
308 | #### Joins
309 |
310 | You can use join in you select statements.
311 |
312 | ```js
313 | import { select, createPgStatement as pg, agg } from '@aws-appsync/utils/rds';
314 | export function request(ctx) {
315 | return pg(select({
316 | from: 'album',
317 | join: [{from: 'artist', using: ['artist_id']}]
318 | }))
319 | }
320 | ```
321 |
322 | Note: use `using` when both sides of the join use the same name for the joining column(s). To specify you custom condition, use `on` with the `sql` util.
323 |
324 | ```js
325 | import { select, createPgStatement as pg, agg } from '@aws-appsync/utils/rds';
326 | export function request(ctx) {
327 | return pg(select({
328 | from: 'album',
329 | join: [{from: 'artist', on: sql`album.some_id = artist.another_id`}]
330 | }))
331 | }
332 | ```
333 |
334 | The following join expressions are supported
335 |
336 | - join
337 | - innerJoin
338 | - leftJoin
339 | - leftOuterJoin
340 | - rightJoin
341 | - rightOuterJoin
342 | - fullOuterJoin - Postgres only
343 | - crossJoin
344 | - joinNatural
345 | - innerJoinNatural
346 | - leftJoinNatural
347 | - leftOuterJoinNatural
348 | - rightJoinNatural
349 | - rightOuterJoinNatural
350 | - fullOuterJoinNatural - Postgres only
351 |
352 | #### Aggregates
353 |
354 | With AppSync, you can do aggregations using the following functions: `min`, `minDistinct` , `max` , `maxDistinct` , `sum` , `sumDistinct` , `avg` , `avgDistinct` , `count` , `countDistinct`. When using aggregations, you can make use of `groupBy` and `having`
355 |
356 | To count the rows in a result: Get the number of albums for every artist with a minimum of 5 albums.
357 |
358 | ```js
359 | import { select, createPgStatement as pg, agg } from '@aws-appsync/utils/rds';
360 | export function request(ctx) {
361 | return pg(select({
362 | table : 'album',
363 | columns: ['artist_id', {count: agg.count('*')}],
364 | groupBy: ['artist_id'],
365 | having: {
366 | album_id: {
367 | count: {ge: 5}
368 | }
369 | }
370 | }))
371 | }
372 | ```
373 |
374 | #### More on aliases
375 |
376 | You can leverage aliases in your queries. Aliases are supported on the `table`, `from`, `columns` and `using` properties.
377 |
378 | ```js
379 | import { select, createPgStatement as pg } from '@aws-appsync/utils/rds';
380 | export function request(ctx) {
381 | return pg(select({
382 | table : {record: 'album' },
383 | columns: ['id', {name: 'title'}]
384 | }));
385 | }
386 | ```
387 |
388 | #### Subqueries
389 |
390 | You can use subqueries in your select statement by leveraging the `from` property. `from` works like `table`, but supports strings, aliases, `sql` and `select`!
391 |
392 | ```javascript
393 | import { select, createPgStatement as pg, agg } from '@aws-appsync/utils/rds';
394 | export function request(ctx) {
395 |
396 | // First, fetch all the albums that have more than 1 genre in their tracklist
397 | const sub = select({
398 | from: 'album',
399 | columns: [
400 | 'album_id', 'title', 'artist_id',
401 | {tracks: agg.count('track_id')},
402 | {genres: agg.countDistinct('genre_id')}
403 | ],
404 | join: [{from: 'track', using: ['album_id']}],
405 | groupBy: [1], // you can use ordinal in the groupBy close
406 | having: {
407 | 'genre_id': {
408 | countDistinct: {gt: 1}
409 | }
410 | },
411 | orderBy: [{column: 'genres', dir: 'desc'}]
412 | })
413 |
414 | // next, use the subquery and retrieve the name of the artist for those albums
415 | return pg(select({
416 | from: { sub }, // an identifier or an alias.
417 | columns: ['album_id', 'title', 'name'],
418 | join: [{from: 'artist', using: ['artist_id']}]
419 | }))
420 | }
421 | ```
422 |
423 | ### Insert
424 |
425 | The insert utility provides a straightforward way of inserting single row items in your database with the INSERT operation.
426 |
427 | #### Single item insertions
428 |
429 | To insert an item, specify the table and then pass in your object of values. The object keys are mapped to your table columns. Columns names are automatically escaped, and values are sent to the database using the variable map:
430 |
431 | ```js
432 | import { insert, createMySQLStatement as mysql } from '@aws-appsync/utils/rds';
433 | export function request(ctx) {
434 | // Generates statement:
435 | // INSERT INTO `album`(`title`, `artist_id`)
436 | // VALUES(:title, :artist_id)
437 | return mysql(insert({ table: 'album', values: ctx.args.input }))
438 | }
439 | ```
440 |
441 | #### MySQL use case
442 |
443 | You can combine an insert followed by a select to retrieve your inserted row:
444 |
445 | ```js
446 | import { insert, select, createMySQLStatement as mysql } from '@aws-appsync/utils/rds';
447 | export function request(ctx) {
448 | const { input: values } = ctx.args;
449 | const insertStatement = insert({ table: 'album', values });
450 | const selectStatement = select({
451 | table: 'album',
452 | columns: '*',
453 | where: { id: { eq: values.id } },
454 | limit: 1,
455 | });
456 |
457 | // Generates statement:
458 | // INSERT INTO `album`(`album_id`, `title`)
459 | // VALUES(:ALBUM_ID, :TITLE)
460 | // and
461 | // SELECT *
462 | // FROM `album`
463 | // WHERE `album_id` = :ALBUM_ID
464 | return mysql(insertStatement, selectStatement)
465 | }
466 | ```
467 |
468 | #### Postgres use case
469 |
470 | With Postgres, you can use returning
471 |
472 | to obtain data from the row that you inserted. It accepts * or an array of column names:
473 |
474 | ```js
475 | import { insert, createPgStatement as pg } from '@aws-appsync/utils/rds';
476 | export function request(ctx) {
477 | const { input: values } = ctx.args;
478 | const statement = insert({
479 | table: 'album',
480 | values,
481 | returning: '*'
482 | });
483 | return pg(statement)
484 | }
485 | ```
486 |
487 | ### Update
488 |
489 | The update utility allows you to update existing rows. You can use the condition object to apply changes to the specified columns in all the rows that satisfy the condition. For example, let's say we have a schema that allows us to make this mutation. We want to update the name of Person with the id value of 3 but only if we've known them (known_since) since the year 2000:
490 |
491 | ```graphql
492 | mutation Update {
493 | updatePerson(
494 | input: {id: 3, name: "Jon"},
495 | condition: {known_since: {ge: "2000"}}
496 | ) {
497 | id
498 | name
499 | }
500 | }
501 | ```
502 |
503 | Our update resolver looks like this:
504 |
505 | ```js
506 | import { update, createPgStatement as pg } from '@aws-appsync/utils/rds';
507 | export function request(ctx) {
508 | const { input: { id, ...values }, condition } = ctx.args;
509 | const where = { ...condition, id: { eq: id } };
510 | const statement = update({
511 | table: 'persons',
512 | values,
513 | where,
514 | returning: ['id', 'name'],
515 | });
516 |
517 | // Generates statement:
518 | // UPDATE "persons"
519 | // SET "name" = :NAME, "birthday" = :BDAY, "country" = :COUNTRY
520 | // WHERE "id" = :ID
521 | // RETURNING "id", "name"
522 | return pg(statement)
523 | }
524 | ```
525 |
526 | We can add a check to our condition to make sure that only the row that has the primary key id equal to 3 is updated. Similarly, for Postgres inserts, you can use returning to return the modified data.
527 | Remove
528 |
529 | The remove utility allows you to delete existing rows. You can use the condition object on all rows that satisfy the condition. Note that delete is a reserved keyword in JavaScript. remove should be used instead:
530 |
531 | ```js
532 | import { remove, createPgStatement as pg } from '@aws-appsync/utils/rds';
533 | export function request(ctx) {
534 | const { input: { id }, condition } = ctx.args;
535 | const where = { ...condition, id: { eq: id } };
536 | const statement = remove({
537 | table: 'persons',
538 | where,
539 | returning: ['id', 'name'],
540 | });
541 |
542 | // Generates statement:
543 | // DELETE "persons"
544 | // WHERE "id" = :ID
545 | // RETURNING "id", "name"
546 | return pg(statement)
547 | }
548 | ```
549 |
550 | ### Casting
551 |
552 | In some cases, you may want more specificity about the correct object type to use in your statement. You can use the provided type hints to specify the type of your parameters. AWS AppSync supports the same type hints as the Data API. You can cast your parameters by using the `typeHint` functions from the AWS AppSync rds module.
553 |
554 | The following example allows you to send an array as a value that is casted as a JSON object. We use the -> operator to retrieve the element at the index 2 in the JSON array:
555 |
556 | ```js
557 | import { sql, createPgStatement as pg, toJsonObject, typeHint } from '@aws-appsync/utils/rds';
558 |
559 | export function request(ctx) {
560 | const arr = ctx.args.list_of_ids
561 | const statement = sql`select ${typeHint.JSON(arr)}->2 as value`
562 | return pg(statement)
563 | }
564 |
565 | export function response(ctx) {
566 | return toJsonObject(ctx.result)[0][0].value
567 | }
568 | ```
569 |
570 | Casting is also useful when handling and comparing DATE, TIME, and TIMESTAMP:
571 |
572 | ```js
573 | import { select, createPgStatement as pg, typeHint } from '@aws-appsync/utils/rds';
574 | export function request(ctx) {
575 | const when = ctx.args.when
576 | const statement = select({
577 | table: 'persons',
578 | where: { createdAt : { gt: typeHint.DATETIME(when) } }
579 | })
580 | return pg(statement)
581 | }
582 | ```
583 |
584 | Here's another example showing how you can send the current date and time:
585 |
586 | ```js
587 | import { sql, createPgStatement as pg, typeHint } from '@aws-appsync/utils/rds';
588 |
589 | export function request(ctx) {
590 | const now = util.time.nowFormatted('YYYY-MM-dd HH:mm:ss')
591 | return createPgStatement(sql`select ${typeHint.TIMESTAMP(now)}`)
592 | }
593 | ```
594 |
595 | Available type hints
596 |
597 | - `typeHint.DATE` - The corresponding parameter is sent as an object of the DATE type to the database. The accepted format is YYYY-MM-DD.
598 | - `typeHint.DECIMAL` - The corresponding parameter is sent as an object of the DECIMAL type to the database.
599 | - `typeHint.JSON` - The corresponding parameter is sent as an object of the JSON type to the database.
600 | - `typeHint.TIME` - The corresponding string parameter value is sent as an object of the TIME type to the database. The accepted format is HH:MM:SS[.FFF].
601 | - `typeHint.TIMESTAMP` - The corresponding string parameter value is sent as an object of the TIMESTAMP type to the database. The accepted format is YYYY-MM-DD HH:MM:SS[.FFF].
602 | - `typeHint.UUID` - The corresponding string parameter value is sent as an object of the UUID type to the database.
603 |
--------------------------------------------------------------------------------
/samples/rds/queries/invoice.js:
--------------------------------------------------------------------------------
1 | import { util } from '@aws-appsync/utils'
2 | import {
3 | select,
4 | toJsonObject,
5 | createPgStatement as pg,
6 | agg,
7 | typeHint as th,
8 | } from '@aws-appsync/utils/rds'
9 |
10 | export function request(ctx) {
11 | const query = select({
12 | from: { i: 'invoice' },
13 | columns: [agg.count('i.invoice_id'), agg.sum('i.total')],
14 | where: {
15 | invoice_date: {
16 | between: [th.TIMESTAMP('2021-01-01 00:00:00'), th.TIMESTAMP('2022-12-31 00:00:00')],
17 | },
18 | },
19 | })
20 | return pg(query)
21 | }
22 |
23 | export function response(ctx) {
24 | const { error, result } = ctx
25 | if (error) {
26 | return util.appendError(error.message, error.type, result)
27 | }
28 | return toJsonObject(result)[0]
29 | }
30 |
--------------------------------------------------------------------------------
/samples/rds/queries/invoices.js:
--------------------------------------------------------------------------------
1 | import { util } from '@aws-appsync/utils'
2 | import { select, toJsonObject, createPgStatement as pg } from '@aws-appsync/utils/rds'
3 |
4 | export function request(ctx) {
5 | const query = select({
6 | columns: ['i.invoice_line_id', { track: 't.name' }, { artist: 'ar.name' }],
7 | from: { i: 'invoice_line' },
8 | join: [
9 | { from: { t: 'track' }, using: ['track_id'] },
10 | { from: { al: 'album' }, using: ['album_id'] },
11 | { from: { ar: 'artist' }, using: ['artist_id'] },
12 | ],
13 | })
14 | return pg(query)
15 | }
16 |
17 | export function response(ctx) {
18 | const { error, result } = ctx
19 | if (error) {
20 | return util.appendError(error.message, error.type, result)
21 | }
22 | return toJsonObject(result)[0]
23 | }
24 |
--------------------------------------------------------------------------------
/samples/rds/queries/playlist_count.js:
--------------------------------------------------------------------------------
1 | import { util } from '@aws-appsync/utils'
2 | import {
3 | select,
4 | toJsonObject,
5 | createPgStatement as pg,
6 | agg,
7 | typeHint as th,
8 | } from '@aws-appsync/utils/rds'
9 |
10 | // -- 16. Provide a query that shows all the Tracks, but displays no IDs. The resultant table should include the Album name, Media type and Genre.
11 | export function request(ctx) {
12 | const query = select({
13 | columns: [
14 | { track: 't.name' },
15 | 't.composer',
16 | 't.milliseconds',
17 | 't.bytes',
18 | 't.unit_price',
19 | { album: 'a.title' },
20 | { genre: 'g.name' },
21 | { 'media type': 'm.name' },
22 | ],
23 | from: { t: 'track' },
24 | join: [
25 | { from: { a: 'album' }, using: ['album_id'] },
26 | { from: { g: 'genre' }, using: ['genre_id'] },
27 | { from: { m: 'media_type' }, using: ['media_type_id'] },
28 | ],
29 | })
30 | return pg(query)
31 | }
32 |
33 | export function response(ctx) {
34 | const { error, result } = ctx
35 | if (error) {
36 | return util.appendError(error.message, error.type, result)
37 | }
38 | return toJsonObject(result)[0]
39 | }
40 |
--------------------------------------------------------------------------------
/samples/rds/queries/sales.js:
--------------------------------------------------------------------------------
1 | import { util } from '@aws-appsync/utils'
2 | import {
3 | select,
4 | toJsonObject,
5 | createPgStatement as pg,
6 | agg,
7 | typeHint as th,
8 | } from '@aws-appsync/utils/rds'
9 |
10 | export function request(ctx) {
11 | const query = select({
12 | columns: [{ total: agg.max('total') }],
13 | from: { t: 'track' },
14 | join: [
15 | { from: { a: 'album' }, using: ['album_id'] },
16 | { from: { g: 'genre' }, using: ['genre_id'] },
17 | { from: { m: 'media_type' }, using: ['media_type_id'] },
18 | ],
19 | })
20 | return pg(query)
21 | }
22 |
23 | export function response(ctx) {
24 | const { error, result } = ctx
25 | if (error) {
26 | return util.appendError(error.message, error.type, result)
27 | }
28 | return toJsonObject(result)[0]
29 | }
30 |
--------------------------------------------------------------------------------
/samples/rds/queries/select.js:
--------------------------------------------------------------------------------
1 | import { util } from '@aws-appsync/utils'
2 | import { select, toJsonObject, createPgStatement as pg, agg } from '@aws-appsync/utils/rds'
3 |
4 | export function request(ctx) {
5 | const query = select({
6 | from: 'album',
7 | columns: ['name', { count: agg.count('*') }],
8 | join: [{ from: 'artist', using: ['artist_id'] }],
9 | groupBy: ['name'],
10 | having: {
11 | album_id: { count: { gt: 1 } },
12 | },
13 | orderBy: [{ column: 'name' }],
14 | })
15 | return pg(query)
16 | }
17 |
18 | export function response(ctx) {
19 | const { error, result } = ctx
20 | if (error) {
21 | return util.appendError(error.message, error.type, result)
22 | }
23 | return toJsonObject(result)[0]
24 | }
25 |
--------------------------------------------------------------------------------
/samples/rds/queries/sql.js:
--------------------------------------------------------------------------------
1 | import { util } from '@aws-appsync/utils'
2 | import { sql, toJsonObject, createPgStatement as pg } from '@aws-appsync/utils/rds'
3 |
4 | export function request(ctx) {
5 | return pg(sql`select count(*) from album where artist_id = ${ctx.args.artist_id}`)
6 | }
7 |
8 | export function response(ctx) {
9 | const { error, result } = ctx
10 | if (error) {
11 | return util.appendError(error.message, error.type, result)
12 | }
13 | return toJsonObject(result)[0][0]
14 | }
15 |
--------------------------------------------------------------------------------
/samples/rds/queries/subquery.js:
--------------------------------------------------------------------------------
1 | import { select, createPgStatement as pg, agg } from '@aws-appsync/utils/rds'
2 | export function request(ctx) {
3 | // First, fetch all the albums that have more than 1 genre in their tracklist
4 | const sub = select({
5 | from: 'album',
6 | columns: [
7 | 'album_id',
8 | 'title',
9 | 'artist_id',
10 | { tracks: agg.count('track_id') },
11 | { genres: agg.countDistinct('genre_id') },
12 | ],
13 | join: [{ from: 'track', using: ['album_id'] }],
14 | groupBy: [1], // you can use ordinal in the groupBy close
15 | having: {
16 | genre_id: {
17 | countDistinct: { gt: 1 },
18 | },
19 | },
20 | orderBy: [{ column: 'genres', dir: 'desc' }],
21 | })
22 |
23 | // next, use the subquery and retrieve the name of the artist for those albums
24 | return pg(
25 | select({
26 | from: { sub }, // an identifier or an alias.
27 | columns: ['album_id', 'title', 'name'],
28 | join: [{ from: 'artist', using: ['artist_id'] }],
29 | }),
30 | )
31 | }
32 |
--------------------------------------------------------------------------------
/scripts/evaluate.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -o errexit -o nounset -o pipefail
3 | shopt -s nullglob
4 |
5 | script_path=$(
6 | cd "$(dirname "${BASH_SOURCE[0]}")"
7 | pwd -P
8 | )
9 |
10 |
11 | node "$script_path/evaluate/index.mjs" "$@"
--------------------------------------------------------------------------------
/scripts/evaluate/index.mjs:
--------------------------------------------------------------------------------
1 | import { execSync } from 'child_process';
2 | import { argv } from 'node:process';
3 | import { program } from 'commander';
4 | import { readFileSync } from 'fs';
5 |
6 | program
7 | .argument('')
8 | .option('-f, --fn ', 'the function to evaluate', 'request')
9 | .option('-c, --context ', 'your context')
10 | .option('-d, --debug', 'debug mode')
11 | .action((resolver, { fn = 'request', context, debug }) => {
12 | try {
13 | const ctx = !context ? '{}' : `file://${context}`;
14 | const RUNTIME = 'name=APPSYNC_JS,runtimeVersion=1.0.0';
15 | const _fn =
16 | fn === 'req' || fn === 'request'
17 | ? 'request'
18 | : fn === 'res' || fn === 'response'
19 | ? 'response'
20 | : 'unknown';
21 |
22 | if (_fn === 'unknown') {
23 | console.error('unknown `fn` value: ', fn);
24 | return;
25 | }
26 | const buffer = execSync(
27 | `aws appsync evaluate-code --code file://${resolver} --context ${ctx} --function ${_fn} --runtime ${RUNTIME}`
28 | );
29 | const json = JSON.parse(buffer.toString());
30 | if (debug) {
31 | console.debug(json);
32 | }
33 | if (json.error) {
34 | console.log('\x1b[31m');
35 | console.log('Error');
36 | console.log('-----\n');
37 | console.error(json.error.message);
38 | if (json.error.codeErrors) {
39 | console.log(json.error.codeErrors);
40 | }
41 | console.log('\x1b[0m');
42 | return;
43 | }
44 | console.log();
45 | console.log('\x1b[7mResult\x1b[0m');
46 | console.log('------\n');
47 | const b = execSync(`echo '${json.evaluationResult}' | jq`, { stdio: 'inherit' });
48 | console.log();
49 | console.log('\x1b[7mLogs\x1b[0m');
50 | console.log('----\n');
51 | json.logs.forEach((l) => console.log(l));
52 | console.log();
53 | // console.log(JSON.stringify(JSON.parse(buffer.toString()).evaluationResult, null, 2))
54 | } catch (error) {
55 | if (debug && buffer) {
56 | console.debug(buffer.toString());
57 | }
58 | console.error(error.message);
59 | }
60 | });
61 | program.parse();
62 |
--------------------------------------------------------------------------------
/scripts/evaluate/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "evaluate",
3 | "version": "1.0.0",
4 | "lockfileVersion": 3,
5 | "requires": true,
6 | "packages": {
7 | "": {
8 | "name": "evaluate",
9 | "version": "1.0.0",
10 | "license": "ISC",
11 | "dependencies": {
12 | "commander": "^11.0.0"
13 | }
14 | },
15 | "node_modules/commander": {
16 | "version": "11.0.0",
17 | "resolved": "https://registry.npmjs.org/commander/-/commander-11.0.0.tgz",
18 | "integrity": "sha512-9HMlXtt/BNoYr8ooyjjNRdIilOTkVJXB+GhxMTtOKwk0R4j4lS4NpjuqmRxroBfnfTSHQIHQB7wryHhXarNjmQ==",
19 | "engines": {
20 | "node": ">=16"
21 | }
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/scripts/evaluate/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "evaluate",
3 | "type": "module",
4 | "version": "1.0.0",
5 | "description": "evaluate AppSync resolvers and functions",
6 | "main": "index.mjs",
7 | "scripts": {
8 | "test": "echo \"Error: no test specified\" && exit 1"
9 | },
10 | "keywords": [],
11 | "author": "",
12 | "license": "ISC",
13 | "dependencies": {
14 | "commander": "^11.0.0"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------