├── 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": "
<%= description %>
<% if (typeof link_groups !== 'undefined') { %>
<% link_groups.forEach(function (link_group) { %> <%= link_group.heading %>
<% }); %> <% } %> "
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": "
<%= description %>
<% if (typeof link_groups !== 'undefined') { %>
<% link_groups.forEach(function (link_group) { %> <%= link_group.heading %>
<% }); %> <% } %> "
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 |
63 |
64 | {hasTodos && }
65 |
66 |
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 |
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 |
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 |
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 |
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 |
112 |
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 | [](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 |
115 |
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 |
--------------------------------------------------------------------------------