├── example ├── .watchmanconfig ├── .gitignore ├── .babelrc ├── tsconfig.json ├── ts │ ├── definitions.d.ts │ ├── __relay_artifacts__ │ │ ├── Todo_todo.graphql.ts │ │ ├── Todo_viewer.graphql.ts │ │ ├── TodoApp_viewer.graphql.ts │ │ ├── RenameTodoMutation.graphql.ts │ │ ├── TodoListFooter_viewer.graphql.ts │ │ ├── MarkAllTodosMutation.graphql.ts │ │ ├── ChangeTodoStatusMutation.graphql.ts │ │ ├── RemoveTodoMutation.graphql.ts │ │ └── RemoveCompletedTodosMutation.graphql.ts │ ├── mutations │ │ ├── RenameTodoMutation.ts │ │ ├── RemoveTodoMutation.ts │ │ ├── MarkAllTodosMutation.ts │ │ ├── ChangeTodoStatusMutation.ts │ │ ├── RemoveCompletedTodosMutation.ts │ │ └── AddTodoMutation.ts │ ├── app.tsx │ └── components │ │ ├── TodoListFooter.tsx │ │ ├── TodoApp.tsx │ │ ├── TodoList.tsx │ │ ├── TodoTextInput.tsx │ │ └── Todo.tsx ├── tslint.json ├── public │ ├── index.html │ ├── learn.json │ └── base.css ├── scripts │ └── updateSchema.js ├── README.md ├── package.json ├── server.js └── data │ ├── database.js │ └── schema.graphql ├── example-hooks ├── .watchmanconfig ├── .gitignore ├── .babelrc ├── ts │ ├── definitions.d.ts │ ├── components │ │ ├── TodoRoot.tsx │ │ ├── TodoApp.tsx │ │ ├── TodoListFooter.tsx │ │ ├── TodoList.tsx │ │ ├── TodoTextInput.tsx │ │ └── Todo.tsx │ ├── ErrorBoundaryWithRetry.tsx │ ├── __relay_artifacts__ │ │ ├── Todo_todo.graphql.ts │ │ ├── Todo_viewer.graphql.ts │ │ ├── TodoApp_viewer.graphql.ts │ │ ├── RenameTodoMutation.graphql.ts │ │ ├── TodoListFooter_viewer.graphql.ts │ │ ├── MarkAllTodosMutation.graphql.ts │ │ ├── ChangeTodoStatusMutation.graphql.ts │ │ ├── RemoveTodoMutation.graphql.ts │ │ └── RemoveCompletedTodosMutation.graphql.ts │ ├── app.tsx │ └── mutations │ │ ├── RenameTodoMutation.ts │ │ ├── RemoveTodoMutation.ts │ │ ├── MarkAllTodosMutation.ts │ │ ├── ChangeTodoStatusMutation.ts │ │ ├── RemoveCompletedTodosMutation.ts │ │ └── AddTodoMutation.ts ├── tsconfig.json ├── tslint.json ├── public │ ├── index.html │ ├── base.css │ └── learn.json ├── scripts │ └── updateSchema.js ├── README.md ├── package.json ├── server.js └── data │ ├── database.js │ └── schema.graphql ├── .prettierignore ├── .husky └── pre-commit ├── tsconfig.build.json ├── test ├── fixtures │ ├── type-generator │ │ ├── plural-fragment.graphql │ │ ├── recursive-fragments.graphql │ │ ├── mutation-with-delete-record.graphql │ │ ├── refetchable-fragment.graphql │ │ ├── conditional.graphql │ │ ├── mutation-input-has-array.graphql │ │ ├── scalar-field.graphql │ │ ├── mutation.graphql │ │ ├── typename-inside-with-overlapping-fields.graphql │ │ ├── mutation-with-client-extension.graphql │ │ ├── query-with-raw-response-on-conditional.graphql │ │ ├── query-with-defer.graphql │ │ ├── query-with-stream.graphql │ │ ├── query-with-stream-connection.graphql │ │ ├── linked-field.graphql │ │ ├── query-with-handles.graphql │ │ ├── query-with-raw-response-on-literal-conditional.graphql │ │ ├── roots.graphql │ │ ├── match-field.graphql │ │ ├── match-field-in-query.graphql │ │ ├── mutation-with-response-on-inline-fragments.graphql │ │ ├── mutation-with-enums-on-fragment.graphql │ │ ├── unmasked-fragment-spreads.graphql │ │ ├── mutation-with-append-prepend-edge.graphql │ │ ├── mutation-with-nested-fragments.graphql │ │ ├── relay-client-id-field.graphql │ │ ├── fragment-spread.graphql │ │ ├── inline-fragment.graphql │ │ └── typename-on-union.graphql │ └── type-generator-disabled │ │ ├── query-with-module-field.graphql │ │ ├── query-with-match-fields.graphql │ │ └── query-with-multiple-match-fields.graphql ├── ambient.d.ts └── FindGraphQLTags-test.ts ├── .github ├── workflows │ ├── dependabot-auto-merge.yml │ ├── ci.yml │ └── release.yml └── dependabot.yml ├── src ├── index.ts ├── loadCompilerOptions.ts ├── addAnyTypeCast.ts └── formatGeneratedModule.ts ├── tslint.json ├── LICENSE.md ├── syncFixtures.ts ├── .gitignore ├── .autorc ├── package.json ├── README.md └── tsconfig.json /example/.watchmanconfig: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /example-hooks/.watchmanconfig: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /example-hooks/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | __snapshots__/ 2 | fixtures/ 3 | example/ts/__relay_artifacts 4 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | yarn lint-staged 5 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["./test/**/*"] 4 | } 5 | -------------------------------------------------------------------------------- /test/fixtures/type-generator/plural-fragment.graphql: -------------------------------------------------------------------------------- 1 | fragment PluralFragment on Node @relay(plural: true) { 2 | id 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/type-generator/recursive-fragments.graphql: -------------------------------------------------------------------------------- 1 | fragment FragmentSpread on Node { 2 | id 3 | ... @include(if: $condition) { 4 | ...FragmentSpread 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /test/fixtures/type-generator/mutation-with-delete-record.graphql: -------------------------------------------------------------------------------- 1 | mutation DeleteRecordMutation( 2 | $input: CommentDeleteInput! 3 | ) { 4 | commentDelete(input: $input) { 5 | deletedCommentId @deleteRecord 6 | } 7 | } -------------------------------------------------------------------------------- /test/fixtures/type-generator/refetchable-fragment.graphql: -------------------------------------------------------------------------------- 1 | fragment RefetchableFragment on Node 2 | @refetchable(queryName: "RefetchableFragmentQuery") { 3 | id 4 | fragAndField: profilePicture { 5 | uri 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /example-hooks/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "passPerPreset": true, 3 | "plugins": [ 4 | ["relay", { "artifactDirectory": "./ts/__relay_artifacts__" }], 5 | "transform-runtime" 6 | ], 7 | "presets": ["react", "env", "stage-0"] 8 | } 9 | -------------------------------------------------------------------------------- /test/fixtures/type-generator/conditional.graphql: -------------------------------------------------------------------------------- 1 | fragment ConditionField on Node { 2 | id @include(if: $condition) 3 | } 4 | 5 | fragment NestedCondition on Node { 6 | ... @include(if: $condition) { 7 | id 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /test/fixtures/type-generator/mutation-input-has-array.graphql: -------------------------------------------------------------------------------- 1 | mutation InputHasArray($input: UpdateAllSeenStateInput) @raw_response_type { 2 | viewerNotificationsUpdateAllSeenState(input: $input) { 3 | stories { 4 | actorCount 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /example/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "passPerPreset": true, 3 | "plugins": [ 4 | ["relay", { "artifactDirectory": "./ts/__relay_artifacts__" }], 5 | "transform-runtime" 6 | ], 7 | "presets": [ 8 | "react", 9 | "es2015", 10 | "stage-0" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /test/fixtures/type-generator/scalar-field.graphql: -------------------------------------------------------------------------------- 1 | fragment ScalarField on User { 2 | id 3 | name 4 | websites 5 | traits 6 | aliasedLinkedField: birthdate { 7 | aliasedField: year 8 | } 9 | screennames { 10 | name 11 | service 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /example/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "jsx": "preserve", 4 | "lib": ["dom", "esnext"], 5 | "module": "es2015", 6 | "noEmit": true, 7 | "moduleResolution": "node", 8 | "strict": true, 9 | "target": "es2015" 10 | }, 11 | "exclude": ["node_modules"] 12 | } 13 | -------------------------------------------------------------------------------- /example/ts/definitions.d.ts: -------------------------------------------------------------------------------- 1 | declare module "relay-devtools" { 2 | export function installRelayDevTools(): void 3 | } 4 | 5 | // FIXME: The @types/classnames typings say there is no default export, which is incorrect 6 | declare module "classnames" { 7 | export default function classnames(classes: any): string 8 | } 9 | -------------------------------------------------------------------------------- /example-hooks/ts/definitions.d.ts: -------------------------------------------------------------------------------- 1 | declare module "relay-devtools" { 2 | export function installRelayDevTools(): void 3 | } 4 | 5 | // FIXME: The @types/classnames typings say there is no default export, which is incorrect 6 | declare module "classnames" { 7 | export default function classnames(classes: any): string 8 | } 9 | -------------------------------------------------------------------------------- /example-hooks/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "jsx": "preserve", 4 | "lib": ["dom", "esnext"], 5 | "module": "es2015", 6 | "noEmit": true, 7 | "moduleResolution": "node", 8 | "strict": true, 9 | "target": "es2015", 10 | "allowSyntheticDefaultImports": true 11 | }, 12 | "exclude": ["node_modules"] 13 | } 14 | -------------------------------------------------------------------------------- /test/fixtures/type-generator/mutation.graphql: -------------------------------------------------------------------------------- 1 | mutation CommentCreateMutation( 2 | $input: CommentCreateInput! 3 | $first: Int 4 | $orderBy: [String!] 5 | ) { 6 | commentCreate(input: $input) { 7 | comment { 8 | id 9 | name 10 | friends(first: $first, orderby: $orderBy) { 11 | count 12 | } 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /test/fixtures/type-generator/typename-inside-with-overlapping-fields.graphql: -------------------------------------------------------------------------------- 1 | fragment TypenameInsideWithOverlappingFields on Viewer { 2 | actor { 3 | __typename 4 | ... on Page { 5 | id 6 | name 7 | } 8 | ... on User { 9 | id 10 | name 11 | profile_picture { 12 | uri 13 | } 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test/fixtures/type-generator/mutation-with-client-extension.graphql: -------------------------------------------------------------------------------- 1 | mutation Test($input: UpdateAllSeenStateInput) @raw_response_type { 2 | viewerNotificationsUpdateAllSeenState(input: $input) { 3 | stories { 4 | foos { 5 | bar 6 | } 7 | } 8 | } 9 | } 10 | 11 | extend type Story { 12 | foos: [Foo] 13 | } 14 | 15 | type Foo { 16 | bar: String 17 | } -------------------------------------------------------------------------------- /test/fixtures/type-generator/query-with-raw-response-on-conditional.graphql: -------------------------------------------------------------------------------- 1 | query ExampleQuery($id: ID!, $condition: Boolean!) @raw_response_type { 2 | node(id: $id) { 3 | ...FriendFragment 4 | } 5 | } 6 | 7 | fragment FriendFragment on User { 8 | ... @include(if: $condition) { 9 | name 10 | lastName 11 | feedback { 12 | id 13 | name 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test/fixtures/type-generator-disabled/query-with-module-field.graphql: -------------------------------------------------------------------------------- 1 | query Test @raw_response_type { 2 | node(id: "1") { 3 | ...Test_user 4 | } 5 | } 6 | 7 | fragment Test_user on User { 8 | plainUserRenderer { 9 | ...Test_userRenderer @module(name: "Renderer.react") 10 | } 11 | } 12 | 13 | fragment Test_userRenderer on PlainUserRenderer { 14 | user { 15 | username 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /test/fixtures/type-generator/query-with-defer.graphql: -------------------------------------------------------------------------------- 1 | query TestDefer @raw_response_type { 2 | node(id: "1") { 3 | ... on User { 4 | name 5 | friends(first: 10) 6 | @stream_connection(key: "TestDefer_friends", initial_count: 0) { 7 | edges { 8 | node { 9 | actor { 10 | name 11 | } 12 | } 13 | } 14 | } 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /test/fixtures/type-generator/query-with-stream.graphql: -------------------------------------------------------------------------------- 1 | query TestStream @raw_response_type { 2 | node(id: "1") { 3 | ... on User { 4 | name 5 | friends(first: 10) 6 | @stream_connection( 7 | key: "PaginationFragment_friends" 8 | initial_count: 1 9 | ) { 10 | edges { 11 | node { 12 | id 13 | } 14 | } 15 | } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /test/fixtures/type-generator/query-with-stream-connection.graphql: -------------------------------------------------------------------------------- 1 | query TestDefer @raw_response_type { 2 | node(id: "1") { 3 | ... on User { 4 | name 5 | friends(first: 10) 6 | @stream_connection(key: "TestDefer_friends", initial_count: 0) { 7 | edges { 8 | node { 9 | actor { 10 | name 11 | } 12 | } 13 | } 14 | } 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /test/fixtures/type-generator/linked-field.graphql: -------------------------------------------------------------------------------- 1 | fragment LinkedField on User { 2 | profilePicture { 3 | uri 4 | width 5 | height 6 | } 7 | hometown { 8 | # object 9 | id 10 | profilePicture { 11 | uri 12 | } 13 | } 14 | actor { 15 | # interface 16 | id 17 | } 18 | } 19 | 20 | query UnionTypeTest { 21 | neverNode { 22 | __typename 23 | ... on FakeNode { 24 | id 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /test/fixtures/type-generator/query-with-handles.graphql: -------------------------------------------------------------------------------- 1 | query LinkedHandleField($id: ID!) @raw_response_type { 2 | node(id: $id) { 3 | ... on User { 4 | friends(first: 10) @__clientField(handle: "clientFriends") { 5 | count 6 | } 7 | } 8 | } 9 | } 10 | 11 | query ScalarHandleField($id: ID!) @raw_response_type { 12 | node(id: $id) { 13 | ... on User { 14 | name @__clientField(handle: "clientName") 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /.github/workflows/dependabot-auto-merge.yml: -------------------------------------------------------------------------------- 1 | name: auto-merge 2 | 3 | on: 4 | pull_request_target: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | auto-merge: 10 | runs-on: ubuntu-latest 11 | if: github.actor == 'dependabot[bot]' 12 | steps: 13 | - uses: ahmadnassri/action-dependabot-auto-merge@v2.4 14 | with: 15 | github-token: ${{ secrets.AUTOMERGE_TOKEN }} 16 | command: "squash and merge" 17 | target: minor 18 | -------------------------------------------------------------------------------- /test/fixtures/type-generator/query-with-raw-response-on-literal-conditional.graphql: -------------------------------------------------------------------------------- 1 | query ExampleQuery($id: ID!) @raw_response_type { 2 | node(id: $id) { 3 | username 4 | ...FriendFragment 5 | ... @include(if: false) { 6 | friends(first: 0) { 7 | count 8 | } 9 | } 10 | } 11 | } 12 | 13 | fragment FriendFragment on User { 14 | ... @include(if: false) { 15 | name 16 | lastName 17 | feedback { 18 | id 19 | name 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /test/fixtures/type-generator/roots.graphql: -------------------------------------------------------------------------------- 1 | query ExampleQuery($id: ID!) { 2 | node(id: $id) { 3 | id 4 | } 5 | } 6 | 7 | fragment ExampleFragment on User { 8 | id 9 | } 10 | 11 | mutation TestMutation($input: CommentCreateInput!) { 12 | commentCreate(input: $input) { 13 | comment { 14 | id 15 | } 16 | } 17 | } 18 | 19 | subscription TestSubscription($input: FeedbackLikeInput) { 20 | feedbackLikeSubscribe(input: $input) { 21 | feedback { 22 | id 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /example/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "extends": ["tslint:recommended", "tslint-plugin-relay"], 4 | "jsRules": {}, 5 | "rules": { 6 | "arrow-parens": false, 7 | "interface-name": false, 8 | "object-literal-sort-keys": false, 9 | "variable-name": false, 10 | "trailing-comma": false, 11 | "quotemark": false, 12 | "ordered-imports": false, 13 | "no-console": false, 14 | "semicolon": false, 15 | "member-access": false, 16 | "curly": false, 17 | "object-literal-shorthand": false 18 | }, 19 | "rulesDirectory": [] 20 | } 21 | -------------------------------------------------------------------------------- /test/fixtures/type-generator/match-field.graphql: -------------------------------------------------------------------------------- 1 | fragment NameRendererFragment on User { 2 | id 3 | nameRenderer @match { 4 | ...PlainUserNameRenderer_name @module(name: "PlainUserNameRenderer.react") 5 | ...MarkdownUserNameRenderer_name 6 | @module(name: "MarkdownUserNameRenderer.react") 7 | } 8 | } 9 | 10 | fragment PlainUserNameRenderer_name on PlainUserNameRenderer { 11 | plaintext 12 | data { 13 | text 14 | } 15 | } 16 | 17 | fragment MarkdownUserNameRenderer_name on MarkdownUserNameRenderer { 18 | markdown 19 | data { 20 | markup 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /example-hooks/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "extends": ["tslint:recommended", "tslint-plugin-relay"], 4 | "jsRules": {}, 5 | "rules": { 6 | "arrow-parens": false, 7 | "interface-name": false, 8 | "object-literal-sort-keys": false, 9 | "variable-name": false, 10 | "trailing-comma": false, 11 | "quotemark": false, 12 | "ordered-imports": false, 13 | "no-console": false, 14 | "semicolon": false, 15 | "member-access": false, 16 | "curly": false, 17 | "object-literal-shorthand": false 18 | }, 19 | "rulesDirectory": [] 20 | } 21 | -------------------------------------------------------------------------------- /test/fixtures/type-generator/match-field-in-query.graphql: -------------------------------------------------------------------------------- 1 | query NameRendererQuery { 2 | me { 3 | nameRenderer @match { 4 | ...PlainUserNameRenderer_name @module(name: "PlainUserNameRenderer.react") 5 | ...MarkdownUserNameRenderer_name 6 | @module(name: "MarkdownUserNameRenderer.react") 7 | } 8 | } 9 | } 10 | 11 | fragment PlainUserNameRenderer_name on PlainUserNameRenderer { 12 | plaintext 13 | data { 14 | text 15 | } 16 | } 17 | 18 | fragment MarkdownUserNameRenderer_name on MarkdownUserNameRenderer { 19 | markdown 20 | data { 21 | markup 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /test/fixtures/type-generator/mutation-with-response-on-inline-fragments.graphql: -------------------------------------------------------------------------------- 1 | mutation TestMutation($input: CommentCreateInput!) @raw_response_type { 2 | commentCreate(input: $input) { 3 | viewer { 4 | actor { 5 | ...InlineFragmentWithOverlappingFields 6 | } 7 | } 8 | } 9 | } 10 | 11 | fragment InlineFragmentWithOverlappingFields on Actor { 12 | ... on User { 13 | hometown { 14 | id 15 | name 16 | } 17 | } 18 | ... on Page { 19 | name 20 | hometown { 21 | id 22 | message { 23 | text 24 | } 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /test/fixtures/type-generator/mutation-with-enums-on-fragment.graphql: -------------------------------------------------------------------------------- 1 | mutation CommentCreateMutation( 2 | $input: CommentCreateInput! 3 | $first: Int 4 | $orderBy: [String!] 5 | ) @raw_response_type { 6 | commentCreate(input: $input) { 7 | comment { 8 | friends(first: $first, orderby: $orderBy) { 9 | edges { 10 | node { 11 | id 12 | __typename 13 | ...FriendFragment 14 | } 15 | } 16 | } 17 | } 18 | } 19 | } 20 | 21 | fragment FriendFragment on User { 22 | name 23 | lastName 24 | profilePicture2 { 25 | test_enums 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { PluginInterface } from "relay-compiler/lib/language/RelayLanguagePluginInterface"; 2 | import { find } from "./FindGraphQLTags"; 3 | import { formatterFactory } from "./formatGeneratedModule"; 4 | import { loadCompilerOptions } from "./loadCompilerOptions"; 5 | import * as TypeScriptGenerator from "./TypeScriptGenerator"; 6 | 7 | export default function plugin(): PluginInterface { 8 | return { 9 | inputExtensions: ["ts", "tsx"], 10 | outputExtension: "ts", 11 | findGraphQLTags: find, 12 | formatModule: formatterFactory(loadCompilerOptions()), 13 | typeGenerator: TypeScriptGenerator, 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /test/fixtures/type-generator/unmasked-fragment-spreads.graphql: -------------------------------------------------------------------------------- 1 | fragment UserProfile on User { 2 | profilePicture(size: $ProfilePicture_SIZE) { 3 | ...PhotoFragment @relay(mask: false) 4 | 5 | # duplicated field should be merged 6 | ...AnotherRecursiveFragment @relay(mask: false) 7 | 8 | # Compose child fragment 9 | ...PhotoFragment 10 | } 11 | } 12 | 13 | fragment PhotoFragment on Image { 14 | uri 15 | ...RecursiveFragment @relay(mask: false) 16 | } 17 | 18 | fragment RecursiveFragment on Image @relay(mask: false) { 19 | uri 20 | width 21 | } 22 | 23 | fragment AnotherRecursiveFragment on Image { 24 | uri 25 | height 26 | } 27 | -------------------------------------------------------------------------------- /test/fixtures/type-generator/mutation-with-append-prepend-edge.graphql: -------------------------------------------------------------------------------- 1 | mutation AppendEdgeMutation( 2 | $input: CommentCreateInput!, 3 | $connections: [ID!]! 4 | ) { 5 | commentCreate(input: $input) { 6 | feedbackCommentEdge @appendEdge(connections: $connections) { 7 | cursor 8 | node { 9 | id 10 | } 11 | } 12 | } 13 | } 14 | 15 | mutation PrependEdgeMutation( 16 | $input: CommentCreateInput!, 17 | $connections: [ID!]! 18 | ) { 19 | commentCreate(input: $input) { 20 | feedbackCommentEdge @prependEdge(connections: $connections) { 21 | cursor 22 | node { 23 | id 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /test/fixtures/type-generator/mutation-with-nested-fragments.graphql: -------------------------------------------------------------------------------- 1 | mutation CommentCreateMutation( 2 | $input: CommentCreateInput! 3 | $first: Int 4 | $orderBy: [String!] 5 | ) @raw_response_type { 6 | commentCreate(input: $input) { 7 | comment { 8 | friends(first: $first, orderby: $orderBy) { 9 | edges { 10 | node { 11 | lastName 12 | ...FriendFragment 13 | } 14 | } 15 | } 16 | } 17 | } 18 | } 19 | 20 | fragment FriendFragment on User { 21 | name 22 | lastName 23 | feedback { 24 | ...FeedbackFragment 25 | } 26 | } 27 | 28 | fragment FeedbackFragment on Feedback { 29 | id 30 | name 31 | } 32 | -------------------------------------------------------------------------------- /test/fixtures/type-generator-disabled/query-with-match-fields.graphql: -------------------------------------------------------------------------------- 1 | query Test @raw_response_type { 2 | node(id: "1") { 3 | ...NameRendererFragment 4 | } 5 | } 6 | 7 | fragment NameRendererFragment on User { 8 | id 9 | nameRenderer @match { 10 | ...PlainUserNameRenderer_name @module(name: "PlainUserNameRenderer.react") 11 | ...MarkdownUserNameRenderer_name 12 | @module(name: "MarkdownUserNameRenderer.react") 13 | } 14 | } 15 | 16 | fragment PlainUserNameRenderer_name on PlainUserNameRenderer { 17 | plaintext 18 | data { 19 | text 20 | } 21 | } 22 | 23 | fragment MarkdownUserNameRenderer_name on MarkdownUserNameRenderer { 24 | markdown 25 | data { 26 | markup 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /test/fixtures/type-generator/relay-client-id-field.graphql: -------------------------------------------------------------------------------- 1 | query RelayClientIDFieldQuery($id: ID!) { 2 | __id # ok on query type 3 | me { 4 | __id # ok on object type with 'id' 5 | __typename 6 | id 7 | } 8 | node(id: $id) { 9 | __id # ok on interface type 10 | __typename 11 | id 12 | ... on Comment { 13 | commentBody(supported: ["PlainCommentBody"]) { 14 | __id # ok on union type 15 | __typename 16 | ... on PlainCommentBody { 17 | __id # ok on object type w/o 'id' 18 | text { 19 | __id # ok on object type w/o 'id' 20 | __typename 21 | text 22 | } 23 | } 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/loadCompilerOptions.ts: -------------------------------------------------------------------------------- 1 | import * as ts from "typescript"; 2 | 3 | export const loadCompilerOptions = (): ts.CompilerOptions => { 4 | const configFileName = ts.findConfigFile(process.cwd(), ts.sys.fileExists); 5 | if (!configFileName) { 6 | return {}; 7 | } 8 | const configFile = ts.readConfigFile(configFileName, ts.sys.readFile); 9 | if (configFile.error) { 10 | return {}; 11 | } 12 | // parse config file contents (to convert strings to enum values etc.) 13 | const parsedConfig = ts.parseJsonConfigFileContent( 14 | configFile.config, 15 | ts.sys, 16 | ts.sys.resolvePath('./') 17 | ); 18 | if (parsedConfig.errors.length > 0) { 19 | return {}; 20 | } 21 | return parsedConfig.options; 22 | }; 23 | -------------------------------------------------------------------------------- /example/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Relay • TodoMVC 7 | 8 | 9 | 10 | 11 |
12 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | pull_request: 5 | jobs: 6 | ci: 7 | name: Continuous Integration 8 | runs-on: ubuntu-latest 9 | strategy: 10 | matrix: 11 | node-version: [12.x, 14.x, 16.x] 12 | steps: 13 | - uses: actions/checkout@v2 14 | - uses: actions/setup-node@v2 15 | with: 16 | node-version: ${{ matrix.node-version }} 17 | - name: Cache node_modules 18 | id: cache-modules 19 | uses: actions/cache@v2 20 | with: 21 | path: node_modules 22 | key: ${{ matrix.node-version }}-${{ runner.OS }}-build-${{ hashFiles('yarn.lock') }} 23 | - run: yarn install 24 | - run: yarn test 25 | - run: yarn build 26 | - run: yarn lint 27 | -------------------------------------------------------------------------------- /example-hooks/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Relay • TodoMVC 7 | 8 | 9 | 10 | 11 |
12 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /example-hooks/ts/components/TodoRoot.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import { useLazyLoadQuery, graphql } from "react-relay/hooks" 3 | 4 | import { TodoRootQuery } from "../__relay_artifacts__/TodoRootQuery.graphql" 5 | 6 | import TodoApp from "./TodoApp" 7 | 8 | const TodoRoot = () => { 9 | const { viewer } = useLazyLoadQuery( 10 | graphql` 11 | query TodoRootQuery { 12 | viewer { 13 | ...TodoApp_viewer 14 | } 15 | } 16 | `, 17 | {}, 18 | ) 19 | 20 | return 21 | } 22 | 23 | const TodoRootWrapper = () => { 24 | return ( 25 | Loading}> 26 | 27 | 28 | ) 29 | } 30 | 31 | export default TodoRootWrapper 32 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "extends": ["tslint:recommended", "tslint-config-prettier"], 4 | "jsRules": {}, 5 | "rules": { 6 | "curly": false, 7 | "interface-name": [true, "never-prefix"], 8 | "max-classes-per-file": false, 9 | "member-access": [false], 10 | "no-shadowed-variable": false, 11 | "member-ordering": false, 12 | "forin": false, 13 | "no-console": false, 14 | "no-namespace": [true, "allow-declarations"], 15 | "no-unused-expression": false, 16 | "no-var-requires": false, 17 | "object-literal-sort-keys": false, 18 | "ordered-imports": true, 19 | "trailing-comma": false, 20 | "interface-over-type-literal": false, 21 | "variable-name": [true, "ban-keywords", "allow-leading-underscore"], 22 | "jsdoc-format": false 23 | }, 24 | "rulesDirectory": [] 25 | } 26 | -------------------------------------------------------------------------------- /test/ambient.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace jest { 2 | interface Matchers { 3 | // Used in TypeScriptGenerator-tests.ts 4 | toMatchFile(width: string): void; 5 | } 6 | } 7 | 8 | declare module "relay-test-utils-internal/lib/generateTestsFromFixtures" { 9 | export function generateTestsFromFixtures( 10 | path: string, 11 | callback: (text: string) => void 12 | ): void; 13 | } 14 | 15 | declare module "relay-test-utils-internal/lib/parseGraphQLText" { 16 | import type { Fragment, Root, Schema } from "relay-compiler"; 17 | function parseGraphQLText( 18 | schema: Schema, 19 | text: string 20 | ): { definitions: ReadonlyArray; schema: Schema }; 21 | export = parseGraphQLText; 22 | } 23 | 24 | declare module "relay-test-utils-internal/lib/TestSchema" { 25 | import type { Schema } from "relay-compiler"; 26 | export const TestSchema: Schema; 27 | } 28 | -------------------------------------------------------------------------------- /test/fixtures/type-generator/fragment-spread.graphql: -------------------------------------------------------------------------------- 1 | fragment FragmentSpread on Node { 2 | id 3 | ...OtherFragment 4 | justFrag: profilePicture { 5 | ...PictureFragment 6 | } 7 | fragAndField: profilePicture { 8 | uri 9 | ...PictureFragment 10 | } 11 | ... on User { 12 | ...UserFrag1 13 | ...UserFrag2 14 | } 15 | } 16 | 17 | fragment ConcreateTypes on Viewer { 18 | actor { 19 | __typename 20 | ... on Page { 21 | id 22 | ...PageFragment 23 | } 24 | ... on User { 25 | name 26 | } 27 | } 28 | } 29 | 30 | fragment PictureFragment on Image { 31 | __typename 32 | } 33 | 34 | fragment OtherFragment on Node { 35 | __typename 36 | } 37 | 38 | fragment PageFragment on Page { 39 | __typename 40 | } 41 | 42 | fragment UserFrag1 on User { 43 | __typename 44 | } 45 | 46 | fragment UserFrag2 on User { 47 | __typename 48 | } 49 | -------------------------------------------------------------------------------- /example-hooks/ts/ErrorBoundaryWithRetry.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | interface Props { 4 | fallback: (error: Error, retryFn: () => void) => JSX.Element 5 | } 6 | interface State { 7 | error: Error | null 8 | } 9 | 10 | class ErrorBoundaryWithRetry extends React.Component { 11 | state = { error: null } 12 | 13 | // @ts-ignore 14 | static getDerivedStateFromError(error) { 15 | return { error: error } 16 | } 17 | 18 | _retry = () => { 19 | this.setState({ error: null }) 20 | } 21 | 22 | render() { 23 | const { children, fallback } = this.props 24 | const { error } = this.state 25 | 26 | if (error) { 27 | if (typeof fallback === "function") { 28 | return fallback(error!, this._retry) 29 | } 30 | return fallback 31 | } 32 | return children 33 | } 34 | } 35 | 36 | export default ErrorBoundaryWithRetry 37 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | release: 10 | runs-on: ubuntu-latest 11 | if: "!contains(github.event.head_commit.message, 'ci skip') && !contains(github.event.head_commit.message, 'skip ci')" 12 | steps: 13 | - uses: actions/checkout@v2 14 | 15 | - name: Prepare repository 16 | run: git fetch --unshallow --tags 17 | 18 | - name: Use Node.js 14.x 19 | uses: actions/setup-node@v1 20 | with: 21 | node-version: 14.x 22 | 23 | - name: Install dependencies 24 | uses: bahmutov/npm-install@v1 25 | 26 | - name: Test and build 27 | run: | 28 | yarn test 29 | yarn build 30 | 31 | - name: Create Release 32 | env: 33 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 34 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 35 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 36 | run: yarn release 37 | -------------------------------------------------------------------------------- /example/scripts/updateSchema.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env babel-node 2 | /** 3 | * This file provided by Facebook is for non-commercial testing and evaluation 4 | * purposes only. Facebook reserves all rights not expressly granted. 5 | * 6 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 7 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 8 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 9 | * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 10 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 11 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 12 | */ 13 | 14 | import fs from 'fs'; 15 | import path from 'path'; 16 | import { schema } from '../data/schema'; 17 | import { printSchema } from 'graphql'; 18 | 19 | const schemaPath = path.resolve(__dirname, '../data/schema.graphql'); 20 | 21 | fs.writeFileSync(schemaPath, printSchema(schema)); 22 | 23 | console.log('Wrote ' + schemaPath); 24 | -------------------------------------------------------------------------------- /example-hooks/scripts/updateSchema.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env babel-node 2 | /** 3 | * This file provided by Facebook is for non-commercial testing and evaluation 4 | * purposes only. Facebook reserves all rights not expressly granted. 5 | * 6 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 7 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 8 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 9 | * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 10 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 11 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 12 | */ 13 | 14 | import fs from 'fs'; 15 | import path from 'path'; 16 | import { schema } from '../data/schema'; 17 | import { printSchema } from 'graphql'; 18 | 19 | const schemaPath = path.resolve(__dirname, '../data/schema.graphql'); 20 | 21 | fs.writeFileSync(schemaPath, printSchema(schema)); 22 | 23 | console.log('Wrote ' + schemaPath); 24 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright 2018 Kaare Hoff Skovgaard , Eloy Durán 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /src/addAnyTypeCast.ts: -------------------------------------------------------------------------------- 1 | import * as ts from "typescript"; 2 | 3 | export default function addAsAnyToObjectLiterals(oldSource: string): string { 4 | function transformer(context: ts.TransformationContext) { 5 | return function transform(rootNode: T) { 6 | function visit(node: ts.Node): ts.Node { 7 | if (node.kind === ts.SyntaxKind.ObjectLiteralExpression) { 8 | return ts.factory.createAsExpression( 9 | node as ts.Expression, 10 | ts.factory.createTypeReferenceNode("any", []) 11 | ); 12 | } 13 | return ts.visitEachChild(node, visit, context); 14 | } 15 | return ts.visitNode(rootNode, visit); 16 | }; 17 | } 18 | 19 | const source = ts.createSourceFile( 20 | "", 21 | oldSource, 22 | ts.ScriptTarget.ES2015, 23 | true, 24 | ts.ScriptKind.TS 25 | ); 26 | 27 | const result = ts.transform(source, [transformer]); 28 | 29 | const printer: ts.Printer = ts.createPrinter({ 30 | newLine: ts.NewLineKind.LineFeed, 31 | }); 32 | return printer.printFile(result.transformed[0] as ts.SourceFile); 33 | } 34 | -------------------------------------------------------------------------------- /test/fixtures/type-generator/inline-fragment.graphql: -------------------------------------------------------------------------------- 1 | fragment InlineFragment on Node { 2 | id 3 | ... on Actor { 4 | id 5 | name 6 | } 7 | ... on User { 8 | message { 9 | text 10 | } 11 | } 12 | } 13 | 14 | fragment InlineFragmentWithOverlappingFields on Actor { 15 | ... on User { 16 | hometown { 17 | id 18 | name 19 | } 20 | } 21 | ... on Page { 22 | name 23 | hometown { 24 | id 25 | message { 26 | text 27 | } 28 | } 29 | } 30 | } 31 | 32 | fragment InlineFragmentConditionalID on Node { 33 | ... on Actor { 34 | id # nullable since it's conditional 35 | name 36 | } 37 | } 38 | 39 | fragment InlineFragmentKitchenSink on Story { 40 | actor { 41 | id 42 | profilePicture { 43 | uri 44 | } 45 | ... on User { 46 | id 47 | name 48 | ...SomeFragment 49 | profilePicture { 50 | width 51 | } 52 | } 53 | ... on Page { 54 | profilePicture { 55 | uri 56 | height 57 | } 58 | } 59 | } 60 | } 61 | 62 | fragment SomeFragment on User { 63 | __typename 64 | } 65 | -------------------------------------------------------------------------------- /test/fixtures/type-generator-disabled/query-with-multiple-match-fields.graphql: -------------------------------------------------------------------------------- 1 | query Test @raw_response_type { 2 | node(id: "1") { 3 | ... on User { 4 | username 5 | ...NameRendererFragment 6 | } 7 | } 8 | viewer { 9 | actor { 10 | ... on User { 11 | name 12 | ...AnotherNameRendererFragment 13 | } 14 | } 15 | } 16 | } 17 | 18 | fragment NameRendererFragment on User { 19 | id 20 | nameRenderer @match { 21 | ...PlainUserNameRenderer_name @module(name: "PlainUserNameRenderer.react") 22 | ...MarkdownUserNameRenderer_name 23 | @module(name: "MarkdownUserNameRenderer.react") 24 | } 25 | } 26 | 27 | fragment AnotherNameRendererFragment on User { 28 | name 29 | nameRenderer @match { 30 | ...PlainUserNameRenderer_name @module(name: "PlainUserNameRenderer.react") 31 | ...MarkdownUserNameRenderer_name 32 | @module(name: "MarkdownUserNameRenderer.react") 33 | } 34 | } 35 | 36 | fragment PlainUserNameRenderer_name on PlainUserNameRenderer { 37 | plaintext 38 | data { 39 | text 40 | } 41 | } 42 | 43 | fragment MarkdownUserNameRenderer_name on MarkdownUserNameRenderer { 44 | markdown 45 | data { 46 | markup 47 | } 48 | } -------------------------------------------------------------------------------- /syncFixtures.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "fs"; 2 | import * as glob from "glob"; 3 | import * as path from "path"; 4 | 5 | type SyncFixturesType = { 6 | souceFilePaths: Array<{ 7 | cwd: string; 8 | pattern: string; 9 | }>; 10 | dest: string; 11 | }; 12 | 13 | const syncFixtures = ({ souceFilePaths, dest }: SyncFixturesType) => { 14 | souceFilePaths.forEach(({ cwd, pattern }) => { 15 | glob 16 | .sync(pattern, { 17 | cwd 18 | }) 19 | .forEach(filePath => { 20 | const file = getFileNameFromPath(filePath); 21 | try { 22 | fs.copyFileSync( 23 | path.join(__dirname, `${cwd}/${filePath}`), 24 | `${dest}/${file}` 25 | ); 26 | console.log(`${filePath} was copied`); 27 | } catch (error) { 28 | console.error(error); 29 | } 30 | }); 31 | }); 32 | }; 33 | 34 | const getFileNameFromPath = (filePath: string) => filePath.split("/").pop(); 35 | 36 | const souceFilePaths = [ 37 | { 38 | cwd: 39 | "../relay/packages/relay-compiler/language/javascript/__tests__/fixtures/flow-generator", 40 | pattern: "**/*.graphql" 41 | } 42 | ]; 43 | 44 | syncFixtures({ souceFilePaths, dest: "./test/fixtures/type-generator" }); 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | .vscode 61 | .idea/ 62 | /lib 63 | /bin 64 | 65 | .npmrc 66 | 67 | # Build artifacts 68 | .yalc 69 | yalc.lock 70 | 71 | .DS_Store 72 | -------------------------------------------------------------------------------- /.autorc: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Eloy Durán", 3 | "email": "eloy.de.enige@gmail.com", 4 | "labels": [ 5 | { 6 | "name": "Version: Major", 7 | "changelogTitle": "💥 Breaking Change", 8 | "description": "Increment the major version when merged", 9 | "releaseType": "major" 10 | }, 11 | { 12 | "name": "Version: Minor", 13 | "changelogTitle": "🚀 Enhancement", 14 | "description": "Increment the minor version when merged", 15 | "releaseType": "minor" 16 | }, 17 | { 18 | "name": "Version: Patch", 19 | "changelogTitle": "🐛 Bug Fix", 20 | "description": "Increment the patch version when merged", 21 | "releaseType": "patch" 22 | }, 23 | { 24 | "name": "Skip Release", 25 | "description": "Preserve the current version when merged", 26 | "releaseType": "skip" 27 | }, 28 | { 29 | "name": "Version: Trivial", 30 | "changelogTitle": "🏠 Internal", 31 | "description": "Changes only affect the internal API", 32 | "releaseType": "none" 33 | }, 34 | { 35 | "name": "Docs", 36 | "changelogTitle": "📝 Documentation", 37 | "description": "Changes only affect the documentation", 38 | "releaseType": "none" 39 | } 40 | ], 41 | "plugins": [ 42 | "npm", 43 | "released" 44 | ] 45 | } 46 | -------------------------------------------------------------------------------- /test/FindGraphQLTags-test.ts: -------------------------------------------------------------------------------- 1 | import * as FindGraphQLTags from "../src/FindGraphQLTags"; 2 | 3 | describe("FindGraphQLTags", () => { 4 | function find(text: string) { 5 | return FindGraphQLTags.find(text, "/path/to/TestModule.ts"); 6 | } 7 | 8 | it("extracts a tag", () => { 9 | expect( 10 | find(` 11 | import { createFragmentContainer, graphql } from 'react-relay' 12 | export default createFragmentContainer( 13 | props =>
{props.artist.name}
, 14 | graphql\` 15 | fragment TestModule_artist on Artist { 16 | name 17 | } 18 | \` 19 | ) 20 | `) 21 | ).toEqual([ 22 | { 23 | keyName: null, 24 | template: /* GraphQL */ ` 25 | fragment TestModule_artist on Artist { 26 | name 27 | } 28 | `, 29 | sourceLocationOffset: { line: 5, column: 16 }, 30 | }, 31 | ]); 32 | }); 33 | 34 | it("extracts a tag in line 1", () => { 35 | expect( 36 | find(`graphql\`fragment TestModule_artist on Artist {name}\``) 37 | ).toEqual([ 38 | { 39 | keyName: null, 40 | template: `fragment TestModule_artist on Artist {name}`, 41 | sourceLocationOffset: { line: 1, column: 8 }, 42 | }, 43 | ]); 44 | }); 45 | // TODO: Cover all cases where tags are extracted 46 | }); 47 | -------------------------------------------------------------------------------- /example/ts/__relay_artifacts__/Todo_todo.graphql.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | /* eslint-disable */ 3 | 4 | import { ReaderFragment } from "relay-runtime"; 5 | import { FragmentRefs } from "relay-runtime"; 6 | export type Todo_todo = { 7 | readonly complete: boolean | null; 8 | readonly id: string; 9 | readonly text: string | null; 10 | readonly " $refType": "Todo_todo"; 11 | }; 12 | export type Todo_todo$data = Todo_todo; 13 | export type Todo_todo$key = { 14 | readonly " $data"?: Todo_todo$data; 15 | readonly " $fragmentRefs": FragmentRefs<"Todo_todo">; 16 | }; 17 | 18 | 19 | 20 | const node: ReaderFragment = { 21 | "argumentDefinitions": [], 22 | "kind": "Fragment", 23 | "metadata": null, 24 | "name": "Todo_todo", 25 | "selections": [ 26 | { 27 | "alias": null, 28 | "args": null, 29 | "kind": "ScalarField", 30 | "name": "complete", 31 | "storageKey": null 32 | }, 33 | { 34 | "alias": null, 35 | "args": null, 36 | "kind": "ScalarField", 37 | "name": "id", 38 | "storageKey": null 39 | }, 40 | { 41 | "alias": null, 42 | "args": null, 43 | "kind": "ScalarField", 44 | "name": "text", 45 | "storageKey": null 46 | } 47 | ], 48 | "type": "Todo", 49 | "abstractKey": null 50 | }; 51 | (node as any).hash = '1f979eb84ff026fe8a89323dd533d1fc'; 52 | export default node; 53 | -------------------------------------------------------------------------------- /example-hooks/ts/__relay_artifacts__/Todo_todo.graphql.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | /* eslint-disable */ 3 | 4 | import { ReaderFragment } from "relay-runtime"; 5 | import { FragmentRefs } from "relay-runtime"; 6 | export type Todo_todo = { 7 | readonly complete: boolean | null; 8 | readonly id: string; 9 | readonly text: string | null; 10 | readonly " $refType": "Todo_todo"; 11 | }; 12 | export type Todo_todo$data = Todo_todo; 13 | export type Todo_todo$key = { 14 | readonly " $data"?: Todo_todo$data; 15 | readonly " $fragmentRefs": FragmentRefs<"Todo_todo">; 16 | }; 17 | 18 | 19 | 20 | const node: ReaderFragment = { 21 | "argumentDefinitions": [], 22 | "kind": "Fragment", 23 | "metadata": null, 24 | "name": "Todo_todo", 25 | "selections": [ 26 | { 27 | "alias": null, 28 | "args": null, 29 | "kind": "ScalarField", 30 | "name": "complete", 31 | "storageKey": null 32 | }, 33 | { 34 | "alias": null, 35 | "args": null, 36 | "kind": "ScalarField", 37 | "name": "id", 38 | "storageKey": null 39 | }, 40 | { 41 | "alias": null, 42 | "args": null, 43 | "kind": "ScalarField", 44 | "name": "text", 45 | "storageKey": null 46 | } 47 | ], 48 | "type": "Todo", 49 | "abstractKey": null 50 | }; 51 | (node as any).hash = '1f979eb84ff026fe8a89323dd533d1fc'; 52 | export default node; 53 | -------------------------------------------------------------------------------- /example/ts/__relay_artifacts__/Todo_viewer.graphql.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | /* eslint-disable */ 3 | 4 | import { ReaderFragment } from "relay-runtime"; 5 | import { FragmentRefs } from "relay-runtime"; 6 | export type Todo_viewer = { 7 | readonly id: string; 8 | readonly totalCount: number | null; 9 | readonly completedCount: number | null; 10 | readonly " $refType": "Todo_viewer"; 11 | }; 12 | export type Todo_viewer$data = Todo_viewer; 13 | export type Todo_viewer$key = { 14 | readonly " $data"?: Todo_viewer$data; 15 | readonly " $fragmentRefs": FragmentRefs<"Todo_viewer">; 16 | }; 17 | 18 | 19 | 20 | const node: ReaderFragment = { 21 | "argumentDefinitions": [], 22 | "kind": "Fragment", 23 | "metadata": null, 24 | "name": "Todo_viewer", 25 | "selections": [ 26 | { 27 | "alias": null, 28 | "args": null, 29 | "kind": "ScalarField", 30 | "name": "id", 31 | "storageKey": null 32 | }, 33 | { 34 | "alias": null, 35 | "args": null, 36 | "kind": "ScalarField", 37 | "name": "totalCount", 38 | "storageKey": null 39 | }, 40 | { 41 | "alias": null, 42 | "args": null, 43 | "kind": "ScalarField", 44 | "name": "completedCount", 45 | "storageKey": null 46 | } 47 | ], 48 | "type": "User", 49 | "abstractKey": null 50 | }; 51 | (node as any).hash = '1e2b17bb7b92d4521c4e72309d996339'; 52 | export default node; 53 | -------------------------------------------------------------------------------- /example-hooks/ts/__relay_artifacts__/Todo_viewer.graphql.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | /* eslint-disable */ 3 | 4 | import { ReaderFragment } from "relay-runtime"; 5 | import { FragmentRefs } from "relay-runtime"; 6 | export type Todo_viewer = { 7 | readonly id: string; 8 | readonly totalCount: number | null; 9 | readonly completedCount: number | null; 10 | readonly " $refType": "Todo_viewer"; 11 | }; 12 | export type Todo_viewer$data = Todo_viewer; 13 | export type Todo_viewer$key = { 14 | readonly " $data"?: Todo_viewer$data; 15 | readonly " $fragmentRefs": FragmentRefs<"Todo_viewer">; 16 | }; 17 | 18 | 19 | 20 | const node: ReaderFragment = { 21 | "argumentDefinitions": [], 22 | "kind": "Fragment", 23 | "metadata": null, 24 | "name": "Todo_viewer", 25 | "selections": [ 26 | { 27 | "alias": null, 28 | "args": null, 29 | "kind": "ScalarField", 30 | "name": "id", 31 | "storageKey": null 32 | }, 33 | { 34 | "alias": null, 35 | "args": null, 36 | "kind": "ScalarField", 37 | "name": "totalCount", 38 | "storageKey": null 39 | }, 40 | { 41 | "alias": null, 42 | "args": null, 43 | "kind": "ScalarField", 44 | "name": "completedCount", 45 | "storageKey": null 46 | } 47 | ], 48 | "type": "User", 49 | "abstractKey": null 50 | }; 51 | (node as any).hash = '1e2b17bb7b92d4521c4e72309d996339'; 52 | export default node; 53 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | ignore: 9 | - dependency-name: "@types/node" 10 | versions: 11 | - 14.14.24 12 | - 14.14.26 13 | - 14.14.30 14 | - 14.14.31 15 | - 14.14.32 16 | - 14.14.33 17 | - 14.14.34 18 | - 14.14.35 19 | - 14.14.36 20 | - 14.14.37 21 | - 15.0.0 22 | - dependency-name: relay-compiler 23 | versions: 24 | - 11.0.0 25 | - 11.0.1 26 | - dependency-name: relay-runtime 27 | versions: 28 | - 11.0.0 29 | - 11.0.1 30 | - dependency-name: concurrently 31 | versions: 32 | - 6.0.0 33 | - 6.0.1 34 | - 6.0.2 35 | - dependency-name: "@types/relay-runtime" 36 | versions: 37 | - 10.1.10 38 | - 10.1.9 39 | - dependency-name: y18n 40 | versions: 41 | - 4.0.1 42 | - dependency-name: husky 43 | versions: 44 | - 5.0.9 45 | - 5.1.0 46 | - 5.1.1 47 | - 5.1.2 48 | - 5.1.3 49 | - 5.2.0 50 | - dependency-name: babel-plugin-relay 51 | versions: 52 | - 11.0.0 53 | - dependency-name: typescript 54 | versions: 55 | - 4.2.2 56 | - 4.2.3 57 | - dependency-name: ts-jest 58 | versions: 59 | - 26.5.2 60 | - 26.5.3 61 | - dependency-name: "@babel/runtime" 62 | versions: 63 | - 7.12.18 64 | - 7.13.2 65 | - 7.13.6 66 | - 7.13.7 67 | - 7.13.8 68 | - 7.13.9 69 | -------------------------------------------------------------------------------- /test/fixtures/type-generator/typename-on-union.graphql: -------------------------------------------------------------------------------- 1 | fragment TypenameInside on Actor { 2 | ... on User { 3 | __typename 4 | firstName 5 | } 6 | ... on Page { 7 | __typename 8 | username 9 | } 10 | } 11 | 12 | fragment TypenameOutside on Actor { 13 | __typename 14 | ... on User { 15 | firstName 16 | } 17 | ... on Page { 18 | username 19 | } 20 | } 21 | 22 | fragment TypenameOutsideWithAbstractType on Node { 23 | __typename 24 | ... on User { 25 | firstName 26 | address { 27 | street # only here 28 | city # common 29 | } 30 | } 31 | ... on Actor { 32 | username 33 | address { 34 | city # common 35 | country # only here 36 | } 37 | } 38 | } 39 | 40 | fragment TypenameWithoutSpreads on User { 41 | __typename 42 | firstName 43 | } 44 | 45 | fragment TypenameWithoutSpreadsAbstractType on Node { 46 | __typename 47 | id 48 | } 49 | 50 | fragment TypenameWithCommonSelections on Actor { 51 | __typename 52 | name 53 | ... on User { 54 | firstName 55 | } 56 | ... on Page { 57 | username 58 | } 59 | } 60 | 61 | fragment TypenameAlias on Actor { 62 | _typeAlias: __typename 63 | ... on User { 64 | firstName 65 | } 66 | ... on Page { 67 | username 68 | } 69 | } 70 | 71 | fragment TypenameAliases on Actor { 72 | _typeAlias1: __typename 73 | _typeAlias2: __typename 74 | ... on User { 75 | firstName 76 | } 77 | ... on Page { 78 | username 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /example-hooks/README.md: -------------------------------------------------------------------------------- 1 | # Relay TodoMVC 2 | 3 | ## Prerequisites 4 | 5 | ``` 6 | yarn global add yalc 7 | ``` 8 | 9 | And in the project root folder 10 | 11 | ``` 12 | yarn watch 13 | ``` 14 | 15 | ## Installation 16 | 17 | ``` 18 | yarn install 19 | ``` 20 | 21 | ## Running 22 | 23 | Set up generated files: 24 | 25 | ``` 26 | yarn update-schema 27 | yarn build 28 | ``` 29 | 30 | Start a local server: 31 | 32 | ``` 33 | yarn start 34 | ``` 35 | 36 | ## Developing 37 | 38 | Any changes you make to files in the `ts/` directory will cause the server to 39 | automatically rebuild the app and refresh your browser. 40 | 41 | If at any time you make changes to `data/schema.js`, stop the server, 42 | regenerate `data/schema.graphql`, and restart the server: 43 | 44 | ``` 45 | yarn update-schema 46 | yarn build 47 | yarn start 48 | ``` 49 | 50 | ## License 51 | 52 | This file provided by Facebook is for non-commercial testing and evaluation 53 | purposes only. Facebook reserves all rights not expressly granted. 54 | 55 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 56 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 57 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 58 | FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 59 | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 60 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 61 | -------------------------------------------------------------------------------- /example/ts/__relay_artifacts__/TodoApp_viewer.graphql.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | /* eslint-disable */ 3 | 4 | import { ReaderFragment } from "relay-runtime"; 5 | import { FragmentRefs } from "relay-runtime"; 6 | export type TodoApp_viewer = { 7 | readonly id: string; 8 | readonly totalCount: number | null; 9 | readonly " $fragmentRefs": FragmentRefs<"TodoListFooter_viewer" | "TodoList_viewer">; 10 | readonly " $refType": "TodoApp_viewer"; 11 | }; 12 | export type TodoApp_viewer$data = TodoApp_viewer; 13 | export type TodoApp_viewer$key = { 14 | readonly " $data"?: TodoApp_viewer$data; 15 | readonly " $fragmentRefs": FragmentRefs<"TodoApp_viewer">; 16 | }; 17 | 18 | 19 | 20 | const node: ReaderFragment = { 21 | "argumentDefinitions": [], 22 | "kind": "Fragment", 23 | "metadata": null, 24 | "name": "TodoApp_viewer", 25 | "selections": [ 26 | { 27 | "alias": null, 28 | "args": null, 29 | "kind": "ScalarField", 30 | "name": "id", 31 | "storageKey": null 32 | }, 33 | { 34 | "alias": null, 35 | "args": null, 36 | "kind": "ScalarField", 37 | "name": "totalCount", 38 | "storageKey": null 39 | }, 40 | { 41 | "args": null, 42 | "kind": "FragmentSpread", 43 | "name": "TodoListFooter_viewer" 44 | }, 45 | { 46 | "args": null, 47 | "kind": "FragmentSpread", 48 | "name": "TodoList_viewer" 49 | } 50 | ], 51 | "type": "User", 52 | "abstractKey": null 53 | }; 54 | (node as any).hash = 'b9743417c7b5ef2bbda96cf675aa9eb4'; 55 | export default node; 56 | -------------------------------------------------------------------------------- /example-hooks/ts/__relay_artifacts__/TodoApp_viewer.graphql.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | /* eslint-disable */ 3 | 4 | import { ReaderFragment } from "relay-runtime"; 5 | import { FragmentRefs } from "relay-runtime"; 6 | export type TodoApp_viewer = { 7 | readonly id: string; 8 | readonly totalCount: number | null; 9 | readonly " $fragmentRefs": FragmentRefs<"TodoListFooter_viewer" | "TodoList_viewer">; 10 | readonly " $refType": "TodoApp_viewer"; 11 | }; 12 | export type TodoApp_viewer$data = TodoApp_viewer; 13 | export type TodoApp_viewer$key = { 14 | readonly " $data"?: TodoApp_viewer$data; 15 | readonly " $fragmentRefs": FragmentRefs<"TodoApp_viewer">; 16 | }; 17 | 18 | 19 | 20 | const node: ReaderFragment = { 21 | "argumentDefinitions": [], 22 | "kind": "Fragment", 23 | "metadata": null, 24 | "name": "TodoApp_viewer", 25 | "selections": [ 26 | { 27 | "alias": null, 28 | "args": null, 29 | "kind": "ScalarField", 30 | "name": "id", 31 | "storageKey": null 32 | }, 33 | { 34 | "alias": null, 35 | "args": null, 36 | "kind": "ScalarField", 37 | "name": "totalCount", 38 | "storageKey": null 39 | }, 40 | { 41 | "args": null, 42 | "kind": "FragmentSpread", 43 | "name": "TodoListFooter_viewer" 44 | }, 45 | { 46 | "args": null, 47 | "kind": "FragmentSpread", 48 | "name": "TodoList_viewer" 49 | } 50 | ], 51 | "type": "User", 52 | "abstractKey": null 53 | }; 54 | (node as any).hash = 'b9743417c7b5ef2bbda96cf675aa9eb4'; 55 | export default node; 56 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # Relay TodoMVC 2 | 3 | ## Prerequisites 4 | 5 | ``` 6 | yarn global add yalc 7 | ``` 8 | 9 | And in the project root folder 10 | 11 | ``` 12 | yarn install 13 | yarn watch 14 | ``` 15 | 16 | ## Installation 17 | In the example folder link development version to example and install 18 | 19 | ``` 20 | yalc add relay-compiler-language-typescript 21 | yarn install 22 | ``` 23 | 24 | ## Running 25 | 26 | Set up generated files: 27 | 28 | ``` 29 | yarn update-schema 30 | yarn build 31 | ``` 32 | 33 | Start a local server: 34 | 35 | ``` 36 | yarn start 37 | ``` 38 | 39 | ## Developing 40 | 41 | Any changes you make to files in the `ts/` directory will cause the server to 42 | automatically rebuild the app and refresh your browser. 43 | 44 | If at any time you make changes to `data/schema.js`, stop the server, 45 | regenerate `data/schema.graphql`, and restart the server: 46 | 47 | ``` 48 | yarn update-schema 49 | yarn build 50 | yarn start 51 | ``` 52 | 53 | ## License 54 | 55 | This file provided by Facebook is for non-commercial testing and evaluation 56 | purposes only. Facebook reserves all rights not expressly granted. 57 | 58 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 59 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 60 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 61 | FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 62 | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 63 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 64 | -------------------------------------------------------------------------------- /example-hooks/ts/app.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * This file provided by Facebook is for non-commercial testing and evaluation 3 | * purposes only. Facebook reserves all rights not expressly granted. 4 | * 5 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 6 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 7 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 8 | * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 9 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 10 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | */ 12 | 13 | import "todomvc-common" 14 | 15 | import React from "react" 16 | import ReactDOM from "react-dom" 17 | 18 | import { RelayEnvironmentProvider } from "react-relay/hooks" 19 | import { Environment, Network, RecordSource, Store } from "relay-runtime" 20 | 21 | import TodoRoot from "./components/TodoRoot" 22 | 23 | const mountNode = document.getElementById("root") 24 | 25 | function fetchQuery(operation: any, variables: any) { 26 | return fetch("/graphql", { 27 | method: "POST", 28 | headers: { 29 | "Content-Type": "application/json", 30 | }, 31 | body: JSON.stringify({ 32 | query: operation.text, 33 | variables, 34 | }), 35 | }).then(response => { 36 | return response.json() 37 | }) 38 | } 39 | 40 | const modernEnvironment = new Environment({ 41 | network: Network.create(fetchQuery), 42 | store: new Store(new RecordSource()), 43 | }) 44 | 45 | ReactDOM.render( 46 | 47 | 48 | , 49 | mountNode, 50 | ) 51 | -------------------------------------------------------------------------------- /example/ts/mutations/RenameTodoMutation.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file provided by Facebook is for non-commercial testing and evaluation 3 | * purposes only. Facebook reserves all rights not expressly granted. 4 | * 5 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 6 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 7 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 8 | * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 9 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 10 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | */ 12 | 13 | import { commitMutation, graphql } from "react-relay" 14 | import { Environment } from "relay-runtime" 15 | 16 | import { Todo_todo } from "../__relay_artifacts__/Todo_todo.graphql" 17 | import { RenameTodoMutation } from "../__relay_artifacts__/RenameTodoMutation.graphql" 18 | 19 | const mutation = graphql` 20 | mutation RenameTodoMutation($input: RenameTodoInput!) { 21 | renameTodo(input: $input) { 22 | todo { 23 | id 24 | text 25 | } 26 | } 27 | } 28 | ` 29 | 30 | function getOptimisticResponse(text: string, todo: Todo_todo) { 31 | return { 32 | renameTodo: { 33 | todo: { 34 | id: todo.id, 35 | text: text, 36 | }, 37 | }, 38 | } 39 | } 40 | 41 | function commit(environment: Environment, text: string, todo: Todo_todo) { 42 | return commitMutation(environment, { 43 | mutation, 44 | variables: { 45 | input: { text, id: todo.id }, 46 | }, 47 | optimisticResponse: getOptimisticResponse(text, todo), 48 | }) 49 | } 50 | 51 | export default { commit } 52 | -------------------------------------------------------------------------------- /example-hooks/ts/mutations/RenameTodoMutation.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file provided by Facebook is for non-commercial testing and evaluation 3 | * purposes only. Facebook reserves all rights not expressly granted. 4 | * 5 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 6 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 7 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 8 | * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 9 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 10 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | */ 12 | 13 | import { commitMutation, graphql } from "react-relay" 14 | import { Environment } from "relay-runtime" 15 | 16 | import { Todo_todo } from "../__relay_artifacts__/Todo_todo.graphql" 17 | import { RenameTodoMutation } from "../__relay_artifacts__/RenameTodoMutation.graphql" 18 | 19 | const mutation = graphql` 20 | mutation RenameTodoMutation($input: RenameTodoInput!) { 21 | renameTodo(input: $input) { 22 | todo { 23 | id 24 | text 25 | } 26 | } 27 | } 28 | ` 29 | 30 | function getOptimisticResponse(text: string, todo: Todo_todo) { 31 | return { 32 | renameTodo: { 33 | todo: { 34 | id: todo.id, 35 | text: text, 36 | }, 37 | }, 38 | } 39 | } 40 | 41 | function commit(environment: Environment, text: string, todo: Todo_todo) { 42 | return commitMutation(environment, { 43 | mutation, 44 | variables: { 45 | input: { text, id: todo.id }, 46 | }, 47 | optimisticResponse: getOptimisticResponse(text, todo), 48 | }) 49 | } 50 | 51 | export default { commit } 52 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "start": "babel-node ./server.js", 5 | "build": "relay-compiler --src ./ts/ --schema ./data/schema.graphql --language typescript --artifactDirectory ./ts/__relay_artifacts__", 6 | "update-schema": "babel-node ./scripts/updateSchema.js", 7 | "lint": "tslint --project tsconfig.json", 8 | "type-check": "tsc --noEmit --pretty" 9 | }, 10 | "prettier": { 11 | "semi": false, 12 | "trailingComma": "all" 13 | }, 14 | "dependencies": { 15 | "@babel/core": "^7.0.0-0", 16 | "babel-core": "^6.26.0", 17 | "babel-loader": "^7.1.2", 18 | "babel-plugin-relay": "^10.0.1", 19 | "babel-plugin-transform-runtime": "^6.12.0", 20 | "babel-preset-es2015": "^6.13.2", 21 | "babel-preset-react": "^6.11.1", 22 | "babel-preset-stage-0": "^6.5.0", 23 | "babel-runtime": "^6.26.0", 24 | "classnames": "2.2.5", 25 | "express": "^4.17.3", 26 | "express-graphql": "^0.7.1", 27 | "graphql": "^15.3.0", 28 | "graphql-relay": "^0.6.0", 29 | "prop-types": "^15.6.2", 30 | "react": "^16.10.2", 31 | "react-dom": "^16.10.2", 32 | "react-relay": "^10.0.1", 33 | "relay-runtime": "^10.0.1", 34 | "todomvc-app-css": "^2.1.0", 35 | "todomvc-common": "^1.0.3", 36 | "webpack": "^4.41.2", 37 | "webpack-dev-server": "^3.1.11", 38 | "whatwg-fetch": "2.0.3" 39 | }, 40 | "devDependencies": { 41 | "@types/node": "^12.11.7", 42 | "@types/prop-types": "^15.5.5", 43 | "@types/react": "^16.4.12", 44 | "@types/react-dom": "^16.0.7", 45 | "@types/react-relay": "^7.0.8", 46 | "@types/relay-runtime": "^10.0.1", 47 | "babel-cli": "^6.26.0", 48 | "babel-eslint": "6.1.2", 49 | "csstype": "^2.5.6", 50 | "fork-ts-checker-webpack-plugin": "^3.1.1", 51 | "relay-compiler": "^10.0.1", 52 | "relay-compiler-language-typescript": "file:../lib", 53 | "ts-loader": "^6.2.0", 54 | "tslint": "^5.20.0", 55 | "tslint-plugin-relay": "^0.0.3", 56 | "typescript": "^3.6.3" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /example-hooks/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "start": "babel-node ./server.js", 5 | "build": "relay-compiler --src ./ts/ --schema ./data/schema.graphql --language typescript --artifactDirectory ./ts/__relay_artifacts__", 6 | "update-schema": "babel-node ./scripts/updateSchema.js", 7 | "lint": "tslint --project tsconfig.json", 8 | "type-check": "tsc --noEmit --pretty" 9 | }, 10 | "prettier": { 11 | "semi": false, 12 | "trailingComma": "all" 13 | }, 14 | "dependencies": { 15 | "classnames": "2.2.5", 16 | "express": "^4.17.3", 17 | "express-graphql": "^0.7.1", 18 | "graphql": "^15.3.0", 19 | "graphql-relay": "^0.6.0", 20 | "prop-types": "^15.6.2", 21 | "react": "0.0.0-experimental-f42431abe", 22 | "react-dom": "0.0.0-experimental-f42431abe", 23 | "react-relay": "^0.0.0-experimental-8058ef82", 24 | "relay-runtime": "^10.0.1", 25 | "todomvc-app-css": "^2.1.0", 26 | "todomvc-common": "^1.0.3", 27 | "webpack": "^4.41.2", 28 | "webpack-dev-server": "^3.1.11", 29 | "whatwg-fetch": "2.0.3" 30 | }, 31 | "devDependencies": { 32 | "@babel/core": "^7.0.0-0", 33 | "@types/node": "^12.11.6", 34 | "@types/prop-types": "^15.5.5", 35 | "@types/react": "^16.4.12", 36 | "@types/react-dom": "^16.0.7", 37 | "@types/react-relay": "^7.0.8", 38 | "@types/relay-runtime": "^10.0.1", 39 | "babel-cli": "^6.26.0", 40 | "babel-core": "^6.26.0", 41 | "babel-eslint": "6.1.2", 42 | "babel-loader": "^7.1.2", 43 | "babel-plugin-relay": "^10.0.1", 44 | "babel-plugin-transform-runtime": "^6.12.0", 45 | "babel-preset-env": "^1.7.0", 46 | "babel-preset-react": "^6.11.1", 47 | "babel-preset-stage-0": "^6.5.0", 48 | "babel-runtime": "^6.26.0", 49 | "csstype": "^2.5.6", 50 | "fork-ts-checker-webpack-plugin": "^3.1.1", 51 | "patch-package": "^6.2.0", 52 | "postinstall-postinstall": "^2.0.0", 53 | "relay-compiler": "^10.0.1", 54 | "relay-compiler-language-typescript": "file:../lib", 55 | "ts-loader": "^6.2.0", 56 | "tslint": "^5.20.0", 57 | "tslint-plugin-relay": "^0.0.3", 58 | "typescript": "^3.6.3" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /example/ts/app.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * This file provided by Facebook is for non-commercial testing and evaluation 3 | * purposes only. Facebook reserves all rights not expressly granted. 4 | * 5 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 6 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 7 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 8 | * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 9 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 10 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | */ 12 | 13 | import "todomvc-common"; 14 | 15 | import * as React from "react"; 16 | import * as ReactDOM from "react-dom"; 17 | 18 | import { QueryRenderer, graphql } from "react-relay"; 19 | import { Environment, Network, RecordSource, Store } from "relay-runtime"; 20 | 21 | import TodoApp from "./components/TodoApp"; 22 | import { appQuery } from "./__relay_artifacts__/appQuery.graphql"; 23 | 24 | const mountNode = document.getElementById("root"); 25 | 26 | function fetchQuery(operation: any, variables: any) { 27 | return fetch("/graphql", { 28 | method: "POST", 29 | headers: { 30 | "Content-Type": "application/json" 31 | }, 32 | body: JSON.stringify({ 33 | query: operation.text, 34 | variables 35 | }) 36 | }).then(response => { 37 | return response.json(); 38 | }); 39 | } 40 | 41 | const modernEnvironment = new Environment({ 42 | network: Network.create(fetchQuery), 43 | store: new Store(new RecordSource()) 44 | }); 45 | 46 | ReactDOM.render( 47 | 48 | environment={modernEnvironment} 49 | query={graphql` 50 | query appQuery { 51 | viewer { 52 | ...TodoApp_viewer 53 | } 54 | } 55 | `} 56 | variables={{}} 57 | render={({ error, props }) => { 58 | if (props && props.viewer) { 59 | return ; 60 | } else if (props || error) { 61 | console.error(`Unexpected data: ${props || error}`); 62 | } else { 63 | return
Loading
; 64 | } 65 | }} 66 | />, 67 | mountNode 68 | ); 69 | -------------------------------------------------------------------------------- /example/ts/mutations/RemoveTodoMutation.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file provided by Facebook is for non-commercial testing and evaluation 3 | * purposes only. Facebook reserves all rights not expressly granted. 4 | * 5 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 6 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 7 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 8 | * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 9 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 10 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | */ 12 | 13 | import { commitMutation, graphql } from "react-relay" 14 | import { 15 | ConnectionHandler, 16 | Environment, 17 | RecordSourceSelectorProxy, 18 | } from "relay-runtime" 19 | 20 | import { Todo_todo } from "../__relay_artifacts__/Todo_todo.graphql" 21 | import { Todo_viewer } from "../__relay_artifacts__/Todo_viewer.graphql" 22 | import { RemoveTodoMutation } from "../__relay_artifacts__/RemoveTodoMutation.graphql" 23 | 24 | const mutation = graphql` 25 | mutation RemoveTodoMutation($input: RemoveTodoInput!) { 26 | removeTodo(input: $input) { 27 | deletedTodoId 28 | viewer { 29 | completedCount 30 | totalCount 31 | } 32 | } 33 | } 34 | ` 35 | 36 | function sharedUpdater( 37 | store: RecordSourceSelectorProxy, 38 | user: Todo_viewer, 39 | deletedID: string, 40 | ) { 41 | const userProxy = store.get(user.id) 42 | const conn = ConnectionHandler.getConnection(userProxy!, "TodoList_todos") 43 | ConnectionHandler.deleteNode(conn!, deletedID) 44 | } 45 | 46 | function commit(environment: Environment, todo: Todo_todo, user: Todo_viewer) { 47 | return commitMutation(environment, { 48 | mutation, 49 | variables: { 50 | input: { id: todo.id }, 51 | }, 52 | updater: store => { 53 | const payload = store.getRootField("removeTodo") 54 | if (!payload) throw new Error("assertion failed") 55 | sharedUpdater(store, user, payload.getValue("deletedTodoId") as string) 56 | }, 57 | optimisticUpdater: store => { 58 | sharedUpdater(store, user, todo.id) 59 | }, 60 | }) 61 | } 62 | 63 | export default { commit } 64 | -------------------------------------------------------------------------------- /example-hooks/ts/mutations/RemoveTodoMutation.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file provided by Facebook is for non-commercial testing and evaluation 3 | * purposes only. Facebook reserves all rights not expressly granted. 4 | * 5 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 6 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 7 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 8 | * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 9 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 10 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | */ 12 | 13 | import { commitMutation, graphql } from "react-relay" 14 | import { 15 | ConnectionHandler, 16 | Environment, 17 | RecordSourceSelectorProxy, 18 | } from "relay-runtime" 19 | 20 | import { Todo_todo } from "../__relay_artifacts__/Todo_todo.graphql" 21 | import { Todo_viewer } from "../__relay_artifacts__/Todo_viewer.graphql" 22 | import { RemoveTodoMutation } from "../__relay_artifacts__/RemoveTodoMutation.graphql" 23 | 24 | const mutation = graphql` 25 | mutation RemoveTodoMutation($input: RemoveTodoInput!) { 26 | removeTodo(input: $input) { 27 | deletedTodoId 28 | viewer { 29 | completedCount 30 | totalCount 31 | } 32 | } 33 | } 34 | ` 35 | 36 | function sharedUpdater( 37 | store: RecordSourceSelectorProxy, 38 | user: Todo_viewer, 39 | deletedID: string, 40 | ) { 41 | const userProxy = store.get(user.id) 42 | const conn = ConnectionHandler.getConnection(userProxy!, "TodoList_todos") 43 | ConnectionHandler.deleteNode(conn!, deletedID) 44 | } 45 | 46 | function commit(environment: Environment, todo: Todo_todo, user: Todo_viewer) { 47 | return commitMutation(environment, { 48 | mutation, 49 | variables: { 50 | input: { id: todo.id }, 51 | }, 52 | updater: store => { 53 | const payload = store.getRootField("removeTodo") 54 | if (!payload) throw new Error("assertion failed") 55 | sharedUpdater(store, user, payload.getValue("deletedTodoId") as string) 56 | }, 57 | optimisticUpdater: store => { 58 | sharedUpdater(store, user, todo.id) 59 | }, 60 | }) 61 | } 62 | 63 | export default { commit } 64 | -------------------------------------------------------------------------------- /example/ts/mutations/MarkAllTodosMutation.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file provided by Facebook is for non-commercial testing and evaluation 3 | * purposes only. Facebook reserves all rights not expressly granted. 4 | * 5 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 6 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 7 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 8 | * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 9 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 10 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | */ 12 | 13 | import { commitMutation, graphql } from "react-relay" 14 | 15 | import { TodoList_viewer } from "../__relay_artifacts__/TodoList_viewer.graphql" 16 | import { Environment } from "relay-runtime" 17 | import { MarkAllTodosMutation } from "../__relay_artifacts__/MarkAllTodosMutation.graphql" 18 | 19 | const mutation = graphql` 20 | mutation MarkAllTodosMutation($input: MarkAllTodosInput!) { 21 | markAllTodos(input: $input) { 22 | changedTodos { 23 | id 24 | complete 25 | } 26 | viewer { 27 | id 28 | completedCount 29 | } 30 | } 31 | } 32 | ` 33 | 34 | function getOptimisticResponse( 35 | complete: boolean, 36 | todos: TodoList_viewer["todos"], 37 | user: TodoList_viewer, 38 | ) { 39 | const payload: any = { viewer: { id: user.id } } 40 | if (todos && todos.edges) { 41 | payload.changedTodos = todos.edges 42 | .filter(edge => edge && edge.node && edge.node.complete !== complete) 43 | .map(edge => ({ 44 | complete: complete, 45 | id: edge && edge.node && edge.node.id, 46 | })) 47 | } 48 | if (user.totalCount != null) { 49 | payload.viewer.completedCount = complete ? user.totalCount : 0 50 | } 51 | return { 52 | markAllTodos: payload, 53 | } 54 | } 55 | 56 | function commit( 57 | environment: Environment, 58 | complete: boolean, 59 | todos: TodoList_viewer["todos"], 60 | user: TodoList_viewer, 61 | ) { 62 | return commitMutation(environment, { 63 | mutation, 64 | variables: { 65 | input: { complete }, 66 | }, 67 | optimisticResponse: getOptimisticResponse(complete, todos, user), 68 | }) 69 | } 70 | 71 | export default { commit } 72 | -------------------------------------------------------------------------------- /example-hooks/ts/mutations/MarkAllTodosMutation.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file provided by Facebook is for non-commercial testing and evaluation 3 | * purposes only. Facebook reserves all rights not expressly granted. 4 | * 5 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 6 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 7 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 8 | * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 9 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 10 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | */ 12 | 13 | import { commitMutation, graphql } from "react-relay" 14 | 15 | import { TodoList_viewer } from "../__relay_artifacts__/TodoList_viewer.graphql" 16 | import { Environment } from "relay-runtime" 17 | import { MarkAllTodosMutation } from "../__relay_artifacts__/MarkAllTodosMutation.graphql" 18 | 19 | const mutation = graphql` 20 | mutation MarkAllTodosMutation($input: MarkAllTodosInput!) { 21 | markAllTodos(input: $input) { 22 | changedTodos { 23 | id 24 | complete 25 | } 26 | viewer { 27 | id 28 | completedCount 29 | } 30 | } 31 | } 32 | ` 33 | 34 | function getOptimisticResponse( 35 | complete: boolean, 36 | todos: TodoList_viewer["todos"], 37 | user: TodoList_viewer, 38 | ) { 39 | const payload: any = { viewer: { id: user.id } } 40 | if (todos && todos.edges) { 41 | payload.changedTodos = todos.edges 42 | .filter(edge => edge && edge.node && edge.node.complete !== complete) 43 | .map(edge => ({ 44 | complete: complete, 45 | id: edge && edge.node && edge.node.id, 46 | })) 47 | } 48 | if (user.totalCount != null) { 49 | payload.viewer.completedCount = complete ? user.totalCount : 0 50 | } 51 | return { 52 | markAllTodos: payload, 53 | } 54 | } 55 | 56 | function commit( 57 | environment: Environment, 58 | complete: boolean, 59 | todos: TodoList_viewer["todos"], 60 | user: TodoList_viewer, 61 | ) { 62 | return commitMutation(environment, { 63 | mutation, 64 | variables: { 65 | input: { complete }, 66 | }, 67 | optimisticResponse: getOptimisticResponse(complete, todos, user), 68 | }) 69 | } 70 | 71 | export default { commit } 72 | -------------------------------------------------------------------------------- /example/server.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file provided by Facebook is for non-commercial testing and evaluation 3 | * purposes only. Facebook reserves all rights not expressly granted. 4 | * 5 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 6 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 7 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 8 | * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 9 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 10 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | */ 12 | 13 | import express from 'express'; 14 | import graphQLHTTP from 'express-graphql'; 15 | import path from 'path'; 16 | import webpack from 'webpack'; 17 | import WebpackDevServer from 'webpack-dev-server'; 18 | import {schema} from './data/schema'; 19 | import ForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin'; 20 | 21 | const APP_PORT = 3000; 22 | const GRAPHQL_PORT = 8080; 23 | 24 | // Expose a GraphQL endpoint 25 | const graphQLServer = express(); 26 | graphQLServer.use('/', graphQLHTTP({schema, pretty: true})); 27 | graphQLServer.listen(GRAPHQL_PORT, () => console.log( 28 | `GraphQL Server is now running on http://localhost:${GRAPHQL_PORT}` 29 | )); 30 | 31 | // Serve the Relay app 32 | const compiler = webpack({ 33 | mode: 'development', 34 | entry: ['whatwg-fetch', path.resolve(__dirname, 'ts', 'app.tsx')], 35 | resolve: { 36 | extensions: ['.ts', '.tsx', '.js'], 37 | }, 38 | module: { 39 | rules: [ 40 | { 41 | test: /\.tsx?$/, 42 | exclude: /node_modules/, 43 | use: [ 44 | { loader: 'babel-loader' }, 45 | { loader: 'ts-loader', options: { transpileOnly: true } }, 46 | ], 47 | }, 48 | ], 49 | }, 50 | plugins: [ 51 | new ForkTsCheckerWebpackPlugin(), 52 | ], 53 | output: {filename: 'app.js', path: '/'}, 54 | }); 55 | const app = new WebpackDevServer(compiler, { 56 | contentBase: '/public/', 57 | proxy: {'/graphql': `http://localhost:${GRAPHQL_PORT}`}, 58 | publicPath: '/js/', 59 | stats: {colors: true}, 60 | }); 61 | // Serve static resources 62 | app.use('/', express.static(path.resolve(__dirname, 'public'))); 63 | app.listen(APP_PORT, () => { 64 | console.log(`App is now running on http://localhost:${APP_PORT}`); 65 | }); 66 | -------------------------------------------------------------------------------- /example-hooks/server.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file provided by Facebook is for non-commercial testing and evaluation 3 | * purposes only. Facebook reserves all rights not expressly granted. 4 | * 5 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 6 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 7 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 8 | * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 9 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 10 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | */ 12 | 13 | import express from 'express'; 14 | import graphQLHTTP from 'express-graphql'; 15 | import path from 'path'; 16 | import webpack from 'webpack'; 17 | import WebpackDevServer from 'webpack-dev-server'; 18 | import {schema} from './data/schema'; 19 | import ForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin'; 20 | 21 | const APP_PORT = 3000; 22 | const GRAPHQL_PORT = 8080; 23 | 24 | // Expose a GraphQL endpoint 25 | const graphQLServer = express(); 26 | graphQLServer.use('/', graphQLHTTP({schema, pretty: true})); 27 | graphQLServer.listen(GRAPHQL_PORT, () => console.log( 28 | `GraphQL Server is now running on http://localhost:${GRAPHQL_PORT}` 29 | )); 30 | 31 | // Serve the Relay app 32 | const compiler = webpack({ 33 | mode: 'development', 34 | entry: ['whatwg-fetch', path.resolve(__dirname, 'ts', 'app.tsx')], 35 | resolve: { 36 | extensions: ['.ts', '.tsx', '.js'], 37 | }, 38 | module: { 39 | rules: [ 40 | { 41 | test: /\.tsx?$/, 42 | exclude: /node_modules/, 43 | use: [ 44 | { loader: 'babel-loader' }, 45 | { loader: 'ts-loader', options: { transpileOnly: true } }, 46 | ], 47 | }, 48 | ], 49 | }, 50 | plugins: [ 51 | new ForkTsCheckerWebpackPlugin(), 52 | ], 53 | output: {filename: 'app.js', path: '/'}, 54 | }); 55 | const app = new WebpackDevServer(compiler, { 56 | contentBase: '/public/', 57 | proxy: {'/graphql': `http://localhost:${GRAPHQL_PORT}`}, 58 | publicPath: '/js/', 59 | stats: {colors: true}, 60 | }); 61 | // Serve static resources 62 | app.use('/', express.static(path.resolve(__dirname, 'public'))); 63 | app.listen(APP_PORT, () => { 64 | console.log(`App is now running on http://localhost:${APP_PORT}`); 65 | }); 66 | -------------------------------------------------------------------------------- /example/public/learn.json: -------------------------------------------------------------------------------- 1 | { 2 | "relay": { 3 | "name": "Relay", 4 | "description": "A JavaScript framework for building data-driven React applications", 5 | "homepage": "facebook.github.io/relay/", 6 | "examples": [{ 7 | "name": "Relay + express-graphql Example", 8 | "url": "", 9 | "source_url": "https://github.com/relayjs/relay-examples/tree/master/todo", 10 | "type": "backend" 11 | }], 12 | "link_groups": [{ 13 | "heading": "Official Resources", 14 | "links": [{ 15 | "name": "Documentation", 16 | "url": "https://facebook.github.io/relay/docs/getting-started.html" 17 | }, { 18 | "name": "API Reference", 19 | "url": "https://facebook.github.io/relay/docs/api-reference-relay.html" 20 | }, { 21 | "name": "Relay on GitHub", 22 | "url": "https://github.com/facebook/relay" 23 | }] 24 | }, { 25 | "heading": "Community", 26 | "links": [{ 27 | "name": "Relay on StackOverflow", 28 | "url": "https://stackoverflow.com/questions/tagged/relayjs" 29 | }] 30 | }] 31 | }, 32 | "templates": { 33 | "todomvc": "

<%= name %>

<% if (typeof examples !== 'undefined') { %> <% examples.forEach(function (example) { %>
<%= example.name %>
<% if (!location.href.match(example.url + '/')) { %> \" href=\"<%= example.url %>\">Demo, <% } if (example.type === 'backend') { %>\"><% } else { %>\"><% } %>Source <% }); %> <% } %>

<%= description %>

<% if (typeof link_groups !== 'undefined') { %>
<% link_groups.forEach(function (link_group) { %>

<%= link_group.heading %>

<% }); %> <% } %>

If you have other helpful links to share, or find any of the links above no longer work, please let us know.
" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /example/ts/mutations/ChangeTodoStatusMutation.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file provided by Facebook is for non-commercial testing and evaluation 3 | * purposes only. Facebook reserves all rights not expressly granted. 4 | * 5 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 6 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 7 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 8 | * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 9 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 10 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | */ 12 | 13 | import { commitMutation, graphql } from "react-relay" 14 | import { Environment } from "relay-runtime" 15 | 16 | import { Todo_todo } from "../__relay_artifacts__/Todo_todo.graphql" 17 | import { Todo_viewer } from "../__relay_artifacts__/Todo_viewer.graphql" 18 | import { ChangeTodoStatusMutation } from "../__relay_artifacts__/ChangeTodoStatusMutation.graphql" 19 | 20 | const mutation = graphql` 21 | mutation ChangeTodoStatusMutation($input: ChangeTodoStatusInput!) { 22 | changeTodoStatus(input: $input) { 23 | todo { 24 | id 25 | complete 26 | } 27 | viewer { 28 | id 29 | completedCount 30 | } 31 | } 32 | } 33 | ` 34 | 35 | function getOptimisticResponse( 36 | complete: boolean, 37 | todo: Todo_todo, 38 | user: Todo_viewer, 39 | ) { 40 | const viewerPayload: { id: string; completedCount: number | null } = { 41 | id: user.id, 42 | completedCount: null, 43 | } 44 | if (user.completedCount != null) { 45 | viewerPayload.completedCount = complete 46 | ? user.completedCount + 1 47 | : user.completedCount - 1 48 | } 49 | return { 50 | changeTodoStatus: { 51 | todo: { 52 | complete: complete, 53 | id: todo.id, 54 | }, 55 | viewer: viewerPayload, 56 | }, 57 | } 58 | } 59 | 60 | function commit( 61 | environment: Environment, 62 | complete: boolean, 63 | todo: Todo_todo, 64 | user: Todo_viewer, 65 | ) { 66 | return commitMutation(environment, { 67 | mutation, 68 | variables: { 69 | input: { complete, id: todo.id }, 70 | }, 71 | optimisticResponse: getOptimisticResponse(complete, todo, user), 72 | }) 73 | } 74 | 75 | export default { commit } 76 | -------------------------------------------------------------------------------- /example-hooks/ts/mutations/ChangeTodoStatusMutation.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file provided by Facebook is for non-commercial testing and evaluation 3 | * purposes only. Facebook reserves all rights not expressly granted. 4 | * 5 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 6 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 7 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 8 | * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 9 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 10 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | */ 12 | 13 | import { commitMutation, graphql } from "react-relay" 14 | import { Environment } from "relay-runtime" 15 | 16 | import { Todo_todo } from "../__relay_artifacts__/Todo_todo.graphql" 17 | import { Todo_viewer } from "../__relay_artifacts__/Todo_viewer.graphql" 18 | import { ChangeTodoStatusMutation } from "../__relay_artifacts__/ChangeTodoStatusMutation.graphql" 19 | 20 | const mutation = graphql` 21 | mutation ChangeTodoStatusMutation($input: ChangeTodoStatusInput!) { 22 | changeTodoStatus(input: $input) { 23 | todo { 24 | id 25 | complete 26 | } 27 | viewer { 28 | id 29 | completedCount 30 | } 31 | } 32 | } 33 | ` 34 | 35 | function getOptimisticResponse( 36 | complete: boolean, 37 | todo: Todo_todo, 38 | user: Todo_viewer, 39 | ) { 40 | const viewerPayload: { id: string; completedCount: number | null } = { 41 | id: user.id, 42 | completedCount: null, 43 | } 44 | if (user.completedCount != null) { 45 | viewerPayload.completedCount = complete 46 | ? user.completedCount + 1 47 | : user.completedCount - 1 48 | } 49 | return { 50 | changeTodoStatus: { 51 | todo: { 52 | complete: complete, 53 | id: todo.id, 54 | }, 55 | viewer: viewerPayload, 56 | }, 57 | } 58 | } 59 | 60 | function commit( 61 | environment: Environment, 62 | complete: boolean, 63 | todo: Todo_todo, 64 | user: Todo_viewer, 65 | ) { 66 | return commitMutation(environment, { 67 | mutation, 68 | variables: { 69 | input: { complete, id: todo.id }, 70 | }, 71 | optimisticResponse: getOptimisticResponse(complete, todo, user), 72 | }) 73 | } 74 | 75 | export default { commit } 76 | -------------------------------------------------------------------------------- /example/public/base.css: -------------------------------------------------------------------------------- 1 | hr { 2 | margin: 20px 0; 3 | border: 0; 4 | border-top: 1px dashed #c5c5c5; 5 | border-bottom: 1px dashed #f7f7f7; 6 | } 7 | 8 | .learn a { 9 | font-weight: normal; 10 | text-decoration: none; 11 | color: #b83f45; 12 | } 13 | 14 | .learn a:hover { 15 | text-decoration: underline; 16 | color: #787e7e; 17 | } 18 | 19 | .learn h3, 20 | .learn h4, 21 | .learn h5 { 22 | margin: 10px 0; 23 | font-weight: 500; 24 | line-height: 1.2; 25 | color: #000; 26 | } 27 | 28 | .learn h3 { 29 | font-size: 24px; 30 | } 31 | 32 | .learn h4 { 33 | font-size: 18px; 34 | } 35 | 36 | .learn h5 { 37 | margin-bottom: 0; 38 | font-size: 14px; 39 | } 40 | 41 | .learn ul { 42 | padding: 0; 43 | margin: 0 0 30px 25px; 44 | } 45 | 46 | .learn li { 47 | line-height: 20px; 48 | } 49 | 50 | .learn p { 51 | font-size: 15px; 52 | font-weight: 300; 53 | line-height: 1.3; 54 | margin-top: 0; 55 | margin-bottom: 0; 56 | } 57 | 58 | #issue-count { 59 | display: none; 60 | } 61 | 62 | .quote { 63 | border: none; 64 | margin: 20px 0 60px 0; 65 | } 66 | 67 | .quote p { 68 | font-style: italic; 69 | } 70 | 71 | .quote p:before { 72 | content: '“'; 73 | font-size: 50px; 74 | opacity: .15; 75 | position: absolute; 76 | top: -20px; 77 | left: 3px; 78 | } 79 | 80 | .quote p:after { 81 | content: '”'; 82 | font-size: 50px; 83 | opacity: .15; 84 | position: absolute; 85 | bottom: -42px; 86 | right: 3px; 87 | } 88 | 89 | .quote footer { 90 | position: absolute; 91 | bottom: -40px; 92 | right: 0; 93 | } 94 | 95 | .quote footer img { 96 | border-radius: 3px; 97 | } 98 | 99 | .quote footer a { 100 | margin-left: 5px; 101 | vertical-align: middle; 102 | } 103 | 104 | .speech-bubble { 105 | position: relative; 106 | padding: 10px; 107 | background: rgba(0, 0, 0, .04); 108 | border-radius: 5px; 109 | } 110 | 111 | .speech-bubble:after { 112 | content: ''; 113 | position: absolute; 114 | top: 100%; 115 | right: 30px; 116 | border: 13px solid transparent; 117 | border-top-color: rgba(0, 0, 0, .04); 118 | } 119 | 120 | .learn-bar > .learn { 121 | position: absolute; 122 | width: 272px; 123 | top: 8px; 124 | left: -300px; 125 | padding: 10px; 126 | border-radius: 5px; 127 | background-color: rgba(255, 255, 255, .6); 128 | transition-property: left; 129 | transition-duration: 500ms; 130 | } 131 | 132 | @media (min-width: 899px) { 133 | .learn-bar { 134 | width: auto; 135 | padding-left: 300px; 136 | } 137 | 138 | .learn-bar > .learn { 139 | left: 8px; 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /example-hooks/public/base.css: -------------------------------------------------------------------------------- 1 | hr { 2 | margin: 20px 0; 3 | border: 0; 4 | border-top: 1px dashed #c5c5c5; 5 | border-bottom: 1px dashed #f7f7f7; 6 | } 7 | 8 | .learn a { 9 | font-weight: normal; 10 | text-decoration: none; 11 | color: #b83f45; 12 | } 13 | 14 | .learn a:hover { 15 | text-decoration: underline; 16 | color: #787e7e; 17 | } 18 | 19 | .learn h3, 20 | .learn h4, 21 | .learn h5 { 22 | margin: 10px 0; 23 | font-weight: 500; 24 | line-height: 1.2; 25 | color: #000; 26 | } 27 | 28 | .learn h3 { 29 | font-size: 24px; 30 | } 31 | 32 | .learn h4 { 33 | font-size: 18px; 34 | } 35 | 36 | .learn h5 { 37 | margin-bottom: 0; 38 | font-size: 14px; 39 | } 40 | 41 | .learn ul { 42 | padding: 0; 43 | margin: 0 0 30px 25px; 44 | } 45 | 46 | .learn li { 47 | line-height: 20px; 48 | } 49 | 50 | .learn p { 51 | font-size: 15px; 52 | font-weight: 300; 53 | line-height: 1.3; 54 | margin-top: 0; 55 | margin-bottom: 0; 56 | } 57 | 58 | #issue-count { 59 | display: none; 60 | } 61 | 62 | .quote { 63 | border: none; 64 | margin: 20px 0 60px 0; 65 | } 66 | 67 | .quote p { 68 | font-style: italic; 69 | } 70 | 71 | .quote p:before { 72 | content: '“'; 73 | font-size: 50px; 74 | opacity: .15; 75 | position: absolute; 76 | top: -20px; 77 | left: 3px; 78 | } 79 | 80 | .quote p:after { 81 | content: '”'; 82 | font-size: 50px; 83 | opacity: .15; 84 | position: absolute; 85 | bottom: -42px; 86 | right: 3px; 87 | } 88 | 89 | .quote footer { 90 | position: absolute; 91 | bottom: -40px; 92 | right: 0; 93 | } 94 | 95 | .quote footer img { 96 | border-radius: 3px; 97 | } 98 | 99 | .quote footer a { 100 | margin-left: 5px; 101 | vertical-align: middle; 102 | } 103 | 104 | .speech-bubble { 105 | position: relative; 106 | padding: 10px; 107 | background: rgba(0, 0, 0, .04); 108 | border-radius: 5px; 109 | } 110 | 111 | .speech-bubble:after { 112 | content: ''; 113 | position: absolute; 114 | top: 100%; 115 | right: 30px; 116 | border: 13px solid transparent; 117 | border-top-color: rgba(0, 0, 0, .04); 118 | } 119 | 120 | .learn-bar > .learn { 121 | position: absolute; 122 | width: 272px; 123 | top: 8px; 124 | left: -300px; 125 | padding: 10px; 126 | border-radius: 5px; 127 | background-color: rgba(255, 255, 255, .6); 128 | transition-property: left; 129 | transition-duration: 500ms; 130 | } 131 | 132 | @media (min-width: 899px) { 133 | .learn-bar { 134 | width: auto; 135 | padding-left: 300px; 136 | } 137 | 138 | .learn-bar > .learn { 139 | left: 8px; 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /example-hooks/public/learn.json: -------------------------------------------------------------------------------- 1 | { 2 | "relay": { 3 | "name": "Relay", 4 | "description": "A JavaScript framework for building data-driven React applications", 5 | "homepage": "facebook.github.io/relay/", 6 | "examples": [ 7 | { 8 | "name": "Relay + express-graphql Example", 9 | "url": "", 10 | "source_url": "https://github.com/relayjs/relay-examples/tree/master/todo", 11 | "type": "backend" 12 | } 13 | ], 14 | "link_groups": [ 15 | { 16 | "heading": "Official Resources", 17 | "links": [ 18 | { 19 | "name": "Documentation", 20 | "url": "https://facebook.github.io/relay/docs/getting-started.html" 21 | }, 22 | { 23 | "name": "API Reference", 24 | "url": "https://facebook.github.io/relay/docs/api-reference-relay.html" 25 | }, 26 | { 27 | "name": "Relay on GitHub", 28 | "url": "https://github.com/facebook/relay" 29 | } 30 | ] 31 | }, 32 | { 33 | "heading": "Community", 34 | "links": [ 35 | { 36 | "name": "Relay on StackOverflow", 37 | "url": "https://stackoverflow.com/questions/tagged/relayjs" 38 | } 39 | ] 40 | } 41 | ] 42 | }, 43 | "templates": { 44 | "todomvc": "

<%= name %>

<% if (typeof examples !== 'undefined') { %> <% examples.forEach(function (example) { %>
<%= example.name %>
<% if (!location.href.match(example.url + '/')) { %> \" href=\"<%= example.url %>\">Demo, <% } if (example.type === 'backend') { %>\"><% } else { %>\"><% } %>Source <% }); %> <% } %>

<%= description %>

<% if (typeof link_groups !== 'undefined') { %>
<% link_groups.forEach(function (link_group) { %>

<%= link_group.heading %>

<% }); %> <% } %>

If you have other helpful links to share, or find any of the links above no longer work, please let us know.
" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /example/ts/mutations/RemoveCompletedTodosMutation.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file provided by Facebook is for non-commercial testing and evaluation 3 | * purposes only. Facebook reserves all rights not expressly granted. 4 | * 5 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 6 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 7 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 8 | * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 9 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 10 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | */ 12 | 13 | import { commitMutation, graphql } from "react-relay" 14 | import { 15 | ConnectionHandler, 16 | Environment, 17 | DataID, 18 | RecordSourceSelectorProxy, 19 | } from "relay-runtime" 20 | 21 | import { TodoListFooter_viewer } from "../__relay_artifacts__/TodoListFooter_viewer.graphql" 22 | import { RemoveCompletedTodosMutation } from "../__relay_artifacts__/RemoveCompletedTodosMutation.graphql" 23 | 24 | const mutation = graphql` 25 | mutation RemoveCompletedTodosMutation($input: RemoveCompletedTodosInput!) { 26 | removeCompletedTodos(input: $input) { 27 | deletedTodoIds 28 | viewer { 29 | completedCount 30 | totalCount 31 | } 32 | } 33 | } 34 | ` 35 | 36 | function sharedUpdater( 37 | store: RecordSourceSelectorProxy, 38 | user: TodoListFooter_viewer, 39 | deletedIDs: string[], 40 | ) { 41 | const userProxy = store.get(user.id) 42 | const conn = ConnectionHandler.getConnection(userProxy!, "TodoList_todos") 43 | deletedIDs.forEach(deletedID => 44 | ConnectionHandler.deleteNode(conn!, deletedID), 45 | ) 46 | } 47 | 48 | function commit( 49 | environment: Environment, 50 | todos: TodoListFooter_viewer["completedTodos"], 51 | user: TodoListFooter_viewer, 52 | ) { 53 | return commitMutation(environment, { 54 | mutation, 55 | variables: { 56 | input: {}, 57 | }, 58 | updater: store => { 59 | const payload = store.getRootField("removeCompletedTodos") 60 | if (!payload) throw new Error("assertion failed") 61 | sharedUpdater(store, user, payload.getValue("deletedTodoIds") as string[]) 62 | }, 63 | optimisticUpdater: store => { 64 | if (todos && todos.edges) { 65 | const deletedIDs = todos.edges 66 | .filter(edge => edge && edge.node && edge.node.complete) 67 | .map(edge => (edge && edge.node && edge.node.id) as string) 68 | sharedUpdater(store, user, deletedIDs) 69 | } 70 | }, 71 | }) 72 | } 73 | 74 | export default { commit } 75 | -------------------------------------------------------------------------------- /example-hooks/ts/mutations/RemoveCompletedTodosMutation.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file provided by Facebook is for non-commercial testing and evaluation 3 | * purposes only. Facebook reserves all rights not expressly granted. 4 | * 5 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 6 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 7 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 8 | * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 9 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 10 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | */ 12 | 13 | import { commitMutation, graphql } from "react-relay" 14 | import { 15 | ConnectionHandler, 16 | Environment, 17 | DataID, 18 | RecordSourceSelectorProxy, 19 | } from "relay-runtime" 20 | 21 | import { TodoListFooter_viewer } from "../__relay_artifacts__/TodoListFooter_viewer.graphql" 22 | import { RemoveCompletedTodosMutation } from "../__relay_artifacts__/RemoveCompletedTodosMutation.graphql" 23 | 24 | const mutation = graphql` 25 | mutation RemoveCompletedTodosMutation($input: RemoveCompletedTodosInput!) { 26 | removeCompletedTodos(input: $input) { 27 | deletedTodoIds 28 | viewer { 29 | completedCount 30 | totalCount 31 | } 32 | } 33 | } 34 | ` 35 | 36 | function sharedUpdater( 37 | store: RecordSourceSelectorProxy, 38 | user: TodoListFooter_viewer, 39 | deletedIDs: string[], 40 | ) { 41 | const userProxy = store.get(user.id) 42 | const conn = ConnectionHandler.getConnection(userProxy!, "TodoList_todos") 43 | deletedIDs.forEach(deletedID => 44 | ConnectionHandler.deleteNode(conn!, deletedID), 45 | ) 46 | } 47 | 48 | function commit( 49 | environment: Environment, 50 | todos: TodoListFooter_viewer["completedTodos"], 51 | user: TodoListFooter_viewer, 52 | ) { 53 | return commitMutation(environment, { 54 | mutation, 55 | variables: { 56 | input: {}, 57 | }, 58 | updater: store => { 59 | const payload = store.getRootField("removeCompletedTodos") 60 | if (!payload) throw new Error("assertion failed") 61 | sharedUpdater(store, user, payload.getValue("deletedTodoIds") as string[]) 62 | }, 63 | optimisticUpdater: store => { 64 | if (todos && todos.edges) { 65 | const deletedIDs = todos.edges 66 | .filter(edge => edge && edge.node && edge.node.complete) 67 | .map(edge => (edge && edge.node && edge.node.id) as string) 68 | sharedUpdater(store, user, deletedIDs) 69 | } 70 | }, 71 | }) 72 | } 73 | 74 | export default { commit } 75 | -------------------------------------------------------------------------------- /example-hooks/ts/components/TodoApp.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * This file provided by Facebook is for non-commercial testing and evaluation 3 | * purposes only. Facebook reserves all rights not expressly granted. 4 | * 5 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 6 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 7 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 8 | * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 9 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 10 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | */ 12 | 13 | import AddTodoMutation from "../mutations/AddTodoMutation" 14 | import TodoList from "./TodoList" 15 | import TodoListFooter from "./TodoListFooter" 16 | import TodoTextInput from "./TodoTextInput" 17 | 18 | import React from "react" 19 | import { graphql, useFragment, useRelayEnvironment } from "react-relay/hooks" 20 | 21 | import { TodoApp_viewer$key } from "../__relay_artifacts__/TodoApp_viewer.graphql" 22 | 23 | interface Props { 24 | viewer: TodoApp_viewer$key 25 | } 26 | 27 | const TodoApp = (props: Props) => { 28 | const environment = useRelayEnvironment() 29 | const [append, setAppend] = React.useState(false); 30 | 31 | const viewer = useFragment( 32 | graphql` 33 | fragment TodoApp_viewer on User { 34 | id 35 | totalCount 36 | ...TodoListFooter_viewer 37 | ...TodoList_viewer 38 | } 39 | `, 40 | props.viewer, 41 | ) 42 | 43 | const handleTextInputSave = (text: string) => { 44 | AddTodoMutation.commit(environment, text, viewer, append) 45 | } 46 | 47 | const hasTodos = (viewer.totalCount || 0) > 0 48 | 49 | const onSetAppend = () => setAppend(prev => !prev); 50 | 51 | return ( 52 |
53 |
54 |
55 |

todos

56 | 62 |
63 | 64 | {hasTodos && } 65 |
66 |
67 |

Double-click to edit a todo

68 |

69 | Created by the{" "} 70 | Relay team 71 |

72 |

73 | Part of TodoMVC 74 |

75 |
76 |
77 | ) 78 | } 79 | 80 | export default TodoApp 81 | -------------------------------------------------------------------------------- /example-hooks/ts/components/TodoListFooter.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * This file provided by Facebook is for non-commercial testing and evaluation 3 | * purposes only. Facebook reserves all rights not expressly granted. 4 | * 5 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 6 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 7 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 8 | * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 9 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 10 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | */ 12 | 13 | import RemoveCompletedTodosMutation from "../mutations/RemoveCompletedTodosMutation" 14 | 15 | import React from "react" 16 | import { graphql, useRelayEnvironment, useFragment } from "react-relay/hooks" 17 | 18 | import { TodoListFooter_viewer$key } from "../__relay_artifacts__/TodoListFooter_viewer.graphql" 19 | 20 | interface Props { 21 | viewer: TodoListFooter_viewer$key 22 | onSetAppend: () => void; 23 | append: boolean; 24 | } 25 | 26 | const TodoListFooter = (props: Props) => { 27 | const environment = useRelayEnvironment() 28 | 29 | const viewer = useFragment( 30 | graphql` 31 | fragment TodoListFooter_viewer on User { 32 | id 33 | completedCount 34 | completedTodos: todos( 35 | status: "completed" 36 | first: 2147483647 # max GraphQLInt 37 | ) { 38 | edges { 39 | node { 40 | id 41 | complete 42 | } 43 | } 44 | } 45 | totalCount 46 | } 47 | `, 48 | props.viewer, 49 | ) 50 | 51 | const numCompletedTodos = viewer.completedCount || 0 52 | const numRemainingTodos = (viewer.totalCount || 0) - numCompletedTodos 53 | 54 | const handleRemoveCompletedTodosClick = () => { 55 | RemoveCompletedTodosMutation.commit( 56 | environment, 57 | viewer.completedTodos, 58 | viewer, 59 | ) 60 | } 61 | 62 | return ( 63 |
64 | 65 | {numRemainingTodos} item 66 | {numRemainingTodos === 1 ? "" : "s"} left 67 | 68 | 76 | {numCompletedTodos > 0 && ( 77 | 83 | )} 84 |
85 | ) 86 | } 87 | 88 | export default TodoListFooter 89 | -------------------------------------------------------------------------------- /example-hooks/ts/components/TodoList.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * This file provided by Facebook is for non-commercial testing and evaluation 3 | * purposes only. Facebook reserves all rights not expressly granted. 4 | * 5 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 6 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 7 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 8 | * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 9 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 10 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | */ 12 | 13 | import MarkAllTodosMutation from "../mutations/MarkAllTodosMutation" 14 | import Todo from "./Todo" 15 | 16 | import React, { ChangeEvent } from "react" 17 | import { graphql, useFragment, useRelayEnvironment } from "react-relay/hooks" 18 | 19 | import { TodoList_viewer$key } from "../__relay_artifacts__/TodoList_viewer.graphql" 20 | 21 | interface Props { 22 | viewer: TodoList_viewer$key 23 | } 24 | 25 | const TodoList = (props: Props) => { 26 | const environment = useRelayEnvironment() 27 | 28 | const viewer = useFragment( 29 | graphql` 30 | fragment TodoList_viewer on User { 31 | todos( 32 | first: 2147483647 # max GraphQLInt 33 | ) @connection(key: "TodoList_todos") { 34 | edges { 35 | node { 36 | id 37 | complete 38 | ...Todo_todo 39 | } 40 | } 41 | } 42 | id 43 | totalCount 44 | completedCount 45 | ...Todo_viewer 46 | } 47 | `, 48 | props.viewer, 49 | ) 50 | 51 | const numTodos = viewer.totalCount 52 | const numCompletedTodos = viewer.completedCount 53 | 54 | const handleMarkAllChange = (e: ChangeEvent) => { 55 | const complete = e.target.checked 56 | MarkAllTodosMutation.commit(environment, complete, viewer.todos, viewer) 57 | } 58 | 59 | const renderTodos = () => { 60 | if (!viewer.todos || !viewer.todos.edges) { 61 | throw new Error("assertion failed") 62 | } 63 | return viewer.todos.edges.map(edge => { 64 | const node = edge && edge.node 65 | if (!node) throw new Error("assertion failed") 66 | return 67 | }) 68 | } 69 | 70 | return ( 71 |
72 | 78 | 79 |
    {renderTodos()}
80 |
81 | ) 82 | } 83 | 84 | export default TodoList 85 | -------------------------------------------------------------------------------- /example/ts/components/TodoListFooter.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * This file provided by Facebook is for non-commercial testing and evaluation 3 | * purposes only. Facebook reserves all rights not expressly granted. 4 | * 5 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 6 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 7 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 8 | * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 9 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 10 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | */ 12 | 13 | import RemoveCompletedTodosMutation from "../mutations/RemoveCompletedTodosMutation"; 14 | 15 | import * as React from "react"; 16 | import { graphql, createFragmentContainer, RelayProp } from "react-relay"; 17 | 18 | import { TodoListFooter_viewer } from "../__relay_artifacts__/TodoListFooter_viewer.graphql"; 19 | import { Environment } from "relay-runtime"; 20 | 21 | interface Props { 22 | relay: RelayProp; 23 | viewer: TodoListFooter_viewer; 24 | append: boolean; 25 | onSetAppend: () => void; 26 | } 27 | 28 | class TodoListFooter extends React.Component { 29 | _handleRemoveCompletedTodosClick = () => { 30 | RemoveCompletedTodosMutation.commit( 31 | this.props.relay.environment, 32 | this.props.viewer.completedTodos, 33 | this.props.viewer 34 | ); 35 | }; 36 | render() { 37 | const numCompletedTodos = this.props.viewer.completedCount || 0; 38 | const numRemainingTodos = 39 | (this.props.viewer.totalCount || 0) - numCompletedTodos; 40 | return ( 41 |
42 | 43 | {numRemainingTodos} item 44 | {numRemainingTodos === 1 ? "" : "s"} left 45 | 46 | 50 | {numCompletedTodos > 0 && ( 51 | 57 | )} 58 |
59 | ); 60 | } 61 | } 62 | 63 | export default createFragmentContainer(TodoListFooter, { 64 | viewer: graphql` 65 | fragment TodoListFooter_viewer on User { 66 | id 67 | completedCount 68 | completedTodos: todos( 69 | status: "completed" 70 | first: 2147483647 # max GraphQLInt 71 | ) { 72 | edges { 73 | node { 74 | id 75 | complete 76 | } 77 | } 78 | } 79 | totalCount 80 | } 81 | ` 82 | }); 83 | -------------------------------------------------------------------------------- /example/data/database.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file provided by Facebook is for non-commercial testing and evaluation 3 | * purposes only. Facebook reserves all rights not expressly granted. 4 | * 5 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 6 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 7 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 8 | * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 9 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 10 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | */ 12 | 13 | export class Todo {} 14 | export class User {} 15 | 16 | // Mock authenticated ID 17 | const VIEWER_ID = 'me'; 18 | 19 | // Mock user data 20 | const viewer = new User(); 21 | viewer.id = VIEWER_ID; 22 | const usersById = { 23 | [VIEWER_ID]: viewer, 24 | }; 25 | 26 | // Mock todo data 27 | const todosById = {}; 28 | const todoIdsByUser = { 29 | [VIEWER_ID]: [], 30 | }; 31 | let nextTodoId = 0; 32 | addTodo('Taste JavaScript', true); 33 | addTodo('Buy a unicorn', false); 34 | 35 | export function addTodo(text, complete) { 36 | const todo = new Todo(); 37 | todo.complete = !!complete; 38 | todo.id = `${nextTodoId++}`; 39 | todo.text = text; 40 | todosById[todo.id] = todo; 41 | todoIdsByUser[VIEWER_ID].push(todo.id); 42 | return todo.id; 43 | } 44 | 45 | export function changeTodoStatus(id, complete) { 46 | const todo = getTodo(id); 47 | todo.complete = complete; 48 | } 49 | 50 | export function getTodo(id) { 51 | return todosById[id]; 52 | } 53 | 54 | export function getTodos(status = 'any') { 55 | const todos = todoIdsByUser[VIEWER_ID].map(id => todosById[id]); 56 | if (status === 'any') { 57 | return todos; 58 | } 59 | return todos.filter(todo => todo.complete === (status === 'completed')); 60 | } 61 | 62 | export function getUser(id) { 63 | return usersById[id]; 64 | } 65 | 66 | export function getViewer() { 67 | return getUser(VIEWER_ID); 68 | } 69 | 70 | export function markAllTodos(complete) { 71 | const changedTodos = []; 72 | getTodos().forEach(todo => { 73 | if (todo.complete !== complete) { 74 | todo.complete = complete; 75 | changedTodos.push(todo); 76 | } 77 | }); 78 | return changedTodos.map(todo => todo.id); 79 | } 80 | 81 | export function removeTodo(id) { 82 | const todoIndex = todoIdsByUser[VIEWER_ID].indexOf(id); 83 | if (todoIndex !== -1) { 84 | todoIdsByUser[VIEWER_ID].splice(todoIndex, 1); 85 | } 86 | delete todosById[id]; 87 | } 88 | 89 | export function removeCompletedTodos() { 90 | const todosToRemove = getTodos().filter(todo => todo.complete); 91 | todosToRemove.forEach(todo => removeTodo(todo.id)); 92 | return todosToRemove.map(todo => todo.id); 93 | } 94 | 95 | export function renameTodo(id, text) { 96 | const todo = getTodo(id); 97 | todo.text = text; 98 | } 99 | -------------------------------------------------------------------------------- /example-hooks/data/database.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file provided by Facebook is for non-commercial testing and evaluation 3 | * purposes only. Facebook reserves all rights not expressly granted. 4 | * 5 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 6 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 7 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 8 | * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 9 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 10 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | */ 12 | 13 | export class Todo {} 14 | export class User {} 15 | 16 | // Mock authenticated ID 17 | const VIEWER_ID = 'me'; 18 | 19 | // Mock user data 20 | const viewer = new User(); 21 | viewer.id = VIEWER_ID; 22 | const usersById = { 23 | [VIEWER_ID]: viewer, 24 | }; 25 | 26 | // Mock todo data 27 | const todosById = {}; 28 | const todoIdsByUser = { 29 | [VIEWER_ID]: [], 30 | }; 31 | let nextTodoId = 0; 32 | addTodo('Taste JavaScript', true); 33 | addTodo('Buy a unicorn', false); 34 | 35 | export function addTodo(text, complete) { 36 | const todo = new Todo(); 37 | todo.complete = !!complete; 38 | todo.id = `${nextTodoId++}`; 39 | todo.text = text; 40 | todosById[todo.id] = todo; 41 | todoIdsByUser[VIEWER_ID].push(todo.id); 42 | return todo.id; 43 | } 44 | 45 | export function changeTodoStatus(id, complete) { 46 | const todo = getTodo(id); 47 | todo.complete = complete; 48 | } 49 | 50 | export function getTodo(id) { 51 | return todosById[id]; 52 | } 53 | 54 | export function getTodos(status = 'any') { 55 | const todos = todoIdsByUser[VIEWER_ID].map(id => todosById[id]); 56 | if (status === 'any') { 57 | return todos; 58 | } 59 | return todos.filter(todo => todo.complete === (status === 'completed')); 60 | } 61 | 62 | export function getUser(id) { 63 | return usersById[id]; 64 | } 65 | 66 | export function getViewer() { 67 | return getUser(VIEWER_ID); 68 | } 69 | 70 | export function markAllTodos(complete) { 71 | const changedTodos = []; 72 | getTodos().forEach(todo => { 73 | if (todo.complete !== complete) { 74 | todo.complete = complete; 75 | changedTodos.push(todo); 76 | } 77 | }); 78 | return changedTodos.map(todo => todo.id); 79 | } 80 | 81 | export function removeTodo(id) { 82 | const todoIndex = todoIdsByUser[VIEWER_ID].indexOf(id); 83 | if (todoIndex !== -1) { 84 | todoIdsByUser[VIEWER_ID].splice(todoIndex, 1); 85 | } 86 | delete todosById[id]; 87 | } 88 | 89 | export function removeCompletedTodos() { 90 | const todosToRemove = getTodos().filter(todo => todo.complete); 91 | todosToRemove.forEach(todo => removeTodo(todo.id)); 92 | return todosToRemove.map(todo => todo.id); 93 | } 94 | 95 | export function renameTodo(id, text) { 96 | const todo = getTodo(id); 97 | todo.text = text; 98 | } 99 | -------------------------------------------------------------------------------- /example/ts/components/TodoApp.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * This file provided by Facebook is for non-commercial testing and evaluation 3 | * purposes only. Facebook reserves all rights not expressly granted. 4 | * 5 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 6 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 7 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 8 | * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 9 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 10 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | */ 12 | 13 | import AddTodoMutation from '../mutations/AddTodoMutation'; 14 | import TodoList from './TodoList'; 15 | import TodoListFooter from './TodoListFooter'; 16 | import TodoTextInput from './TodoTextInput'; 17 | 18 | import * as React from 'react'; 19 | import { 20 | createFragmentContainer, 21 | graphql, 22 | RelayProp, 23 | } from 'react-relay'; 24 | 25 | import { TodoApp_viewer } from '../__relay_artifacts__/TodoApp_viewer.graphql'; 26 | 27 | interface Props { 28 | relay: RelayProp 29 | viewer: TodoApp_viewer 30 | } 31 | 32 | interface State { 33 | append: boolean; 34 | } 35 | 36 | class TodoApp extends React.Component { 37 | state = { 38 | append: false 39 | }; 40 | 41 | onSetAppend = () => this.setState(({ append }) => ({ append: !append })); 42 | 43 | _handleTextInputSave = (text: string) => { 44 | AddTodoMutation.commit( 45 | this.props.relay.environment, 46 | text, 47 | this.props.viewer, 48 | this.state.append 49 | ); 50 | }; 51 | render() { 52 | const hasTodos = (this.props.viewer.totalCount || 0) > 0; 53 | return ( 54 |
55 |
56 |
57 |

58 | todos 59 |

60 | 66 |
67 | 68 | {hasTodos && } 69 |
70 | 83 |
84 | ); 85 | } 86 | } 87 | 88 | export default createFragmentContainer(TodoApp, { 89 | viewer: graphql` 90 | fragment TodoApp_viewer on User { 91 | id, 92 | totalCount, 93 | ...TodoListFooter_viewer, 94 | ...TodoList_viewer, 95 | } 96 | `, 97 | }); 98 | -------------------------------------------------------------------------------- /example/ts/components/TodoList.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * This file provided by Facebook is for non-commercial testing and evaluation 3 | * purposes only. Facebook reserves all rights not expressly granted. 4 | * 5 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 6 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 7 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 8 | * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 9 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 10 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | */ 12 | 13 | import MarkAllTodosMutation from '../mutations/MarkAllTodosMutation'; 14 | import Todo from './Todo'; 15 | 16 | import * as React from 'react'; 17 | import { 18 | createFragmentContainer, 19 | graphql, 20 | RelayProp, 21 | } from 'react-relay'; 22 | 23 | import { TodoList_viewer } from '../__relay_artifacts__/TodoList_viewer.graphql'; 24 | import { ChangeEvent } from 'react'; 25 | 26 | interface Props { 27 | relay: RelayProp, 28 | viewer: TodoList_viewer 29 | } 30 | 31 | class TodoList extends React.Component { 32 | _handleMarkAllChange = (e: ChangeEvent) => { 33 | const complete = e.target.checked; 34 | MarkAllTodosMutation.commit( 35 | this.props.relay.environment, 36 | complete, 37 | this.props.viewer.todos, 38 | this.props.viewer, 39 | ); 40 | }; 41 | renderTodos() { 42 | if (!this.props.viewer.todos || !this.props.viewer.todos.edges) { 43 | throw new Error('assertion failed'); 44 | } 45 | return this.props.viewer.todos.edges.map(edge => { 46 | const node = edge && edge.node; 47 | if (!node) throw new Error('assertion failed'); 48 | return 53 | }); 54 | } 55 | render() { 56 | const numTodos = this.props.viewer.totalCount; 57 | const numCompletedTodos = this.props.viewer.completedCount; 58 | return ( 59 |
60 | 66 | 69 |
    70 | {this.renderTodos()} 71 |
72 |
73 | ); 74 | } 75 | } 76 | 77 | export default createFragmentContainer(TodoList, { 78 | viewer: graphql` 79 | fragment TodoList_viewer on User { 80 | todos( 81 | first: 2147483647 # max GraphQLInt 82 | ) @connection(key: "TodoList_todos") { 83 | edges { 84 | node { 85 | id, 86 | complete, 87 | ...Todo_todo, 88 | }, 89 | }, 90 | }, 91 | id, 92 | totalCount, 93 | completedCount, 94 | ...Todo_viewer, 95 | } 96 | `, 97 | }); 98 | -------------------------------------------------------------------------------- /src/formatGeneratedModule.ts: -------------------------------------------------------------------------------- 1 | import { FormatModule } from "relay-compiler"; 2 | import * as ts from "typescript"; 3 | import addAnyTypeCast from "./addAnyTypeCast"; 4 | 5 | const createRequireRegex = () => /require\('(.*)'\)/g; 6 | 7 | function getModuleName(path: string) { 8 | const [moduleName] = path.replace("./", "").split("."); 9 | return moduleName; 10 | } 11 | 12 | // collects all require calls and converts them top-level imports 13 | const requireToImport = (content: string): string => { 14 | const requireRegex = createRequireRegex(); 15 | 16 | // collect all require paths (unique) 17 | const requirePaths = new Set(); 18 | while (true) { 19 | const res = requireRegex.exec(content); 20 | if (res === null) { 21 | break; 22 | } 23 | requirePaths.add(res[1]); 24 | } 25 | // replace all require paths 26 | Array.from(requirePaths).forEach((requirePath) => { 27 | content = content.replace( 28 | `require('${requirePath}')`, 29 | getModuleName(requirePath) 30 | ); 31 | }); 32 | // create top-level imports 33 | const topLevelImports = Array.from(requirePaths) 34 | .sort() 35 | .map( 36 | (requirePath) => 37 | `import ${getModuleName(requirePath)} from "${requirePath.replace( 38 | ".ts", 39 | "" 40 | )}";` 41 | ); 42 | // add top-level imports 43 | content = `${topLevelImports.join("\n")} 44 | ${content}`; 45 | return content; 46 | }; 47 | 48 | type FormatContentOptions = { 49 | replaceRequire: boolean; 50 | }; 51 | 52 | function formatContent( 53 | rawContent: string, 54 | options: FormatContentOptions 55 | ): string { 56 | if (!options.replaceRequire) { 57 | return rawContent; 58 | } 59 | return requireToImport(rawContent); 60 | } 61 | 62 | export const formatterFactory = 63 | (compilerOptions: ts.CompilerOptions = {}): FormatModule => 64 | ({ 65 | moduleName, 66 | documentType, 67 | docText, 68 | concreteText, 69 | typeText, 70 | hash, 71 | sourceHash, 72 | }) => { 73 | const { noImplicitAny, module = -1 } = compilerOptions; 74 | 75 | const documentTypeImport = documentType 76 | ? `import { ${documentType} } from "relay-runtime";` 77 | : ""; 78 | const docTextComment = docText ? "\n/*\n" + docText.trim() + "\n*/\n" : ""; 79 | let nodeStatement = `const node: ${ 80 | documentType || "never" 81 | } = ${concreteText};`; 82 | if (noImplicitAny) { 83 | nodeStatement = addAnyTypeCast(nodeStatement).trim(); 84 | } 85 | const rawContent = `${typeText || ""} 86 | 87 | ${docTextComment} 88 | ${nodeStatement} 89 | (node as any).hash = '${sourceHash}'; 90 | export default node; 91 | `; 92 | 93 | const banner = compilerOptions.banner ? compilerOptions.banner + "\n" : ""; 94 | const content = `${banner}/* tslint:disable */ 95 | /* eslint-disable */ 96 | // @ts-nocheck 97 | ${hash ? `/* ${hash} */\n` : ""} 98 | ${documentTypeImport} 99 | ${formatContent(rawContent, { 100 | replaceRequire: module >= ts.ModuleKind.ES2015, 101 | })}`; 102 | return content; 103 | }; 104 | -------------------------------------------------------------------------------- /example-hooks/ts/mutations/AddTodoMutation.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file provided by Facebook is for non-commercial testing and evaluation 3 | * purposes only. Facebook reserves all rights not expressly granted. 4 | * 5 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 6 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 7 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 8 | * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 9 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 10 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | */ 12 | 13 | import { commitMutation, graphql } from "react-relay" 14 | import { 15 | ConnectionHandler, 16 | RecordSourceSelectorProxy, 17 | Environment, 18 | } from "relay-runtime" 19 | 20 | import { TodoApp_viewer } from "../__relay_artifacts__/TodoApp_viewer.graphql" 21 | import { AddTodoMutation } from "../__relay_artifacts__/AddTodoMutation.graphql" 22 | 23 | const mutation = graphql` 24 | mutation AddTodoMutation($input: AddTodoInput!, $connections: [String!]!, $append: Boolean! = true) { 25 | addTodo(input: $input) { 26 | todoEdge @include(if: $append) @appendEdge(connections: $connections) { 27 | __typename 28 | cursor 29 | node { 30 | complete 31 | id 32 | text 33 | } 34 | } 35 | todoEdge @skip(if: $append) @prependEdge(connections: $connections) { 36 | __typename 37 | cursor 38 | node { 39 | complete 40 | id 41 | text 42 | } 43 | } 44 | viewer { 45 | id 46 | totalCount 47 | } 48 | } 49 | } 50 | ` 51 | 52 | function sharedUpdater( 53 | store: RecordSourceSelectorProxy, 54 | user: TodoApp_viewer, 55 | newEdge: any, 56 | ) { 57 | const userProxy = store.get(user.id) 58 | const conn = ConnectionHandler.getConnection(userProxy!, "TodoList_todos") 59 | ConnectionHandler.insertEdgeAfter(conn!, newEdge) 60 | } 61 | 62 | let tempID = 0 63 | 64 | function commit(environment: Environment, text: string, user: TodoApp_viewer, append: boolean) { 65 | return commitMutation(environment, { 66 | mutation, 67 | variables: { 68 | input: { 69 | text, 70 | clientMutationId: (tempID++).toString(), 71 | }, 72 | append, 73 | connections: [`client:${user.id}:__TodoList_todos_connection`] 74 | }, 75 | optimisticUpdater: store => { 76 | const id = "client:newTodo:" + tempID++ 77 | const node = store.create(id, "Todo") 78 | node.setValue(text, "text") 79 | node.setValue(id, "id") 80 | const newEdge = store.create("client:newEdge:" + tempID++, "TodoEdge") 81 | newEdge.setLinkedRecord(node, "node") 82 | sharedUpdater(store, user, newEdge) 83 | const userProxy = store.get(user.id) 84 | if (!userProxy) throw new Error("assertion failed") 85 | userProxy.setValue( 86 | (userProxy.getValue("totalCount") as number) + 1, 87 | "totalCount", 88 | ) 89 | }, 90 | }) 91 | } 92 | 93 | export default { commit } 94 | -------------------------------------------------------------------------------- /example/ts/mutations/AddTodoMutation.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file provided by Facebook is for non-commercial testing and evaluation 3 | * purposes only. Facebook reserves all rights not expressly granted. 4 | * 5 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 6 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 7 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 8 | * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 9 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 10 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | */ 12 | 13 | import { commitMutation, graphql } from "react-relay" 14 | import { 15 | ConnectionHandler, 16 | RecordSourceSelectorProxy, 17 | Environment, 18 | } from "relay-runtime" 19 | 20 | import { TodoApp_viewer } from "../__relay_artifacts__/TodoApp_viewer.graphql" 21 | import { AddTodoMutation } from "../__relay_artifacts__/AddTodoMutation.graphql" 22 | 23 | const mutation = graphql` 24 | mutation AddTodoMutation($input: AddTodoInput!, $connections: [String!]!, $append: Boolean! = true) { 25 | addTodo(input: $input) { 26 | todoEdge @include(if: $append) @appendEdge(connections: $connections) { 27 | __typename 28 | cursor 29 | node { 30 | complete 31 | id 32 | text 33 | } 34 | } 35 | todoEdge @skip(if: $append) @prependEdge(connections: $connections) { 36 | __typename 37 | cursor 38 | node { 39 | complete 40 | id 41 | text 42 | } 43 | } 44 | viewer { 45 | id 46 | totalCount 47 | } 48 | } 49 | } 50 | ` 51 | 52 | function sharedUpdater( 53 | store: RecordSourceSelectorProxy, 54 | user: TodoApp_viewer, 55 | newEdge: any, 56 | ) { 57 | const userProxy = store.get(user.id) 58 | const conn = ConnectionHandler.getConnection(userProxy!, "TodoList_todos") 59 | ConnectionHandler.insertEdgeAfter(conn!, newEdge) 60 | } 61 | 62 | let tempID = 0 63 | 64 | function commit(environment: Environment, text: string, user: TodoApp_viewer, append: boolean) { 65 | return commitMutation(environment, { 66 | mutation, 67 | variables: { 68 | input: { 69 | text, 70 | clientMutationId: (tempID++).toString(), 71 | }, 72 | append, 73 | connections: [`client:${user.id}:__TodoList_todos_connection`] 74 | }, 75 | optimisticUpdater: store => { 76 | const id = "client:newTodo:" + tempID++ 77 | const node = store.create(id, "Todo") 78 | node.setValue(text, "text") 79 | node.setValue(id, "id") 80 | const newEdge = store.create("client:newEdge:" + tempID++, "TodoEdge") 81 | newEdge.setLinkedRecord(node, "node") 82 | sharedUpdater(store, user, newEdge) 83 | const userProxy = store.get(user.id) 84 | if (!userProxy) throw new Error("assertion failed") 85 | userProxy.setValue( 86 | (userProxy.getValue("totalCount") as number) + 1, 87 | "totalCount", 88 | ) 89 | }, 90 | }) 91 | } 92 | 93 | export default { commit } 94 | -------------------------------------------------------------------------------- /example-hooks/ts/components/TodoTextInput.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * This file provided by Facebook is for non-commercial testing and evaluation 3 | * purposes only. Facebook reserves all rights not expressly granted. 4 | * 5 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 6 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 7 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 8 | * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 9 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 10 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | */ 12 | 13 | import React from "react" 14 | import ReactDOM from "react-dom" 15 | import PropTypes from "prop-types" 16 | import { ChangeEvent } from "react" 17 | 18 | const ENTER_KEY_CODE = 13 19 | const ESC_KEY_CODE = 27 20 | 21 | interface Props extends React.HTMLProps { 22 | className?: string 23 | commitOnBlur?: boolean 24 | initialValue?: string | null 25 | onDelete?: () => void 26 | onCancel?: () => void 27 | onSave: (text: string) => void 28 | placeholder?: string 29 | } 30 | 31 | export default class TodoTextInput extends React.Component { 32 | static defaultProps = { 33 | commitOnBlur: false, 34 | } 35 | static propTypes = { 36 | className: PropTypes.string, 37 | commitOnBlur: PropTypes.bool.isRequired, 38 | initialValue: PropTypes.string, 39 | onCancel: PropTypes.func, 40 | onDelete: PropTypes.func, 41 | onSave: PropTypes.func.isRequired, 42 | placeholder: PropTypes.string, 43 | } 44 | state = { 45 | isEditing: false, 46 | text: this.props.initialValue || "", 47 | } 48 | componentDidMount() { 49 | const element = ReactDOM.findDOMNode(this) as HTMLElement 50 | element.focus() 51 | } 52 | _commitChanges = () => { 53 | const newText = this.state.text.trim() 54 | if (this.props.onDelete && newText === "") { 55 | this.props.onDelete() 56 | } else if (this.props.onCancel && newText === this.props.initialValue) { 57 | this.props.onCancel() 58 | } else if (newText !== "") { 59 | this.props.onSave(newText) 60 | this.setState({ text: "" }) 61 | } 62 | } 63 | _handleBlur = () => { 64 | if (this.props.commitOnBlur) { 65 | this._commitChanges() 66 | } 67 | } 68 | _handleChange = (e: ChangeEvent) => { 69 | this.setState({ text: (e.target as HTMLInputElement).value }) 70 | } 71 | // FIXME: KeyboardEvent in the global.d.ts file that ships with react.d.ts is not generic 72 | // _handleKeyDown = (e: KeyboardEvent) => { 73 | _handleKeyDown = (e: any) => { 74 | if (this.props.onCancel && e.keyCode === ESC_KEY_CODE) { 75 | this.props.onCancel() 76 | } else if (e.keyCode === ENTER_KEY_CODE) { 77 | this._commitChanges() 78 | } 79 | } 80 | render() { 81 | return ( 82 | 90 | ) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /example/ts/components/TodoTextInput.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * This file provided by Facebook is for non-commercial testing and evaluation 3 | * purposes only. Facebook reserves all rights not expressly granted. 4 | * 5 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 6 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 7 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 8 | * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 9 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 10 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | */ 12 | 13 | import * as React from 'react'; 14 | import * as ReactDOM from 'react-dom'; 15 | import * as PropTypes from 'prop-types'; 16 | import { ChangeEvent } from 'react'; 17 | 18 | const ENTER_KEY_CODE = 13; 19 | const ESC_KEY_CODE = 27; 20 | 21 | interface Props extends React.HTMLProps { 22 | className?: string, 23 | commitOnBlur?: boolean, 24 | initialValue?: string | null, 25 | onDelete?: () => void, 26 | onCancel?: () => void, 27 | onSave: (text: string) => void, 28 | placeholder?: string, 29 | } 30 | 31 | export default class TodoTextInput extends React.Component { 32 | static defaultProps = { 33 | commitOnBlur: false, 34 | }; 35 | static propTypes = { 36 | className: PropTypes.string, 37 | commitOnBlur: PropTypes.bool.isRequired, 38 | initialValue: PropTypes.string, 39 | onCancel: PropTypes.func, 40 | onDelete: PropTypes.func, 41 | onSave: PropTypes.func.isRequired, 42 | placeholder: PropTypes.string, 43 | }; 44 | state = { 45 | isEditing: false, 46 | text: this.props.initialValue || '', 47 | }; 48 | componentDidMount() { 49 | const element = ReactDOM.findDOMNode(this) as HTMLElement 50 | element.focus(); 51 | } 52 | _commitChanges = () => { 53 | const newText = this.state.text.trim(); 54 | if (this.props.onDelete && newText === '') { 55 | this.props.onDelete(); 56 | } else if (this.props.onCancel && newText === this.props.initialValue) { 57 | this.props.onCancel(); 58 | } else if (newText !== '') { 59 | this.props.onSave(newText); 60 | this.setState({text: ''}); 61 | } 62 | }; 63 | _handleBlur = () => { 64 | if (this.props.commitOnBlur) { 65 | this._commitChanges(); 66 | } 67 | }; 68 | _handleChange = (e: ChangeEvent) => { 69 | this.setState({text: (e.target as HTMLInputElement).value}); 70 | }; 71 | // FIXME: KeyboardEvent in the global.d.ts file that ships with react.d.ts is not generic 72 | // _handleKeyDown = (e: KeyboardEvent) => { 73 | _handleKeyDown = (e: any) => { 74 | if (this.props.onCancel && e.keyCode === ESC_KEY_CODE) { 75 | this.props.onCancel(); 76 | } else if (e.keyCode === ENTER_KEY_CODE) { 77 | this._commitChanges(); 78 | } 79 | }; 80 | render() { 81 | return ( 82 | 90 | ); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /example/ts/__relay_artifacts__/RenameTodoMutation.graphql.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | /* eslint-disable */ 3 | 4 | import { ConcreteRequest } from "relay-runtime"; 5 | export type RenameTodoInput = { 6 | id: string; 7 | text: string; 8 | clientMutationId?: string | null; 9 | }; 10 | export type RenameTodoMutationVariables = { 11 | input: RenameTodoInput; 12 | }; 13 | export type RenameTodoMutationResponse = { 14 | readonly renameTodo: { 15 | readonly todo: { 16 | readonly id: string; 17 | readonly text: string | null; 18 | } | null; 19 | } | null; 20 | }; 21 | export type RenameTodoMutation = { 22 | readonly response: RenameTodoMutationResponse; 23 | readonly variables: RenameTodoMutationVariables; 24 | }; 25 | 26 | 27 | 28 | /* 29 | mutation RenameTodoMutation( 30 | $input: RenameTodoInput! 31 | ) { 32 | renameTodo(input: $input) { 33 | todo { 34 | id 35 | text 36 | } 37 | } 38 | } 39 | */ 40 | 41 | const node: ConcreteRequest = (function(){ 42 | var v0 = [ 43 | { 44 | "defaultValue": null, 45 | "kind": "LocalArgument", 46 | "name": "input" 47 | } 48 | ], 49 | v1 = [ 50 | { 51 | "alias": null, 52 | "args": [ 53 | { 54 | "kind": "Variable", 55 | "name": "input", 56 | "variableName": "input" 57 | } 58 | ], 59 | "concreteType": "RenameTodoPayload", 60 | "kind": "LinkedField", 61 | "name": "renameTodo", 62 | "plural": false, 63 | "selections": [ 64 | { 65 | "alias": null, 66 | "args": null, 67 | "concreteType": "Todo", 68 | "kind": "LinkedField", 69 | "name": "todo", 70 | "plural": false, 71 | "selections": [ 72 | { 73 | "alias": null, 74 | "args": null, 75 | "kind": "ScalarField", 76 | "name": "id", 77 | "storageKey": null 78 | }, 79 | { 80 | "alias": null, 81 | "args": null, 82 | "kind": "ScalarField", 83 | "name": "text", 84 | "storageKey": null 85 | } 86 | ], 87 | "storageKey": null 88 | } 89 | ], 90 | "storageKey": null 91 | } 92 | ]; 93 | return { 94 | "fragment": { 95 | "argumentDefinitions": (v0/*: any*/), 96 | "kind": "Fragment", 97 | "metadata": null, 98 | "name": "RenameTodoMutation", 99 | "selections": (v1/*: any*/), 100 | "type": "Mutation", 101 | "abstractKey": null 102 | }, 103 | "kind": "Request", 104 | "operation": { 105 | "argumentDefinitions": (v0/*: any*/), 106 | "kind": "Operation", 107 | "name": "RenameTodoMutation", 108 | "selections": (v1/*: any*/) 109 | }, 110 | "params": { 111 | "cacheID": "d970fd7dbf118794415dec7324d463e3", 112 | "id": null, 113 | "metadata": {}, 114 | "name": "RenameTodoMutation", 115 | "operationKind": "mutation", 116 | "text": "mutation RenameTodoMutation(\n $input: RenameTodoInput!\n) {\n renameTodo(input: $input) {\n todo {\n id\n text\n }\n }\n}\n" 117 | } 118 | }; 119 | })(); 120 | (node as any).hash = 'de4aa1639055c2e6a78ee22cce29870a'; 121 | export default node; 122 | -------------------------------------------------------------------------------- /example-hooks/ts/__relay_artifacts__/RenameTodoMutation.graphql.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | /* eslint-disable */ 3 | 4 | import { ConcreteRequest } from "relay-runtime"; 5 | export type RenameTodoInput = { 6 | id: string; 7 | text: string; 8 | clientMutationId?: string | null; 9 | }; 10 | export type RenameTodoMutationVariables = { 11 | input: RenameTodoInput; 12 | }; 13 | export type RenameTodoMutationResponse = { 14 | readonly renameTodo: { 15 | readonly todo: { 16 | readonly id: string; 17 | readonly text: string | null; 18 | } | null; 19 | } | null; 20 | }; 21 | export type RenameTodoMutation = { 22 | readonly response: RenameTodoMutationResponse; 23 | readonly variables: RenameTodoMutationVariables; 24 | }; 25 | 26 | 27 | 28 | /* 29 | mutation RenameTodoMutation( 30 | $input: RenameTodoInput! 31 | ) { 32 | renameTodo(input: $input) { 33 | todo { 34 | id 35 | text 36 | } 37 | } 38 | } 39 | */ 40 | 41 | const node: ConcreteRequest = (function(){ 42 | var v0 = [ 43 | { 44 | "defaultValue": null, 45 | "kind": "LocalArgument", 46 | "name": "input" 47 | } 48 | ], 49 | v1 = [ 50 | { 51 | "alias": null, 52 | "args": [ 53 | { 54 | "kind": "Variable", 55 | "name": "input", 56 | "variableName": "input" 57 | } 58 | ], 59 | "concreteType": "RenameTodoPayload", 60 | "kind": "LinkedField", 61 | "name": "renameTodo", 62 | "plural": false, 63 | "selections": [ 64 | { 65 | "alias": null, 66 | "args": null, 67 | "concreteType": "Todo", 68 | "kind": "LinkedField", 69 | "name": "todo", 70 | "plural": false, 71 | "selections": [ 72 | { 73 | "alias": null, 74 | "args": null, 75 | "kind": "ScalarField", 76 | "name": "id", 77 | "storageKey": null 78 | }, 79 | { 80 | "alias": null, 81 | "args": null, 82 | "kind": "ScalarField", 83 | "name": "text", 84 | "storageKey": null 85 | } 86 | ], 87 | "storageKey": null 88 | } 89 | ], 90 | "storageKey": null 91 | } 92 | ]; 93 | return { 94 | "fragment": { 95 | "argumentDefinitions": (v0/*: any*/), 96 | "kind": "Fragment", 97 | "metadata": null, 98 | "name": "RenameTodoMutation", 99 | "selections": (v1/*: any*/), 100 | "type": "Mutation", 101 | "abstractKey": null 102 | }, 103 | "kind": "Request", 104 | "operation": { 105 | "argumentDefinitions": (v0/*: any*/), 106 | "kind": "Operation", 107 | "name": "RenameTodoMutation", 108 | "selections": (v1/*: any*/) 109 | }, 110 | "params": { 111 | "cacheID": "d970fd7dbf118794415dec7324d463e3", 112 | "id": null, 113 | "metadata": {}, 114 | "name": "RenameTodoMutation", 115 | "operationKind": "mutation", 116 | "text": "mutation RenameTodoMutation(\n $input: RenameTodoInput!\n) {\n renameTodo(input: $input) {\n todo {\n id\n text\n }\n }\n}\n" 117 | } 118 | }; 119 | })(); 120 | (node as any).hash = 'de4aa1639055c2e6a78ee22cce29870a'; 121 | export default node; 122 | -------------------------------------------------------------------------------- /example/data/schema.graphql: -------------------------------------------------------------------------------- 1 | input AddTodoInput { 2 | text: String! 3 | clientMutationId: String 4 | } 5 | 6 | type AddTodoPayload { 7 | todoEdge: TodoEdge 8 | viewer: User 9 | clientMutationId: String 10 | } 11 | 12 | input ChangeTodoStatusInput { 13 | complete: Boolean! 14 | id: ID! 15 | clientMutationId: String 16 | } 17 | 18 | type ChangeTodoStatusPayload { 19 | todo: Todo 20 | viewer: User 21 | clientMutationId: String 22 | } 23 | 24 | input MarkAllTodosInput { 25 | complete: Boolean! 26 | clientMutationId: String 27 | } 28 | 29 | type MarkAllTodosPayload { 30 | changedTodos: [Todo] 31 | viewer: User 32 | clientMutationId: String 33 | } 34 | 35 | type Mutation { 36 | addTodo(input: AddTodoInput!): AddTodoPayload 37 | changeTodoStatus(input: ChangeTodoStatusInput!): ChangeTodoStatusPayload 38 | markAllTodos(input: MarkAllTodosInput!): MarkAllTodosPayload 39 | removeCompletedTodos(input: RemoveCompletedTodosInput!): RemoveCompletedTodosPayload 40 | removeTodo(input: RemoveTodoInput!): RemoveTodoPayload 41 | renameTodo(input: RenameTodoInput!): RenameTodoPayload 42 | } 43 | 44 | """An object with an ID""" 45 | interface Node { 46 | """The id of the object.""" 47 | id: ID! 48 | } 49 | 50 | """Information about pagination in a connection.""" 51 | type PageInfo { 52 | """When paginating forwards, are there more items?""" 53 | hasNextPage: Boolean! 54 | 55 | """When paginating backwards, are there more items?""" 56 | hasPreviousPage: Boolean! 57 | 58 | """When paginating backwards, the cursor to continue.""" 59 | startCursor: String 60 | 61 | """When paginating forwards, the cursor to continue.""" 62 | endCursor: String 63 | } 64 | 65 | type Query { 66 | viewer: User 67 | 68 | """Fetches an object given its ID""" 69 | node( 70 | """The ID of an object""" 71 | id: ID! 72 | ): Node 73 | } 74 | 75 | input RemoveCompletedTodosInput { 76 | clientMutationId: String 77 | } 78 | 79 | type RemoveCompletedTodosPayload { 80 | deletedTodoIds: [String] 81 | viewer: User 82 | clientMutationId: String 83 | } 84 | 85 | input RemoveTodoInput { 86 | id: ID! 87 | clientMutationId: String 88 | } 89 | 90 | type RemoveTodoPayload { 91 | deletedTodoId: ID 92 | viewer: User 93 | clientMutationId: String 94 | } 95 | 96 | input RenameTodoInput { 97 | id: ID! 98 | text: String! 99 | clientMutationId: String 100 | } 101 | 102 | type RenameTodoPayload { 103 | todo: Todo 104 | clientMutationId: String 105 | } 106 | 107 | type Todo implements Node { 108 | """The ID of an object""" 109 | id: ID! 110 | text: String 111 | complete: Boolean 112 | } 113 | 114 | """A connection to a list of items.""" 115 | type TodoConnection { 116 | """Information to aid in pagination.""" 117 | pageInfo: PageInfo! 118 | 119 | """A list of edges.""" 120 | edges: [TodoEdge] 121 | } 122 | 123 | """An edge in a connection.""" 124 | type TodoEdge { 125 | """The item at the end of the edge""" 126 | node: Todo 127 | 128 | """A cursor for use in pagination""" 129 | cursor: String! 130 | } 131 | 132 | type User implements Node { 133 | """The ID of an object""" 134 | id: ID! 135 | todos(status: String = "any", after: String, first: Int, before: String, last: Int): TodoConnection 136 | totalCount: Int 137 | completedCount: Int 138 | } 139 | -------------------------------------------------------------------------------- /example-hooks/data/schema.graphql: -------------------------------------------------------------------------------- 1 | input AddTodoInput { 2 | text: String! 3 | clientMutationId: String 4 | } 5 | 6 | type AddTodoPayload { 7 | todoEdge: TodoEdge 8 | viewer: User 9 | clientMutationId: String 10 | } 11 | 12 | input ChangeTodoStatusInput { 13 | complete: Boolean! 14 | id: ID! 15 | clientMutationId: String 16 | } 17 | 18 | type ChangeTodoStatusPayload { 19 | todo: Todo 20 | viewer: User 21 | clientMutationId: String 22 | } 23 | 24 | input MarkAllTodosInput { 25 | complete: Boolean! 26 | clientMutationId: String 27 | } 28 | 29 | type MarkAllTodosPayload { 30 | changedTodos: [Todo] 31 | viewer: User 32 | clientMutationId: String 33 | } 34 | 35 | type Mutation { 36 | addTodo(input: AddTodoInput!): AddTodoPayload 37 | changeTodoStatus(input: ChangeTodoStatusInput!): ChangeTodoStatusPayload 38 | markAllTodos(input: MarkAllTodosInput!): MarkAllTodosPayload 39 | removeCompletedTodos(input: RemoveCompletedTodosInput!): RemoveCompletedTodosPayload 40 | removeTodo(input: RemoveTodoInput!): RemoveTodoPayload 41 | renameTodo(input: RenameTodoInput!): RenameTodoPayload 42 | } 43 | 44 | """An object with an ID""" 45 | interface Node { 46 | """The id of the object.""" 47 | id: ID! 48 | } 49 | 50 | """Information about pagination in a connection.""" 51 | type PageInfo { 52 | """When paginating forwards, are there more items?""" 53 | hasNextPage: Boolean! 54 | 55 | """When paginating backwards, are there more items?""" 56 | hasPreviousPage: Boolean! 57 | 58 | """When paginating backwards, the cursor to continue.""" 59 | startCursor: String 60 | 61 | """When paginating forwards, the cursor to continue.""" 62 | endCursor: String 63 | } 64 | 65 | type Query { 66 | viewer: User 67 | 68 | """Fetches an object given its ID""" 69 | node( 70 | """The ID of an object""" 71 | id: ID! 72 | ): Node 73 | } 74 | 75 | input RemoveCompletedTodosInput { 76 | clientMutationId: String 77 | } 78 | 79 | type RemoveCompletedTodosPayload { 80 | deletedTodoIds: [String] 81 | viewer: User 82 | clientMutationId: String 83 | } 84 | 85 | input RemoveTodoInput { 86 | id: ID! 87 | clientMutationId: String 88 | } 89 | 90 | type RemoveTodoPayload { 91 | deletedTodoId: ID 92 | viewer: User 93 | clientMutationId: String 94 | } 95 | 96 | input RenameTodoInput { 97 | id: ID! 98 | text: String! 99 | clientMutationId: String 100 | } 101 | 102 | type RenameTodoPayload { 103 | todo: Todo 104 | clientMutationId: String 105 | } 106 | 107 | type Todo implements Node { 108 | """The ID of an object""" 109 | id: ID! 110 | text: String 111 | complete: Boolean 112 | } 113 | 114 | """A connection to a list of items.""" 115 | type TodoConnection { 116 | """Information to aid in pagination.""" 117 | pageInfo: PageInfo! 118 | 119 | """A list of edges.""" 120 | edges: [TodoEdge] 121 | } 122 | 123 | """An edge in a connection.""" 124 | type TodoEdge { 125 | """The item at the end of the edge""" 126 | node: Todo 127 | 128 | """A cursor for use in pagination""" 129 | cursor: String! 130 | } 131 | 132 | type User implements Node { 133 | """The ID of an object""" 134 | id: ID! 135 | todos(status: String = "any", after: String, first: Int, before: String, last: Int): TodoConnection 136 | totalCount: Int 137 | completedCount: Int 138 | } 139 | -------------------------------------------------------------------------------- /example/ts/__relay_artifacts__/TodoListFooter_viewer.graphql.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | /* eslint-disable */ 3 | 4 | import { ReaderFragment } from "relay-runtime"; 5 | import { FragmentRefs } from "relay-runtime"; 6 | export type TodoListFooter_viewer = { 7 | readonly id: string; 8 | readonly completedCount: number | null; 9 | readonly completedTodos: { 10 | readonly edges: ReadonlyArray<{ 11 | readonly node: { 12 | readonly id: string; 13 | readonly complete: boolean | null; 14 | } | null; 15 | } | null> | null; 16 | } | null; 17 | readonly totalCount: number | null; 18 | readonly " $refType": "TodoListFooter_viewer"; 19 | }; 20 | export type TodoListFooter_viewer$data = TodoListFooter_viewer; 21 | export type TodoListFooter_viewer$key = { 22 | readonly " $data"?: TodoListFooter_viewer$data; 23 | readonly " $fragmentRefs": FragmentRefs<"TodoListFooter_viewer">; 24 | }; 25 | 26 | 27 | 28 | const node: ReaderFragment = (function(){ 29 | var v0 = { 30 | "alias": null, 31 | "args": null, 32 | "kind": "ScalarField", 33 | "name": "id", 34 | "storageKey": null 35 | }; 36 | return { 37 | "argumentDefinitions": [], 38 | "kind": "Fragment", 39 | "metadata": null, 40 | "name": "TodoListFooter_viewer", 41 | "selections": [ 42 | (v0/*: any*/), 43 | { 44 | "alias": null, 45 | "args": null, 46 | "kind": "ScalarField", 47 | "name": "completedCount", 48 | "storageKey": null 49 | }, 50 | { 51 | "alias": "completedTodos", 52 | "args": [ 53 | { 54 | "kind": "Literal", 55 | "name": "first", 56 | "value": 2147483647 57 | }, 58 | { 59 | "kind": "Literal", 60 | "name": "status", 61 | "value": "completed" 62 | } 63 | ], 64 | "concreteType": "TodoConnection", 65 | "kind": "LinkedField", 66 | "name": "todos", 67 | "plural": false, 68 | "selections": [ 69 | { 70 | "alias": null, 71 | "args": null, 72 | "concreteType": "TodoEdge", 73 | "kind": "LinkedField", 74 | "name": "edges", 75 | "plural": true, 76 | "selections": [ 77 | { 78 | "alias": null, 79 | "args": null, 80 | "concreteType": "Todo", 81 | "kind": "LinkedField", 82 | "name": "node", 83 | "plural": false, 84 | "selections": [ 85 | (v0/*: any*/), 86 | { 87 | "alias": null, 88 | "args": null, 89 | "kind": "ScalarField", 90 | "name": "complete", 91 | "storageKey": null 92 | } 93 | ], 94 | "storageKey": null 95 | } 96 | ], 97 | "storageKey": null 98 | } 99 | ], 100 | "storageKey": "todos(first:2147483647,status:\"completed\")" 101 | }, 102 | { 103 | "alias": null, 104 | "args": null, 105 | "kind": "ScalarField", 106 | "name": "totalCount", 107 | "storageKey": null 108 | } 109 | ], 110 | "type": "User", 111 | "abstractKey": null 112 | }; 113 | })(); 114 | (node as any).hash = '2490c58e1768d71f3824c1facd127033'; 115 | export default node; 116 | -------------------------------------------------------------------------------- /example-hooks/ts/__relay_artifacts__/TodoListFooter_viewer.graphql.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | /* eslint-disable */ 3 | 4 | import { ReaderFragment } from "relay-runtime"; 5 | import { FragmentRefs } from "relay-runtime"; 6 | export type TodoListFooter_viewer = { 7 | readonly id: string; 8 | readonly completedCount: number | null; 9 | readonly completedTodos: { 10 | readonly edges: ReadonlyArray<{ 11 | readonly node: { 12 | readonly id: string; 13 | readonly complete: boolean | null; 14 | } | null; 15 | } | null> | null; 16 | } | null; 17 | readonly totalCount: number | null; 18 | readonly " $refType": "TodoListFooter_viewer"; 19 | }; 20 | export type TodoListFooter_viewer$data = TodoListFooter_viewer; 21 | export type TodoListFooter_viewer$key = { 22 | readonly " $data"?: TodoListFooter_viewer$data; 23 | readonly " $fragmentRefs": FragmentRefs<"TodoListFooter_viewer">; 24 | }; 25 | 26 | 27 | 28 | const node: ReaderFragment = (function(){ 29 | var v0 = { 30 | "alias": null, 31 | "args": null, 32 | "kind": "ScalarField", 33 | "name": "id", 34 | "storageKey": null 35 | }; 36 | return { 37 | "argumentDefinitions": [], 38 | "kind": "Fragment", 39 | "metadata": null, 40 | "name": "TodoListFooter_viewer", 41 | "selections": [ 42 | (v0/*: any*/), 43 | { 44 | "alias": null, 45 | "args": null, 46 | "kind": "ScalarField", 47 | "name": "completedCount", 48 | "storageKey": null 49 | }, 50 | { 51 | "alias": "completedTodos", 52 | "args": [ 53 | { 54 | "kind": "Literal", 55 | "name": "first", 56 | "value": 2147483647 57 | }, 58 | { 59 | "kind": "Literal", 60 | "name": "status", 61 | "value": "completed" 62 | } 63 | ], 64 | "concreteType": "TodoConnection", 65 | "kind": "LinkedField", 66 | "name": "todos", 67 | "plural": false, 68 | "selections": [ 69 | { 70 | "alias": null, 71 | "args": null, 72 | "concreteType": "TodoEdge", 73 | "kind": "LinkedField", 74 | "name": "edges", 75 | "plural": true, 76 | "selections": [ 77 | { 78 | "alias": null, 79 | "args": null, 80 | "concreteType": "Todo", 81 | "kind": "LinkedField", 82 | "name": "node", 83 | "plural": false, 84 | "selections": [ 85 | (v0/*: any*/), 86 | { 87 | "alias": null, 88 | "args": null, 89 | "kind": "ScalarField", 90 | "name": "complete", 91 | "storageKey": null 92 | } 93 | ], 94 | "storageKey": null 95 | } 96 | ], 97 | "storageKey": null 98 | } 99 | ], 100 | "storageKey": "todos(first:2147483647,status:\"completed\")" 101 | }, 102 | { 103 | "alias": null, 104 | "args": null, 105 | "kind": "ScalarField", 106 | "name": "totalCount", 107 | "storageKey": null 108 | } 109 | ], 110 | "type": "User", 111 | "abstractKey": null 112 | }; 113 | })(); 114 | (node as any).hash = '2490c58e1768d71f3824c1facd127033'; 115 | export default node; 116 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "relay-compiler-language-typescript", 3 | "version": "15.0.1", 4 | "description": "A language plugin for Relay that adds TypeScript support, including emitting type definitions.", 5 | "keywords": [ 6 | "graphql", 7 | "react", 8 | "relay", 9 | "typescript" 10 | ], 11 | "homepage": "https://github.com/relay-tools/relay-compiler-language-typescript", 12 | "bugs": { 13 | "url": "https://github.com/relay-tools/relay-compiler-language-typescript/issues" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/relay-tools/relay-compiler-language-typescript.git" 18 | }, 19 | "license": "MIT", 20 | "contributors": [ 21 | { 22 | "name": "Kaare Hoff Skovgaard", 23 | "email": "kaare@kaareskovgaard.net" 24 | }, 25 | { 26 | "name": "Eloy Durán", 27 | "email": "eloy.de.enige@gmail.com" 28 | } 29 | ], 30 | "main": "lib/index.js", 31 | "types": "lib/index.d.ts", 32 | "files": [ 33 | "lib" 34 | ], 35 | "scripts": { 36 | "build": "rimraf lib && tsc --project tsconfig.build.json", 37 | "lint": "tslint -c tslint.json --project tsconfig.json", 38 | "prettier": "prettier --write '{src,types,test}/**/*.ts'", 39 | "prepublish": "npm run build", 40 | "relay": "node bin/relay-compiler.js --schema test/schema.graphql --src test/ --outputDir __generated__", 41 | "sync-fixtures": "ts-node ./syncFixtures.ts", 42 | "test": "npm run type-check && jest", 43 | "type-check": "tsc --noEmit --pretty", 44 | "watch": "concurrently 'tsc --watch --project tsconfig.build.json' 'chokidar \"lib/**/*.js\" -c \"yalc publish --force --push\"'", 45 | "release": "npx auto@v10.30.0 shipit -v", 46 | "prepare": "husky install" 47 | }, 48 | "lint-staged": { 49 | "**/*.json": [ 50 | "prettier --write", 51 | "git add" 52 | ], 53 | "{src,types}/**/*.ts": [ 54 | "tslint -c tslint.json --fix", 55 | "prettier --write", 56 | "git add" 57 | ] 58 | }, 59 | "prettier": {}, 60 | "jest": { 61 | "moduleFileExtensions": [ 62 | "js", 63 | "ts", 64 | "tsx" 65 | ], 66 | "testRegex": "test/.+?-test\\.tsx?$", 67 | "transform": { 68 | "^.+\\.tsx?$": "ts-jest" 69 | } 70 | }, 71 | "dependencies": { 72 | "invariant": "^2.2.4" 73 | }, 74 | "devDependencies": { 75 | "@babel/runtime": "7.23.1", 76 | "@types/invariant": "2.2.35", 77 | "@types/jest": "^27.0.2", 78 | "@types/node": "18.16.3", 79 | "@types/relay-compiler": "^8.0.1", 80 | "@types/relay-runtime": "^12.0.0", 81 | "babel-plugin-relay": "^12.0.0", 82 | "chokidar-cli": "^3.0.0", 83 | "concurrently": "^7.0.0", 84 | "glob": "^7.1.6", 85 | "graphql": "^15.6.0", 86 | "husky": "^7.0.0", 87 | "jest": "^27.2.2", 88 | "jest-cli": "^27.2.2", 89 | "lint-staged": "^12.0.2", 90 | "prettier": "^2.2.1", 91 | "relay-compiler": "^12.0.0", 92 | "relay-runtime": "^12.0.0", 93 | "relay-test-utils-internal": "^12.0.0", 94 | "rimraf": "^3.0.2", 95 | "ts-jest": "^27.0.5", 96 | "ts-node": "^10.2.1", 97 | "tslint": "^6.1.3", 98 | "tslint-config-prettier": "^1.18.0", 99 | "typescript": "4.9.5" 100 | }, 101 | "peerDependencies": { 102 | "@types/react-relay": ">=11.0.2", 103 | "@types/relay-runtime": ">=12.0.0", 104 | "relay-compiler": ">=12.0.0", 105 | "relay-runtime": ">=12.0.0", 106 | "typescript": ">=4.5.0" 107 | }, 108 | "publishConfig": { 109 | "registry": "https://registry.npmjs.org/" 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /example-hooks/ts/components/Todo.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * This file provided by Facebook is for non-commercial testing and evaluation 3 | * purposes only. Facebook reserves all rights not expressly granted. 4 | * 5 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 6 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 7 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 8 | * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 9 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 10 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | */ 12 | import React, { ChangeEvent } from "react" 13 | 14 | import ChangeTodoStatusMutation from "../mutations/ChangeTodoStatusMutation" 15 | import RemoveTodoMutation from "../mutations/RemoveTodoMutation" 16 | import RenameTodoMutation from "../mutations/RenameTodoMutation" 17 | import TodoTextInput from "./TodoTextInput" 18 | 19 | import { graphql, useRelayEnvironment, useFragment } from "react-relay/hooks" 20 | 21 | import classnames from "classnames" 22 | 23 | import { Todo_todo$key } from "../__relay_artifacts__/Todo_todo.graphql" 24 | import { Todo_viewer$key } from "../__relay_artifacts__/Todo_viewer.graphql" 25 | 26 | interface Props { 27 | todo: Todo_todo$key 28 | viewer: Todo_viewer$key 29 | } 30 | 31 | const Todo = (props: Props) => { 32 | const [isEditing, setIsEditing] = React.useState(false) 33 | 34 | const environment = useRelayEnvironment() 35 | 36 | const todo = useFragment( 37 | graphql` 38 | fragment Todo_todo on Todo { 39 | complete 40 | id 41 | text 42 | } 43 | `, 44 | props.todo, 45 | ) 46 | 47 | const viewer = useFragment( 48 | graphql` 49 | fragment Todo_viewer on User { 50 | id 51 | totalCount 52 | completedCount 53 | } 54 | `, 55 | props.viewer, 56 | ) 57 | 58 | const handleCompleteChange = (e: ChangeEvent) => { 59 | const complete = e.target.checked 60 | ChangeTodoStatusMutation.commit(environment, complete, todo, viewer) 61 | } 62 | const handleDestroyClick = () => { 63 | removeTodo() 64 | } 65 | const handleLabelDoubleClick = () => { 66 | setIsEditing(true) 67 | } 68 | const handleTextInputCancel = () => { 69 | setIsEditing(false) 70 | } 71 | const handleTextInputDelete = () => { 72 | setIsEditing(false) 73 | removeTodo() 74 | } 75 | const handleTextInputSave = (text: string) => { 76 | setIsEditing(false) 77 | RenameTodoMutation.commit(environment, text, todo) 78 | } 79 | function removeTodo() { 80 | RemoveTodoMutation.commit(environment, todo, viewer) 81 | } 82 | 83 | function renderTextInput() { 84 | return ( 85 | 93 | ) 94 | } 95 | 96 | return ( 97 |
  • 103 |
    104 | 110 | 111 |
    113 | {isEditing && renderTextInput()} 114 |
  • 115 | ) 116 | } 117 | 118 | export default Todo 119 | -------------------------------------------------------------------------------- /example/ts/__relay_artifacts__/MarkAllTodosMutation.graphql.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | /* eslint-disable */ 3 | 4 | import { ConcreteRequest } from "relay-runtime"; 5 | export type MarkAllTodosInput = { 6 | complete: boolean; 7 | clientMutationId?: string | null; 8 | }; 9 | export type MarkAllTodosMutationVariables = { 10 | input: MarkAllTodosInput; 11 | }; 12 | export type MarkAllTodosMutationResponse = { 13 | readonly markAllTodos: { 14 | readonly changedTodos: ReadonlyArray<{ 15 | readonly id: string; 16 | readonly complete: boolean | null; 17 | } | null> | null; 18 | readonly viewer: { 19 | readonly id: string; 20 | readonly completedCount: number | null; 21 | } | null; 22 | } | null; 23 | }; 24 | export type MarkAllTodosMutation = { 25 | readonly response: MarkAllTodosMutationResponse; 26 | readonly variables: MarkAllTodosMutationVariables; 27 | }; 28 | 29 | 30 | 31 | /* 32 | mutation MarkAllTodosMutation( 33 | $input: MarkAllTodosInput! 34 | ) { 35 | markAllTodos(input: $input) { 36 | changedTodos { 37 | id 38 | complete 39 | } 40 | viewer { 41 | id 42 | completedCount 43 | } 44 | } 45 | } 46 | */ 47 | 48 | const node: ConcreteRequest = (function(){ 49 | var v0 = [ 50 | { 51 | "defaultValue": null, 52 | "kind": "LocalArgument", 53 | "name": "input" 54 | } 55 | ], 56 | v1 = { 57 | "alias": null, 58 | "args": null, 59 | "kind": "ScalarField", 60 | "name": "id", 61 | "storageKey": null 62 | }, 63 | v2 = [ 64 | { 65 | "alias": null, 66 | "args": [ 67 | { 68 | "kind": "Variable", 69 | "name": "input", 70 | "variableName": "input" 71 | } 72 | ], 73 | "concreteType": "MarkAllTodosPayload", 74 | "kind": "LinkedField", 75 | "name": "markAllTodos", 76 | "plural": false, 77 | "selections": [ 78 | { 79 | "alias": null, 80 | "args": null, 81 | "concreteType": "Todo", 82 | "kind": "LinkedField", 83 | "name": "changedTodos", 84 | "plural": true, 85 | "selections": [ 86 | (v1/*: any*/), 87 | { 88 | "alias": null, 89 | "args": null, 90 | "kind": "ScalarField", 91 | "name": "complete", 92 | "storageKey": null 93 | } 94 | ], 95 | "storageKey": null 96 | }, 97 | { 98 | "alias": null, 99 | "args": null, 100 | "concreteType": "User", 101 | "kind": "LinkedField", 102 | "name": "viewer", 103 | "plural": false, 104 | "selections": [ 105 | (v1/*: any*/), 106 | { 107 | "alias": null, 108 | "args": null, 109 | "kind": "ScalarField", 110 | "name": "completedCount", 111 | "storageKey": null 112 | } 113 | ], 114 | "storageKey": null 115 | } 116 | ], 117 | "storageKey": null 118 | } 119 | ]; 120 | return { 121 | "fragment": { 122 | "argumentDefinitions": (v0/*: any*/), 123 | "kind": "Fragment", 124 | "metadata": null, 125 | "name": "MarkAllTodosMutation", 126 | "selections": (v2/*: any*/), 127 | "type": "Mutation", 128 | "abstractKey": null 129 | }, 130 | "kind": "Request", 131 | "operation": { 132 | "argumentDefinitions": (v0/*: any*/), 133 | "kind": "Operation", 134 | "name": "MarkAllTodosMutation", 135 | "selections": (v2/*: any*/) 136 | }, 137 | "params": { 138 | "cacheID": "d46e7ec7a20692a47ac3fc3dfb222488", 139 | "id": null, 140 | "metadata": {}, 141 | "name": "MarkAllTodosMutation", 142 | "operationKind": "mutation", 143 | "text": "mutation MarkAllTodosMutation(\n $input: MarkAllTodosInput!\n) {\n markAllTodos(input: $input) {\n changedTodos {\n id\n complete\n }\n viewer {\n id\n completedCount\n }\n }\n}\n" 144 | } 145 | }; 146 | })(); 147 | (node as any).hash = '00fd81d60a24546c792660837e3fc6bd'; 148 | export default node; 149 | -------------------------------------------------------------------------------- /example-hooks/ts/__relay_artifacts__/MarkAllTodosMutation.graphql.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | /* eslint-disable */ 3 | 4 | import { ConcreteRequest } from "relay-runtime"; 5 | export type MarkAllTodosInput = { 6 | complete: boolean; 7 | clientMutationId?: string | null; 8 | }; 9 | export type MarkAllTodosMutationVariables = { 10 | input: MarkAllTodosInput; 11 | }; 12 | export type MarkAllTodosMutationResponse = { 13 | readonly markAllTodos: { 14 | readonly changedTodos: ReadonlyArray<{ 15 | readonly id: string; 16 | readonly complete: boolean | null; 17 | } | null> | null; 18 | readonly viewer: { 19 | readonly id: string; 20 | readonly completedCount: number | null; 21 | } | null; 22 | } | null; 23 | }; 24 | export type MarkAllTodosMutation = { 25 | readonly response: MarkAllTodosMutationResponse; 26 | readonly variables: MarkAllTodosMutationVariables; 27 | }; 28 | 29 | 30 | 31 | /* 32 | mutation MarkAllTodosMutation( 33 | $input: MarkAllTodosInput! 34 | ) { 35 | markAllTodos(input: $input) { 36 | changedTodos { 37 | id 38 | complete 39 | } 40 | viewer { 41 | id 42 | completedCount 43 | } 44 | } 45 | } 46 | */ 47 | 48 | const node: ConcreteRequest = (function(){ 49 | var v0 = [ 50 | { 51 | "defaultValue": null, 52 | "kind": "LocalArgument", 53 | "name": "input" 54 | } 55 | ], 56 | v1 = { 57 | "alias": null, 58 | "args": null, 59 | "kind": "ScalarField", 60 | "name": "id", 61 | "storageKey": null 62 | }, 63 | v2 = [ 64 | { 65 | "alias": null, 66 | "args": [ 67 | { 68 | "kind": "Variable", 69 | "name": "input", 70 | "variableName": "input" 71 | } 72 | ], 73 | "concreteType": "MarkAllTodosPayload", 74 | "kind": "LinkedField", 75 | "name": "markAllTodos", 76 | "plural": false, 77 | "selections": [ 78 | { 79 | "alias": null, 80 | "args": null, 81 | "concreteType": "Todo", 82 | "kind": "LinkedField", 83 | "name": "changedTodos", 84 | "plural": true, 85 | "selections": [ 86 | (v1/*: any*/), 87 | { 88 | "alias": null, 89 | "args": null, 90 | "kind": "ScalarField", 91 | "name": "complete", 92 | "storageKey": null 93 | } 94 | ], 95 | "storageKey": null 96 | }, 97 | { 98 | "alias": null, 99 | "args": null, 100 | "concreteType": "User", 101 | "kind": "LinkedField", 102 | "name": "viewer", 103 | "plural": false, 104 | "selections": [ 105 | (v1/*: any*/), 106 | { 107 | "alias": null, 108 | "args": null, 109 | "kind": "ScalarField", 110 | "name": "completedCount", 111 | "storageKey": null 112 | } 113 | ], 114 | "storageKey": null 115 | } 116 | ], 117 | "storageKey": null 118 | } 119 | ]; 120 | return { 121 | "fragment": { 122 | "argumentDefinitions": (v0/*: any*/), 123 | "kind": "Fragment", 124 | "metadata": null, 125 | "name": "MarkAllTodosMutation", 126 | "selections": (v2/*: any*/), 127 | "type": "Mutation", 128 | "abstractKey": null 129 | }, 130 | "kind": "Request", 131 | "operation": { 132 | "argumentDefinitions": (v0/*: any*/), 133 | "kind": "Operation", 134 | "name": "MarkAllTodosMutation", 135 | "selections": (v2/*: any*/) 136 | }, 137 | "params": { 138 | "cacheID": "d46e7ec7a20692a47ac3fc3dfb222488", 139 | "id": null, 140 | "metadata": {}, 141 | "name": "MarkAllTodosMutation", 142 | "operationKind": "mutation", 143 | "text": "mutation MarkAllTodosMutation(\n $input: MarkAllTodosInput!\n) {\n markAllTodos(input: $input) {\n changedTodos {\n id\n complete\n }\n viewer {\n id\n completedCount\n }\n }\n}\n" 144 | } 145 | }; 146 | })(); 147 | (node as any).hash = '00fd81d60a24546c792660837e3fc6bd'; 148 | export default node; 149 | -------------------------------------------------------------------------------- /example/ts/__relay_artifacts__/ChangeTodoStatusMutation.graphql.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | /* eslint-disable */ 3 | 4 | import { ConcreteRequest } from "relay-runtime"; 5 | export type ChangeTodoStatusInput = { 6 | complete: boolean; 7 | id: string; 8 | clientMutationId?: string | null; 9 | }; 10 | export type ChangeTodoStatusMutationVariables = { 11 | input: ChangeTodoStatusInput; 12 | }; 13 | export type ChangeTodoStatusMutationResponse = { 14 | readonly changeTodoStatus: { 15 | readonly todo: { 16 | readonly id: string; 17 | readonly complete: boolean | null; 18 | } | null; 19 | readonly viewer: { 20 | readonly id: string; 21 | readonly completedCount: number | null; 22 | } | null; 23 | } | null; 24 | }; 25 | export type ChangeTodoStatusMutation = { 26 | readonly response: ChangeTodoStatusMutationResponse; 27 | readonly variables: ChangeTodoStatusMutationVariables; 28 | }; 29 | 30 | 31 | 32 | /* 33 | mutation ChangeTodoStatusMutation( 34 | $input: ChangeTodoStatusInput! 35 | ) { 36 | changeTodoStatus(input: $input) { 37 | todo { 38 | id 39 | complete 40 | } 41 | viewer { 42 | id 43 | completedCount 44 | } 45 | } 46 | } 47 | */ 48 | 49 | const node: ConcreteRequest = (function(){ 50 | var v0 = [ 51 | { 52 | "defaultValue": null, 53 | "kind": "LocalArgument", 54 | "name": "input" 55 | } 56 | ], 57 | v1 = { 58 | "alias": null, 59 | "args": null, 60 | "kind": "ScalarField", 61 | "name": "id", 62 | "storageKey": null 63 | }, 64 | v2 = [ 65 | { 66 | "alias": null, 67 | "args": [ 68 | { 69 | "kind": "Variable", 70 | "name": "input", 71 | "variableName": "input" 72 | } 73 | ], 74 | "concreteType": "ChangeTodoStatusPayload", 75 | "kind": "LinkedField", 76 | "name": "changeTodoStatus", 77 | "plural": false, 78 | "selections": [ 79 | { 80 | "alias": null, 81 | "args": null, 82 | "concreteType": "Todo", 83 | "kind": "LinkedField", 84 | "name": "todo", 85 | "plural": false, 86 | "selections": [ 87 | (v1/*: any*/), 88 | { 89 | "alias": null, 90 | "args": null, 91 | "kind": "ScalarField", 92 | "name": "complete", 93 | "storageKey": null 94 | } 95 | ], 96 | "storageKey": null 97 | }, 98 | { 99 | "alias": null, 100 | "args": null, 101 | "concreteType": "User", 102 | "kind": "LinkedField", 103 | "name": "viewer", 104 | "plural": false, 105 | "selections": [ 106 | (v1/*: any*/), 107 | { 108 | "alias": null, 109 | "args": null, 110 | "kind": "ScalarField", 111 | "name": "completedCount", 112 | "storageKey": null 113 | } 114 | ], 115 | "storageKey": null 116 | } 117 | ], 118 | "storageKey": null 119 | } 120 | ]; 121 | return { 122 | "fragment": { 123 | "argumentDefinitions": (v0/*: any*/), 124 | "kind": "Fragment", 125 | "metadata": null, 126 | "name": "ChangeTodoStatusMutation", 127 | "selections": (v2/*: any*/), 128 | "type": "Mutation", 129 | "abstractKey": null 130 | }, 131 | "kind": "Request", 132 | "operation": { 133 | "argumentDefinitions": (v0/*: any*/), 134 | "kind": "Operation", 135 | "name": "ChangeTodoStatusMutation", 136 | "selections": (v2/*: any*/) 137 | }, 138 | "params": { 139 | "cacheID": "add8723f75323848b2de0e56f1e9e0d7", 140 | "id": null, 141 | "metadata": {}, 142 | "name": "ChangeTodoStatusMutation", 143 | "operationKind": "mutation", 144 | "text": "mutation ChangeTodoStatusMutation(\n $input: ChangeTodoStatusInput!\n) {\n changeTodoStatus(input: $input) {\n todo {\n id\n complete\n }\n viewer {\n id\n completedCount\n }\n }\n}\n" 145 | } 146 | }; 147 | })(); 148 | (node as any).hash = '82df4993530f2c7019c4cb7382a187fa'; 149 | export default node; 150 | -------------------------------------------------------------------------------- /example-hooks/ts/__relay_artifacts__/ChangeTodoStatusMutation.graphql.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | /* eslint-disable */ 3 | 4 | import { ConcreteRequest } from "relay-runtime"; 5 | export type ChangeTodoStatusInput = { 6 | complete: boolean; 7 | id: string; 8 | clientMutationId?: string | null; 9 | }; 10 | export type ChangeTodoStatusMutationVariables = { 11 | input: ChangeTodoStatusInput; 12 | }; 13 | export type ChangeTodoStatusMutationResponse = { 14 | readonly changeTodoStatus: { 15 | readonly todo: { 16 | readonly id: string; 17 | readonly complete: boolean | null; 18 | } | null; 19 | readonly viewer: { 20 | readonly id: string; 21 | readonly completedCount: number | null; 22 | } | null; 23 | } | null; 24 | }; 25 | export type ChangeTodoStatusMutation = { 26 | readonly response: ChangeTodoStatusMutationResponse; 27 | readonly variables: ChangeTodoStatusMutationVariables; 28 | }; 29 | 30 | 31 | 32 | /* 33 | mutation ChangeTodoStatusMutation( 34 | $input: ChangeTodoStatusInput! 35 | ) { 36 | changeTodoStatus(input: $input) { 37 | todo { 38 | id 39 | complete 40 | } 41 | viewer { 42 | id 43 | completedCount 44 | } 45 | } 46 | } 47 | */ 48 | 49 | const node: ConcreteRequest = (function(){ 50 | var v0 = [ 51 | { 52 | "defaultValue": null, 53 | "kind": "LocalArgument", 54 | "name": "input" 55 | } 56 | ], 57 | v1 = { 58 | "alias": null, 59 | "args": null, 60 | "kind": "ScalarField", 61 | "name": "id", 62 | "storageKey": null 63 | }, 64 | v2 = [ 65 | { 66 | "alias": null, 67 | "args": [ 68 | { 69 | "kind": "Variable", 70 | "name": "input", 71 | "variableName": "input" 72 | } 73 | ], 74 | "concreteType": "ChangeTodoStatusPayload", 75 | "kind": "LinkedField", 76 | "name": "changeTodoStatus", 77 | "plural": false, 78 | "selections": [ 79 | { 80 | "alias": null, 81 | "args": null, 82 | "concreteType": "Todo", 83 | "kind": "LinkedField", 84 | "name": "todo", 85 | "plural": false, 86 | "selections": [ 87 | (v1/*: any*/), 88 | { 89 | "alias": null, 90 | "args": null, 91 | "kind": "ScalarField", 92 | "name": "complete", 93 | "storageKey": null 94 | } 95 | ], 96 | "storageKey": null 97 | }, 98 | { 99 | "alias": null, 100 | "args": null, 101 | "concreteType": "User", 102 | "kind": "LinkedField", 103 | "name": "viewer", 104 | "plural": false, 105 | "selections": [ 106 | (v1/*: any*/), 107 | { 108 | "alias": null, 109 | "args": null, 110 | "kind": "ScalarField", 111 | "name": "completedCount", 112 | "storageKey": null 113 | } 114 | ], 115 | "storageKey": null 116 | } 117 | ], 118 | "storageKey": null 119 | } 120 | ]; 121 | return { 122 | "fragment": { 123 | "argumentDefinitions": (v0/*: any*/), 124 | "kind": "Fragment", 125 | "metadata": null, 126 | "name": "ChangeTodoStatusMutation", 127 | "selections": (v2/*: any*/), 128 | "type": "Mutation", 129 | "abstractKey": null 130 | }, 131 | "kind": "Request", 132 | "operation": { 133 | "argumentDefinitions": (v0/*: any*/), 134 | "kind": "Operation", 135 | "name": "ChangeTodoStatusMutation", 136 | "selections": (v2/*: any*/) 137 | }, 138 | "params": { 139 | "cacheID": "add8723f75323848b2de0e56f1e9e0d7", 140 | "id": null, 141 | "metadata": {}, 142 | "name": "ChangeTodoStatusMutation", 143 | "operationKind": "mutation", 144 | "text": "mutation ChangeTodoStatusMutation(\n $input: ChangeTodoStatusInput!\n) {\n changeTodoStatus(input: $input) {\n todo {\n id\n complete\n }\n viewer {\n id\n completedCount\n }\n }\n}\n" 145 | } 146 | }; 147 | })(); 148 | (node as any).hash = '82df4993530f2c7019c4cb7382a187fa'; 149 | export default node; 150 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Obsolete 2 | 3 | This repository is obsolete as [relay@13](https://github.com/facebook/relay/releases/tag/v13.0.0) now supports TypeScript directly. 4 | 5 | # relay-compiler-language-typescript 6 | 7 | [![Build Status](https://travis-ci.org/relay-tools/relay-compiler-language-typescript.svg?branch=master)](https://travis-ci.org/relay-tools/relay-compiler-language-typescript) 8 | 9 | A language plugin for [Relay](https://facebook.github.io/relay/) that adds 10 | TypeScript support, including emitting type definitions. 11 | 12 | ## Installation 13 | 14 | Add the package to your dev dependencies: 15 | 16 | ``` 17 | yarn add graphql relay-compiler --dev 18 | yarn add typescript relay-compiler-language-typescript --dev 19 | ``` 20 | 21 | **Note:** Starting with version 15.0.0 relay-compiler-language-typescript requires a minimum TypeScript version of 4.5.0 being installed in your project. 22 | 23 | ## Configuration 24 | 25 | ### relay-compiler 26 | 27 | Then configure your `relay-compiler` script to use it, like so: 28 | 29 | ```json 30 | { 31 | "scripts": { 32 | "relay": 33 | "relay-compiler --src ./src --schema data/schema.graphql --language typescript --artifactDirectory ./src/__generated__" 34 | } 35 | } 36 | ``` 37 | 38 | This is going to store all artifacts in a single directory, which you also need 39 | to instruct `babel-plugin-relay` to use in your `.babelrc`: 40 | 41 | ```json 42 | { 43 | "plugins": [["relay", { "artifactDirectory": "./src/__generated__" }]] 44 | } 45 | ``` 46 | 47 | ### TypeScript 48 | 49 | Also be sure to configure the TypeScript compiler to transpile to `ES2015` 50 | modules (or higher) and leave transpilation to `CommonJS` modules (if required) 51 | up to Babel with the following `tsconfig.json` settings: 52 | 53 | ```json5 54 | { 55 | "compilerOptions": { 56 | "module": "ES2015", // ES2015 or higher 57 | "target": "ES2020" // best use the highest target setting compatible with your Babel setup 58 | } 59 | } 60 | ``` 61 | 62 | The reason for this is that `tsc` would otherwise generate code where the 63 | imported `graphql` function is being namespaced (`react_relay_1` in this 64 | example): 65 | 66 | ```js 67 | react_relay_1.createFragmentContainer( 68 | MyComponent, 69 | react_relay_1.graphql` 70 | ... 71 | ` 72 | ); 73 | ``` 74 | 75 | … which makes it impossible for `babel-plugin-relay` to find the locations 76 | where the `graphql` function is being used. 77 | 78 | *The generated code uses ES2015 module syntax if `module` is set to ES2015 or 79 | higher in your `tsconfig.json`. Note that the `eagerESModules` option from 80 | `relay-compiler` has no effect on the generated code if `module` is ES2015 or 81 | higher.* 82 | 83 | #### Custom Headers 84 | 85 | If you need to add a custom header to generated files, perhaps for a custom linter 86 | or to get boilerplate license code in, that can be passed in also in compilerOptions 87 | as `banner`: 88 | 89 | ```json 90 | { 91 | "compilerOptions": { 92 | "banner": "/* © 2021 Example.org - @generated code */" 93 | } 94 | } 95 | ``` 96 | 97 | ## Problems 98 | 99 | ### React Hot Loader 100 | 101 | React Hot Loader is known to not always work well with generated code such as 102 | our typing artifacts, which will lead to loading modules _with_ TypeScript types 103 | into the browser and break. As a maintainer of RHL 104 | [pointed out](https://github.com/gaearon/react-hot-loader/issues/1032) in a 105 | similar issue: 106 | 107 | > The problem - hot reloading is not "complete" 108 | 109 | So 110 | [until RHL will be made “complete”](https://github.com/gaearon/react-hot-loader/issues/1024) 111 | this project can’t gurantee to always work well with it, nor is it our control 112 | to do anything about that. 113 | 114 | ## Also see 115 | 116 | * You can find a copy of the Relay 117 | [example TODO app](https://github.com/relay-tools/relay-compiler-language-typescript/tree/master/example) 118 | inside this repository or you can take a look at the 119 | [Artsy React Native app](https://github.com/artsy/eigen). 120 | * There are Relay tslint rules available 121 | [here](https://github.com/relay-tools/tslint-plugin-relay). 122 | 123 | ## License 124 | 125 | This package is available under the MIT license. See the included LICENSE file 126 | for details. 127 | -------------------------------------------------------------------------------- /example/ts/components/Todo.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * This file provided by Facebook is for non-commercial testing and evaluation 3 | * purposes only. Facebook reserves all rights not expressly granted. 4 | * 5 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 6 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 7 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 8 | * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 9 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 10 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | */ 12 | 13 | import ChangeTodoStatusMutation from '../mutations/ChangeTodoStatusMutation'; 14 | import RemoveTodoMutation from '../mutations/RemoveTodoMutation'; 15 | import RenameTodoMutation from '../mutations/RenameTodoMutation'; 16 | import TodoTextInput from './TodoTextInput'; 17 | 18 | import * as React from 'react'; 19 | import { 20 | createFragmentContainer, 21 | graphql, 22 | RelayProp, 23 | } from 'react-relay'; 24 | 25 | import classnames from 'classnames'; 26 | 27 | import { Todo_todo } from '../__relay_artifacts__/Todo_todo.graphql'; 28 | import { Todo_viewer } from '../__relay_artifacts__/Todo_viewer.graphql'; 29 | import { ChangeEvent } from 'react'; 30 | import { Environment } from 'relay-runtime'; 31 | 32 | interface Props { 33 | relay: RelayProp 34 | todo: Todo_todo 35 | viewer: Todo_viewer 36 | } 37 | 38 | class Todo extends React.Component { 39 | state = { 40 | isEditing: false, 41 | }; 42 | _handleCompleteChange = (e: ChangeEvent) => { 43 | const complete = e.target.checked; 44 | ChangeTodoStatusMutation.commit( 45 | (this.props.relay && this.props.relay.environment) as Environment, 46 | complete, 47 | this.props.todo, 48 | this.props.viewer, 49 | ); 50 | }; 51 | _handleDestroyClick = () => { 52 | this._removeTodo(); 53 | }; 54 | _handleLabelDoubleClick = () => { 55 | this._setEditMode(true); 56 | }; 57 | _handleTextInputCancel = () => { 58 | this._setEditMode(false); 59 | }; 60 | _handleTextInputDelete = () => { 61 | this._setEditMode(false); 62 | this._removeTodo(); 63 | }; 64 | _handleTextInputSave = (text: string) => { 65 | this._setEditMode(false); 66 | RenameTodoMutation.commit( 67 | (this.props.relay && this.props.relay.environment) as Environment, 68 | text, 69 | this.props.todo, 70 | ); 71 | }; 72 | _removeTodo() { 73 | RemoveTodoMutation.commit( 74 | (this.props.relay && this.props.relay.environment) as Environment, 75 | this.props.todo, 76 | this.props.viewer, 77 | ); 78 | } 79 | _setEditMode = (shouldEdit: boolean) => { 80 | this.setState({isEditing: shouldEdit}); 81 | }; 82 | renderTextInput() { 83 | return ( 84 | 92 | ); 93 | } 94 | render() { 95 | return ( 96 |
  • 101 |
    102 | 108 | 111 |
    116 | {this.state.isEditing && this.renderTextInput()} 117 |
  • 118 | ); 119 | } 120 | } 121 | 122 | export default createFragmentContainer(Todo, { 123 | todo: graphql` 124 | fragment Todo_todo on Todo { 125 | complete, 126 | id, 127 | text, 128 | } 129 | `, 130 | viewer: graphql` 131 | fragment Todo_viewer on User { 132 | id, 133 | totalCount, 134 | completedCount, 135 | } 136 | `, 137 | }); 138 | -------------------------------------------------------------------------------- /example/ts/__relay_artifacts__/RemoveTodoMutation.graphql.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | /* eslint-disable */ 3 | 4 | import { ConcreteRequest } from "relay-runtime"; 5 | export type RemoveTodoInput = { 6 | id: string; 7 | clientMutationId?: string | null; 8 | }; 9 | export type RemoveTodoMutationVariables = { 10 | input: RemoveTodoInput; 11 | }; 12 | export type RemoveTodoMutationResponse = { 13 | readonly removeTodo: { 14 | readonly deletedTodoId: string | null; 15 | readonly viewer: { 16 | readonly completedCount: number | null; 17 | readonly totalCount: number | null; 18 | } | null; 19 | } | null; 20 | }; 21 | export type RemoveTodoMutation = { 22 | readonly response: RemoveTodoMutationResponse; 23 | readonly variables: RemoveTodoMutationVariables; 24 | }; 25 | 26 | 27 | 28 | /* 29 | mutation RemoveTodoMutation( 30 | $input: RemoveTodoInput! 31 | ) { 32 | removeTodo(input: $input) { 33 | deletedTodoId 34 | viewer { 35 | completedCount 36 | totalCount 37 | id 38 | } 39 | } 40 | } 41 | */ 42 | 43 | const node: ConcreteRequest = (function(){ 44 | var v0 = [ 45 | { 46 | "defaultValue": null, 47 | "kind": "LocalArgument", 48 | "name": "input" 49 | } 50 | ], 51 | v1 = [ 52 | { 53 | "kind": "Variable", 54 | "name": "input", 55 | "variableName": "input" 56 | } 57 | ], 58 | v2 = { 59 | "alias": null, 60 | "args": null, 61 | "kind": "ScalarField", 62 | "name": "deletedTodoId", 63 | "storageKey": null 64 | }, 65 | v3 = { 66 | "alias": null, 67 | "args": null, 68 | "kind": "ScalarField", 69 | "name": "completedCount", 70 | "storageKey": null 71 | }, 72 | v4 = { 73 | "alias": null, 74 | "args": null, 75 | "kind": "ScalarField", 76 | "name": "totalCount", 77 | "storageKey": null 78 | }; 79 | return { 80 | "fragment": { 81 | "argumentDefinitions": (v0/*: any*/), 82 | "kind": "Fragment", 83 | "metadata": null, 84 | "name": "RemoveTodoMutation", 85 | "selections": [ 86 | { 87 | "alias": null, 88 | "args": (v1/*: any*/), 89 | "concreteType": "RemoveTodoPayload", 90 | "kind": "LinkedField", 91 | "name": "removeTodo", 92 | "plural": false, 93 | "selections": [ 94 | (v2/*: any*/), 95 | { 96 | "alias": null, 97 | "args": null, 98 | "concreteType": "User", 99 | "kind": "LinkedField", 100 | "name": "viewer", 101 | "plural": false, 102 | "selections": [ 103 | (v3/*: any*/), 104 | (v4/*: any*/) 105 | ], 106 | "storageKey": null 107 | } 108 | ], 109 | "storageKey": null 110 | } 111 | ], 112 | "type": "Mutation", 113 | "abstractKey": null 114 | }, 115 | "kind": "Request", 116 | "operation": { 117 | "argumentDefinitions": (v0/*: any*/), 118 | "kind": "Operation", 119 | "name": "RemoveTodoMutation", 120 | "selections": [ 121 | { 122 | "alias": null, 123 | "args": (v1/*: any*/), 124 | "concreteType": "RemoveTodoPayload", 125 | "kind": "LinkedField", 126 | "name": "removeTodo", 127 | "plural": false, 128 | "selections": [ 129 | (v2/*: any*/), 130 | { 131 | "alias": null, 132 | "args": null, 133 | "concreteType": "User", 134 | "kind": "LinkedField", 135 | "name": "viewer", 136 | "plural": false, 137 | "selections": [ 138 | (v3/*: any*/), 139 | (v4/*: any*/), 140 | { 141 | "alias": null, 142 | "args": null, 143 | "kind": "ScalarField", 144 | "name": "id", 145 | "storageKey": null 146 | } 147 | ], 148 | "storageKey": null 149 | } 150 | ], 151 | "storageKey": null 152 | } 153 | ] 154 | }, 155 | "params": { 156 | "cacheID": "0daba1c994a0eb6a7bff7c1474776547", 157 | "id": null, 158 | "metadata": {}, 159 | "name": "RemoveTodoMutation", 160 | "operationKind": "mutation", 161 | "text": "mutation RemoveTodoMutation(\n $input: RemoveTodoInput!\n) {\n removeTodo(input: $input) {\n deletedTodoId\n viewer {\n completedCount\n totalCount\n id\n }\n }\n}\n" 162 | } 163 | }; 164 | })(); 165 | (node as any).hash = '560d32d6f18b4072042cf217a41beb97'; 166 | export default node; 167 | -------------------------------------------------------------------------------- /example-hooks/ts/__relay_artifacts__/RemoveTodoMutation.graphql.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | /* eslint-disable */ 3 | 4 | import { ConcreteRequest } from "relay-runtime"; 5 | export type RemoveTodoInput = { 6 | id: string; 7 | clientMutationId?: string | null; 8 | }; 9 | export type RemoveTodoMutationVariables = { 10 | input: RemoveTodoInput; 11 | }; 12 | export type RemoveTodoMutationResponse = { 13 | readonly removeTodo: { 14 | readonly deletedTodoId: string | null; 15 | readonly viewer: { 16 | readonly completedCount: number | null; 17 | readonly totalCount: number | null; 18 | } | null; 19 | } | null; 20 | }; 21 | export type RemoveTodoMutation = { 22 | readonly response: RemoveTodoMutationResponse; 23 | readonly variables: RemoveTodoMutationVariables; 24 | }; 25 | 26 | 27 | 28 | /* 29 | mutation RemoveTodoMutation( 30 | $input: RemoveTodoInput! 31 | ) { 32 | removeTodo(input: $input) { 33 | deletedTodoId 34 | viewer { 35 | completedCount 36 | totalCount 37 | id 38 | } 39 | } 40 | } 41 | */ 42 | 43 | const node: ConcreteRequest = (function(){ 44 | var v0 = [ 45 | { 46 | "defaultValue": null, 47 | "kind": "LocalArgument", 48 | "name": "input" 49 | } 50 | ], 51 | v1 = [ 52 | { 53 | "kind": "Variable", 54 | "name": "input", 55 | "variableName": "input" 56 | } 57 | ], 58 | v2 = { 59 | "alias": null, 60 | "args": null, 61 | "kind": "ScalarField", 62 | "name": "deletedTodoId", 63 | "storageKey": null 64 | }, 65 | v3 = { 66 | "alias": null, 67 | "args": null, 68 | "kind": "ScalarField", 69 | "name": "completedCount", 70 | "storageKey": null 71 | }, 72 | v4 = { 73 | "alias": null, 74 | "args": null, 75 | "kind": "ScalarField", 76 | "name": "totalCount", 77 | "storageKey": null 78 | }; 79 | return { 80 | "fragment": { 81 | "argumentDefinitions": (v0/*: any*/), 82 | "kind": "Fragment", 83 | "metadata": null, 84 | "name": "RemoveTodoMutation", 85 | "selections": [ 86 | { 87 | "alias": null, 88 | "args": (v1/*: any*/), 89 | "concreteType": "RemoveTodoPayload", 90 | "kind": "LinkedField", 91 | "name": "removeTodo", 92 | "plural": false, 93 | "selections": [ 94 | (v2/*: any*/), 95 | { 96 | "alias": null, 97 | "args": null, 98 | "concreteType": "User", 99 | "kind": "LinkedField", 100 | "name": "viewer", 101 | "plural": false, 102 | "selections": [ 103 | (v3/*: any*/), 104 | (v4/*: any*/) 105 | ], 106 | "storageKey": null 107 | } 108 | ], 109 | "storageKey": null 110 | } 111 | ], 112 | "type": "Mutation", 113 | "abstractKey": null 114 | }, 115 | "kind": "Request", 116 | "operation": { 117 | "argumentDefinitions": (v0/*: any*/), 118 | "kind": "Operation", 119 | "name": "RemoveTodoMutation", 120 | "selections": [ 121 | { 122 | "alias": null, 123 | "args": (v1/*: any*/), 124 | "concreteType": "RemoveTodoPayload", 125 | "kind": "LinkedField", 126 | "name": "removeTodo", 127 | "plural": false, 128 | "selections": [ 129 | (v2/*: any*/), 130 | { 131 | "alias": null, 132 | "args": null, 133 | "concreteType": "User", 134 | "kind": "LinkedField", 135 | "name": "viewer", 136 | "plural": false, 137 | "selections": [ 138 | (v3/*: any*/), 139 | (v4/*: any*/), 140 | { 141 | "alias": null, 142 | "args": null, 143 | "kind": "ScalarField", 144 | "name": "id", 145 | "storageKey": null 146 | } 147 | ], 148 | "storageKey": null 149 | } 150 | ], 151 | "storageKey": null 152 | } 153 | ] 154 | }, 155 | "params": { 156 | "cacheID": "0daba1c994a0eb6a7bff7c1474776547", 157 | "id": null, 158 | "metadata": {}, 159 | "name": "RemoveTodoMutation", 160 | "operationKind": "mutation", 161 | "text": "mutation RemoveTodoMutation(\n $input: RemoveTodoInput!\n) {\n removeTodo(input: $input) {\n deletedTodoId\n viewer {\n completedCount\n totalCount\n id\n }\n }\n}\n" 162 | } 163 | }; 164 | })(); 165 | (node as any).hash = '560d32d6f18b4072042cf217a41beb97'; 166 | export default node; 167 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | "target": "es5" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'. */, 5 | "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */, 6 | "lib": [ 7 | "es2015", 8 | "esnext.asynciterable", 9 | "dom" 10 | ] /* Specify library files to be included in the compilation: */, 11 | "allowJs": true /* Allow javascript files to be compiled. */, 12 | "checkJs": false /* Report errors in .js files. */, 13 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 14 | "declaration": true /* Generates corresponding '.d.ts' file. */, 15 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 16 | // "outFile": "./", /* Concatenate and emit output to single file. */ 17 | "outDir": "./lib" /* Redirect output structure to the directory. */, 18 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 19 | // "removeComments": true, /* Do not emit comments to output. */ 20 | // "noEmit": true, /* Do not emit outputs. */ 21 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 22 | "downlevelIteration": true /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */, 23 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 24 | /* Strict Type-Checking Options */ 25 | "strict": true /* Enable all strict type-checking options. */, 26 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 27 | // "strictNullChecks": true, /* Enable strict null checks. */ 28 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 29 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 30 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 31 | /* Additional Checks */ 32 | "noUnusedLocals": true /* Report errors on unused locals. */, 33 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 34 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 35 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 36 | "exactOptionalPropertyTypes": true, 37 | /* Module Resolution Options */ 38 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 39 | "baseUrl": "./" /* Base directory to resolve non-absolute module names. */, 40 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 41 | // "typeRoots": [], /* List of folders to include type definitions from. */ 42 | // "types": [], /* Type declaration files to be included in compilation. */ 43 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 44 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 45 | /* Source Map Options */ 46 | // "sourceRoot": "./", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 47 | // "mapRoot": "./", /* Specify the location where debugger should locate map files instead of generated locations. */ 48 | "inlineSourceMap": true /* Emit a single file with source maps instead of having a separate file. */ 49 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 50 | /* Experimental Options */ 51 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 52 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 53 | }, 54 | "include": ["./src/**/*", "./types/**/*", "./test/**/*"] 55 | } 56 | -------------------------------------------------------------------------------- /example/ts/__relay_artifacts__/RemoveCompletedTodosMutation.graphql.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | /* eslint-disable */ 3 | 4 | import { ConcreteRequest } from "relay-runtime"; 5 | export type RemoveCompletedTodosInput = { 6 | clientMutationId?: string | null; 7 | }; 8 | export type RemoveCompletedTodosMutationVariables = { 9 | input: RemoveCompletedTodosInput; 10 | }; 11 | export type RemoveCompletedTodosMutationResponse = { 12 | readonly removeCompletedTodos: { 13 | readonly deletedTodoIds: ReadonlyArray | null; 14 | readonly viewer: { 15 | readonly completedCount: number | null; 16 | readonly totalCount: number | null; 17 | } | null; 18 | } | null; 19 | }; 20 | export type RemoveCompletedTodosMutation = { 21 | readonly response: RemoveCompletedTodosMutationResponse; 22 | readonly variables: RemoveCompletedTodosMutationVariables; 23 | }; 24 | 25 | 26 | 27 | /* 28 | mutation RemoveCompletedTodosMutation( 29 | $input: RemoveCompletedTodosInput! 30 | ) { 31 | removeCompletedTodos(input: $input) { 32 | deletedTodoIds 33 | viewer { 34 | completedCount 35 | totalCount 36 | id 37 | } 38 | } 39 | } 40 | */ 41 | 42 | const node: ConcreteRequest = (function(){ 43 | var v0 = [ 44 | { 45 | "defaultValue": null, 46 | "kind": "LocalArgument", 47 | "name": "input" 48 | } 49 | ], 50 | v1 = [ 51 | { 52 | "kind": "Variable", 53 | "name": "input", 54 | "variableName": "input" 55 | } 56 | ], 57 | v2 = { 58 | "alias": null, 59 | "args": null, 60 | "kind": "ScalarField", 61 | "name": "deletedTodoIds", 62 | "storageKey": null 63 | }, 64 | v3 = { 65 | "alias": null, 66 | "args": null, 67 | "kind": "ScalarField", 68 | "name": "completedCount", 69 | "storageKey": null 70 | }, 71 | v4 = { 72 | "alias": null, 73 | "args": null, 74 | "kind": "ScalarField", 75 | "name": "totalCount", 76 | "storageKey": null 77 | }; 78 | return { 79 | "fragment": { 80 | "argumentDefinitions": (v0/*: any*/), 81 | "kind": "Fragment", 82 | "metadata": null, 83 | "name": "RemoveCompletedTodosMutation", 84 | "selections": [ 85 | { 86 | "alias": null, 87 | "args": (v1/*: any*/), 88 | "concreteType": "RemoveCompletedTodosPayload", 89 | "kind": "LinkedField", 90 | "name": "removeCompletedTodos", 91 | "plural": false, 92 | "selections": [ 93 | (v2/*: any*/), 94 | { 95 | "alias": null, 96 | "args": null, 97 | "concreteType": "User", 98 | "kind": "LinkedField", 99 | "name": "viewer", 100 | "plural": false, 101 | "selections": [ 102 | (v3/*: any*/), 103 | (v4/*: any*/) 104 | ], 105 | "storageKey": null 106 | } 107 | ], 108 | "storageKey": null 109 | } 110 | ], 111 | "type": "Mutation", 112 | "abstractKey": null 113 | }, 114 | "kind": "Request", 115 | "operation": { 116 | "argumentDefinitions": (v0/*: any*/), 117 | "kind": "Operation", 118 | "name": "RemoveCompletedTodosMutation", 119 | "selections": [ 120 | { 121 | "alias": null, 122 | "args": (v1/*: any*/), 123 | "concreteType": "RemoveCompletedTodosPayload", 124 | "kind": "LinkedField", 125 | "name": "removeCompletedTodos", 126 | "plural": false, 127 | "selections": [ 128 | (v2/*: any*/), 129 | { 130 | "alias": null, 131 | "args": null, 132 | "concreteType": "User", 133 | "kind": "LinkedField", 134 | "name": "viewer", 135 | "plural": false, 136 | "selections": [ 137 | (v3/*: any*/), 138 | (v4/*: any*/), 139 | { 140 | "alias": null, 141 | "args": null, 142 | "kind": "ScalarField", 143 | "name": "id", 144 | "storageKey": null 145 | } 146 | ], 147 | "storageKey": null 148 | } 149 | ], 150 | "storageKey": null 151 | } 152 | ] 153 | }, 154 | "params": { 155 | "cacheID": "4e579d172ceaf5d902f3da90438a1cfe", 156 | "id": null, 157 | "metadata": {}, 158 | "name": "RemoveCompletedTodosMutation", 159 | "operationKind": "mutation", 160 | "text": "mutation RemoveCompletedTodosMutation(\n $input: RemoveCompletedTodosInput!\n) {\n removeCompletedTodos(input: $input) {\n deletedTodoIds\n viewer {\n completedCount\n totalCount\n id\n }\n }\n}\n" 161 | } 162 | }; 163 | })(); 164 | (node as any).hash = '303799d791e6e233861ee011ff3bdbb8'; 165 | export default node; 166 | -------------------------------------------------------------------------------- /example-hooks/ts/__relay_artifacts__/RemoveCompletedTodosMutation.graphql.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | /* eslint-disable */ 3 | 4 | import { ConcreteRequest } from "relay-runtime"; 5 | export type RemoveCompletedTodosInput = { 6 | clientMutationId?: string | null; 7 | }; 8 | export type RemoveCompletedTodosMutationVariables = { 9 | input: RemoveCompletedTodosInput; 10 | }; 11 | export type RemoveCompletedTodosMutationResponse = { 12 | readonly removeCompletedTodos: { 13 | readonly deletedTodoIds: ReadonlyArray | null; 14 | readonly viewer: { 15 | readonly completedCount: number | null; 16 | readonly totalCount: number | null; 17 | } | null; 18 | } | null; 19 | }; 20 | export type RemoveCompletedTodosMutation = { 21 | readonly response: RemoveCompletedTodosMutationResponse; 22 | readonly variables: RemoveCompletedTodosMutationVariables; 23 | }; 24 | 25 | 26 | 27 | /* 28 | mutation RemoveCompletedTodosMutation( 29 | $input: RemoveCompletedTodosInput! 30 | ) { 31 | removeCompletedTodos(input: $input) { 32 | deletedTodoIds 33 | viewer { 34 | completedCount 35 | totalCount 36 | id 37 | } 38 | } 39 | } 40 | */ 41 | 42 | const node: ConcreteRequest = (function(){ 43 | var v0 = [ 44 | { 45 | "defaultValue": null, 46 | "kind": "LocalArgument", 47 | "name": "input" 48 | } 49 | ], 50 | v1 = [ 51 | { 52 | "kind": "Variable", 53 | "name": "input", 54 | "variableName": "input" 55 | } 56 | ], 57 | v2 = { 58 | "alias": null, 59 | "args": null, 60 | "kind": "ScalarField", 61 | "name": "deletedTodoIds", 62 | "storageKey": null 63 | }, 64 | v3 = { 65 | "alias": null, 66 | "args": null, 67 | "kind": "ScalarField", 68 | "name": "completedCount", 69 | "storageKey": null 70 | }, 71 | v4 = { 72 | "alias": null, 73 | "args": null, 74 | "kind": "ScalarField", 75 | "name": "totalCount", 76 | "storageKey": null 77 | }; 78 | return { 79 | "fragment": { 80 | "argumentDefinitions": (v0/*: any*/), 81 | "kind": "Fragment", 82 | "metadata": null, 83 | "name": "RemoveCompletedTodosMutation", 84 | "selections": [ 85 | { 86 | "alias": null, 87 | "args": (v1/*: any*/), 88 | "concreteType": "RemoveCompletedTodosPayload", 89 | "kind": "LinkedField", 90 | "name": "removeCompletedTodos", 91 | "plural": false, 92 | "selections": [ 93 | (v2/*: any*/), 94 | { 95 | "alias": null, 96 | "args": null, 97 | "concreteType": "User", 98 | "kind": "LinkedField", 99 | "name": "viewer", 100 | "plural": false, 101 | "selections": [ 102 | (v3/*: any*/), 103 | (v4/*: any*/) 104 | ], 105 | "storageKey": null 106 | } 107 | ], 108 | "storageKey": null 109 | } 110 | ], 111 | "type": "Mutation", 112 | "abstractKey": null 113 | }, 114 | "kind": "Request", 115 | "operation": { 116 | "argumentDefinitions": (v0/*: any*/), 117 | "kind": "Operation", 118 | "name": "RemoveCompletedTodosMutation", 119 | "selections": [ 120 | { 121 | "alias": null, 122 | "args": (v1/*: any*/), 123 | "concreteType": "RemoveCompletedTodosPayload", 124 | "kind": "LinkedField", 125 | "name": "removeCompletedTodos", 126 | "plural": false, 127 | "selections": [ 128 | (v2/*: any*/), 129 | { 130 | "alias": null, 131 | "args": null, 132 | "concreteType": "User", 133 | "kind": "LinkedField", 134 | "name": "viewer", 135 | "plural": false, 136 | "selections": [ 137 | (v3/*: any*/), 138 | (v4/*: any*/), 139 | { 140 | "alias": null, 141 | "args": null, 142 | "kind": "ScalarField", 143 | "name": "id", 144 | "storageKey": null 145 | } 146 | ], 147 | "storageKey": null 148 | } 149 | ], 150 | "storageKey": null 151 | } 152 | ] 153 | }, 154 | "params": { 155 | "cacheID": "4e579d172ceaf5d902f3da90438a1cfe", 156 | "id": null, 157 | "metadata": {}, 158 | "name": "RemoveCompletedTodosMutation", 159 | "operationKind": "mutation", 160 | "text": "mutation RemoveCompletedTodosMutation(\n $input: RemoveCompletedTodosInput!\n) {\n removeCompletedTodos(input: $input) {\n deletedTodoIds\n viewer {\n completedCount\n totalCount\n id\n }\n }\n}\n" 161 | } 162 | }; 163 | })(); 164 | (node as any).hash = '303799d791e6e233861ee011ff3bdbb8'; 165 | export default node; 166 | --------------------------------------------------------------------------------