├── .babelrc ├── .editorconfig ├── .github └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── backend ├── schema.graphql └── template.yml ├── package-lock.json ├── package.json ├── script ├── deploy ├── publish ├── serve └── with-appsync-env ├── src ├── components │ ├── App.js │ ├── CreateTodo.js │ ├── Todo.js │ ├── TodoList.js │ └── __generated__ │ │ ├── AppQuery.graphql.js │ │ ├── CreateTodoMutation.graphql.js │ │ ├── CreateTodoSubscription.graphql.js │ │ ├── CreateTodo_viewer.graphql.js │ │ ├── TodoDeleteMutation.graphql.js │ │ ├── TodoListDeleteSubscription.graphql.js │ │ ├── TodoListPaginationQuery.graphql.js │ │ ├── TodoListUpdateSubscription.graphql.js │ │ ├── TodoList_viewer.graphql.js │ │ ├── TodoUpdateMutation.graphql.js │ │ ├── Todo_todo.graphql.js │ │ └── Todo_viewer.graphql.js ├── environment.js └── index.js └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-react" 4 | ], 5 | "plugins": [ 6 | "relay" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | *Issue #, if available:* 2 | 3 | *Description of changes:* 4 | 5 | 6 | By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice. 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.environment 2 | /node_modules/ 3 | /dist/ 4 | /.cfn-tmp/ -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check [existing open](https://github.com/aws-samples/aws-appsync-relay/issues), or [recently closed](https://github.com/aws-samples/aws-appsync-relay/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aclosed%20), issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *master* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any ['help wanted'](https://github.com/aws-samples/aws-appsync-relay/labels/help%20wanted) issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](https://github.com/aws-samples/aws-appsync-relay/blob/master/LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 60 | 61 | We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes. 62 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2018 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 this 4 | software and associated documentation files (the "Software"), to deal in the Software 5 | without restriction, including without limitation the rights to use, copy, modify, 6 | merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 7 | 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 IMPLIED, 10 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 11 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 12 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 13 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 14 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | AWS AppSync Relay 2 | ================= 3 | 4 | This sample project shows how to use the Relay library together with an AWS AppSync backend. See [the blog post](https://medium.com/open-graphql/using-relay-with-aws-appsync-55c89ca02066) for more in-depth discussion. 5 | 6 | Here are links to some of the key technologies used in the project: 7 | 8 | - [AWS AppSync](https://docs.aws.amazon.com/appsync/latest/devguide/welcome.html) 9 | - The [AWS Amplify Library](https://aws-amplify.github.io/amplify-js/media/quick_start?platform=purejs) 10 | - [AWS CloudFormation](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/Welcome.html) 11 | - [React](https://reactjs.org/docs/getting-started.html) 12 | - [Relay](https://facebook.github.io/relay/docs/en/introduction-to-relay.html) 13 | 14 | ## Launching the AppSync backend 15 | 16 | To run locally, you'll first need to deploy the backend in AWS, like this: 17 | 18 | ``` 19 | aws cloudformation package --template-file backend/template.yml --s3-bucket my-package-bucket > packaged_template.yml 20 | aws cloudformation deploy --template-file packaged_template.yml --stack-name AppSyncRelay --capabilities CAPABILITY_NAMED_IAM 21 | ``` 22 | 23 | Replace "my-package-bucket" with any S3 bucket you want to use to store files required by the CloudFormation stack, such as the GraphQL schema. Note that the `--capabilities` option is required to allow CloudFormation to create IAM roles. 24 | 25 | You can also do this with the `script/deploy` script, which requires setting the `PACKAGE_BUCKET` env var. You can also optionally set `STACK_NAME` for the name of the CloudFormation stack (the default is `AppSyncRelay`). Note that executable scripts in the `script` directory assume a Unix/macOS/Linux environment, Bash, and `jq` are available. 26 | 27 | ## Running the app locally 28 | 29 | To point the app to the right backend resources, you'll need to set an env var with config info like this: 30 | ``` 31 | APPSYNC_CONFIG='{ 32 | "UserPool": "", 33 | "AppSyncEndpoint": "", 34 | "ClientId": "", 35 | "AppSyncRegion": "" 36 | }' 37 | ``` 38 | 39 | You can then run the app locally like this: 40 | 41 | ``` 42 | npm install 43 | npm run serve 44 | ``` 45 | 46 | The app will be available at localhost:8080. 47 | 48 | To automatically pull down the appropriate backend configuration from CloudFormation and run the app, you can also use `script/serve`. This requires the `STACK_NAME` env var to be set to the CloudFormation stack name. 49 | 50 | ## Deploying the frontend as a static site 51 | 52 | If you want to deploy the frontend to the cloud as well, you can host it as a static site on S3. Once you have [configured a bucket appropriately](https://docs.aws.amazon.com/AmazonS3/latest/dev/WebsiteHosting.html), you can build for production and deploy like this (you will again need backend config information exported in the `APPSYNC_CONFIG` env var as above): 53 | 54 | ``` 55 | npm run build 56 | aws s3 sync dist s3://my-static-site-bucket 57 | ``` 58 | 59 | Here "my-static-site-bucket" should be replaced with your S3 bucket configured for static website hosting. You can alternatively use the `script/publish` script, which accepts `STATIC_SITE_BUCKET` as an env var. 60 | 61 | ## License Summary 62 | 63 | This sample code is made available under a modified MIT license. See the LICENSE file. 64 | -------------------------------------------------------------------------------- /backend/schema.graphql: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | 4 | type PageInfo { 5 | hasNextPage: Boolean! 6 | hasPreviousPage: Boolean! 7 | endCursor: String 8 | startCursor: String 9 | } 10 | 11 | interface Node { 12 | id: ID 13 | } 14 | 15 | type Todo implements Node { 16 | id: ID 17 | userId: ID! 18 | createdAt: String! 19 | text: String! 20 | complete: Boolean! 21 | } 22 | 23 | type TodoConnection { 24 | nextToken: String 25 | edges: [TodoEdge!] 26 | pageInfo: PageInfo! 27 | } 28 | 29 | type TodoEdge { 30 | node: Todo! 31 | cursor: String 32 | } 33 | 34 | input CreateTodoInput { 35 | text: String! 36 | clientMutationId: ID 37 | } 38 | 39 | type CreateTodoPayload { 40 | edge: TodoEdge 41 | clientMutationId: ID 42 | userId: ID! 43 | } 44 | 45 | input UpdateTodoInput { 46 | id: ID! 47 | complete: Boolean 48 | clientMutationId: ID 49 | } 50 | 51 | type UpdateTodoPayload { 52 | node: Todo 53 | clientMutationId: ID 54 | userId: ID! 55 | } 56 | 57 | input DeleteTodoInput { 58 | id: ID! 59 | clientMutationId: ID 60 | } 61 | 62 | type DeleteTodoPayload { 63 | deletedId: ID 64 | clientMutationId: ID 65 | userId: ID! 66 | } 67 | 68 | type Viewer implements Node { 69 | id: ID 70 | listTodos(after: String, first: Int): TodoConnection! 71 | } 72 | 73 | type Query { 74 | viewer: Viewer! 75 | # Per https://github.com/facebook/relay/issues/1653, this field is optional, 76 | # as it's up to the app developer to use. We won't use it in this example 77 | # but it would be easy to add if desired 78 | # node(id: ID!): Node 79 | } 80 | 81 | type Mutation { 82 | createTodo(input: CreateTodoInput!): CreateTodoPayload! 83 | updateTodo(input: UpdateTodoInput!): UpdateTodoPayload! 84 | deleteTodo(input: DeleteTodoInput!): DeleteTodoPayload! 85 | } 86 | 87 | type Subscription { 88 | createdTodo(userId: ID!): CreateTodoPayload 89 | @aws_subscribe(mutations: ["createTodo"]) 90 | updatedTodo(userId: ID!): UpdateTodoPayload 91 | @aws_subscribe(mutations: ["updateTodo"]) 92 | deletedTodo(userId: ID!): DeleteTodoPayload 93 | @aws_subscribe(mutations: ["deleteTodo"]) 94 | } 95 | 96 | schema { 97 | query: Query 98 | mutation: Mutation 99 | subscription: Subscription 100 | } 101 | -------------------------------------------------------------------------------- /backend/template.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | 4 | --- 5 | AWSTemplateFormatVersion: 2010-09-09 6 | Transform: AWS::Serverless-2016-10-31 7 | Metadata: 8 | ResolverTemplates: 9 | - &EmptyRequest | 10 | { 11 | "version": "2017-02-28", 12 | "payload": {} 13 | } 14 | - &ViewerResponse | 15 | {"id": "$context.identity.sub"} 16 | - &ListRequest | 17 | { 18 | "version": "2017-02-28", 19 | "operation": "Query", 20 | "query": { 21 | "expression": "userId = :u", 22 | "expressionValues": { 23 | ":u": {"S": "$context.identity.sub"} 24 | } 25 | }, 26 | "index": "CreatedAtIndex", 27 | "nextToken": #if($context.args.after) "$context.args.after" #else null #end, 28 | "limit": #if($context.args.first) $context.args.first #else 10 #end, 29 | "scanIndexForward": false, 30 | "consistentRead": false, 31 | "select": "ALL_ATTRIBUTES" 32 | } 33 | - &ListResponse | 34 | #set($edges = []) 35 | #foreach($item in $context.result.items) 36 | ## Omitting the cursor field on EdgeType is a bit of a departure 37 | ## from the Relay spec, but AppSync DynamoDB resolver mappings 38 | ## deal strictly in opaque, page-level pagination tokens. 39 | ## As of Relay 1.6.0, the cursor field on EdgeType is not used internally, 40 | ## and the only consequence of this field missing is that the optional cursor 41 | ## argument to `ConnectionHandler.inserteEdgeBefore/After` will have no effect. 42 | ## See also https://github.com/facebook/relay/issues/2457 43 | $util.quiet($edges.add({"node": $item, "cursor": null})) 44 | #end 45 | { 46 | "edges": $util.toJson($edges), 47 | "pageInfo": { 48 | "hasPreviousPage": false, 49 | "hasNextPage": #if($context.result.nextToken) true #else false #end, 50 | "endCursor": #if($context.result.nextToken) "$context.result.nextToken" #else null #end 51 | } 52 | } 53 | - &CreateRequest | 54 | #set($input = $context.args.input) 55 | #set($input.createdAt = $util.time.nowISO8601()) 56 | #set($input.complete = false) 57 | #set($input.userId = "$context.identity.sub") 58 | { 59 | "version": "2017-02-28", 60 | "operation": "PutItem", 61 | "key": { 62 | "id": $util.dynamodb.toDynamoDBJson($util.autoId()) 63 | }, 64 | "attributeValues": $util.dynamodb.toMapValuesJson($input) 65 | } 66 | - &CreateResponse | 67 | ## Need to include `userId` as a top-level field for authorization 68 | { 69 | "edge": { 70 | "node": $util.toJson($context.result) 71 | }, 72 | "clientMutationId": "$context.arguments.input.clientMutationId", 73 | "userId": "$context.identity.sub" 74 | } 75 | - &UpdateRequest | 76 | { 77 | "version": "2017-02-28", 78 | "operation": "UpdateItem", 79 | "key": { 80 | "id": $util.dynamodb.toDynamoDBJson($context.args.input.id), 81 | }, 82 | "update": { 83 | "expression": "SET complete = :c", 84 | "expressionValues": { 85 | ":c": $util.dynamodb.toDynamoDBJson($context.args.input.complete) 86 | } 87 | }, 88 | "condition": { 89 | "expression": "userId = :sub", 90 | "expressionValues": { 91 | ":sub": { "S": "$context.identity.sub" } 92 | } 93 | } 94 | } 95 | - &UpdateResponse | 96 | ## Need to include `userId` as a top-level field for authorization 97 | { 98 | "node": $util.toJson($context.result), 99 | "clientMutationId": "$context.arguments.input.clientMutationId", 100 | "userId": "$context.identity.sub" 101 | } 102 | - &DeleteRequest | 103 | { 104 | "version": "2017-02-28", 105 | "operation": "DeleteItem", 106 | "key": { 107 | "id": $util.dynamodb.toDynamoDBJson($context.args.input.id), 108 | }, 109 | "condition": { 110 | "expression": "userId = :sub", 111 | "expressionValues": { 112 | ":sub": { "S": "$context.identity.sub" } 113 | } 114 | } 115 | } 116 | - &DeleteResponse | 117 | ## Need to include `userId` as a top-level field for authorization 118 | { 119 | "deletedId": "$context.result.id", 120 | "clientMutationId": "$context.arguments.input.clientMutationId", 121 | "userId": "$context.identity.sub" 122 | } 123 | - &SubscriptionResponse | 124 | #if($context.identity.sub != $context.arguments.userId) 125 | $utils.unauthorized() 126 | #else 127 | ##User is authorized, but we return null to continue 128 | null 129 | #end 130 | Resources: 131 | UserPool: 132 | Type: AWS::Cognito::UserPool 133 | Properties: 134 | AutoVerifiedAttributes: 135 | - email 136 | UserPoolName: !Sub ${AWS::StackName}_user_pool 137 | Client: 138 | Type: AWS::Cognito::UserPoolClient 139 | Properties: 140 | UserPoolId: !Ref UserPool 141 | AppsyncLogRole: 142 | Type: AWS::IAM::Role 143 | Properties: 144 | RoleName: !Sub ${AWS::StackName}_appsync_log_role 145 | AssumeRolePolicyDocument: 146 | Statement: 147 | - Action: 148 | - sts:AssumeRole 149 | Effect: Allow 150 | Principal: 151 | Service: 152 | - appsync.amazonaws.com 153 | Policies: 154 | - PolicyName: !Sub ${AWS::StackName}_appsync_log_policy 155 | PolicyDocument: 156 | Statement: 157 | - Action: 158 | - logs:CreateLogGroup 159 | - logs:CreateLogStream 160 | - logs:PutLogEvents 161 | Effect: Allow 162 | Resource: 163 | - arn:aws:logs:*:*:* 164 | GraphQLApi: 165 | Type: AWS::AppSync::GraphQLApi 166 | Properties: 167 | Name: !Sub ${AWS::StackName}_api 168 | AuthenticationType: AMAZON_COGNITO_USER_POOLS 169 | LogConfig: 170 | CloudWatchLogsRoleArn: !GetAtt AppsyncLogRole.Arn 171 | FieldLogLevel: ALL 172 | UserPoolConfig: 173 | UserPoolId: !Ref UserPool 174 | AwsRegion: !Ref AWS::Region 175 | DefaultAction: ALLOW 176 | GraphQLSchema: 177 | Type: AWS::AppSync::GraphQLSchema 178 | Properties: 179 | DefinitionS3Location: schema.graphql 180 | ApiId: !GetAtt GraphQLApi.ApiId 181 | Table: 182 | Type: AWS::DynamoDB::Table 183 | Properties: 184 | TableName: !Sub ${AWS::StackName}_table 185 | AttributeDefinitions: 186 | - AttributeName: id 187 | AttributeType: S 188 | - AttributeName: userId 189 | AttributeType: S 190 | - AttributeName: createdAt 191 | AttributeType: S 192 | KeySchema: 193 | - AttributeName: id 194 | KeyType: HASH 195 | ProvisionedThroughput: 196 | ReadCapacityUnits: 5 197 | WriteCapacityUnits: 5 198 | GlobalSecondaryIndexes: 199 | - IndexName: CreatedAtIndex 200 | KeySchema: 201 | - AttributeName: userId 202 | KeyType: HASH 203 | - AttributeName: createdAt 204 | KeyType: RANGE 205 | Projection: 206 | ProjectionType: ALL 207 | ProvisionedThroughput: 208 | ReadCapacityUnits: 5 209 | WriteCapacityUnits: 5 210 | DynamoDBRole: 211 | Type: AWS::IAM::Role 212 | Properties: 213 | RoleName: !Sub ${AWS::StackName}_datasource_role 214 | AssumeRolePolicyDocument: 215 | Statement: 216 | - Action: 217 | - sts:AssumeRole 218 | Effect: Allow 219 | Principal: 220 | Service: 221 | - appsync.amazonaws.com 222 | Policies: 223 | - PolicyName: !Sub ${AWS::StackName}_datasource_policy 224 | PolicyDocument: 225 | Version: 2012-10-17 226 | Statement: 227 | - Action: 228 | - dynamodb:GetItem 229 | - dynamodb:PutItem 230 | - dynamodb:DeleteItem 231 | - dynamodb:UpdateItem 232 | - dynamodb:Query 233 | - dynamodb:Scan 234 | - dynamodb:BatchGetItem 235 | - dynamodb:BatchWriteItem 236 | Effect: Allow 237 | Resource: 238 | - !GetAtt Table.Arn 239 | - !Sub ${Table.Arn}/* 240 | DynamoSource: 241 | Type: AWS::AppSync::DataSource 242 | Properties: 243 | Name: !Sub ${AWS::StackName}_dynamo_datasource 244 | Type: AMAZON_DYNAMODB 245 | ServiceRoleArn: !GetAtt DynamoDBRole.Arn 246 | ApiId: !GetAtt GraphQLApi.ApiId 247 | DynamoDBConfig: 248 | TableName: !Ref Table 249 | AwsRegion: !Ref AWS::Region 250 | UseCallerCredentials: false 251 | LocalSource: 252 | Type: AWS::AppSync::DataSource 253 | Properties: 254 | Name: !Sub ${AWS::StackName}_local_datasource 255 | Type: NONE 256 | ApiId: !GetAtt GraphQLApi.ApiId 257 | ViewerResolver: 258 | Type: AWS::AppSync::Resolver 259 | Properties: 260 | ApiId: !GetAtt GraphQLApi.ApiId 261 | TypeName: Query 262 | FieldName: viewer 263 | DataSourceName: !GetAtt LocalSource.Name 264 | RequestMappingTemplate: *EmptyRequest 265 | ResponseMappingTemplate: *ViewerResponse 266 | ListTodosResolver: 267 | Type: AWS::AppSync::Resolver 268 | Properties: 269 | ApiId: !GetAtt GraphQLApi.ApiId 270 | TypeName: Viewer 271 | FieldName: listTodos 272 | DataSourceName: !GetAtt DynamoSource.Name 273 | RequestMappingTemplate: *ListRequest 274 | ResponseMappingTemplate: *ListResponse 275 | CreateTodoResolver: 276 | Type: AWS::AppSync::Resolver 277 | Properties: 278 | ApiId: !GetAtt GraphQLApi.ApiId 279 | TypeName: Mutation 280 | FieldName: createTodo 281 | DataSourceName: !GetAtt DynamoSource.Name 282 | RequestMappingTemplate: *CreateRequest 283 | ResponseMappingTemplate: *CreateResponse 284 | UpdateTodoResolver: 285 | Type: AWS::AppSync::Resolver 286 | Properties: 287 | ApiId: !GetAtt GraphQLApi.ApiId 288 | TypeName: Mutation 289 | FieldName: updateTodo 290 | DataSourceName: !GetAtt DynamoSource.Name 291 | RequestMappingTemplate: *UpdateRequest 292 | ResponseMappingTemplate: *UpdateResponse 293 | DeleteTodoResolver: 294 | Type: AWS::AppSync::Resolver 295 | Properties: 296 | ApiId: !GetAtt GraphQLApi.ApiId 297 | TypeName: Mutation 298 | FieldName: deleteTodo 299 | DataSourceName: !GetAtt DynamoSource.Name 300 | RequestMappingTemplate: *DeleteRequest 301 | ResponseMappingTemplate: *DeleteResponse 302 | CreatedResolver: 303 | Type: AWS::AppSync::Resolver 304 | Properties: 305 | ApiId: !GetAtt GraphQLApi.ApiId 306 | TypeName: Subscription 307 | FieldName: createdTodo 308 | DataSourceName: !GetAtt LocalSource.Name 309 | RequestMappingTemplate: *EmptyRequest 310 | ResponseMappingTemplate: *SubscriptionResponse 311 | UpdatedResolver: 312 | Type: AWS::AppSync::Resolver 313 | Properties: 314 | ApiId: !GetAtt GraphQLApi.ApiId 315 | TypeName: Subscription 316 | FieldName: updatedTodo 317 | DataSourceName: !GetAtt LocalSource.Name 318 | RequestMappingTemplate: *EmptyRequest 319 | ResponseMappingTemplate: *SubscriptionResponse 320 | DeletedResolver: 321 | Type: AWS::AppSync::Resolver 322 | Properties: 323 | ApiId: !GetAtt GraphQLApi.ApiId 324 | TypeName: Subscription 325 | FieldName: deletedTodo 326 | DataSourceName: !GetAtt LocalSource.Name 327 | RequestMappingTemplate: *EmptyRequest 328 | ResponseMappingTemplate: *SubscriptionResponse 329 | Outputs: 330 | AppSyncEndpoint: 331 | Value: !GetAtt GraphQLApi.GraphQLUrl 332 | AppSyncRegion: 333 | Value: !Ref AWS::Region 334 | UserPool: 335 | Value: !Ref UserPool 336 | ClientId: 337 | Value: !Ref Client 338 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "appsync-relay", 3 | "version": "1.0.0", 4 | "description": "A simple example of using Facebook's Relay with AWS AppSync.", 5 | "devDependencies": { 6 | "@babel/core": "^7.8.7", 7 | "@babel/preset-env": "^7.8.7", 8 | "@babel/preset-react": "^7.8.3", 9 | "babel-loader": "^8.0.6", 10 | "babel-plugin-relay": "^9.0.0", 11 | "css-loader": "^3.4.2", 12 | "graphql": "^14.6.0", 13 | "html-webpack-plugin": "^3.2.0", 14 | "relay-compiler": "^9.0.0", 15 | "style-loader": "^1.1.3", 16 | "webpack": "^4.42.0", 17 | "webpack-cli": "^3.3.11", 18 | "webpack-dev-server": "^3.11.0" 19 | }, 20 | "scripts": { 21 | "build": "webpack --mode production", 22 | "serve": "webpack-dev-server --mode development", 23 | "relay": "relay-compiler --src ./src --schema ./backend/schema.graphql" 24 | }, 25 | "dependencies": { 26 | "@material-ui/core": "^4.11.3", 27 | "@material-ui/icons": "^4.11.2", 28 | "aws-amplify": "^3.3.23", 29 | "aws-amplify-react": "^3.1.7", 30 | "react": "^16.13.0", 31 | "react-dom": "^16.13.0", 32 | "react-relay": "^9.0.0" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /script/deploy: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | # Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 4 | # SPDX-License-Identifier: MIT-0 5 | 6 | set -eu 7 | 8 | if ! [[ $PACKAGE_BUCKET ]]; then 9 | echo "PACKAGE_BUCKET env var must be set" 10 | exit 1 11 | fi 12 | 13 | template_file=$(mktemp) 14 | aws cloudformation package --template-file backend/template.yml \ 15 | --s3-bucket $PACKAGE_BUCKET > $template_file 16 | aws cloudformation deploy --template-file $template_file \ 17 | --stack-name ${STACK_NAME-AppSyncRelay} --capabilities CAPABILITY_NAMED_IAM 18 | -------------------------------------------------------------------------------- /script/publish: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 4 | # SPDX-License-Identifier: MIT-0 5 | 6 | set -eu 7 | 8 | if ! [[ $STATIC_SITE_BUCKET ]]; then 9 | echo "$STATIC_SITE_BUCKET env var must be set" 10 | exit 1 11 | fi 12 | 13 | "$(dirname $BASH_SOURCE)/with-appsync-env" npm run build 14 | aws s3 sync "$(dirname $BASH_SOURCE)/../dist" "s3://$STATIC_SITE_BUCKET" 15 | -------------------------------------------------------------------------------- /script/serve: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 4 | # SPDX-License-Identifier: MIT-0 5 | 6 | set -eu 7 | 8 | "$(dirname $BASH_SOURCE)/with-appsync-env" npm run serve 9 | -------------------------------------------------------------------------------- /script/with-appsync-env: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 4 | # SPDX-License-Identifier: MIT-0 5 | 6 | set -eu 7 | 8 | jq_prog='.Stacks[0].Outputs | map({key: .OutputKey, value: .OutputValue}) | from_entries' 9 | echo "Fetching config for CloudFormation stack..." 10 | export APPSYNC_CONFIG="$(aws cloudformation describe-stacks --stack-name ${STACK_NAME-AppSyncRelay} | jq "$jq_prog")" 11 | 12 | "$@" 13 | -------------------------------------------------------------------------------- /src/components/App.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: MIT-0 3 | 4 | import React from 'react'; 5 | import { Auth } from 'aws-amplify'; 6 | import { withAuthenticator } from 'aws-amplify-react'; 7 | import { QueryRenderer, graphql } from 'react-relay'; 8 | import AppBar from '@material-ui/core/AppBar'; 9 | import Button from '@material-ui/core/Button'; 10 | import Toolbar from '@material-ui/core/Toolbar'; 11 | import Typography from '@material-ui/core/Typography'; 12 | import CssBaseline from '@material-ui/core/CssBaseline'; 13 | import Paper from '@material-ui/core/Paper'; 14 | 15 | import CreateTodo from './CreateTodo'; 16 | import TodoList from './TodoList'; 17 | import environment from '../environment'; 18 | 19 | class App extends React.Component { 20 | constructor(props) { 21 | super(props); 22 | this.state = {userInfo: null}; 23 | Auth.currentUserInfo().then(info => { 24 | this.setState({userInfo: info}); 25 | }) 26 | } 27 | 28 | render() { 29 | return ( 30 | 31 | 32 | 33 | 34 | 35 | Todo List 36 | 37 | 38 | Logged in as {this.state.userInfo && this.state.userInfo.username}. 39 | 40 | 45 | 46 | 47 |
48 | 49 | { 61 | let viewer = props && props.viewer; 62 | return ( 63 | 64 | 65 | );}} /> 66 | 67 |
68 |
); 69 | } 70 | } 71 | 72 | export default withAuthenticator(App); 73 | -------------------------------------------------------------------------------- /src/components/CreateTodo.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: MIT-0 3 | 4 | import React from 'react'; 5 | import {graphql, commitMutation, createFragmentContainer, requestSubscription} from 'react-relay'; 6 | import {ConnectionHandler} from 'relay-runtime'; 7 | import AddIcon from '@material-ui/icons/Add'; 8 | import Button from '@material-ui/core/Button'; 9 | import IconButton from '@material-ui/core/IconButton'; 10 | import Input from '@material-ui/core/Input'; 11 | import InputLabel from '@material-ui/core/InputLabel'; 12 | import FormControl from '@material-ui/core/FormControl'; 13 | import Grid from '@material-ui/core/Grid'; 14 | 15 | 16 | // The default `RANGE_ADD` updater almost works, 17 | // but it would create duplicate entries for 18 | // local creations and corresponding subscription updates. 19 | // See also https://github.com/facebook/relay/issues/2023 20 | function makeUpdater(viewer, rootField) { 21 | return (store) => { 22 | let serverEdge = store.getRootField(rootField).getLinkedRecord('edge'); 23 | let getEdgeId = (edge) => edge.getLinkedRecord('node').getValue('id'); 24 | // Check if the todo is already in the list 25 | // (Checking with `store.get` is insufficient, because the new record is already in the store) 26 | let newId = getEdgeId(serverEdge); 27 | let viewerProxy = store.get(viewer.id); 28 | let conn = ConnectionHandler.getConnection(viewerProxy, 'TodoList_listTodos'); 29 | if (!conn.getLinkedRecords('edges').filter((edge) => getEdgeId(edge) === newId).length) { 30 | // Only add the new edge if it's not there yet 31 | // Need to make a new edge record because passing the one from the payload directly 32 | // can cause duplicate references to the same record in the store 33 | let newEdge = ConnectionHandler.createEdge(store, conn, serverEdge.getLinkedRecord('node'), 'TodoEdge'); 34 | ConnectionHandler.insertEdgeBefore(conn, newEdge); 35 | } 36 | }; 37 | } 38 | 39 | 40 | const createMutation = graphql` 41 | mutation CreateTodoMutation( 42 | $input: CreateTodoInput! 43 | ) { 44 | createTodo(input: $input) { 45 | edge { 46 | node { 47 | complete 48 | text 49 | } 50 | } 51 | userId 52 | } 53 | } 54 | `; 55 | 56 | 57 | var tempId = 0; 58 | function createTodo(env, viewer, text) { 59 | const variables = { 60 | input: { 61 | text 62 | }, 63 | }; 64 | let updater = makeUpdater(viewer, 'createTodo'); 65 | commitMutation( 66 | env, 67 | { 68 | mutation: createMutation, 69 | variables, 70 | onCompleted: resp => console.log('Creation response:', resp), 71 | onError: err => console.error('Creation error:', err), 72 | optimisticResponse: { 73 | createTodo: { 74 | edge: { 75 | node: { 76 | id: 'Todo' + tempId++, 77 | complete: false, 78 | text: text 79 | } 80 | } 81 | } 82 | }, 83 | updater, 84 | optimisticUpdater: updater 85 | } 86 | ); 87 | } 88 | 89 | 90 | const createSubscription = graphql` 91 | subscription CreateTodoSubscription($user: ID!) { 92 | createdTodo(userId: $user) { 93 | edge { 94 | node { 95 | text 96 | complete 97 | } 98 | } 99 | } 100 | } 101 | `; 102 | 103 | 104 | function subscribeToCreates(env, viewer) { 105 | return requestSubscription(env, 106 | { 107 | subscription: createSubscription, 108 | variables: {user: viewer.id}, 109 | onCompleted: () => console.log('Create subscription closed.'), 110 | onError: err => console.error('Error subscribing to todo updates:', err), 111 | onNext: resp => console.log('Create event:', resp), 112 | updater: makeUpdater(viewer, 'createdTodo') 113 | }); 114 | } 115 | 116 | 117 | class CreateTodo extends React.Component { 118 | constructor(props) { 119 | super(props); 120 | this.state = {newTodo: ''}; 121 | this.subscription = null; 122 | } 123 | 124 | componentDidUpdate() { 125 | if (this.props && !this.subscription) { 126 | this.subscription = subscribeToCreates(this.props.relay.environment, this.props.viewer); 127 | } 128 | } 129 | 130 | onChange(event) { 131 | this.setState({newTodo: event.target.value}); 132 | } 133 | 134 | onSubmit(event) { 135 | // returning false doesn't work in React's onSubmit 136 | event.preventDefault(); 137 | if (this.state.newTodo) { 138 | createTodo(this.props.relay.environment, this.props.viewer, this.state.newTodo); 139 | this.setState({newTodo: ''}); 140 | } 141 | } 142 | 143 | render() { 144 | return ( 145 |
146 | 147 | 148 | 149 | New Todo 150 | 156 | 157 | 158 | 159 | 160 | 161 | 162 |
); 163 | } 164 | } 165 | 166 | 167 | export default createFragmentContainer( 168 | CreateTodo, 169 | { 170 | viewer: graphql` 171 | fragment CreateTodo_viewer on Viewer { 172 | id 173 | }` 174 | } 175 | ); 176 | -------------------------------------------------------------------------------- /src/components/Todo.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: MIT-0 3 | 4 | import React from 'react'; 5 | import {graphql, commitMutation, createFragmentContainer} from 'react-relay'; 6 | import IconButton from '@material-ui/core/IconButton'; 7 | import Checkbox from '@material-ui/core/Checkbox'; 8 | import Clear from '@material-ui/icons/Clear'; 9 | import ListItem from '@material-ui/core/ListItem'; 10 | import ListItemText from '@material-ui/core/ListItemText'; 11 | 12 | 13 | const updateMutation = graphql` 14 | mutation TodoUpdateMutation( 15 | $input: UpdateTodoInput! 16 | ) { 17 | updateTodo(input: $input) { 18 | node { 19 | complete 20 | } 21 | userId 22 | } 23 | } 24 | `; 25 | 26 | 27 | function updateTodo(env, todo, complete) { 28 | const variables = { 29 | input: { 30 | id: todo.id, 31 | complete 32 | }, 33 | }; 34 | commitMutation( 35 | env, 36 | { 37 | mutation: updateMutation, 38 | variables, 39 | onCompleted: resp => console.log('Update response:', resp), 40 | onError: err => console.error('Update error:', err), 41 | optimisticResponse: { 42 | updateTodo: { 43 | node: { 44 | id: todo.id, 45 | complete 46 | } 47 | } 48 | } 49 | } 50 | ); 51 | } 52 | 53 | 54 | const deleteMutation = graphql` 55 | mutation TodoDeleteMutation( 56 | $input: DeleteTodoInput! 57 | ) { 58 | deleteTodo(input: $input) { 59 | deletedId 60 | userId 61 | } 62 | } 63 | `; 64 | 65 | 66 | function deleteTodo(env, viewerId, todo) { 67 | const variables = { 68 | input: { 69 | id: todo.id, 70 | }, 71 | }; 72 | commitMutation( 73 | env, 74 | { 75 | mutation: deleteMutation, 76 | variables, 77 | onCompleted: resp => console.log('Delete response:', resp), 78 | onError: err => console.error('Delete error:', err), 79 | optimisticResponse: { 80 | deleteTodo: { 81 | deletedId: todo.id 82 | } 83 | }, 84 | configs: [{ 85 | type: 'RANGE_DELETE', 86 | parentID: viewerId, 87 | connectionKeys: [{key: 'TodoList_listTodos'}], 88 | pathToConnection: ['viewer', 'edges'], 89 | deletedIDFieldName: 'deletedId', 90 | }] 91 | } 92 | ); 93 | } 94 | 95 | 96 | class Todo extends React.Component { 97 | onChange(event) { 98 | updateTodo(this.props.relay.environment, this.props.todo, event.target.checked); 99 | } 100 | 101 | onDelete(event) { 102 | deleteTodo(this.props.relay.environment, this.props.viewer.id, this.props.todo); 103 | } 104 | 105 | render() { 106 | let complete = this.props.todo.complete; 107 | return ( 108 | 111 | 113 | {this.props.todo.text} 114 | 115 | 116 | 117 | 118 | ); 119 | } 120 | } 121 | 122 | 123 | export default createFragmentContainer( 124 | Todo, 125 | { 126 | todo: graphql` 127 | fragment Todo_todo on Todo { 128 | id 129 | complete 130 | text 131 | }`, 132 | // Need a reference to viewer to get the parentId for deletion 133 | viewer: graphql` 134 | fragment Todo_viewer on Viewer { 135 | id 136 | }` 137 | } 138 | ); 139 | -------------------------------------------------------------------------------- /src/components/TodoList.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: MIT-0 3 | 4 | import React from 'react'; 5 | import {graphql, createPaginationContainer, requestSubscription} from 'react-relay'; 6 | import Button from '@material-ui/core/Button'; 7 | import List from '@material-ui/core/List'; 8 | import Typography from '@material-ui/core/Typography'; 9 | import CircularProgress from '@material-ui/core/CircularProgress'; 10 | 11 | import Todo from './Todo'; 12 | 13 | 14 | const updateSubscription = graphql` 15 | subscription TodoListUpdateSubscription($user: ID!) { 16 | updatedTodo(userId: $user) { 17 | node { 18 | complete 19 | } 20 | } 21 | } 22 | `; 23 | 24 | const deleteSubscription = graphql` 25 | subscription TodoListDeleteSubscription($user: ID!) { 26 | deletedTodo(userId: $user) { 27 | deletedId 28 | } 29 | } 30 | `; 31 | 32 | 33 | function subscribeToDeletes(env, viewer) { 34 | return requestSubscription(env, 35 | { 36 | subscription: deleteSubscription, 37 | variables: {user: viewer.id}, 38 | onCompleted: () => console.log('Delete subscription closed.'), 39 | onError: err => console.error('Error subscribing to todo deletes:', err), 40 | onNext: resp => console.log('Delete event:', resp), 41 | configs: [{ 42 | type: 'RANGE_DELETE', 43 | parentID: viewer.id, 44 | connectionKeys: [{key: 'TodoList_listTodos'}], 45 | pathToConnection: ['viewer', 'edges'], 46 | deletedIDFieldName: 'deletedId', 47 | }] 48 | }); 49 | } 50 | 51 | 52 | class TodoList extends React.Component { 53 | constructor(props) { 54 | super(props); 55 | this.subscription = false; 56 | } 57 | 58 | componentDidUpdate() { 59 | if (this.props && !this.subscription) { 60 | this.subscription = requestSubscription(this.props.relay.environment, 61 | { 62 | subscription: updateSubscription, 63 | variables: {user: this.props.viewer.id}, 64 | onCompleted: () => console.log('Update subscription closed.'), 65 | onNext: resp => console.log('Update event:', resp), 66 | onError: err => console.error('Error subscribing to todo updates:', err) 67 | }); 68 | subscribeToDeletes(this.props.relay.environment, this.props.viewer); 69 | } 70 | } 71 | 72 | loadMore() { 73 | if (!this.props.relay.hasMore() || this.props.relay.isLoading()) { 74 | return; 75 | } 76 | 77 | this.props.relay.loadMore( 78 | 5, 79 | (error = null) => { 80 | if (error) { 81 | console.error('Network Error', 'Unable to connect to the network.'); 82 | } 83 | }, 84 | ); 85 | } 86 | 87 | 88 | render() { 89 | let TodoStatus = (props) => { 90 | return (
91 | {props.loading && } 92 | {props.text && {props.text}} 93 |
); 94 | }; 95 | if (this.props.error) { 96 | return ; 97 | } 98 | let viewer = this.props.viewer; 99 | if (!viewer) { 100 | return ; 101 | } 102 | let edges = viewer.listTodos.edges; 103 | if (edges.length === 0) { 104 | return ; 105 | } else { 106 | return ( 107 | 108 | {edges.map((edge) => )} 109 | 110 | {this.props.relay.hasMore() && 111 |
112 | 113 |
} 114 |
); 115 | } 116 | } 117 | } 118 | 119 | export default createPaginationContainer( 120 | TodoList, 121 | { 122 | viewer: graphql` 123 | fragment TodoList_viewer on Viewer 124 | @argumentDefinitions( 125 | count: {type: "Int"} 126 | cursor: {type: "String"} 127 | ) { 128 | id 129 | ...Todo_viewer 130 | listTodos(first: $count after: $cursor) @connection(key: "TodoList_listTodos") { 131 | edges { 132 | node { 133 | id 134 | ...Todo_todo 135 | } 136 | } 137 | } 138 | }` 139 | }, 140 | { 141 | direction: 'forward', 142 | getVariables(props, {count, cursor}, fragmentVariables) { 143 | return { 144 | count, 145 | cursor 146 | }; 147 | }, 148 | query: graphql` 149 | query TodoListPaginationQuery( 150 | $count: Int! 151 | $cursor: String 152 | ) { 153 | viewer { 154 | ...TodoList_viewer @arguments(count: $count, cursor: $cursor) 155 | } 156 | }` 157 | } 158 | ); 159 | -------------------------------------------------------------------------------- /src/components/__generated__/AppQuery.graphql.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @flow 3 | * @relayHash a7ae53e00192f8ff10118e322bd4f16f 4 | */ 5 | 6 | /* eslint-disable */ 7 | 8 | 'use strict'; 9 | 10 | /*:: 11 | import type { ConcreteRequest } from 'relay-runtime'; 12 | type CreateTodo_viewer$ref = any; 13 | type TodoList_viewer$ref = any; 14 | export type AppQueryVariables = {| 15 | count?: ?number 16 | |}; 17 | export type AppQueryResponse = {| 18 | +viewer: {| 19 | +id: ?string, 20 | +$fragmentRefs: CreateTodo_viewer$ref & TodoList_viewer$ref, 21 | |} 22 | |}; 23 | export type AppQuery = {| 24 | variables: AppQueryVariables, 25 | response: AppQueryResponse, 26 | |}; 27 | */ 28 | 29 | 30 | /* 31 | query AppQuery( 32 | $count: Int 33 | ) { 34 | viewer { 35 | id 36 | ...CreateTodo_viewer 37 | ...TodoList_viewer_yu5n1 38 | } 39 | } 40 | 41 | fragment CreateTodo_viewer on Viewer { 42 | id 43 | } 44 | 45 | fragment TodoList_viewer_yu5n1 on Viewer { 46 | id 47 | ...Todo_viewer 48 | listTodos(first: $count) { 49 | edges { 50 | node { 51 | id 52 | ...Todo_todo 53 | __typename 54 | } 55 | cursor 56 | } 57 | pageInfo { 58 | endCursor 59 | hasNextPage 60 | } 61 | } 62 | } 63 | 64 | fragment Todo_todo on Todo { 65 | id 66 | complete 67 | text 68 | } 69 | 70 | fragment Todo_viewer on Viewer { 71 | id 72 | } 73 | */ 74 | 75 | const node/*: ConcreteRequest*/ = (function(){ 76 | var v0 = [ 77 | { 78 | "kind": "LocalArgument", 79 | "name": "count", 80 | "type": "Int", 81 | "defaultValue": null 82 | } 83 | ], 84 | v1 = { 85 | "kind": "ScalarField", 86 | "alias": null, 87 | "name": "id", 88 | "args": null, 89 | "storageKey": null 90 | }, 91 | v2 = [ 92 | { 93 | "kind": "Variable", 94 | "name": "first", 95 | "variableName": "count" 96 | } 97 | ]; 98 | return { 99 | "kind": "Request", 100 | "fragment": { 101 | "kind": "Fragment", 102 | "name": "AppQuery", 103 | "type": "Query", 104 | "metadata": null, 105 | "argumentDefinitions": (v0/*: any*/), 106 | "selections": [ 107 | { 108 | "kind": "LinkedField", 109 | "alias": null, 110 | "name": "viewer", 111 | "storageKey": null, 112 | "args": null, 113 | "concreteType": "Viewer", 114 | "plural": false, 115 | "selections": [ 116 | (v1/*: any*/), 117 | { 118 | "kind": "FragmentSpread", 119 | "name": "CreateTodo_viewer", 120 | "args": null 121 | }, 122 | { 123 | "kind": "FragmentSpread", 124 | "name": "TodoList_viewer", 125 | "args": [ 126 | { 127 | "kind": "Variable", 128 | "name": "count", 129 | "variableName": "count" 130 | } 131 | ] 132 | } 133 | ] 134 | } 135 | ] 136 | }, 137 | "operation": { 138 | "kind": "Operation", 139 | "name": "AppQuery", 140 | "argumentDefinitions": (v0/*: any*/), 141 | "selections": [ 142 | { 143 | "kind": "LinkedField", 144 | "alias": null, 145 | "name": "viewer", 146 | "storageKey": null, 147 | "args": null, 148 | "concreteType": "Viewer", 149 | "plural": false, 150 | "selections": [ 151 | (v1/*: any*/), 152 | { 153 | "kind": "LinkedField", 154 | "alias": null, 155 | "name": "listTodos", 156 | "storageKey": null, 157 | "args": (v2/*: any*/), 158 | "concreteType": "TodoConnection", 159 | "plural": false, 160 | "selections": [ 161 | { 162 | "kind": "LinkedField", 163 | "alias": null, 164 | "name": "edges", 165 | "storageKey": null, 166 | "args": null, 167 | "concreteType": "TodoEdge", 168 | "plural": true, 169 | "selections": [ 170 | { 171 | "kind": "LinkedField", 172 | "alias": null, 173 | "name": "node", 174 | "storageKey": null, 175 | "args": null, 176 | "concreteType": "Todo", 177 | "plural": false, 178 | "selections": [ 179 | (v1/*: any*/), 180 | { 181 | "kind": "ScalarField", 182 | "alias": null, 183 | "name": "complete", 184 | "args": null, 185 | "storageKey": null 186 | }, 187 | { 188 | "kind": "ScalarField", 189 | "alias": null, 190 | "name": "text", 191 | "args": null, 192 | "storageKey": null 193 | }, 194 | { 195 | "kind": "ScalarField", 196 | "alias": null, 197 | "name": "__typename", 198 | "args": null, 199 | "storageKey": null 200 | } 201 | ] 202 | }, 203 | { 204 | "kind": "ScalarField", 205 | "alias": null, 206 | "name": "cursor", 207 | "args": null, 208 | "storageKey": null 209 | } 210 | ] 211 | }, 212 | { 213 | "kind": "LinkedField", 214 | "alias": null, 215 | "name": "pageInfo", 216 | "storageKey": null, 217 | "args": null, 218 | "concreteType": "PageInfo", 219 | "plural": false, 220 | "selections": [ 221 | { 222 | "kind": "ScalarField", 223 | "alias": null, 224 | "name": "endCursor", 225 | "args": null, 226 | "storageKey": null 227 | }, 228 | { 229 | "kind": "ScalarField", 230 | "alias": null, 231 | "name": "hasNextPage", 232 | "args": null, 233 | "storageKey": null 234 | } 235 | ] 236 | } 237 | ] 238 | }, 239 | { 240 | "kind": "LinkedHandle", 241 | "alias": null, 242 | "name": "listTodos", 243 | "args": (v2/*: any*/), 244 | "handle": "connection", 245 | "key": "TodoList_listTodos", 246 | "filters": null 247 | } 248 | ] 249 | } 250 | ] 251 | }, 252 | "params": { 253 | "operationKind": "query", 254 | "name": "AppQuery", 255 | "id": null, 256 | "text": "query AppQuery(\n $count: Int\n) {\n viewer {\n id\n ...CreateTodo_viewer\n ...TodoList_viewer_yu5n1\n }\n}\n\nfragment CreateTodo_viewer on Viewer {\n id\n}\n\nfragment TodoList_viewer_yu5n1 on Viewer {\n id\n ...Todo_viewer\n listTodos(first: $count) {\n edges {\n node {\n id\n ...Todo_todo\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n}\n\nfragment Todo_todo on Todo {\n id\n complete\n text\n}\n\nfragment Todo_viewer on Viewer {\n id\n}\n", 257 | "metadata": {} 258 | } 259 | }; 260 | })(); 261 | // prettier-ignore 262 | (node/*: any*/).hash = 'a8ade4fce8fc38befe514e37d4a42722'; 263 | 264 | module.exports = node; 265 | -------------------------------------------------------------------------------- /src/components/__generated__/CreateTodoMutation.graphql.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @flow 3 | * @relayHash 80f4bc20ba55b90cd704065ae0db034b 4 | */ 5 | 6 | /* eslint-disable */ 7 | 8 | 'use strict'; 9 | 10 | /*:: 11 | import type { ConcreteRequest } from 'relay-runtime'; 12 | export type CreateTodoInput = {| 13 | text: string, 14 | clientMutationId?: ?string, 15 | |}; 16 | export type CreateTodoMutationVariables = {| 17 | input: CreateTodoInput 18 | |}; 19 | export type CreateTodoMutationResponse = {| 20 | +createTodo: {| 21 | +edge: ?{| 22 | +node: {| 23 | +complete: boolean, 24 | +text: string, 25 | |} 26 | |}, 27 | +userId: string, 28 | |} 29 | |}; 30 | export type CreateTodoMutation = {| 31 | variables: CreateTodoMutationVariables, 32 | response: CreateTodoMutationResponse, 33 | |}; 34 | */ 35 | 36 | 37 | /* 38 | mutation CreateTodoMutation( 39 | $input: CreateTodoInput! 40 | ) { 41 | createTodo(input: $input) { 42 | edge { 43 | node { 44 | complete 45 | text 46 | id 47 | } 48 | } 49 | userId 50 | } 51 | } 52 | */ 53 | 54 | const node/*: ConcreteRequest*/ = (function(){ 55 | var v0 = [ 56 | { 57 | "kind": "LocalArgument", 58 | "name": "input", 59 | "type": "CreateTodoInput!", 60 | "defaultValue": null 61 | } 62 | ], 63 | v1 = [ 64 | { 65 | "kind": "Variable", 66 | "name": "input", 67 | "variableName": "input" 68 | } 69 | ], 70 | v2 = { 71 | "kind": "ScalarField", 72 | "alias": null, 73 | "name": "complete", 74 | "args": null, 75 | "storageKey": null 76 | }, 77 | v3 = { 78 | "kind": "ScalarField", 79 | "alias": null, 80 | "name": "text", 81 | "args": null, 82 | "storageKey": null 83 | }, 84 | v4 = { 85 | "kind": "ScalarField", 86 | "alias": null, 87 | "name": "userId", 88 | "args": null, 89 | "storageKey": null 90 | }; 91 | return { 92 | "kind": "Request", 93 | "fragment": { 94 | "kind": "Fragment", 95 | "name": "CreateTodoMutation", 96 | "type": "Mutation", 97 | "metadata": null, 98 | "argumentDefinitions": (v0/*: any*/), 99 | "selections": [ 100 | { 101 | "kind": "LinkedField", 102 | "alias": null, 103 | "name": "createTodo", 104 | "storageKey": null, 105 | "args": (v1/*: any*/), 106 | "concreteType": "CreateTodoPayload", 107 | "plural": false, 108 | "selections": [ 109 | { 110 | "kind": "LinkedField", 111 | "alias": null, 112 | "name": "edge", 113 | "storageKey": null, 114 | "args": null, 115 | "concreteType": "TodoEdge", 116 | "plural": false, 117 | "selections": [ 118 | { 119 | "kind": "LinkedField", 120 | "alias": null, 121 | "name": "node", 122 | "storageKey": null, 123 | "args": null, 124 | "concreteType": "Todo", 125 | "plural": false, 126 | "selections": [ 127 | (v2/*: any*/), 128 | (v3/*: any*/) 129 | ] 130 | } 131 | ] 132 | }, 133 | (v4/*: any*/) 134 | ] 135 | } 136 | ] 137 | }, 138 | "operation": { 139 | "kind": "Operation", 140 | "name": "CreateTodoMutation", 141 | "argumentDefinitions": (v0/*: any*/), 142 | "selections": [ 143 | { 144 | "kind": "LinkedField", 145 | "alias": null, 146 | "name": "createTodo", 147 | "storageKey": null, 148 | "args": (v1/*: any*/), 149 | "concreteType": "CreateTodoPayload", 150 | "plural": false, 151 | "selections": [ 152 | { 153 | "kind": "LinkedField", 154 | "alias": null, 155 | "name": "edge", 156 | "storageKey": null, 157 | "args": null, 158 | "concreteType": "TodoEdge", 159 | "plural": false, 160 | "selections": [ 161 | { 162 | "kind": "LinkedField", 163 | "alias": null, 164 | "name": "node", 165 | "storageKey": null, 166 | "args": null, 167 | "concreteType": "Todo", 168 | "plural": false, 169 | "selections": [ 170 | (v2/*: any*/), 171 | (v3/*: any*/), 172 | { 173 | "kind": "ScalarField", 174 | "alias": null, 175 | "name": "id", 176 | "args": null, 177 | "storageKey": null 178 | } 179 | ] 180 | } 181 | ] 182 | }, 183 | (v4/*: any*/) 184 | ] 185 | } 186 | ] 187 | }, 188 | "params": { 189 | "operationKind": "mutation", 190 | "name": "CreateTodoMutation", 191 | "id": null, 192 | "text": "mutation CreateTodoMutation(\n $input: CreateTodoInput!\n) {\n createTodo(input: $input) {\n edge {\n node {\n complete\n text\n id\n }\n }\n userId\n }\n}\n", 193 | "metadata": {} 194 | } 195 | }; 196 | })(); 197 | // prettier-ignore 198 | (node/*: any*/).hash = '4634d2390811c36de4232fe3806cb075'; 199 | 200 | module.exports = node; 201 | -------------------------------------------------------------------------------- /src/components/__generated__/CreateTodoSubscription.graphql.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @flow 3 | * @relayHash 74f9758cddfa20ccc6e07de975556549 4 | */ 5 | 6 | /* eslint-disable */ 7 | 8 | 'use strict'; 9 | 10 | /*:: 11 | import type { ConcreteRequest } from 'relay-runtime'; 12 | export type CreateTodoSubscriptionVariables = {| 13 | user: string 14 | |}; 15 | export type CreateTodoSubscriptionResponse = {| 16 | +createdTodo: ?{| 17 | +edge: ?{| 18 | +node: {| 19 | +text: string, 20 | +complete: boolean, 21 | |} 22 | |} 23 | |} 24 | |}; 25 | export type CreateTodoSubscription = {| 26 | variables: CreateTodoSubscriptionVariables, 27 | response: CreateTodoSubscriptionResponse, 28 | |}; 29 | */ 30 | 31 | 32 | /* 33 | subscription CreateTodoSubscription( 34 | $user: ID! 35 | ) { 36 | createdTodo(userId: $user) { 37 | edge { 38 | node { 39 | text 40 | complete 41 | id 42 | } 43 | } 44 | } 45 | } 46 | */ 47 | 48 | const node/*: ConcreteRequest*/ = (function(){ 49 | var v0 = [ 50 | { 51 | "kind": "LocalArgument", 52 | "name": "user", 53 | "type": "ID!", 54 | "defaultValue": null 55 | } 56 | ], 57 | v1 = [ 58 | { 59 | "kind": "Variable", 60 | "name": "userId", 61 | "variableName": "user" 62 | } 63 | ], 64 | v2 = { 65 | "kind": "ScalarField", 66 | "alias": null, 67 | "name": "text", 68 | "args": null, 69 | "storageKey": null 70 | }, 71 | v3 = { 72 | "kind": "ScalarField", 73 | "alias": null, 74 | "name": "complete", 75 | "args": null, 76 | "storageKey": null 77 | }; 78 | return { 79 | "kind": "Request", 80 | "fragment": { 81 | "kind": "Fragment", 82 | "name": "CreateTodoSubscription", 83 | "type": "Subscription", 84 | "metadata": null, 85 | "argumentDefinitions": (v0/*: any*/), 86 | "selections": [ 87 | { 88 | "kind": "LinkedField", 89 | "alias": null, 90 | "name": "createdTodo", 91 | "storageKey": null, 92 | "args": (v1/*: any*/), 93 | "concreteType": "CreateTodoPayload", 94 | "plural": false, 95 | "selections": [ 96 | { 97 | "kind": "LinkedField", 98 | "alias": null, 99 | "name": "edge", 100 | "storageKey": null, 101 | "args": null, 102 | "concreteType": "TodoEdge", 103 | "plural": false, 104 | "selections": [ 105 | { 106 | "kind": "LinkedField", 107 | "alias": null, 108 | "name": "node", 109 | "storageKey": null, 110 | "args": null, 111 | "concreteType": "Todo", 112 | "plural": false, 113 | "selections": [ 114 | (v2/*: any*/), 115 | (v3/*: any*/) 116 | ] 117 | } 118 | ] 119 | } 120 | ] 121 | } 122 | ] 123 | }, 124 | "operation": { 125 | "kind": "Operation", 126 | "name": "CreateTodoSubscription", 127 | "argumentDefinitions": (v0/*: any*/), 128 | "selections": [ 129 | { 130 | "kind": "LinkedField", 131 | "alias": null, 132 | "name": "createdTodo", 133 | "storageKey": null, 134 | "args": (v1/*: any*/), 135 | "concreteType": "CreateTodoPayload", 136 | "plural": false, 137 | "selections": [ 138 | { 139 | "kind": "LinkedField", 140 | "alias": null, 141 | "name": "edge", 142 | "storageKey": null, 143 | "args": null, 144 | "concreteType": "TodoEdge", 145 | "plural": false, 146 | "selections": [ 147 | { 148 | "kind": "LinkedField", 149 | "alias": null, 150 | "name": "node", 151 | "storageKey": null, 152 | "args": null, 153 | "concreteType": "Todo", 154 | "plural": false, 155 | "selections": [ 156 | (v2/*: any*/), 157 | (v3/*: any*/), 158 | { 159 | "kind": "ScalarField", 160 | "alias": null, 161 | "name": "id", 162 | "args": null, 163 | "storageKey": null 164 | } 165 | ] 166 | } 167 | ] 168 | } 169 | ] 170 | } 171 | ] 172 | }, 173 | "params": { 174 | "operationKind": "subscription", 175 | "name": "CreateTodoSubscription", 176 | "id": null, 177 | "text": "subscription CreateTodoSubscription(\n $user: ID!\n) {\n createdTodo(userId: $user) {\n edge {\n node {\n text\n complete\n id\n }\n }\n }\n}\n", 178 | "metadata": {} 179 | } 180 | }; 181 | })(); 182 | // prettier-ignore 183 | (node/*: any*/).hash = 'b0e4ce9c2f86588a82119eaa9ed7587e'; 184 | 185 | module.exports = node; 186 | -------------------------------------------------------------------------------- /src/components/__generated__/CreateTodo_viewer.graphql.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @flow 3 | */ 4 | 5 | /* eslint-disable */ 6 | 7 | 'use strict'; 8 | 9 | /*:: 10 | import type { ReaderFragment } from 'relay-runtime'; 11 | import type { FragmentReference } from "relay-runtime"; 12 | declare export opaque type CreateTodo_viewer$ref: FragmentReference; 13 | declare export opaque type CreateTodo_viewer$fragmentType: CreateTodo_viewer$ref; 14 | export type CreateTodo_viewer = {| 15 | +id: ?string, 16 | +$refType: CreateTodo_viewer$ref, 17 | |}; 18 | export type CreateTodo_viewer$data = CreateTodo_viewer; 19 | export type CreateTodo_viewer$key = { 20 | +$data?: CreateTodo_viewer$data, 21 | +$fragmentRefs: CreateTodo_viewer$ref, 22 | ... 23 | }; 24 | */ 25 | 26 | 27 | const node/*: ReaderFragment*/ = { 28 | "kind": "Fragment", 29 | "name": "CreateTodo_viewer", 30 | "type": "Viewer", 31 | "metadata": null, 32 | "argumentDefinitions": [], 33 | "selections": [ 34 | { 35 | "kind": "ScalarField", 36 | "alias": null, 37 | "name": "id", 38 | "args": null, 39 | "storageKey": null 40 | } 41 | ] 42 | }; 43 | // prettier-ignore 44 | (node/*: any*/).hash = 'e5a0872ea82d8e63c9a2c4b13d61d791'; 45 | 46 | module.exports = node; 47 | -------------------------------------------------------------------------------- /src/components/__generated__/TodoDeleteMutation.graphql.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @flow 3 | * @relayHash 7660fcecdce96d3b6116b9ebd6a913a3 4 | */ 5 | 6 | /* eslint-disable */ 7 | 8 | 'use strict'; 9 | 10 | /*:: 11 | import type { ConcreteRequest } from 'relay-runtime'; 12 | export type DeleteTodoInput = {| 13 | id: string, 14 | clientMutationId?: ?string, 15 | |}; 16 | export type TodoDeleteMutationVariables = {| 17 | input: DeleteTodoInput 18 | |}; 19 | export type TodoDeleteMutationResponse = {| 20 | +deleteTodo: {| 21 | +deletedId: ?string, 22 | +userId: string, 23 | |} 24 | |}; 25 | export type TodoDeleteMutation = {| 26 | variables: TodoDeleteMutationVariables, 27 | response: TodoDeleteMutationResponse, 28 | |}; 29 | */ 30 | 31 | 32 | /* 33 | mutation TodoDeleteMutation( 34 | $input: DeleteTodoInput! 35 | ) { 36 | deleteTodo(input: $input) { 37 | deletedId 38 | userId 39 | } 40 | } 41 | */ 42 | 43 | const node/*: ConcreteRequest*/ = (function(){ 44 | var v0 = [ 45 | { 46 | "kind": "LocalArgument", 47 | "name": "input", 48 | "type": "DeleteTodoInput!", 49 | "defaultValue": null 50 | } 51 | ], 52 | v1 = [ 53 | { 54 | "kind": "LinkedField", 55 | "alias": null, 56 | "name": "deleteTodo", 57 | "storageKey": null, 58 | "args": [ 59 | { 60 | "kind": "Variable", 61 | "name": "input", 62 | "variableName": "input" 63 | } 64 | ], 65 | "concreteType": "DeleteTodoPayload", 66 | "plural": false, 67 | "selections": [ 68 | { 69 | "kind": "ScalarField", 70 | "alias": null, 71 | "name": "deletedId", 72 | "args": null, 73 | "storageKey": null 74 | }, 75 | { 76 | "kind": "ScalarField", 77 | "alias": null, 78 | "name": "userId", 79 | "args": null, 80 | "storageKey": null 81 | } 82 | ] 83 | } 84 | ]; 85 | return { 86 | "kind": "Request", 87 | "fragment": { 88 | "kind": "Fragment", 89 | "name": "TodoDeleteMutation", 90 | "type": "Mutation", 91 | "metadata": null, 92 | "argumentDefinitions": (v0/*: any*/), 93 | "selections": (v1/*: any*/) 94 | }, 95 | "operation": { 96 | "kind": "Operation", 97 | "name": "TodoDeleteMutation", 98 | "argumentDefinitions": (v0/*: any*/), 99 | "selections": (v1/*: any*/) 100 | }, 101 | "params": { 102 | "operationKind": "mutation", 103 | "name": "TodoDeleteMutation", 104 | "id": null, 105 | "text": "mutation TodoDeleteMutation(\n $input: DeleteTodoInput!\n) {\n deleteTodo(input: $input) {\n deletedId\n userId\n }\n}\n", 106 | "metadata": {} 107 | } 108 | }; 109 | })(); 110 | // prettier-ignore 111 | (node/*: any*/).hash = '5c1d2d514aaf1983d6103c59835731bb'; 112 | 113 | module.exports = node; 114 | -------------------------------------------------------------------------------- /src/components/__generated__/TodoListDeleteSubscription.graphql.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @flow 3 | * @relayHash b892c2eab8f9304330fc691787a98fcc 4 | */ 5 | 6 | /* eslint-disable */ 7 | 8 | 'use strict'; 9 | 10 | /*:: 11 | import type { ConcreteRequest } from 'relay-runtime'; 12 | export type TodoListDeleteSubscriptionVariables = {| 13 | user: string 14 | |}; 15 | export type TodoListDeleteSubscriptionResponse = {| 16 | +deletedTodo: ?{| 17 | +deletedId: ?string 18 | |} 19 | |}; 20 | export type TodoListDeleteSubscription = {| 21 | variables: TodoListDeleteSubscriptionVariables, 22 | response: TodoListDeleteSubscriptionResponse, 23 | |}; 24 | */ 25 | 26 | 27 | /* 28 | subscription TodoListDeleteSubscription( 29 | $user: ID! 30 | ) { 31 | deletedTodo(userId: $user) { 32 | deletedId 33 | } 34 | } 35 | */ 36 | 37 | const node/*: ConcreteRequest*/ = (function(){ 38 | var v0 = [ 39 | { 40 | "kind": "LocalArgument", 41 | "name": "user", 42 | "type": "ID!", 43 | "defaultValue": null 44 | } 45 | ], 46 | v1 = [ 47 | { 48 | "kind": "LinkedField", 49 | "alias": null, 50 | "name": "deletedTodo", 51 | "storageKey": null, 52 | "args": [ 53 | { 54 | "kind": "Variable", 55 | "name": "userId", 56 | "variableName": "user" 57 | } 58 | ], 59 | "concreteType": "DeleteTodoPayload", 60 | "plural": false, 61 | "selections": [ 62 | { 63 | "kind": "ScalarField", 64 | "alias": null, 65 | "name": "deletedId", 66 | "args": null, 67 | "storageKey": null 68 | } 69 | ] 70 | } 71 | ]; 72 | return { 73 | "kind": "Request", 74 | "fragment": { 75 | "kind": "Fragment", 76 | "name": "TodoListDeleteSubscription", 77 | "type": "Subscription", 78 | "metadata": null, 79 | "argumentDefinitions": (v0/*: any*/), 80 | "selections": (v1/*: any*/) 81 | }, 82 | "operation": { 83 | "kind": "Operation", 84 | "name": "TodoListDeleteSubscription", 85 | "argumentDefinitions": (v0/*: any*/), 86 | "selections": (v1/*: any*/) 87 | }, 88 | "params": { 89 | "operationKind": "subscription", 90 | "name": "TodoListDeleteSubscription", 91 | "id": null, 92 | "text": "subscription TodoListDeleteSubscription(\n $user: ID!\n) {\n deletedTodo(userId: $user) {\n deletedId\n }\n}\n", 93 | "metadata": {} 94 | } 95 | }; 96 | })(); 97 | // prettier-ignore 98 | (node/*: any*/).hash = 'cce9c768f846af9534b3df1c9aac67bc'; 99 | 100 | module.exports = node; 101 | -------------------------------------------------------------------------------- /src/components/__generated__/TodoListPaginationQuery.graphql.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @flow 3 | * @relayHash 1e8f4e74f4e159d32c1cc87be4727aa6 4 | */ 5 | 6 | /* eslint-disable */ 7 | 8 | 'use strict'; 9 | 10 | /*:: 11 | import type { ConcreteRequest } from 'relay-runtime'; 12 | type TodoList_viewer$ref = any; 13 | export type TodoListPaginationQueryVariables = {| 14 | count: number, 15 | cursor?: ?string, 16 | |}; 17 | export type TodoListPaginationQueryResponse = {| 18 | +viewer: {| 19 | +$fragmentRefs: TodoList_viewer$ref 20 | |} 21 | |}; 22 | export type TodoListPaginationQuery = {| 23 | variables: TodoListPaginationQueryVariables, 24 | response: TodoListPaginationQueryResponse, 25 | |}; 26 | */ 27 | 28 | 29 | /* 30 | query TodoListPaginationQuery( 31 | $count: Int! 32 | $cursor: String 33 | ) { 34 | viewer { 35 | ...TodoList_viewer_1G22uz 36 | id 37 | } 38 | } 39 | 40 | fragment TodoList_viewer_1G22uz on Viewer { 41 | id 42 | ...Todo_viewer 43 | listTodos(first: $count, after: $cursor) { 44 | edges { 45 | node { 46 | id 47 | ...Todo_todo 48 | __typename 49 | } 50 | cursor 51 | } 52 | pageInfo { 53 | endCursor 54 | hasNextPage 55 | } 56 | } 57 | } 58 | 59 | fragment Todo_todo on Todo { 60 | id 61 | complete 62 | text 63 | } 64 | 65 | fragment Todo_viewer on Viewer { 66 | id 67 | } 68 | */ 69 | 70 | const node/*: ConcreteRequest*/ = (function(){ 71 | var v0 = [ 72 | { 73 | "kind": "LocalArgument", 74 | "name": "count", 75 | "type": "Int!", 76 | "defaultValue": null 77 | }, 78 | { 79 | "kind": "LocalArgument", 80 | "name": "cursor", 81 | "type": "String", 82 | "defaultValue": null 83 | } 84 | ], 85 | v1 = { 86 | "kind": "ScalarField", 87 | "alias": null, 88 | "name": "id", 89 | "args": null, 90 | "storageKey": null 91 | }, 92 | v2 = [ 93 | { 94 | "kind": "Variable", 95 | "name": "after", 96 | "variableName": "cursor" 97 | }, 98 | { 99 | "kind": "Variable", 100 | "name": "first", 101 | "variableName": "count" 102 | } 103 | ]; 104 | return { 105 | "kind": "Request", 106 | "fragment": { 107 | "kind": "Fragment", 108 | "name": "TodoListPaginationQuery", 109 | "type": "Query", 110 | "metadata": null, 111 | "argumentDefinitions": (v0/*: any*/), 112 | "selections": [ 113 | { 114 | "kind": "LinkedField", 115 | "alias": null, 116 | "name": "viewer", 117 | "storageKey": null, 118 | "args": null, 119 | "concreteType": "Viewer", 120 | "plural": false, 121 | "selections": [ 122 | { 123 | "kind": "FragmentSpread", 124 | "name": "TodoList_viewer", 125 | "args": [ 126 | { 127 | "kind": "Variable", 128 | "name": "count", 129 | "variableName": "count" 130 | }, 131 | { 132 | "kind": "Variable", 133 | "name": "cursor", 134 | "variableName": "cursor" 135 | } 136 | ] 137 | } 138 | ] 139 | } 140 | ] 141 | }, 142 | "operation": { 143 | "kind": "Operation", 144 | "name": "TodoListPaginationQuery", 145 | "argumentDefinitions": (v0/*: any*/), 146 | "selections": [ 147 | { 148 | "kind": "LinkedField", 149 | "alias": null, 150 | "name": "viewer", 151 | "storageKey": null, 152 | "args": null, 153 | "concreteType": "Viewer", 154 | "plural": false, 155 | "selections": [ 156 | (v1/*: any*/), 157 | { 158 | "kind": "LinkedField", 159 | "alias": null, 160 | "name": "listTodos", 161 | "storageKey": null, 162 | "args": (v2/*: any*/), 163 | "concreteType": "TodoConnection", 164 | "plural": false, 165 | "selections": [ 166 | { 167 | "kind": "LinkedField", 168 | "alias": null, 169 | "name": "edges", 170 | "storageKey": null, 171 | "args": null, 172 | "concreteType": "TodoEdge", 173 | "plural": true, 174 | "selections": [ 175 | { 176 | "kind": "LinkedField", 177 | "alias": null, 178 | "name": "node", 179 | "storageKey": null, 180 | "args": null, 181 | "concreteType": "Todo", 182 | "plural": false, 183 | "selections": [ 184 | (v1/*: any*/), 185 | { 186 | "kind": "ScalarField", 187 | "alias": null, 188 | "name": "complete", 189 | "args": null, 190 | "storageKey": null 191 | }, 192 | { 193 | "kind": "ScalarField", 194 | "alias": null, 195 | "name": "text", 196 | "args": null, 197 | "storageKey": null 198 | }, 199 | { 200 | "kind": "ScalarField", 201 | "alias": null, 202 | "name": "__typename", 203 | "args": null, 204 | "storageKey": null 205 | } 206 | ] 207 | }, 208 | { 209 | "kind": "ScalarField", 210 | "alias": null, 211 | "name": "cursor", 212 | "args": null, 213 | "storageKey": null 214 | } 215 | ] 216 | }, 217 | { 218 | "kind": "LinkedField", 219 | "alias": null, 220 | "name": "pageInfo", 221 | "storageKey": null, 222 | "args": null, 223 | "concreteType": "PageInfo", 224 | "plural": false, 225 | "selections": [ 226 | { 227 | "kind": "ScalarField", 228 | "alias": null, 229 | "name": "endCursor", 230 | "args": null, 231 | "storageKey": null 232 | }, 233 | { 234 | "kind": "ScalarField", 235 | "alias": null, 236 | "name": "hasNextPage", 237 | "args": null, 238 | "storageKey": null 239 | } 240 | ] 241 | } 242 | ] 243 | }, 244 | { 245 | "kind": "LinkedHandle", 246 | "alias": null, 247 | "name": "listTodos", 248 | "args": (v2/*: any*/), 249 | "handle": "connection", 250 | "key": "TodoList_listTodos", 251 | "filters": null 252 | } 253 | ] 254 | } 255 | ] 256 | }, 257 | "params": { 258 | "operationKind": "query", 259 | "name": "TodoListPaginationQuery", 260 | "id": null, 261 | "text": "query TodoListPaginationQuery(\n $count: Int!\n $cursor: String\n) {\n viewer {\n ...TodoList_viewer_1G22uz\n id\n }\n}\n\nfragment TodoList_viewer_1G22uz on Viewer {\n id\n ...Todo_viewer\n listTodos(first: $count, after: $cursor) {\n edges {\n node {\n id\n ...Todo_todo\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n}\n\nfragment Todo_todo on Todo {\n id\n complete\n text\n}\n\nfragment Todo_viewer on Viewer {\n id\n}\n", 262 | "metadata": {} 263 | } 264 | }; 265 | })(); 266 | // prettier-ignore 267 | (node/*: any*/).hash = '96b5b1e5a70ab7f02d5793b185926213'; 268 | 269 | module.exports = node; 270 | -------------------------------------------------------------------------------- /src/components/__generated__/TodoListUpdateSubscription.graphql.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @flow 3 | * @relayHash 77c4b8104233a077c3993d66abe8c9ba 4 | */ 5 | 6 | /* eslint-disable */ 7 | 8 | 'use strict'; 9 | 10 | /*:: 11 | import type { ConcreteRequest } from 'relay-runtime'; 12 | export type TodoListUpdateSubscriptionVariables = {| 13 | user: string 14 | |}; 15 | export type TodoListUpdateSubscriptionResponse = {| 16 | +updatedTodo: ?{| 17 | +node: ?{| 18 | +complete: boolean 19 | |} 20 | |} 21 | |}; 22 | export type TodoListUpdateSubscription = {| 23 | variables: TodoListUpdateSubscriptionVariables, 24 | response: TodoListUpdateSubscriptionResponse, 25 | |}; 26 | */ 27 | 28 | 29 | /* 30 | subscription TodoListUpdateSubscription( 31 | $user: ID! 32 | ) { 33 | updatedTodo(userId: $user) { 34 | node { 35 | complete 36 | id 37 | } 38 | } 39 | } 40 | */ 41 | 42 | const node/*: ConcreteRequest*/ = (function(){ 43 | var v0 = [ 44 | { 45 | "kind": "LocalArgument", 46 | "name": "user", 47 | "type": "ID!", 48 | "defaultValue": null 49 | } 50 | ], 51 | v1 = [ 52 | { 53 | "kind": "Variable", 54 | "name": "userId", 55 | "variableName": "user" 56 | } 57 | ], 58 | v2 = { 59 | "kind": "ScalarField", 60 | "alias": null, 61 | "name": "complete", 62 | "args": null, 63 | "storageKey": null 64 | }; 65 | return { 66 | "kind": "Request", 67 | "fragment": { 68 | "kind": "Fragment", 69 | "name": "TodoListUpdateSubscription", 70 | "type": "Subscription", 71 | "metadata": null, 72 | "argumentDefinitions": (v0/*: any*/), 73 | "selections": [ 74 | { 75 | "kind": "LinkedField", 76 | "alias": null, 77 | "name": "updatedTodo", 78 | "storageKey": null, 79 | "args": (v1/*: any*/), 80 | "concreteType": "UpdateTodoPayload", 81 | "plural": false, 82 | "selections": [ 83 | { 84 | "kind": "LinkedField", 85 | "alias": null, 86 | "name": "node", 87 | "storageKey": null, 88 | "args": null, 89 | "concreteType": "Todo", 90 | "plural": false, 91 | "selections": [ 92 | (v2/*: any*/) 93 | ] 94 | } 95 | ] 96 | } 97 | ] 98 | }, 99 | "operation": { 100 | "kind": "Operation", 101 | "name": "TodoListUpdateSubscription", 102 | "argumentDefinitions": (v0/*: any*/), 103 | "selections": [ 104 | { 105 | "kind": "LinkedField", 106 | "alias": null, 107 | "name": "updatedTodo", 108 | "storageKey": null, 109 | "args": (v1/*: any*/), 110 | "concreteType": "UpdateTodoPayload", 111 | "plural": false, 112 | "selections": [ 113 | { 114 | "kind": "LinkedField", 115 | "alias": null, 116 | "name": "node", 117 | "storageKey": null, 118 | "args": null, 119 | "concreteType": "Todo", 120 | "plural": false, 121 | "selections": [ 122 | (v2/*: any*/), 123 | { 124 | "kind": "ScalarField", 125 | "alias": null, 126 | "name": "id", 127 | "args": null, 128 | "storageKey": null 129 | } 130 | ] 131 | } 132 | ] 133 | } 134 | ] 135 | }, 136 | "params": { 137 | "operationKind": "subscription", 138 | "name": "TodoListUpdateSubscription", 139 | "id": null, 140 | "text": "subscription TodoListUpdateSubscription(\n $user: ID!\n) {\n updatedTodo(userId: $user) {\n node {\n complete\n id\n }\n }\n}\n", 141 | "metadata": {} 142 | } 143 | }; 144 | })(); 145 | // prettier-ignore 146 | (node/*: any*/).hash = 'e9163b88adf21364a4289c3ff06a6610'; 147 | 148 | module.exports = node; 149 | -------------------------------------------------------------------------------- /src/components/__generated__/TodoList_viewer.graphql.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @flow 3 | */ 4 | 5 | /* eslint-disable */ 6 | 7 | 'use strict'; 8 | 9 | /*:: 10 | import type { ReaderFragment } from 'relay-runtime'; 11 | type Todo_todo$ref = any; 12 | type Todo_viewer$ref = any; 13 | import type { FragmentReference } from "relay-runtime"; 14 | declare export opaque type TodoList_viewer$ref: FragmentReference; 15 | declare export opaque type TodoList_viewer$fragmentType: TodoList_viewer$ref; 16 | export type TodoList_viewer = {| 17 | +id: ?string, 18 | +listTodos: {| 19 | +edges: ?$ReadOnlyArray<{| 20 | +node: {| 21 | +id: ?string, 22 | +$fragmentRefs: Todo_todo$ref, 23 | |} 24 | |}> 25 | |}, 26 | +$fragmentRefs: Todo_viewer$ref, 27 | +$refType: TodoList_viewer$ref, 28 | |}; 29 | export type TodoList_viewer$data = TodoList_viewer; 30 | export type TodoList_viewer$key = { 31 | +$data?: TodoList_viewer$data, 32 | +$fragmentRefs: TodoList_viewer$ref, 33 | ... 34 | }; 35 | */ 36 | 37 | 38 | const node/*: ReaderFragment*/ = (function(){ 39 | var v0 = { 40 | "kind": "ScalarField", 41 | "alias": null, 42 | "name": "id", 43 | "args": null, 44 | "storageKey": null 45 | }; 46 | return { 47 | "kind": "Fragment", 48 | "name": "TodoList_viewer", 49 | "type": "Viewer", 50 | "metadata": { 51 | "connection": [ 52 | { 53 | "count": "count", 54 | "cursor": "cursor", 55 | "direction": "forward", 56 | "path": [ 57 | "listTodos" 58 | ] 59 | } 60 | ] 61 | }, 62 | "argumentDefinitions": [ 63 | { 64 | "kind": "LocalArgument", 65 | "name": "count", 66 | "type": "Int", 67 | "defaultValue": null 68 | }, 69 | { 70 | "kind": "LocalArgument", 71 | "name": "cursor", 72 | "type": "String", 73 | "defaultValue": null 74 | } 75 | ], 76 | "selections": [ 77 | (v0/*: any*/), 78 | { 79 | "kind": "LinkedField", 80 | "alias": "listTodos", 81 | "name": "__TodoList_listTodos_connection", 82 | "storageKey": null, 83 | "args": null, 84 | "concreteType": "TodoConnection", 85 | "plural": false, 86 | "selections": [ 87 | { 88 | "kind": "LinkedField", 89 | "alias": null, 90 | "name": "edges", 91 | "storageKey": null, 92 | "args": null, 93 | "concreteType": "TodoEdge", 94 | "plural": true, 95 | "selections": [ 96 | { 97 | "kind": "LinkedField", 98 | "alias": null, 99 | "name": "node", 100 | "storageKey": null, 101 | "args": null, 102 | "concreteType": "Todo", 103 | "plural": false, 104 | "selections": [ 105 | (v0/*: any*/), 106 | { 107 | "kind": "ScalarField", 108 | "alias": null, 109 | "name": "__typename", 110 | "args": null, 111 | "storageKey": null 112 | }, 113 | { 114 | "kind": "FragmentSpread", 115 | "name": "Todo_todo", 116 | "args": null 117 | } 118 | ] 119 | }, 120 | { 121 | "kind": "ScalarField", 122 | "alias": null, 123 | "name": "cursor", 124 | "args": null, 125 | "storageKey": null 126 | } 127 | ] 128 | }, 129 | { 130 | "kind": "LinkedField", 131 | "alias": null, 132 | "name": "pageInfo", 133 | "storageKey": null, 134 | "args": null, 135 | "concreteType": "PageInfo", 136 | "plural": false, 137 | "selections": [ 138 | { 139 | "kind": "ScalarField", 140 | "alias": null, 141 | "name": "endCursor", 142 | "args": null, 143 | "storageKey": null 144 | }, 145 | { 146 | "kind": "ScalarField", 147 | "alias": null, 148 | "name": "hasNextPage", 149 | "args": null, 150 | "storageKey": null 151 | } 152 | ] 153 | } 154 | ] 155 | }, 156 | { 157 | "kind": "FragmentSpread", 158 | "name": "Todo_viewer", 159 | "args": null 160 | } 161 | ] 162 | }; 163 | })(); 164 | // prettier-ignore 165 | (node/*: any*/).hash = '096aae81d72bca23ff2013743e0ca892'; 166 | 167 | module.exports = node; 168 | -------------------------------------------------------------------------------- /src/components/__generated__/TodoUpdateMutation.graphql.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @flow 3 | * @relayHash f845ab6d7c6b6852d63d35372b92fea5 4 | */ 5 | 6 | /* eslint-disable */ 7 | 8 | 'use strict'; 9 | 10 | /*:: 11 | import type { ConcreteRequest } from 'relay-runtime'; 12 | export type UpdateTodoInput = {| 13 | id: string, 14 | complete?: ?boolean, 15 | clientMutationId?: ?string, 16 | |}; 17 | export type TodoUpdateMutationVariables = {| 18 | input: UpdateTodoInput 19 | |}; 20 | export type TodoUpdateMutationResponse = {| 21 | +updateTodo: {| 22 | +node: ?{| 23 | +complete: boolean 24 | |}, 25 | +userId: string, 26 | |} 27 | |}; 28 | export type TodoUpdateMutation = {| 29 | variables: TodoUpdateMutationVariables, 30 | response: TodoUpdateMutationResponse, 31 | |}; 32 | */ 33 | 34 | 35 | /* 36 | mutation TodoUpdateMutation( 37 | $input: UpdateTodoInput! 38 | ) { 39 | updateTodo(input: $input) { 40 | node { 41 | complete 42 | id 43 | } 44 | userId 45 | } 46 | } 47 | */ 48 | 49 | const node/*: ConcreteRequest*/ = (function(){ 50 | var v0 = [ 51 | { 52 | "kind": "LocalArgument", 53 | "name": "input", 54 | "type": "UpdateTodoInput!", 55 | "defaultValue": null 56 | } 57 | ], 58 | v1 = [ 59 | { 60 | "kind": "Variable", 61 | "name": "input", 62 | "variableName": "input" 63 | } 64 | ], 65 | v2 = { 66 | "kind": "ScalarField", 67 | "alias": null, 68 | "name": "complete", 69 | "args": null, 70 | "storageKey": null 71 | }, 72 | v3 = { 73 | "kind": "ScalarField", 74 | "alias": null, 75 | "name": "userId", 76 | "args": null, 77 | "storageKey": null 78 | }; 79 | return { 80 | "kind": "Request", 81 | "fragment": { 82 | "kind": "Fragment", 83 | "name": "TodoUpdateMutation", 84 | "type": "Mutation", 85 | "metadata": null, 86 | "argumentDefinitions": (v0/*: any*/), 87 | "selections": [ 88 | { 89 | "kind": "LinkedField", 90 | "alias": null, 91 | "name": "updateTodo", 92 | "storageKey": null, 93 | "args": (v1/*: any*/), 94 | "concreteType": "UpdateTodoPayload", 95 | "plural": false, 96 | "selections": [ 97 | { 98 | "kind": "LinkedField", 99 | "alias": null, 100 | "name": "node", 101 | "storageKey": null, 102 | "args": null, 103 | "concreteType": "Todo", 104 | "plural": false, 105 | "selections": [ 106 | (v2/*: any*/) 107 | ] 108 | }, 109 | (v3/*: any*/) 110 | ] 111 | } 112 | ] 113 | }, 114 | "operation": { 115 | "kind": "Operation", 116 | "name": "TodoUpdateMutation", 117 | "argumentDefinitions": (v0/*: any*/), 118 | "selections": [ 119 | { 120 | "kind": "LinkedField", 121 | "alias": null, 122 | "name": "updateTodo", 123 | "storageKey": null, 124 | "args": (v1/*: any*/), 125 | "concreteType": "UpdateTodoPayload", 126 | "plural": false, 127 | "selections": [ 128 | { 129 | "kind": "LinkedField", 130 | "alias": null, 131 | "name": "node", 132 | "storageKey": null, 133 | "args": null, 134 | "concreteType": "Todo", 135 | "plural": false, 136 | "selections": [ 137 | (v2/*: any*/), 138 | { 139 | "kind": "ScalarField", 140 | "alias": null, 141 | "name": "id", 142 | "args": null, 143 | "storageKey": null 144 | } 145 | ] 146 | }, 147 | (v3/*: any*/) 148 | ] 149 | } 150 | ] 151 | }, 152 | "params": { 153 | "operationKind": "mutation", 154 | "name": "TodoUpdateMutation", 155 | "id": null, 156 | "text": "mutation TodoUpdateMutation(\n $input: UpdateTodoInput!\n) {\n updateTodo(input: $input) {\n node {\n complete\n id\n }\n userId\n }\n}\n", 157 | "metadata": {} 158 | } 159 | }; 160 | })(); 161 | // prettier-ignore 162 | (node/*: any*/).hash = '1686c973944c12c4375d14ccea72d991'; 163 | 164 | module.exports = node; 165 | -------------------------------------------------------------------------------- /src/components/__generated__/Todo_todo.graphql.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @flow 3 | */ 4 | 5 | /* eslint-disable */ 6 | 7 | 'use strict'; 8 | 9 | /*:: 10 | import type { ReaderFragment } from 'relay-runtime'; 11 | import type { FragmentReference } from "relay-runtime"; 12 | declare export opaque type Todo_todo$ref: FragmentReference; 13 | declare export opaque type Todo_todo$fragmentType: Todo_todo$ref; 14 | export type Todo_todo = {| 15 | +id: ?string, 16 | +complete: boolean, 17 | +text: string, 18 | +$refType: Todo_todo$ref, 19 | |}; 20 | export type Todo_todo$data = Todo_todo; 21 | export type Todo_todo$key = { 22 | +$data?: Todo_todo$data, 23 | +$fragmentRefs: Todo_todo$ref, 24 | ... 25 | }; 26 | */ 27 | 28 | 29 | const node/*: ReaderFragment*/ = { 30 | "kind": "Fragment", 31 | "name": "Todo_todo", 32 | "type": "Todo", 33 | "metadata": null, 34 | "argumentDefinitions": [], 35 | "selections": [ 36 | { 37 | "kind": "ScalarField", 38 | "alias": null, 39 | "name": "id", 40 | "args": null, 41 | "storageKey": null 42 | }, 43 | { 44 | "kind": "ScalarField", 45 | "alias": null, 46 | "name": "complete", 47 | "args": null, 48 | "storageKey": null 49 | }, 50 | { 51 | "kind": "ScalarField", 52 | "alias": null, 53 | "name": "text", 54 | "args": null, 55 | "storageKey": null 56 | } 57 | ] 58 | }; 59 | // prettier-ignore 60 | (node/*: any*/).hash = '1bacf21a9a7fd8b20040bec87f55c267'; 61 | 62 | module.exports = node; 63 | -------------------------------------------------------------------------------- /src/components/__generated__/Todo_viewer.graphql.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @flow 3 | */ 4 | 5 | /* eslint-disable */ 6 | 7 | 'use strict'; 8 | 9 | /*:: 10 | import type { ReaderFragment } from 'relay-runtime'; 11 | import type { FragmentReference } from "relay-runtime"; 12 | declare export opaque type Todo_viewer$ref: FragmentReference; 13 | declare export opaque type Todo_viewer$fragmentType: Todo_viewer$ref; 14 | export type Todo_viewer = {| 15 | +id: ?string, 16 | +$refType: Todo_viewer$ref, 17 | |}; 18 | export type Todo_viewer$data = Todo_viewer; 19 | export type Todo_viewer$key = { 20 | +$data?: Todo_viewer$data, 21 | +$fragmentRefs: Todo_viewer$ref, 22 | ... 23 | }; 24 | */ 25 | 26 | 27 | const node/*: ReaderFragment*/ = { 28 | "kind": "Fragment", 29 | "name": "Todo_viewer", 30 | "type": "Viewer", 31 | "metadata": null, 32 | "argumentDefinitions": [], 33 | "selections": [ 34 | { 35 | "kind": "ScalarField", 36 | "alias": null, 37 | "name": "id", 38 | "args": null, 39 | "storageKey": null 40 | } 41 | ] 42 | }; 43 | // prettier-ignore 44 | (node/*: any*/).hash = '4fd564e83b8cbf119069009fd150eac7'; 45 | 46 | module.exports = node; 47 | -------------------------------------------------------------------------------- /src/environment.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: MIT-0 3 | 4 | import { API, graphqlOperation } from 'aws-amplify'; 5 | import { Environment, Network, RecordSource, Store } from 'relay-runtime'; 6 | 7 | 8 | function fetchQuery(operation, variables) { 9 | return API.graphql(graphqlOperation(operation.text, variables)); 10 | } 11 | 12 | function subscribe(operation, variables) { 13 | return API.graphql(graphqlOperation(operation.text, variables)).map(({value})=>value); 14 | } 15 | 16 | const environment = new Environment({ 17 | network: Network.create(fetchQuery, subscribe), 18 | store: new Store(new RecordSource()), 19 | }); 20 | 21 | export default environment; 22 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: MIT-0 3 | 4 | import Amplify, {Analytics} from 'aws-amplify'; 5 | import React from 'react'; 6 | import ReactDOM from 'react-dom'; 7 | 8 | import App from './components/App'; 9 | 10 | const AC = APPSYNC_CONFIG; 11 | 12 | Analytics.configure({disabled: true}); 13 | Amplify.configure({ 14 | aws_appsync_graphqlEndpoint: AC.AppSyncEndpoint, 15 | aws_appsync_region: AC.AppSyncRegion, 16 | aws_appsync_authenticationType: 'AMAZON_COGNITO_USER_POOLS', 17 | Auth: { 18 | userPoolId: AC.UserPool, 19 | userPoolWebClientId: AC.ClientId, 20 | } 21 | }); 22 | 23 | const root = document.createElement('div'); 24 | document.body.appendChild(root); 25 | ReactDOM.render( 26 | , 27 | root 28 | ); 29 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: MIT-0 3 | 4 | const path = require('path'); 5 | const webpack = require('webpack'); 6 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 7 | 8 | if (!process.env.APPSYNC_CONFIG) { 9 | console.error(`APPSYNC_CONFIG env var not set! 10 | Ensure it is set like APPSYNC_CONFIG='{ 11 | "UserPool": "", 12 | "AppSyncEndpoint": "", 13 | "ClientId": "", 14 | "AppSyncRegion": "" 15 | }'`); 16 | process.exit(1); 17 | } 18 | 19 | module.exports = (env, argv) => { 20 | let plugins = []; 21 | plugins = [ 22 | new webpack.DefinePlugin({APPSYNC_CONFIG: process.env.APPSYNC_CONFIG}), 23 | new HtmlWebpackPlugin({title: "AppSync + Relay"}) 24 | ] 25 | let config = { 26 | entry: './src/index.js', 27 | output: { 28 | filename: 'bundle.[chunkhash].js', 29 | path: path.resolve(__dirname, 'dist') 30 | }, 31 | devtool: 'source-map', 32 | devServer: { 33 | overlay: { 34 | warnings: true, 35 | errors: true 36 | } 37 | }, 38 | module: { 39 | rules: [ 40 | { 41 | test: /\.js$/, 42 | exclude: /(node_modules)/, 43 | use: { 44 | loader: 'babel-loader' 45 | } 46 | }, 47 | { 48 | test: /\.css$/, 49 | use: ["style-loader", "css-loader"] 50 | } 51 | ] 52 | }, 53 | plugins: plugins 54 | } 55 | return config; 56 | }; 57 | --------------------------------------------------------------------------------