├── docs ├── .nvmrc ├── static │ └── _redirects ├── .gitignore ├── package.json ├── gatsby-config.js └── source │ ├── index.mdx │ ├── essentials │ ├── installation.md │ ├── getting-started.md │ ├── mutations.md │ └── queries.md │ └── advanced │ └── fragments.md ├── project ├── build.properties └── plugins.sbt ├── .gitignore ├── example ├── opt-launcher.js ├── public │ ├── favicon.ico │ ├── manifest.json │ ├── index.html │ └── index-fastopt.html ├── hot-launcher.js ├── src │ └── main │ │ ├── graphql │ │ └── todo.graphql │ │ └── scala │ │ └── com │ │ └── apollographql │ │ └── scalajs │ │ ├── TodosView.scala │ │ ├── Main.scala │ │ └── AddTodo.scala ├── README.md ├── webpack-opt.config.js ├── webpack-fastopt.config.js ├── build.sbt └── schema.json ├── tests ├── src │ └── test │ │ ├── graphql │ │ ├── mutations │ │ │ ├── todos.graphql │ │ │ ├── addtodo.graphql │ │ │ └── apollo.config.js │ │ └── queries │ │ │ ├── currencyrates.graphql │ │ │ ├── usdrates.graphql │ │ │ └── apollo.config.js │ │ └── scala │ │ └── com │ │ └── apollographql │ │ └── scalajs │ │ ├── ApolloLinkTest.scala │ │ ├── react │ │ ├── ReactApolloTest.scala │ │ ├── UseMutationTest.scala │ │ ├── UseQueryTest.scala │ │ ├── QueryComponentTest.scala │ │ └── MutationComponentTest.scala │ │ └── ApolloClientTest.scala ├── build.sbt ├── queries.json └── mutations.json ├── renovate.json ├── .gitmodules ├── core ├── build.sbt └── src │ └── main │ └── scala │ └── com │ └── apollographql │ └── scalajs │ ├── cache │ ├── ApolloCache.scala │ ├── package.scala │ └── InMemoryCache.scala │ ├── GraphQLQuery.scala │ ├── GraphQLTag.scala │ ├── link │ ├── package.scala │ ├── HttpLink.scala │ ├── Observable.scala │ └── ApolloLink.scala │ ├── OptionalResult.scala │ └── ApolloClient.scala ├── react ├── build.sbt └── src │ └── main │ └── scala │ └── com │ └── apollographql │ └── scalajs │ └── react │ ├── ApolloProvider.scala │ ├── ReactApollo.scala │ ├── Mutation.scala │ ├── Hooks.scala │ └── Query.scala ├── netlify.toml ├── .travis.yml ├── .github ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── .gitleaks.toml ├── LICENSE └── README.md /docs/.nvmrc: -------------------------------------------------------------------------------- 1 | 11 2 | -------------------------------------------------------------------------------- /docs/static/_redirects: -------------------------------------------------------------------------------- 1 | / /docs/scalajs/ 2 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.3.6 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | target/ 3 | project/project/ 4 | .idea/ 5 | node_modules/ 6 | -------------------------------------------------------------------------------- /example/opt-launcher.js: -------------------------------------------------------------------------------- 1 | require("./react-apollo-scalajs-example-opt.js").main(); 2 | -------------------------------------------------------------------------------- /tests/src/test/graphql/mutations/todos.graphql: -------------------------------------------------------------------------------- 1 | query AllTodosId { 2 | todos { 3 | id 4 | } 5 | } -------------------------------------------------------------------------------- /example/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollographql/apollo-scalajs/HEAD/example/public/favicon.ico -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | Thumbs.db 3 | db.json 4 | *.log 5 | node_modules/ 6 | public/* 7 | .cache 8 | .deploy*/ 9 | docs.json -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "packageFiles": [ 3 | "docs/package.json" 4 | ], 5 | "extends": [ 6 | "apollo-docs" 7 | ] 8 | } -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "docs/themes/meteor"] 2 | path = docs/themes/meteor 3 | url = https://github.com/meteor/hexo-theme-meteor.git 4 | -------------------------------------------------------------------------------- /tests/src/test/graphql/mutations/addtodo.graphql: -------------------------------------------------------------------------------- 1 | mutation AddTodo($typ: String!) { 2 | addTodo(type: $typ) { 3 | typ: type 4 | } 5 | } -------------------------------------------------------------------------------- /example/hot-launcher.js: -------------------------------------------------------------------------------- 1 | require("./react-apollo-scalajs-example-fastopt.js").main(); 2 | 3 | if (module.hot) { 4 | module.hot.accept(); 5 | } 6 | -------------------------------------------------------------------------------- /tests/src/test/graphql/queries/currencyrates.graphql: -------------------------------------------------------------------------------- 1 | query CurrencyRates($cur: String!) { 2 | rates(currency: $cur) { 3 | currency 4 | } 5 | } -------------------------------------------------------------------------------- /core/build.sbt: -------------------------------------------------------------------------------- 1 | enablePlugins(ScalaJSPlugin) 2 | 3 | name := "apollo-scalajs-core" 4 | 5 | libraryDependencies += "me.shadaj" %%% "slinky-readwrite" % "0.6.5" 6 | -------------------------------------------------------------------------------- /react/build.sbt: -------------------------------------------------------------------------------- 1 | enablePlugins(ScalaJSPlugin) 2 | 3 | name := "apollo-scalajs-react" 4 | 5 | libraryDependencies += "me.shadaj" %%% "slinky-core" % "0.6.5" 6 | 7 | -------------------------------------------------------------------------------- /core/src/main/scala/com/apollographql/scalajs/cache/ApolloCache.scala: -------------------------------------------------------------------------------- 1 | package com.apollographql.scalajs.cache 2 | 3 | import scala.scalajs.js 4 | 5 | @js.native 6 | trait ApolloCache extends js.Object 7 | -------------------------------------------------------------------------------- /tests/src/test/graphql/queries/usdrates.graphql: -------------------------------------------------------------------------------- 1 | fragment CurrencyFragment on ExchangeRate { 2 | currency 3 | } 4 | 5 | query USDRates { 6 | rates(currency: "USD") { 7 | ...CurrencyFragment 8 | } 9 | } -------------------------------------------------------------------------------- /example/src/main/graphql/todo.graphql: -------------------------------------------------------------------------------- 1 | query AllTodos { 2 | todos { 3 | id 4 | typ: type 5 | } 6 | } 7 | 8 | mutation AddTodo($typ: String!) { 9 | addTodo(type: $typ) { 10 | id 11 | typ: type 12 | } 13 | } -------------------------------------------------------------------------------- /tests/src/test/graphql/mutations/apollo.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | client: { 3 | service: { 4 | name: "test-schema", 5 | localSchemaFile: "tests/mutations.json" 6 | }, 7 | includes: [ "*.graphql" ] 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /tests/src/test/graphql/queries/apollo.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | client: { 3 | service: { 4 | name: "test-schema", 5 | localSchemaFile: "tests/queries.json" 6 | }, 7 | includes: [ "*.graphql" ] 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /core/src/main/scala/com/apollographql/scalajs/cache/package.scala: -------------------------------------------------------------------------------- 1 | package com.apollographql.scalajs 2 | 3 | import scala.scalajs.js 4 | 5 | package object cache { 6 | type CacheResolver = js.Function3[js.Any, js.Dictionary[js.Any], CacheResolverContext, js.Any] 7 | } 8 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.2.0") 2 | 3 | addSbtPlugin("ch.epfl.scala" % "sbt-scalajs-bundler" % "0.18.0") 4 | 5 | addSbtPlugin("org.foundweekends" % "sbt-bintray" % "0.5.6") 6 | 7 | addSbtPlugin("com.dwijnand" % "sbt-dynver" % "4.0.0") 8 | -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | base = "docs/" 3 | publish = "docs/public/" 4 | command = "gatsby build --prefix-paths && mkdir -p docs/scalajs && mv public/* docs/scalajs && mv docs public/ && mv public/docs/scalajs/_redirects public" 5 | [build.environment] 6 | NPM_VERSION = "6" 7 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "prestart": "gatsby clean", 4 | "start": "gatsby develop" 5 | }, 6 | "dependencies": { 7 | "gatsby": "^2.21.0", 8 | "gatsby-theme-apollo-docs": "^4.2.0", 9 | "react": "^16.9.0", 10 | "react-dom": "^16.9.0" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: scala 2 | branches: 3 | only: 4 | - master 5 | - /^v\d+(\.\d+)+$/ 6 | install: 7 | - . $HOME/.nvm/nvm.sh 8 | - nvm install stable 9 | - npm install -g apollo@2.31.0 10 | script: 11 | - sbt +tests/test 12 | - sbt ";set scalaJSStage in Global := FullOptStage; +tests/test" 13 | -------------------------------------------------------------------------------- /core/src/main/scala/com/apollographql/scalajs/GraphQLQuery.scala: -------------------------------------------------------------------------------- 1 | package com.apollographql.scalajs 2 | 3 | trait GraphQLQuery { 4 | val operation: DocumentNode 5 | 6 | type Data 7 | type Variables 8 | } 9 | 10 | trait GraphQLMutation { 11 | val operation: DocumentNode 12 | 13 | type Data 14 | type Variables 15 | } 16 | -------------------------------------------------------------------------------- /core/src/main/scala/com/apollographql/scalajs/GraphQLTag.scala: -------------------------------------------------------------------------------- 1 | package com.apollographql.scalajs 2 | 3 | import scala.scalajs.js 4 | import scala.scalajs.js.annotation.JSImport 5 | 6 | @js.native 7 | trait DocumentNode extends js.Object 8 | 9 | @js.native 10 | @JSImport("@apollo/client", "gql") 11 | object gql extends js.Object { 12 | def apply(query: String): DocumentNode = js.native 13 | } 14 | -------------------------------------------------------------------------------- /example/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # react-apollo-scalajs example 2 | This is a simple example demonstrating the features of `react-apollo-scalajs`, such as performing queries and mutations with variables. 3 | 4 | To launch the example, start SBT in the root directory and start the Webpack Dev Server 5 | 6 | ```bash 7 | sbt example/fastOptJS::startWebpackDevServer 8 | ``` 9 | 10 | Open up http://localhost:8080/webpack-dev-server and select the main bundle to test out the example! -------------------------------------------------------------------------------- /core/src/main/scala/com/apollographql/scalajs/link/package.scala: -------------------------------------------------------------------------------- 1 | package com.apollographql.scalajs 2 | 3 | import scala.scalajs.js 4 | import scala.scalajs.js.| 5 | 6 | package object link { 7 | type Subscriber[T] = js.Function1[SubscriptionObserver[T], Unit | js.Function0[Unit] | Subscription] 8 | 9 | type NextLink = js.Function1[Operation, Observable[FetchResult]] 10 | type RequestHandler = js.Function2[Operation, js.UndefOr[NextLink], Observable[FetchResult]] 11 | } 12 | -------------------------------------------------------------------------------- /.gitleaks.toml: -------------------------------------------------------------------------------- 1 | 2 | [[ rules ]] 3 | id = "high-entropy-base64" 4 | [ rules.allowlist ] 5 | commits = [ 6 | "5926b555e4866ebe8daf80408adbd5743ef2a2ab", 7 | 8 | ] 9 | 10 | [[ rules ]] 11 | id = "generic-api-key" 12 | [ rules.allowlist ] 13 | commits = [ 14 | # The same secret as was showing up above appeared in a differnet signature 15 | # This is a defunct API key for algolia docsearch 16 | "5926b555e4866ebe8daf80408adbd5743ef2a2ab" 17 | ] 18 | -------------------------------------------------------------------------------- /react/src/main/scala/com/apollographql/scalajs/react/ApolloProvider.scala: -------------------------------------------------------------------------------- 1 | package com.apollographql.scalajs.react 2 | 3 | import scala.scalajs.js 4 | import slinky.core.ExternalComponent 5 | import slinky.core.annotations.react 6 | import scala.scalajs.js.annotation.JSImport 7 | import com.apollographql.scalajs.ApolloClient 8 | 9 | @react object ApolloProvider extends ExternalComponent { 10 | case class Props(client: ApolloClient) 11 | 12 | @js.native 13 | @JSImport("@apollo/client", "ApolloProvider") 14 | val apolloProviderComponent: js.Object = js.native 15 | 16 | override val component = apolloProviderComponent 17 | } 18 | -------------------------------------------------------------------------------- /react/src/main/scala/com/apollographql/scalajs/react/ReactApollo.scala: -------------------------------------------------------------------------------- 1 | package com.apollographql.scalajs.react 2 | 3 | import slinky.core.facade.ReactElement 4 | 5 | import scala.scalajs.js 6 | import scala.scalajs.js.annotation.JSImport 7 | 8 | @js.native 9 | @JSImport("@apollo/client/react/components", JSImport.Namespace) 10 | object ReactApollo extends js.Object { 11 | val Query: js.Object = js.native 12 | val Mutation: js.Object = js.native 13 | } 14 | 15 | @js.native 16 | @JSImport("@apollo/client/react/ssr", JSImport.Namespace) 17 | object ReactApolloServer extends js.Object { 18 | def renderToStringWithData(component: ReactElement): js.Promise[String] = js.native 19 | } 20 | -------------------------------------------------------------------------------- /core/src/main/scala/com/apollographql/scalajs/link/HttpLink.scala: -------------------------------------------------------------------------------- 1 | package com.apollographql.scalajs.link 2 | 3 | import scala.scalajs.js 4 | import scala.scalajs.js.annotation.JSImport 5 | 6 | trait HttpLinkOptions extends js.Object { 7 | val uri: String 8 | val fetch: js.UndefOr[js.Object] = js.undefined 9 | } 10 | 11 | object HttpLinkOptions { 12 | def apply(uri: String, fetch: js.UndefOr[js.Object] = js.undefined): HttpLinkOptions = { 13 | js.Dynamic.literal( 14 | uri = uri, 15 | fetch = fetch 16 | ).asInstanceOf[HttpLinkOptions] 17 | } 18 | } 19 | 20 | @JSImport("@apollo/client", "HttpLink") 21 | @js.native 22 | class HttpLink(options: HttpLinkOptions) extends ApolloLink 23 | -------------------------------------------------------------------------------- /core/src/main/scala/com/apollographql/scalajs/OptionalResult.scala: -------------------------------------------------------------------------------- 1 | package com.apollographql.scalajs 2 | 3 | import scala.scalajs.js 4 | import scala.language.implicitConversions 5 | 6 | @js.native trait OptionalValue[T] extends js.Object 7 | object OptionalValue { 8 | implicit class OptionalValueExt[T](private val optionalValue: OptionalValue[T]) extends AnyVal { 9 | def toOption: Option[T] = Option(optionalValue.asInstanceOf[T]) 10 | } 11 | 12 | final def empty[T]: OptionalValue[T] = null.asInstanceOf[OptionalValue[T]] 13 | implicit def toOption[T](v: OptionalValue[T]): Option[T] = v.toOption 14 | implicit def fromValue[T](v: T): OptionalValue[T] = v.asInstanceOf[OptionalValue[T]] 15 | implicit def fromOption[T](v: Option[T])(implicit ev: Null <:< T): OptionalValue[T] = v.orNull.asInstanceOf[OptionalValue[T]] 16 | } 17 | -------------------------------------------------------------------------------- /docs/gatsby-config.js: -------------------------------------------------------------------------------- 1 | const themeOptions = require('gatsby-theme-apollo-docs/theme-options'); 2 | 3 | module.exports = { 4 | pathPrefix: '/docs/scalajs', 5 | plugins: [ 6 | { 7 | resolve: 'gatsby-theme-apollo-docs', 8 | options: { 9 | ...themeOptions, 10 | root: __dirname, 11 | subtitle: 'Apollo Scala.js', 12 | description: 'Use Apollo Client from your Scala.js applications', 13 | githubRepo: 'apollographql/apollo-scalajs', 14 | sidebarCategories: { 15 | null: [ 16 | 'index' 17 | ], 18 | Essentials: [ 19 | 'essentials/installation', 20 | 'essentials/getting-started', 21 | 'essentials/queries', 22 | 'essentials/mutations' 23 | ], 24 | Advanced: [ 25 | 'advanced/fragments' 26 | ] 27 | } 28 | } 29 | } 30 | ] 31 | }; 32 | -------------------------------------------------------------------------------- /tests/src/test/scala/com/apollographql/scalajs/ApolloLinkTest.scala: -------------------------------------------------------------------------------- 1 | package com.apollographql.scalajs 2 | 3 | import com.apollographql.scalajs.link.{ApolloLink, GraphQLRequest, HttpLink, HttpLinkOptions} 4 | import org.scalatest.{Assertion, AsyncFunSuite} 5 | 6 | import scala.concurrent.Promise 7 | import scala.scalajs.js 8 | 9 | class ApolloLinkTest extends AsyncFunSuite { 10 | js.Dynamic.global.window.fetch = UnfetchFetch 11 | 12 | implicit override def executionContext = 13 | scala.concurrent.ExecutionContext.Implicits.global 14 | 15 | test("Can perform a query with an HttpLink") { 16 | val resultPromise = Promise[Assertion] 17 | ApolloLink.execute( 18 | new HttpLink(HttpLinkOptions("https://graphql-currency-rates.glitch.me")), 19 | GraphQLRequest( 20 | gql( 21 | """{ 22 | | rates(currency: "USD") { 23 | | currency 24 | | } 25 | |}""".stripMargin 26 | ) 27 | ) 28 | ).forEach { res => 29 | resultPromise.success(assert(true)) 30 | } 31 | 32 | resultPromise.future 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /core/src/main/scala/com/apollographql/scalajs/link/Observable.scala: -------------------------------------------------------------------------------- 1 | package com.apollographql.scalajs.link 2 | 3 | import scala.scalajs.js 4 | import scala.scalajs.js.annotation.JSImport 5 | 6 | @js.native 7 | trait SubscriptionObserver[T] extends js.Object { 8 | var closed: Boolean = js.native 9 | def next(value: T): Unit = js.native 10 | def error(errorValue: js.Any): Unit = js.native 11 | def complete(): Unit = js.native 12 | } 13 | 14 | @js.native 15 | trait Subscription extends js.Object { 16 | var closed: Boolean = js.native 17 | def unsubscribe(): Unit = js.native 18 | } 19 | 20 | @JSImport("apollo-link", "Observable") 21 | @js.native 22 | class Observable[T](subscriber: Subscriber[T]) extends js.Object { 23 | def subscribe(observerOrNext: js.Function1[T, Unit], 24 | error: js.UndefOr[js.Function1[T, Unit]] = js.undefined, 25 | complete: js.UndefOr[js.Function0[Unit]] = js.undefined): Subscription = js.native 26 | def subscribe(observer: SubscriptionObserver[T]): Subscription = js.native 27 | 28 | def forEach(fn: js.Function1[T, Unit]): js.Promise[Unit] = js.native 29 | } 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Meteor Development Group 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /example/src/main/scala/com/apollographql/scalajs/TodosView.scala: -------------------------------------------------------------------------------- 1 | package com.apollographql.scalajs 2 | 3 | import com.apollographql.scalajs.react.Query 4 | import slinky.core.StatelessComponent 5 | import slinky.core.annotations.react 6 | import slinky.core.facade.ReactElement 7 | import slinky.web.html._ 8 | import slinky.core.facade.Fragment 9 | import slinky.core.facade.Hooks._ 10 | import slinky.core.SyntheticEvent 11 | import org.scalajs.dom.{html, Event} 12 | 13 | @react class TodosView extends StatelessComponent { 14 | type Props = Unit 15 | 16 | def render(): ReactElement = { 17 | Fragment( 18 | h1("Todos"), 19 | Query(AllTodosQuery) { res => 20 | res.data.map { d => 21 | div( 22 | d.todos.toList.flatMap { todos => 23 | todos.toList.flatMap { todo => 24 | todo.map { todo => 25 | div(key := todo.id.toString)( 26 | h1(todo.typ), 27 | ) 28 | } 29 | } 30 | } 31 | ): ReactElement 32 | }.getOrElse(h1("loading!")) 33 | }, 34 | AddTodo() 35 | ) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /core/src/main/scala/com/apollographql/scalajs/cache/InMemoryCache.scala: -------------------------------------------------------------------------------- 1 | package com.apollographql.scalajs.cache 2 | 3 | import scala.scalajs.js 4 | import scala.scalajs.js.annotation.JSImport 5 | 6 | @js.native 7 | trait CacheResolverContext extends js.Object { 8 | def getCacheKey(value: js.Object): js.Object = js.native 9 | } 10 | 11 | trait InMemoryCacheConfig { 12 | val resultCaching: js.UndefOr[Boolean] 13 | val possibleTypes: js.UndefOr[js.Dynamic] 14 | val typePolicies: js.UndefOr[js.Dynamic] 15 | } 16 | 17 | object InMemoryCacheConfig { 18 | def apply( 19 | resultCaching: js.UndefOr[Boolean] = js.undefined, 20 | possibleTypes: js.UndefOr[js.Dynamic] = js.undefined, 21 | typePolicies: js.UndefOr[js.Dynamic] = js.undefined 22 | ): InMemoryCacheConfig = { 23 | js.Dynamic.literal( 24 | resultCaching = resultCaching, 25 | possibleTypes = possibleTypes, 26 | typePolicies = typePolicies 27 | ).asInstanceOf[InMemoryCacheConfig] 28 | } 29 | } 30 | 31 | @JSImport("@apollo/client/cache", "InMemoryCache") 32 | @js.native 33 | class InMemoryCache(options: js.UndefOr[InMemoryCacheConfig] = js.undefined) extends ApolloCache 34 | -------------------------------------------------------------------------------- /example/src/main/scala/com/apollographql/scalajs/Main.scala: -------------------------------------------------------------------------------- 1 | package com.apollographql.scalajs 2 | 3 | import com.apollographql.scalajs.react.ApolloProvider 4 | import slinky.web.ReactDOM 5 | import slinky.web.html._ 6 | import slinky.hot 7 | import org.scalajs.dom.{document, html} 8 | 9 | import scala.scalajs.js 10 | import scala.scalajs.js.annotation.JSExportTopLevel 11 | import scala.scalajs.LinkingInfo 12 | import com.apollographql.scalajs.cache.InMemoryCache 13 | 14 | object Main { 15 | @JSExportTopLevel("main") 16 | def main(): Unit = { 17 | if (LinkingInfo.developmentMode) { 18 | hot.initialize() 19 | } 20 | 21 | if (js.typeOf(js.Dynamic.global.window.reactContainer) == "undefined") { 22 | js.Dynamic.global.window.reactContainer = document.createElement("div") 23 | document.body.appendChild(js.Dynamic.global.window.reactContainer.asInstanceOf[html.Element]) 24 | } 25 | 26 | val client = new ApolloClient( 27 | ApolloClientOptions( 28 | uri = "https://graphql-todo-tracker.glitch.me", 29 | cache = new InMemoryCache() 30 | ) 31 | ) 32 | 33 | ReactDOM.render( 34 | ApolloProvider(client)( 35 | TodosView() 36 | ), 37 | js.Dynamic.global.window.reactContainer.asInstanceOf[html.Element] 38 | ) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /example/webpack-opt.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require("webpack"); 2 | var path = require("path"); 3 | 4 | var HtmlWebpackPlugin = require('html-webpack-plugin'); 5 | var CopyWebpackPlugin = require('copy-webpack-plugin'); 6 | 7 | module.exports = { 8 | "entry": { 9 | "react-apollo-scalajs-example-opt": [ path.resolve(__dirname, "./opt-launcher.js") ] 10 | }, 11 | "output": { 12 | "path": path.resolve(__dirname, "../../../../build"), 13 | "filename": "[name]-bundle.js" 14 | }, 15 | resolve: { 16 | alias: { 17 | "resources": path.resolve(__dirname, "../../../../src/main/resources") 18 | } 19 | }, 20 | module: { 21 | rules: [ 22 | { 23 | test: /\.css$/, 24 | use: [ 'style-loader', 'css-loader' ] 25 | }, 26 | // "file" loader for svg 27 | { 28 | test: /\.svg$/, 29 | use: [ 30 | { 31 | loader: 'file-loader', 32 | query: { 33 | name: 'static/media/[name].[hash:8].[ext]' 34 | } 35 | } 36 | ] 37 | } 38 | ] 39 | }, 40 | plugins: [ 41 | new CopyWebpackPlugin([ 42 | { from: path.resolve(__dirname, "../../../../public") } 43 | ]), 44 | new HtmlWebpackPlugin({ 45 | template: path.resolve(__dirname, "../../../../public/index.html") 46 | }), 47 | new webpack.DefinePlugin({ 48 | 'process.env': { 49 | NODE_ENV: JSON.stringify('production') 50 | } 51 | }), 52 | new webpack.optimize.UglifyJsPlugin() 53 | ] 54 | } 55 | -------------------------------------------------------------------------------- /example/webpack-fastopt.config.js: -------------------------------------------------------------------------------- 1 | var path = require("path"); 2 | var HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | var CopyWebpackPlugin = require('copy-webpack-plugin'); 4 | 5 | module.exports = { 6 | entry: { 7 | "react-apollo-scalajs-example-fastopt": ["./react-apollo-scalajs-example-fastopt-entrypoint.js"], 8 | "launcher": ["./hot-launcher.js"] 9 | }, 10 | output: { 11 | path: __dirname, 12 | filename: "[name]-library.js", 13 | library: "appLibrary", 14 | libraryTarget: "var" 15 | }, 16 | devtool: "source-map", 17 | resolve: { 18 | alias: { 19 | "resources": path.resolve(__dirname, "../../../../src/main/resources") 20 | } 21 | }, 22 | module: { 23 | rules: [ 24 | { 25 | test: /\.css$/, 26 | use: [ 'style-loader', 'css-loader' ] 27 | }, 28 | // "file" loader for svg 29 | { 30 | test: /\.svg$/, 31 | use: [ 32 | { 33 | loader: 'file-loader', 34 | query: { 35 | name: 'static/media/[name].[hash:8].[ext]' 36 | } 37 | } 38 | ] 39 | } 40 | ], 41 | noParse: (content) => { 42 | return content.endsWith("-fastopt.js"); 43 | } 44 | }, 45 | plugins: [ 46 | new CopyWebpackPlugin([ 47 | { from: path.resolve(__dirname, "../../../../public") } 48 | ]), 49 | new HtmlWebpackPlugin({ 50 | template: path.resolve(__dirname, "../../../../public/index-fastopt.html"), 51 | inject: false 52 | }) 53 | ] 54 | } 55 | -------------------------------------------------------------------------------- /docs/source/index.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Introduction 3 | description: '' 4 | --- 5 | 6 | Apollo Scala.js is a strongly-typed interface for using Apollo Client and React Apollo from Scala.js applications. With Apollo Scala.js, you can elegantly implement Scala components that pull data with GraphQL and use the data in a typesafe manner. Combined with Apollo CLI, you can write queries separately and automatically get static types to handle GraphQL data in your application. 7 | 8 | Apollo Scala.js offers both a low-level interface for direct control over how GraphQL results are handled and integration with React Apollo through Slinky for embedding GraphQL in React applications. Just like Apollo Client, Apollo Scala.js also supports React Native, so you can take GraphQL wherever you go. 9 | 10 | ## Examples 11 | For a simple application demonstrating how to perform queries and mutations with React Apollo in a Scala.js app, take a look at the [example](https://github.com/apollographql/react-apollo-scalajs/tree/master/example) folder. 12 | 13 | ## Additional Resources 14 | + If you're using the React API, take a look at the docs for [Slinky](https://slinky.shadaj.me), which Apollo Scala.js uses to provide access to the React Apollo's API. 15 | + [Apollo CLI](https://github.com/apollographql/apollo-cli) has built-in support for generating Scala types from GraphQL queries that are compatible with Apollo Scala.js 16 | + The official [Apollo Client docs](https://www.apollographql.com/docs/react/) provide full details on what you can do with Apollo Client, which Apollo Scala.js is built on 17 | -------------------------------------------------------------------------------- /example/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 22 | React App 23 | 24 | 25 | 28 |
29 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /example/src/main/scala/com/apollographql/scalajs/AddTodo.scala: -------------------------------------------------------------------------------- 1 | package com.apollographql.scalajs 2 | 3 | import scalajs.js 4 | import com.apollographql.scalajs.react.Query 5 | import slinky.core._ 6 | import slinky.core.annotations.react 7 | import slinky.core.facade._ 8 | import slinky.web.html._ 9 | import slinky.core.facade.Hooks._ 10 | import org.scalajs.dom.{html, Event} 11 | import com.apollographql.scalajs.react.Mutation 12 | import com.apollographql.scalajs.react.UpdateStrategy 13 | import com.apollographql.scalajs.react._ 14 | import com.apollographql.scalajs.react.HooksApi._ 15 | 16 | @react object AddTodo { 17 | type Props = Unit 18 | 19 | val component = FunctionalComponent[Props] { props => 20 | val (todoText, setTodoText) = useState("") 21 | 22 | val (todoMut, res) = { 23 | val hook = useMutation[AddTodoMutation.type]( 24 | AddTodoMutation.operation, 25 | MutationHookOptions[AddTodoMutation.type]( 26 | refetchQueries = js.Array("AllTodos".asInstanceOf[js.Dynamic]) 27 | ) 28 | ) 29 | 30 | ( 31 | (variables: AddTodoMutation.Variables) => { 32 | hook._1.apply(MutationHookOptions[AddTodoMutation.type](variables = variables)).toFuture 33 | }, 34 | hook._2 35 | ) 36 | } 37 | 38 | div( 39 | input(value := todoText, onChange := ((e) => setTodoText(e.target.value))), 40 | button( 41 | if (res.loading) "Adding" else "Add Todo", 42 | onClick := ((_) => { 43 | todoMut(AddTodoMutation.Variables(todoText)) 44 | setTodoText("") 45 | }), 46 | disabled := res.loading || todoText.trim.isEmpty) 47 | ) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /core/src/main/scala/com/apollographql/scalajs/link/ApolloLink.scala: -------------------------------------------------------------------------------- 1 | package com.apollographql.scalajs.link 2 | 3 | import com.apollographql.scalajs.DocumentNode 4 | 5 | import scala.scalajs.js 6 | import scala.scalajs.js.annotation.JSImport 7 | import scala.scalajs.js.| 8 | 9 | @js.native 10 | trait GraphQLRequest extends js.Object { 11 | val query: DocumentNode = js.native 12 | } 13 | 14 | object GraphQLRequest { 15 | def apply(query: DocumentNode): GraphQLRequest = { 16 | js.Dynamic.literal( 17 | query = query 18 | ).asInstanceOf[GraphQLRequest] 19 | } 20 | } 21 | 22 | @js.native 23 | trait FetchResult extends js.Object 24 | 25 | @js.native 26 | trait Operation extends js.Object { 27 | def setContext(context: js.Dictionary[js.Any]): js.Dictionary[js.Any] = js.native 28 | def getContext(): js.Dictionary[js.Any] = js.native 29 | def toKey(): String = js.native 30 | } 31 | 32 | @JSImport("@apollo/client", "ApolloLink") 33 | @js.native 34 | class ApolloLink(handler: js.UndefOr[js.Function2[Operation, js.UndefOr[NextLink], Observable[FetchResult]]] = js.undefined) extends js.Object { 35 | def concat(next: ApolloLink | RequestHandler): ApolloLink = js.native 36 | def request(operation: Operation, forward: js.UndefOr[NextLink]): Observable[FetchResult] = js.native 37 | } 38 | 39 | @JSImport("@apollo/client", "ApolloLink") 40 | @js.native 41 | object ApolloLink extends js.Object { 42 | def empty(): ApolloLink = js.native 43 | def from(links: js.Array[ApolloLink]): ApolloLink = js.native 44 | def concat(first: ApolloLink, second: ApolloLink): ApolloLink = js.native 45 | def execute(link: ApolloLink, operation: GraphQLRequest): Observable[FetchResult] = js.native 46 | } 47 | -------------------------------------------------------------------------------- /tests/build.sbt: -------------------------------------------------------------------------------- 1 | enablePlugins(ScalaJSBundlerPlugin) 2 | 3 | libraryDependencies += "org.scalatest" %%% "scalatest" % "3.1.1" % Test 4 | libraryDependencies += "me.shadaj" %%% "slinky-web" % "0.6.6" % Test 5 | 6 | npmDependencies in Compile += "@apollo/client" -> "3.2.4" 7 | 8 | npmDevDependencies in Compile += "apollo" -> "2.31.0" 9 | 10 | Test / npmDependencies += "react" -> "16.8.4" 11 | Test / npmDependencies += "react-dom" -> "16.8.4" 12 | 13 | Compile / npmDependencies += "unfetch" -> "2.1.1" 14 | 15 | Test / requireJsDomEnv := true 16 | 17 | val namespace = "com.apollographql.scalajs" 18 | 19 | (Test / sourceGenerators) += Def.task { 20 | import scala.sys.process._ 21 | 22 | val out = (Test / sourceManaged).value 23 | 24 | out.mkdirs() 25 | 26 | val graphQLScala = out / "queries.scala" 27 | 28 | Seq( 29 | "apollo", "client:codegen", 30 | "--config", "tests/src/test/graphql/queries/apollo.config.js", 31 | "--target", "scala", 32 | "--namespace", namespace, graphQLScala.getAbsolutePath 33 | ).! 34 | 35 | Seq(graphQLScala) 36 | } 37 | 38 | (Test / sourceGenerators) += Def.task { 39 | import scala.sys.process._ 40 | 41 | val out = (Test / sourceManaged).value 42 | 43 | out.mkdirs() 44 | 45 | val graphQLScala = out / "mutations.scala" 46 | 47 | Seq( 48 | "apollo", "client:codegen", 49 | "--config", "tests/src/test/graphql/mutations/apollo.config.js", 50 | "--target", "scala", 51 | "--namespace", namespace, graphQLScala.getAbsolutePath 52 | ).! 53 | 54 | Seq(graphQLScala) 55 | } 56 | 57 | Test / watchSources ++= ((Test / sourceDirectory).value / "graphql" ** "*.graphql").get 58 | 59 | scalaJSLinkerConfig ~= { _.withESFeatures(_.withUseECMAScript2015(false)) } 60 | -------------------------------------------------------------------------------- /example/public/index-fastopt.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 22 | React App 23 | 24 | 25 | 28 |
29 | 36 | 37 | 38 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /example/build.sbt: -------------------------------------------------------------------------------- 1 | enablePlugins(ScalaJSBundlerPlugin) 2 | 3 | name := "react-apollo-scalajs-example" 4 | 5 | libraryDependencies += "me.shadaj" %%% "slinky-web" % "0.6.6" 6 | libraryDependencies += "me.shadaj" %%% "slinky-hot" % "0.6.6" 7 | 8 | npmDependencies in Compile += "react" -> "16.14.0" 9 | npmDependencies in Compile += "react-dom" -> "16.14.0" 10 | npmDependencies in Compile += "react-proxy" -> "1.1.8" 11 | 12 | npmDependencies in Compile += "@apollo/client" -> "3.2.4" 13 | 14 | npmDevDependencies in Compile += "apollo" -> "2.31.0" 15 | npmDevDependencies in Compile += "file-loader" -> "1.1.5" 16 | npmDevDependencies in Compile += "style-loader" -> "0.19.0" 17 | npmDevDependencies in Compile += "css-loader" -> "0.28.7" 18 | npmDevDependencies in Compile += "html-webpack-plugin" -> "4.3.0" 19 | npmDevDependencies in Compile += "copy-webpack-plugin" -> "5.1.1" 20 | 21 | webpackConfigFile in fastOptJS := Some(baseDirectory.value / "webpack-fastopt.config.js") 22 | webpackConfigFile in fullOptJS := Some(baseDirectory.value / "webpack-opt.config.js") 23 | 24 | webpackDevServerExtraArgs := Seq("--inline", "--hot") 25 | 26 | webpackBundlingMode in fastOptJS := BundlingMode.LibraryOnly() 27 | 28 | 29 | val namespace = "com.apollographql.scalajs" 30 | 31 | (sourceGenerators in Compile) += Def.task { 32 | import scala.sys.process._ 33 | 34 | val out = (sourceManaged in Compile).value 35 | 36 | out.mkdirs() 37 | 38 | val graphQLScala = out / "graphql.scala" 39 | 40 | Seq( 41 | "apollo", "client:codegen", s"--queries=${((sourceDirectory in Compile).value / "graphql").getAbsolutePath}/*.graphql", 42 | s"--localSchemaFile=${(baseDirectory.value / "schema.json").getAbsolutePath}", 43 | "--target=scala", 44 | s"--namespace=$namespace", 45 | graphQLScala.getAbsolutePath 46 | ).! 47 | 48 | Seq(graphQLScala) 49 | } 50 | 51 | watchSources ++= ((sourceDirectory in Compile).value / "graphql" ** "*.graphql").get 52 | -------------------------------------------------------------------------------- /docs/source/essentials/installation.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Installation 3 | order: 0 4 | --- 5 | 6 | Add the dependency to your `build.sbt` 7 | 8 | ```scala 9 | resolvers += "Apollo Bintray" at "https://dl.bintray.com/apollographql/maven/" 10 | libraryDependencies += "com.apollographql" %%% "apollo-scalajs-core" % "0.8.0" 11 | libraryDependencies += "com.apollographql" %%% "apollo-scalajs-react" % "0.8.0" 12 | ``` 13 | 14 | If you are using the React API, you may want to add other Slinky modules such as the web and hot-reloading module, so check out the instructions at https://slinky.shadaj.me/docs/installation/. 15 | 16 | ## NPM Dependencies 17 | If you are using [Scala.js Bundler](https://scalacenter.github.io/scalajs-bundler/), you will need to add the Apollo Client dependencies to your `build.sbt`. 18 | 19 | ```scala 20 | npmDependencies in Compile += "@apollo/client" -> "3.2.4" 21 | ``` 22 | 23 | ## Apollo CLI 24 | To set up the code generator, which generates static types based on your GraphQL queries, first install the Apollo CLI. 25 | 26 | ```bash 27 | npm install -g apollo 28 | ``` 29 | 30 | Then, you can configure SBT to automatically run it and add the resulting Scala sources to your build. 31 | 32 | ```scala 33 | val namespace = "com.my.package.graphql" 34 | 35 | (sourceGenerators in Compile) += Def.task { 36 | import scala.sys.process._ 37 | 38 | val out = (sourceManaged in Compile).value 39 | 40 | out.mkdirs() 41 | 42 | Seq( 43 | "apollo", "client:codegen", 44 | "--config", "apollo.config.js", 45 | "--target", "scala", 46 | "--namespace", namespace, graphQLScala.getAbsolutePath 47 | ).! 48 | 49 | Seq(out / "graphql.scala") 50 | } 51 | 52 | watchSources ++= ((sourceDirectory in Compile).value / "graphql" ** "*.graphql").get 53 | ``` 54 | 55 | With the accompanying [`apollo.config.js`](https://www.apollographql.com/docs/references/apollo-config.html): 56 | 57 | ```js 58 | module.exports = { 59 | client: { 60 | service: { 61 | name: "test-schema", 62 | localSchemaFile: "schema.json" 63 | }, 64 | includes: [ "src/main/graphql/*.graphql" ] 65 | } 66 | }; 67 | ``` 68 | -------------------------------------------------------------------------------- /tests/src/test/scala/com/apollographql/scalajs/react/ReactApolloTest.scala: -------------------------------------------------------------------------------- 1 | package com.apollographql.scalajs.react 2 | 3 | import com.apollographql.scalajs.cache.InMemoryCache 4 | import com.apollographql.scalajs.link.{HttpLink, HttpLinkOptions} 5 | import com.apollographql.scalajs.{ApolloClient, CurrencyRatesQuery, UnfetchFetch} 6 | import org.scalajs.dom.document 7 | import org.scalatest.funsuite.AsyncFunSuite 8 | import org.scalatest.Assertion 9 | import slinky.web.ReactDOM 10 | import slinky.web.html.div 11 | 12 | import scala.concurrent.Promise 13 | import scala.scalajs.js 14 | import scala.scalajs.js.JSON 15 | import com.apollographql.scalajs.ApolloClientOptions 16 | 17 | class ReactApolloTest extends AsyncFunSuite { 18 | js.Dynamic.global.window.fetch = UnfetchFetch 19 | 20 | implicit override def executionContext = 21 | scala.concurrent.ExecutionContext.Implicits.global 22 | 23 | test("Can mount an ApolloProvider with a client instance") { 24 | assert(!js.isUndefined( 25 | ReactDOM.render( 26 | ApolloProvider( 27 | client = new ApolloClient( 28 | ApolloClientOptions( 29 | uri = "https://graphql-currency-rates.glitch.me", 30 | cache = new InMemoryCache() 31 | ) 32 | ) 33 | )( 34 | div() 35 | ), 36 | document.createElement("div") 37 | ) 38 | )) 39 | } 40 | 41 | test("Can server-side render data to string based on a query") { 42 | val link = new HttpLink(options = HttpLinkOptions(uri = "https://graphql-currency-rates.glitch.me")) 43 | val cache = new InMemoryCache() 44 | val client = new ApolloClient(options = ApolloClientOptions(ssrMode = true, link = link, cache = cache)) 45 | 46 | ReactApolloServer.renderToStringWithData( 47 | ApolloProvider(ApolloProvider.Props(client = client))( 48 | Query( 49 | CurrencyRatesQuery, 50 | CurrencyRatesQuery.Variables("USD") 51 | ) { d => 52 | if (d.data.isDefined) { 53 | div(d.data.get.rates.get.head.get.currency.get) 54 | } else "" 55 | } 56 | ) 57 | ).toFuture.map { html => 58 | assert(html == """
AED
""") 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /tests/src/test/scala/com/apollographql/scalajs/react/UseMutationTest.scala: -------------------------------------------------------------------------------- 1 | package com.apollographql.scalajs.react 2 | 3 | import com.apollographql.scalajs._ 4 | import org.scalajs.dom.document 5 | import org.scalatest.funsuite.AsyncFunSuite 6 | import org.scalatest.Assertion 7 | import slinky.web.ReactDOM 8 | import slinky.web.html.div 9 | import slinky.core.annotations.react 10 | import slinky.core.facade.Hooks._ 11 | 12 | import scala.concurrent.Promise 13 | import scala.scalajs.js 14 | import com.apollographql.scalajs.cache.InMemoryCache 15 | import com.apollographql.scalajs.react.HooksApi._ 16 | import slinky.core.FunctionalComponent 17 | 18 | class UseMutationTest extends AsyncFunSuite { 19 | js.Dynamic.global.window.fetch = UnfetchFetch 20 | 21 | implicit override def executionContext = 22 | scala.concurrent.ExecutionContext.Implicits.global 23 | 24 | test("Can make simple mutation") { 25 | val gotDataPromise = Promise[Assertion] 26 | 27 | @react object UseMutationTestComponent { 28 | case class Props() 29 | 30 | val component = FunctionalComponent[Props] { _ => 31 | val (mut, result) = { 32 | val hook = useMutation[AddTodoMutation.type]( 33 | AddTodoMutation.operation 34 | ) 35 | 36 | ( 37 | (variables: AddTodoMutation.Variables) => { 38 | hook._1.apply(MutationHookOptions[AddTodoMutation.type](variables = variables)).toFuture 39 | }, 40 | hook._2 41 | ) 42 | } 43 | 44 | mut(AddTodoMutation.Variables(typ = "test")) 45 | 46 | useEffect(() => { 47 | if (result.data.isDefined) { 48 | gotDataPromise.success(assert(result.data.get.addTodo.nonEmpty)) 49 | } 50 | }, Seq(result)) 51 | 52 | div() 53 | } 54 | } 55 | 56 | ReactDOM.render( 57 | ApolloProvider( 58 | client = new ApolloClient( 59 | ApolloClientOptions( 60 | uri = "https://graphql-todo-tracker.glitch.me", 61 | cache = new InMemoryCache() 62 | ) 63 | ) 64 | )( 65 | UseMutationTestComponent() 66 | ), 67 | document.createElement("div") 68 | ) 69 | 70 | gotDataPromise.future 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /docs/source/advanced/fragments.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Using Fragments 3 | description: '' 4 | --- 5 | 6 | Fragments are a powerful tool in statically-typed languages, allowing you to share a common model type across multiple queries in your application. Apollo Scala.js handles fragment types through implicit conversions between the original data type generated for a specific query and the fragment type that contains a specific subset of fields. 7 | 8 | ## Generating types for fragments 9 | The Apollo CLI handles fragments just like any other GraphQL definition. Because all your GraphQL files are merged before code generation runs, fragments can be defined in their own file and referenced from other queries in your project. 10 | 11 | Let's take a look at the code generation output for a simple query that references a fragment: 12 | 13 | ```graphql 14 | fragment CurrencyFragment on ExchangeRate { 15 | currency 16 | } 17 | 18 | query USDRatesWithFragment { 19 | rates(currency: "USD") { 20 | ...CurrencyFragment 21 | } 22 | } 23 | ``` 24 | 25 | This will result in the following type definitions, with a query-specific type `UsdRatesWithFragmentQuery.Data.Rate` but also a general fragment type `CurrencyFragment`: 26 | 27 | ```scala 28 | object UsdRatesWithFragmentQuery extends com.apollographql.scalajs.GraphQLQuery { 29 | // ... 30 | 31 | case class Data(rates: Option[Seq[Option[Data.Rate]]]) { 32 | } 33 | 34 | object Data { 35 | val possibleTypes = scala.collection.Set("Query") 36 | 37 | case class Rate(currency: Option[String]) { 38 | } 39 | 40 | object Rate { 41 | val possibleTypes = scala.collection.Set("ExchangeRate") 42 | implicit def toCurrencyFragment(a: Rate): CurrencyFragment = CurrencyFragment(a.currency) 43 | } 44 | } 45 | } 46 | 47 | case class CurrencyFragment(currency: Option[String]) { 48 | } 49 | ``` 50 | 51 | ## Applying fragments in application code 52 | 53 | If we have components of our application that will be recieving currency data from different queries, we could then use the `CurrencyFragment` type to recieve that data. 54 | 55 | ```scala 56 | def myGeneralCurrencyCode(currencyData: CurrencyFragment) = 57 | currencyData.currency.getOrElse("Currency not known") 58 | 59 | // ... 60 | 61 | Query(UsdRatesWithFragmentQuery) { d => 62 | if (d.data.isDefined) { 63 | div({ 64 | for { 65 | rates <- d.data.get.rates // Seq[Option[Data.Rate]] 66 | maybeRate <- rates // Option[Data.Rate] 67 | rate <- maybeRate // Data.Rate 68 | } yield div(myGeneralCurrencyCode(rate /* converted to CurrencyFragment here */)) 69 | }) 70 | gotDataPromise.success(assert(d.data.get.rates.exists(_.nonEmpty))) 71 | } else { 72 | div("Loading!") 73 | } 74 | } 75 | ``` 76 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Archival 2 | This repo was archived by the Apollo Security team on 2023-05-26 3 | 4 | 5 | # Apollo Scala.js 6 | _use Apollo Client and React Apollo from your Scala.js apps!_ 7 | 8 | ## View the [docs](https://www.apollographql.com/docs/scalajs) 9 | 10 | ## Installation 11 | Add the dependency to your build.sbt 12 | ```scala 13 | resolvers += "Apollo Bintray" at "https://dl.bintray.com/apollographql/maven/" 14 | libraryDependencies += "com.apollographql" %%% "apollo-scalajs-core" % "0.8.0" // if you are writing a vanilla Scala.js app 15 | libraryDependencies += "com.apollographql" %%% "apollo-scalajs-react" % "0.8.0" // if you are writing a React Scala.js app 16 | ``` 17 | 18 | You probably also want to add other Slinky modules such as the web module, so check out the instructions at https://slinky.dev 19 | 20 | To set up the code generator, which uses the Apollo CLI to generate static types for your GraphQL queries, first install `apollo` 21 | ```npm i -g apollo``` 22 | 23 | and then set up SBT to automatically run it 24 | 25 | ```scala 26 | val namespace = "your package here" 27 | 28 | (sourceGenerators in Compile) += Def.task { 29 | import scala.sys.process._ 30 | 31 | val out = (sourceManaged in Compile).value 32 | 33 | out.mkdirs() 34 | 35 | Seq( 36 | "apollo", "client:codegen", s"--queries=${((sourceDirectory in Compile).value / "graphql").getAbsolutePath}/*.graphql", 37 | s"--localSchemaFile=${(baseDirectory.value / "schema.json").getAbsolutePath}", 38 | "--target=scala", 39 | s"--namespace=$namespace", 40 | graphQLScala.getAbsolutePath 41 | ).! 42 | 43 | Seq(out / "graphql.scala") 44 | } 45 | ``` 46 | 47 | ## Usage 48 | Once you have placed some GraphQL queries in `src/main/graphql`, you can use the generated types to create GraphQL-powered React components! 49 | 50 | To integrate GraphQL data in your React tree, simply use the `Query` component to render subtrees based on a specified query. 51 | 52 | ```scala 53 | Query(UsdRatesQuery) { queryStatus => 54 | if (queryStatus.loading) "Loading..." 55 | else if (queryStatus.error) s"Error! ${queryStatus.error.message}" 56 | else { 57 | div(queryStatus.data.get.rates.mkString(", ")) 58 | } 59 | } 60 | ``` 61 | 62 | For more on implementing advanced components, follow the instructions at https://slinky.shadaj.me 63 | 64 | Next, to initialize Apollo Client in your application, first create an instance of the client (here using Apollo Boost) 65 | 66 | ```scala 67 | val client = ApolloBoostClient( 68 | uri = "https://graphql-currency-rates.glitch.me" 69 | ) 70 | ``` 71 | 72 | Finally, wrap your React component tree inside an `ApolloProvider` component, which all components inside to perform GraphQL queries with the specified client 73 | 74 | ```scala 75 | ApolloProvider(client)( 76 | ... 77 | ) 78 | ``` 79 | -------------------------------------------------------------------------------- /core/src/main/scala/com/apollographql/scalajs/ApolloClient.scala: -------------------------------------------------------------------------------- 1 | package com.apollographql.scalajs 2 | 3 | import scala.concurrent.{ExecutionContext, Future} 4 | import scala.scalajs.js 5 | import scala.scalajs.js.annotation.JSImport 6 | 7 | @js.native 8 | trait ApolloClientOptions extends js.Object { 9 | val uri: js.UndefOr[String] = js.native 10 | val link: js.UndefOr[js.Object] = js.native 11 | val cache: js.UndefOr[js.Object] = js.native 12 | val name: js.UndefOr[String] = js.native 13 | val version: js.UndefOr[String] = js.native 14 | val ssrMode: js.UndefOr[Boolean] = js.native 15 | val ssrForceFetchDelay: js.UndefOr[Int] = js.native 16 | val connectToDevTools: js.UndefOr[Boolean] = js.native 17 | val queryDeduplication: js.UndefOr[Boolean] = js.native 18 | val defaultOptions: js.UndefOr[js.Dynamic] = js.native 19 | } 20 | 21 | object ApolloClientOptions { 22 | def apply( 23 | uri: js.UndefOr[String] = js.undefined, 24 | link: js.UndefOr[js.Object] = js.undefined, 25 | cache: js.UndefOr[js.Object] = js.undefined, 26 | name: js.UndefOr[String] = js.undefined, 27 | version: js.UndefOr[String] = js.undefined, 28 | ssrMode: js.UndefOr[Boolean] = js.undefined, 29 | ssrForceFetchDelay: js.UndefOr[Int] = js.undefined, 30 | connectToDevTools: js.UndefOr[Boolean] = js.undefined, 31 | queryDeduplication: js.UndefOr[Boolean] = js.undefined, 32 | defaultOptions: js.UndefOr[js.Dynamic] = js.undefined 33 | ): ApolloClientOptions = { 34 | js.Dynamic.literal( 35 | uri = uri, 36 | link = link, 37 | cache = cache, 38 | name = name, 39 | version = version, 40 | ssrMode = ssrMode, 41 | ssrForceFetchDelay = ssrForceFetchDelay, 42 | connectToDevTools = connectToDevTools, 43 | queryDeduplication = queryDeduplication, 44 | defaultOptions = defaultOptions 45 | ).asInstanceOf[ApolloClientOptions] 46 | } 47 | } 48 | 49 | @JSImport("@apollo/client", "ApolloClient") 50 | @js.native 51 | class ApolloClient(options: ApolloClientOptions) extends js.Object { 52 | def query[D <: js.Object, V <: js.Any](options: QueryOptions[V]): js.Promise[ApolloQueryResult[D]] = js.native 53 | } 54 | 55 | @js.native 56 | trait QueryOptions[V] extends js.Object { 57 | val query: DocumentNode = js.native 58 | val variables: js.UndefOr[V] = js.native 59 | } 60 | 61 | object QueryOptions { 62 | def apply[V]( 63 | query: DocumentNode, 64 | variables: js.UndefOr[V] = js.undefined 65 | ) = { 66 | js.Dynamic.literal( 67 | query = query, 68 | variables = variables.asInstanceOf[js.Object] 69 | ).asInstanceOf[QueryOptions[V]] 70 | } 71 | } 72 | 73 | @js.native 74 | trait ApolloQueryResult[D] extends js.Object { 75 | val data: js.UndefOr[D] 76 | val errors: js.UndefOr[js.Array[js.Dynamic]] 77 | val error: js.UndefOr[js.Dynamic] 78 | val loading: Boolean 79 | val networkStatus: Int 80 | val partial: js.UndefOr[Boolean] 81 | } 82 | -------------------------------------------------------------------------------- /tests/src/test/scala/com/apollographql/scalajs/react/UseQueryTest.scala: -------------------------------------------------------------------------------- 1 | package com.apollographql.scalajs.react 2 | 3 | import com.apollographql.scalajs._ 4 | import org.scalajs.dom.document 5 | import org.scalatest.funsuite.AsyncFunSuite 6 | import org.scalatest.Assertion 7 | import slinky.web.ReactDOM 8 | import slinky.web.html.div 9 | import slinky.core.annotations.react 10 | import slinky.core.facade.Hooks._ 11 | 12 | import scala.concurrent.Promise 13 | import scala.scalajs.js 14 | import com.apollographql.scalajs.cache.InMemoryCache 15 | import com.apollographql.scalajs.react.HooksApi._ 16 | import slinky.core.FunctionalComponent 17 | 18 | class UseQueryTest extends AsyncFunSuite { 19 | js.Dynamic.global.window.fetch = UnfetchFetch 20 | 21 | implicit override def executionContext = 22 | scala.concurrent.ExecutionContext.Implicits.global 23 | 24 | test("Can make a simple query") { 25 | val gotDataPromise = Promise[Assertion] 26 | 27 | @react object UseQueryTestComponent { 28 | case class Props() 29 | 30 | val component = FunctionalComponent[Props] { _ => 31 | val QueryResult(data, _, _) = useQuery[UsdRatesQuery.type](UsdRatesQuery.operation) 32 | 33 | useEffect(() => { 34 | if (data.isDefined) { 35 | gotDataPromise.success(assert(data.get.rates.nonEmpty)) 36 | } 37 | }, Seq(data)) 38 | 39 | div() 40 | } 41 | } 42 | 43 | ReactDOM.render( 44 | ApolloProvider( 45 | client = new ApolloClient( 46 | ApolloClientOptions( 47 | uri = "https://graphql-currency-rates.glitch.me", 48 | cache = new InMemoryCache() 49 | ) 50 | ) 51 | )( 52 | UseQueryTestComponent() 53 | ), 54 | document.createElement("div") 55 | ) 56 | 57 | gotDataPromise.future 58 | } 59 | 60 | test("Can make a Query with variables") { 61 | val gotDataPromise = Promise[Assertion] 62 | 63 | @react object UseQueryTestComponent { 64 | case class Props() 65 | 66 | val component = FunctionalComponent[Props] { _ => 67 | val QueryResult(data, _, _) = useQuery[CurrencyRatesQuery.type]( 68 | CurrencyRatesQuery.operation, 69 | QueryHookOptions[CurrencyRatesQuery.type](variables = CurrencyRatesQuery.Variables("USD")) 70 | ) 71 | 72 | useEffect(() => { 73 | if (data.isDefined) { 74 | gotDataPromise.success(assert(data.get.rates.nonEmpty)) 75 | } 76 | }, Seq(data)) 77 | 78 | div() 79 | } 80 | } 81 | 82 | ReactDOM.render( 83 | ApolloProvider( 84 | client = new ApolloClient( 85 | ApolloClientOptions( 86 | uri = "https://graphql-currency-rates.glitch.me", 87 | cache = new InMemoryCache() 88 | ) 89 | ) 90 | )( 91 | UseQueryTestComponent() 92 | ), 93 | document.createElement("div") 94 | ) 95 | 96 | gotDataPromise.future 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /tests/src/test/scala/com/apollographql/scalajs/ApolloClientTest.scala: -------------------------------------------------------------------------------- 1 | package com.apollographql.scalajs 2 | 3 | import com.apollographql.scalajs.cache.InMemoryCache 4 | import com.apollographql.scalajs.link.{HttpLink, HttpLinkOptions} 5 | import org.scalatest.AsyncFunSuite 6 | 7 | import scala.scalajs.js 8 | import scala.scalajs.js.annotation.JSImport 9 | 10 | @js.native 11 | @JSImport("unfetch", JSImport.Default) 12 | object UnfetchFetch extends js.Object 13 | 14 | class ApolloClientTest extends AsyncFunSuite { 15 | js.Dynamic.global.window.fetch = UnfetchFetch 16 | 17 | implicit override def executionContext = 18 | scala.concurrent.ExecutionContext.Implicits.global 19 | 20 | test("Can create an instance of Apollo Client") { 21 | assert(!js.isUndefined( 22 | new ApolloClient( 23 | ApolloClientOptions( 24 | uri = "https://graphql-currency-rates.glitch.me", 25 | cache = new InMemoryCache() 26 | ) 27 | ) 28 | )) 29 | } 30 | 31 | test("Can perform a simple query and get the results") { 32 | new ApolloClient( 33 | ApolloClientOptions( 34 | uri = "https://graphql-currency-rates.glitch.me", 35 | cache = new InMemoryCache() 36 | ) 37 | ).query[js.Object, js.Object]( 38 | QueryOptions( 39 | gql( 40 | """{ 41 | | rates(currency: "USD") { 42 | | currency 43 | | } 44 | |}""".stripMargin 45 | ) 46 | ) 47 | ).toFuture.map { r => 48 | assert(r.data.asInstanceOf[js.Dynamic] 49 | .rates.asInstanceOf[js.Array[js.Object]].length > 0) 50 | } 51 | } 52 | 53 | test("Can perform a query with variables and get the results") { 54 | new ApolloClient( 55 | ApolloClientOptions( 56 | uri = "https://graphql-currency-rates.glitch.me", 57 | cache = new InMemoryCache() 58 | ) 59 | ).query[js.Object, js.Object]( 60 | QueryOptions( 61 | gql( 62 | """query GetRates($cur: String!) { 63 | | rates(currency: $cur) { 64 | | currency 65 | | } 66 | |}""".stripMargin 67 | ), 68 | variables = js.Dynamic.literal( 69 | cur = "USD" 70 | ) 71 | ) 72 | ).toFuture.map { r => 73 | assert(r.data.asInstanceOf[js.Dynamic] 74 | .rates.asInstanceOf[js.Array[js.Object]].length > 0) 75 | } 76 | } 77 | 78 | test("Can perform a query with typed variables and response and get the results") { 79 | trait Variables extends js.Object { 80 | val cur: String 81 | } 82 | 83 | trait Rate extends js.Object{ 84 | val currency: String 85 | } 86 | 87 | trait QueryResult extends js.Object { 88 | val rates: js.Array[Rate] 89 | } 90 | 91 | new ApolloClient( 92 | ApolloClientOptions( 93 | uri = "https://graphql-currency-rates.glitch.me", 94 | cache = new InMemoryCache() 95 | ) 96 | ).query[QueryResult, Variables]( 97 | QueryOptions( 98 | query = gql( 99 | """query GetRates($cur: String!) { 100 | | rates(currency: $cur) { 101 | | currency 102 | | } 103 | |}""".stripMargin 104 | ), 105 | variables = new Variables { 106 | override val cur = "USD" 107 | } 108 | ) 109 | ).toFuture.map { r => 110 | assert(r.data.get.rates.nonEmpty) 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /docs/source/essentials/getting-started.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Getting Started 3 | order: 1 4 | --- 5 | 6 | Now that you have your build set up, let's start writing our first GraphQL application! 7 | 8 | ## Create a client 9 | First, we need to create an instance of Apollo Client, which will handle sending our GraphQL queries and caching the results (among many other features). 10 | 11 | Inside your main function, we can use Apollo Boost to set up a client with a default HTTP link and in-memory cache. 12 | ```scala 13 | import com.apollographql.scalajs._ 14 | 15 | def main(): Unit = { // called when the app launches 16 | val client = ApolloBoostClient( 17 | uri = "https://graphql-currency-rates.glitch.me" 18 | ) 19 | } 20 | ``` 21 | 22 | That’s it! Now your client is ready to start fetching data. Before we hook up Apollo Scala.js to React, let’s try sending a query with plain Scala first with the `client.query` function. 23 | 24 | ```scala 25 | client.query[js.Object](gql( // gql is a member of the com.apollographql.scalajs package that parses your query 26 | """{ 27 | | rates(currency: "USD") { 28 | | currency 29 | | } 30 | |}""".stripMargin 31 | ))).foreach { result => 32 | println(result.data) 33 | } 34 | ``` 35 | 36 | If you open up the console, you should see the result of your GraphQL query. Now, let's learn how to connect Apollo Scala.js to React so we can start building query components with React Apollo. 37 | 38 | ## Connecting your client to React 39 | To connect Apollo Client to React, you will need to use the `ApolloProvider` component, which can be found in the `com.apollographql.scalajs.react` package. For details on what the `ApolloProvider` component does, see the [React Apollo Docs](https://www.apollographql.com/docs/react/essentials/get-started/#creating-provider). We suggest placing your `ApolloProvider` somewhere high in your app, above any places where you need to access GraphQL data. 40 | 41 | ```scala 42 | import com.apollographql.scalajs.react.ApolloProvider 43 | 44 | ReactDOM.render( 45 | ApolloProvider(client)( 46 | ... 47 | ), 48 | rootElement 49 | ) 50 | ``` 51 | 52 | ## Request data 53 | Now that you have an `ApolloProvider` mounted, it's time to start performing queries with the `Query` component! 54 | 55 | First, specify the type of your query result, then pass in your `gql`-parsed GraphQL query and a function that determines what to render as a curried parameter. To keep things simple, let's leave our component untyped by specifying `js.Object` as the return type. 56 | 57 | The curried parameter is a function that takes the current query resolution result and returns a React tree. Because we don't always have the data requested, the `data` property on the result object is an `Option`. 58 | 59 | ```scala 60 | import com.apollographql.scalajs.react.Query 61 | 62 | Query[js.Object](gql( 63 | """{ 64 | | rates(currency: "USD") { 65 | | currency 66 | | } 67 | |}""".stripMargin 68 | ))) { result => 69 | if (result.loading) { 70 | h1("Loading!") 71 | } else { 72 | div(result.data.get.toString) 73 | } 74 | } 75 | ``` 76 | 77 | If you include this component in your React tree, you should see your GraphQL query result rendered to your screen. 78 | 79 | ## Next steps 80 | Now that you’ve learned how to fetch data with Apollo Scala.js, you’re ready to dive deeper into creating more complex queries and mutations. After this section, we recommend moving onto: 81 | + [Queries](/essentials/queries/): Learn how to fetch queries with arguments and handle the results with static types 82 | + [Mutations](/essentials/mutations/): Learn how to update data with mutations and type your parameters and results -------------------------------------------------------------------------------- /docs/source/essentials/mutations.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Mutations 3 | order: 3 4 | --- 5 | 6 | Now that you know how to get GraphQL data, it's time to update that data with GraphQL mutations! 7 | 8 | ## The Mutation component 9 | Using the Mutation component from Scala.js is just like using it with the original JavaScript API. Simply pass in a parsed GraphQL mutation object (from `gql`) and a function that decides when to perform the mutaiton what to render given the state of the mutation. Because the Mutation component tracks all steps of performing a mutation, you can easily render loading and error states by reading properties of the mutation status given to you. 10 | 11 | When you use the Mutation component, Apollo Client also keeps your local cache in sync with the server by updating the cache with the mutation response. 12 | 13 | Let's see this in action! 14 | 15 | ```scala 16 | import com.apollographql.scalajs.react.Mutation 17 | 18 | Mutation[js.Object, js.Object]( 19 | gql( 20 | """mutation addTodo($type: String!) { 21 | | addTodo(type: $type) { 22 | | id 23 | | type 24 | | } 25 | |}""".stripMargin 26 | ) 27 | ) { (addTodo, status) => 28 | button(onClick := () => { 29 | addTodo(js.Dynamic.literal( 30 | "type" -> "myTodoType" 31 | )) 32 | }) 33 | } 34 | ``` 35 | 36 | Here, we specified the type of the variables and result to be `js.Object`, essentially making it untyped. This data isn't super friendly to work with, though, so let's see how we can type our data. 37 | 38 | ## Typing Mutation Variables and Responses 39 | Apollo Scala.js allows you to specify models for the variables and mutations results. These models are usually case classes and can be handwritten or generated by Apollo CLI (see [automatic mutation types](#automatic-mutation-types) for how to set this up). 40 | 41 | Let's say we have a mutation 42 | ```graphql 43 | mutation addTodo($todoType: String!) { 44 | addTodo(type: $todoType) { 45 | id 46 | } 47 | } 48 | ``` 49 | 50 | The corresponding type for this would look something like this: 51 | ```scala 52 | case class Variables(todoType: String) 53 | case class Todo(id: String) 54 | case class MutationData(addTodo: Todo) 55 | ``` 56 | 57 | Once we have these types written, all we have to do is replace the `js.Object`s with `MutationData` and `Variables` to get typed mutation calls and responses! Apollo Scala.js handles the process of generating JavaScript objects from your variables parsing the result into Scala objects, so you can focus on using the data. 58 | 59 | ```scala 60 | Mutation[MutationData, Variables]( 61 | gql( 62 | """mutation addTodo($type: String!) { 63 | | addTodo(type: $type) { 64 | | id 65 | | type 66 | | } 67 | |}""".stripMargin 68 | ) 69 | )) { (addTodo, mutationStatus) => 70 | button(onClick := () => { 71 | addTodo(Variables( 72 | todoType = "myTodoType" 73 | )) 74 | }) 75 | } 76 | ``` 77 | 78 | ## Automatic Mutation Types 79 | With `apollo`, we can automatically generate mutation objects that tie together GraphQL mutations with variables and response types based on the schema definition. First, make sure you have followed the [Apollo CLI Installation](/essentials/installation/#apollo-cli) steps and downloaded a schema by following the [instructions](https://github.com/apollographql/apollo-cli#apollo-schemadownload-output). For this example, we will be using the GraphQL server at `https://graphql-todo-tracker.glitch.me`. 80 | 81 | With Apollo CLI installed, we can define our first mutation! Under `src/main/graphql`, you can define static mutation that will be converted to Scala code by Apollo CLI. For example, we could define a mutation `addtodo.graphql`: 82 | ```graphql 83 | mutation AddTodo($typ: String!) { 84 | addTodo(type: $typ) { 85 | typ: type 86 | } 87 | } 88 | ``` 89 | 90 | Which results in an object `AddTodoMutation` being generated. To use this in a mutation component, we can simply pass the generated object in instead of a mutation string and Apollo Scala.js will automatically gather the variables and result type based on the generated code. 91 | 92 | ```scala 93 | Mutation(AddTodoMutation) { (addTodo, mutationStatus) => 94 | button(onClick := () => { 95 | addTodo(AddTodoMutation.Variables( 96 | typ = "myTodoType" 97 | )) 98 | }) 99 | } 100 | ``` 101 | -------------------------------------------------------------------------------- /react/src/main/scala/com/apollographql/scalajs/react/Mutation.scala: -------------------------------------------------------------------------------- 1 | package com.apollographql.scalajs.react 2 | 3 | import com.apollographql.scalajs._ 4 | import slinky.core.ExternalComponent 5 | import slinky.core.facade.ReactElement 6 | import slinky.readwrite.{Reader, Writer} 7 | 8 | import scala.concurrent.Future 9 | import scala.language.implicitConversions 10 | import scala.scalajs.js 11 | import scala.scalajs.js.| 12 | import scala.scalajs.js.annotation.JSImport 13 | 14 | case class MutationData[T](loading: Boolean, called: Boolean, error: Option[js.Error], data: Option[T]) 15 | object MutationData { 16 | implicit def reader[T](implicit tReader: Reader[T]): Reader[MutationData[T]] = { o => 17 | val dyn = o.asInstanceOf[js.Dynamic] 18 | val loading = Reader.booleanReader.read(dyn.loading.asInstanceOf[js.Object]) 19 | MutationData( 20 | loading, 21 | Reader.booleanReader.read(dyn.called.asInstanceOf[js.Object]), 22 | Reader.optionReader[js.Error].read(dyn.error.asInstanceOf[js.Object]), 23 | if (loading) None else Reader.optionReader(tReader).read(dyn.data.asInstanceOf[js.Object]) 24 | ) 25 | } 26 | } 27 | 28 | case class CallMutationProps[V](variables: V) 29 | object CallMutationProps { 30 | implicit def vToCall[V](value: V): CallMutationProps[V] = CallMutationProps(value) 31 | implicit def writer[T](implicit tWriter: Writer[T]): Writer[CallMutationProps[T]] = { v => 32 | js.Dynamic.literal( 33 | variables = tWriter.write(v.variables) 34 | ) 35 | } 36 | } 37 | 38 | case class UpdateStrategy(refetchQueries: Seq[String] = Seq()) 39 | object Mutation extends ExternalComponent { 40 | case class Props(mutation: DocumentNode, 41 | refetchQueries: Seq[String], 42 | children: (js.Object, js.Object) => ReactElement) 43 | 44 | def apply[Res](query: DocumentNode) 45 | (children: (CallMutationProps[Unit] => Future[MutationResult[Res]], MutationData[Res]) => ReactElement) 46 | (implicit tReader: Reader[Res]): slinky.core.BuildingComponent[Element, js.Object] = { 47 | apply(query, UpdateStrategy())(children) 48 | } 49 | 50 | type DynamicMutationAsyncCallback[Var, Res] = CallMutationProps[Var] => Future[MutationResult[Res]] 51 | 52 | def apply[Res, Var](query: DocumentNode) 53 | (children: (DynamicMutationAsyncCallback[Var, Res], MutationData[Res]) => ReactElement) 54 | (implicit tReader: Reader[Res], vWriter: Writer[Var]): slinky.core.BuildingComponent[Element, js.Object] = { 55 | apply(query, UpdateStrategy())(children) 56 | } 57 | 58 | def apply[Res, Var](query: DocumentNode, updateStrategy: UpdateStrategy) 59 | (children: (DynamicMutationAsyncCallback[Var, Res], MutationData[Res]) => ReactElement) 60 | (implicit tReader: Reader[Res], vWriter: Writer[Var]): slinky.core.BuildingComponent[Element, js.Object] = { 61 | val queryDataReader = MutationData.reader(tReader) 62 | apply(Props( 63 | mutation = query, 64 | refetchQueries = updateStrategy.refetchQueries, 65 | children = (call, d) => { 66 | children( 67 | implicitly[Reader[CallMutationProps[Var] => Future[MutationResult[Res]]]].read(call), 68 | queryDataReader.read(d) 69 | ) 70 | } 71 | )) 72 | } 73 | 74 | type StaticMutationAsyncCallback[Q <: GraphQLMutation] = CallMutationProps[Q#Variables] => Future[MutationResult[Q#Data]] 75 | 76 | def apply[Q <: GraphQLMutation](query: Q) 77 | (children: (StaticMutationAsyncCallback[Q], MutationData[Q#Data]) => ReactElement) 78 | (implicit dataReader: Reader[Q#Data], 79 | variablesWriter: Writer[Q#Variables]): slinky.core.BuildingComponent[Element, js.Object] = { 80 | apply(query: Q, UpdateStrategy())(children) 81 | } 82 | 83 | def apply[Q <: GraphQLMutation](query: Q, updateStrategy: UpdateStrategy) 84 | (children: (StaticMutationAsyncCallback[Q], MutationData[Q#Data]) => ReactElement) 85 | (implicit dataReader: Reader[Q#Data], 86 | variablesWriter: Writer[Q#Variables]): slinky.core.BuildingComponent[Element, js.Object] = { 87 | val queryDataReader = MutationData.reader(dataReader) 88 | apply(Props( 89 | mutation = query.operation, 90 | refetchQueries = updateStrategy.refetchQueries, 91 | children = (call, d) => { 92 | children( 93 | implicitly[Reader[CallMutationProps[Q#Variables] => Future[MutationResult[Q#Data]]]].read(call), 94 | queryDataReader.read(d) 95 | ) 96 | } 97 | )) 98 | } 99 | 100 | @js.native 101 | @JSImport("@apollo/client/react/components", "Mutation") 102 | object MutationComponent extends js.Object 103 | 104 | override val component = MutationComponent 105 | } 106 | -------------------------------------------------------------------------------- /react/src/main/scala/com/apollographql/scalajs/react/Hooks.scala: -------------------------------------------------------------------------------- 1 | package com.apollographql.scalajs.react 2 | 3 | import scala.scalajs.js 4 | import scala.scalajs.js.annotation.JSImport 5 | import com.apollographql.scalajs._ 6 | 7 | @JSImport("@apollo/client", JSImport.Namespace) 8 | @js.native 9 | object HooksApi extends js.Object { 10 | def useQuery[Q <: GraphQLQuery]( 11 | query: DocumentNode, 12 | props: js.UndefOr[QueryHookOptions[Q]] = js.undefined 13 | ): QueryResult[Q] = js.native 14 | 15 | def useMutation[Q <: GraphQLMutation]( 16 | query: DocumentNode, 17 | props: js.UndefOr[MutationHookOptions[Q]] = js.undefined 18 | ): js.Tuple2[js.Function1[js.UndefOr[MutationHookOptions[Q]], js.Promise[MutationResult[Q#Data]]], MutationResult[Q#Data]] = js.native 19 | } 20 | 21 | trait QueryHookOptions[Q <: GraphQLQuery] extends js.Object { 22 | val variables: js.UndefOr[Q#Variables] = js.undefined 23 | val pollInterval: js.UndefOr[Int] = js.undefined 24 | val notifyOnNetworkStatusChange: js.UndefOr[Boolean] = js.undefined 25 | val fetchPolicy: js.UndefOr[FetchPolicy] = js.undefined 26 | val errorPolicy: js.UndefOr[ErrorPolicy] = js.undefined 27 | val ssr: js.UndefOr[Boolean] = js.undefined 28 | val displayName: js.UndefOr[String] = js.undefined 29 | val skip: js.UndefOr[Boolean] = js.undefined 30 | val partialRefetch: js.UndefOr[Boolean] = js.undefined 31 | val client: js.UndefOr[ApolloClient] = js.undefined 32 | val returnPartialData: js.UndefOr[Boolean] = js.undefined 33 | } 34 | 35 | object QueryHookOptions { 36 | def apply[Q <: GraphQLQuery]( 37 | variables: js.UndefOr[Q#Variables] = js.undefined, 38 | pollInterval: js.UndefOr[Int] = js.undefined, 39 | notifyOnNetworkStatusChange: js.UndefOr[Boolean] = js.undefined, 40 | fetchPolicy: js.UndefOr[FetchPolicy] = js.undefined, 41 | errorPolicy: js.UndefOr[ErrorPolicy] = js.undefined, 42 | ssr: js.UndefOr[Boolean] = js.undefined, 43 | displayName: js.UndefOr[String] = js.undefined, 44 | skip: js.UndefOr[Boolean] = js.undefined, 45 | partialRefetch: js.UndefOr[Boolean] = js.undefined, 46 | client: js.UndefOr[ApolloClient] = js.undefined, 47 | returnPartialData: js.UndefOr[Boolean] = js.undefined 48 | ): QueryHookOptions[Q] = { 49 | js.Dynamic 50 | .literal( 51 | variables = variables.asInstanceOf[js.Object], 52 | pollInterval = pollInterval, 53 | notifyOnNetworkStatusChange = notifyOnNetworkStatusChange, 54 | fetchPolicy = fetchPolicy, 55 | errorPolicy = errorPolicy, 56 | ssr = ssr, 57 | displayName = displayName, 58 | skip = skip, 59 | partialRefetch = partialRefetch, 60 | client = client, 61 | returnPartialData = returnPartialData 62 | ) 63 | .asInstanceOf[QueryHookOptions[Q]] 64 | } 65 | } 66 | 67 | trait FetchPolicy extends js.Object 68 | 69 | object FetchPolicy { 70 | val CacheFirst: FetchPolicy = "cache-first".asInstanceOf[FetchPolicy] 71 | val NetworkOnly: FetchPolicy = "network-only".asInstanceOf[FetchPolicy] 72 | val CacheOnly: FetchPolicy = "cache-only".asInstanceOf[FetchPolicy] 73 | val NoCache: FetchPolicy = "no-cache".asInstanceOf[FetchPolicy] 74 | val Standby: FetchPolicy = "standby".asInstanceOf[FetchPolicy] 75 | val CacheAndNetwork: FetchPolicy = "cache-and-network".asInstanceOf[FetchPolicy] 76 | } 77 | 78 | 79 | trait ErrorPolicy extends js.Object 80 | 81 | object ErrorPolicy { 82 | val None: ErrorPolicy = "none".asInstanceOf[ErrorPolicy] 83 | val Ignore: ErrorPolicy = "ignore".asInstanceOf[ErrorPolicy] 84 | val All: ErrorPolicy = "all".asInstanceOf[ErrorPolicy] 85 | } 86 | 87 | @js.native 88 | trait QueryResult[Q <: GraphQLQuery] extends js.Object { 89 | val data: js.UndefOr[Q#Data] = js.native 90 | val loading: Boolean = js.native 91 | val error: js.UndefOr[js.Error] = js.native 92 | val variables: js.UndefOr[Q#Variables] = js.native 93 | val networkStatus: Int = js.native 94 | val refetch: js.Function1[Q#Variables, js.Promise[ApolloQueryResult[Q#Data]]] = js.native 95 | val fetchMore: js.Function3[DocumentNode, Q#Variables, js.Function, js.Promise[ApolloQueryResult[Q#Data]]] = js.native 96 | val startPolling: js.Function1[Int, Unit] = js.native 97 | val subscribeToMore: js.Function1[js.Dynamic, js.Function0[Unit]] = js.native 98 | val updateQuery: js.Function2[Q#Data, js.Dynamic, Q#Data] = js.native 99 | val client: ApolloClient = js.native 100 | val called: Boolean = js.native 101 | } 102 | 103 | object QueryResult { 104 | // An unapply for the common use case to extract values 105 | def unapply[Q <: GraphQLQuery](result: QueryResult[Q]): Option[(js.UndefOr[Q#Data], js.UndefOr[js.Error], Boolean)] = { 106 | Some((result.data, result.error, result.loading)) 107 | } 108 | } 109 | 110 | @js.native 111 | trait MutationHookOptions[Q <: GraphQLMutation] extends js.Object { 112 | val variables: js.UndefOr[Q#Variables] = js.undefined 113 | val refetchQueries: js.UndefOr[js.Array[js.Dynamic]] = js.undefined 114 | } 115 | 116 | object MutationHookOptions { 117 | def apply[Q <: GraphQLMutation]( 118 | variables: js.UndefOr[Q#Variables] = js.undefined, 119 | refetchQueries: js.UndefOr[js.Array[js.Dynamic]] = js.undefined 120 | ): MutationHookOptions[Q] = { 121 | js.Dynamic 122 | .literal( 123 | variables = variables.asInstanceOf[js.Object], 124 | refetchQueries = refetchQueries 125 | ) 126 | .asInstanceOf[MutationHookOptions[Q]] 127 | } 128 | } 129 | 130 | @js.native 131 | trait MutationResult[D] extends js.Object { 132 | val data: js.UndefOr[D] = js.native 133 | val loading: Boolean = js.native 134 | val error: js.UndefOr[js.Error] = js.native 135 | val called: Boolean = js.native 136 | val client: ApolloClient = js.native 137 | } 138 | -------------------------------------------------------------------------------- /tests/src/test/scala/com/apollographql/scalajs/react/QueryComponentTest.scala: -------------------------------------------------------------------------------- 1 | package com.apollographql.scalajs.react 2 | 3 | import com.apollographql.scalajs._ 4 | import org.scalajs.dom.document 5 | import org.scalatest.funsuite.AsyncFunSuite 6 | import org.scalatest.Assertion 7 | import slinky.web.ReactDOM 8 | import slinky.web.html.div 9 | 10 | import scala.concurrent.Promise 11 | import scala.scalajs.js 12 | import com.apollographql.scalajs.cache.InMemoryCache 13 | 14 | class QueryComponentTest extends AsyncFunSuite { 15 | js.Dynamic.global.window.fetch = UnfetchFetch 16 | 17 | implicit override def executionContext = 18 | scala.concurrent.ExecutionContext.Implicits.global 19 | 20 | test("Can mount a Query component and render data based on the query") { 21 | val gotDataPromise = Promise[Assertion] 22 | case class ResultShape(rates: Seq[js.Object]) 23 | ReactDOM.render( 24 | ApolloProvider( 25 | client = new ApolloClient( 26 | ApolloClientOptions( 27 | uri = "https://graphql-currency-rates.glitch.me", 28 | cache = new InMemoryCache() 29 | ) 30 | ) 31 | )( 32 | Query[ResultShape](gql( 33 | """{ 34 | | rates(currency: "USD") { 35 | | currency 36 | | } 37 | |}""".stripMargin 38 | )) { d => 39 | if (d.data.isDefined) { 40 | gotDataPromise.success(assert(d.data.get.rates.nonEmpty)) 41 | } 42 | 43 | div() 44 | } 45 | ), 46 | document.createElement("div") 47 | ) 48 | 49 | gotDataPromise.future 50 | } 51 | 52 | test("Can mount a Query component that takes variables and render data based on the query") { 53 | val gotDataPromise = Promise[Assertion] 54 | case class ResultShape(rates: Seq[js.Object]) 55 | case class Variables(cur: String) 56 | ReactDOM.render( 57 | ApolloProvider( 58 | client = new ApolloClient( 59 | ApolloClientOptions( 60 | uri = "https://graphql-currency-rates.glitch.me", 61 | cache = new InMemoryCache() 62 | ) 63 | ) 64 | )( 65 | Query[ResultShape, Variables](gql( 66 | """query GetRates($cur: String!) { 67 | | rates(currency: $cur) { 68 | | currency 69 | | } 70 | |}""".stripMargin 71 | ), Variables("USD")) { d => 72 | if (d.data.isDefined) { 73 | gotDataPromise.success(assert(d.data.get.rates.nonEmpty)) 74 | } 75 | 76 | div() 77 | } 78 | ), 79 | document.createElement("div") 80 | ) 81 | 82 | gotDataPromise.future 83 | } 84 | 85 | test("Can mount a Query component and render data based a query returning multiple values") { 86 | val gotDataPromise = Promise[Assertion] 87 | case class ResultShape(rates: Seq[js.Object], bar: Seq[js.Object]) 88 | ReactDOM.render( 89 | ApolloProvider( 90 | client = new ApolloClient( 91 | ApolloClientOptions( 92 | uri = "https://graphql-currency-rates.glitch.me", 93 | cache = new InMemoryCache() 94 | ) 95 | ) 96 | )( 97 | Query[ResultShape](gql( 98 | """{ 99 | | rates(currency: "USD") { 100 | | currency 101 | | } 102 | | 103 | | bar: rates(currency: "USD") { 104 | | currency 105 | | } 106 | |}""".stripMargin 107 | )) { d => 108 | if (d.data.isDefined) { 109 | gotDataPromise.success(assert(d.data.get.rates.nonEmpty && d.data.get.bar.nonEmpty)) 110 | } 111 | 112 | div() 113 | } 114 | ), 115 | document.createElement("div") 116 | ) 117 | 118 | gotDataPromise.future 119 | } 120 | 121 | test("Can mount a Query component and render data based a static query") { 122 | val gotDataPromise = Promise[Assertion] 123 | ReactDOM.render( 124 | ApolloProvider( 125 | client = new ApolloClient( 126 | ApolloClientOptions( 127 | uri = "https://graphql-currency-rates.glitch.me", 128 | cache = new InMemoryCache() 129 | ) 130 | ) 131 | )( 132 | Query(UsdRatesQuery) { d => 133 | if (d.data.isDefined) { 134 | gotDataPromise.success(assert(d.data.get.rates.exists(_.nonEmpty))) 135 | } 136 | 137 | div() 138 | } 139 | ), 140 | document.createElement("div") 141 | ) 142 | 143 | gotDataPromise.future 144 | } 145 | 146 | test("Can mount a Query component and render data based a static query with variables") { 147 | val gotDataPromise = Promise[Assertion] 148 | ReactDOM.render( 149 | ApolloProvider( 150 | client = new ApolloClient( 151 | ApolloClientOptions( 152 | uri = "https://graphql-currency-rates.glitch.me", 153 | cache = new InMemoryCache() 154 | ) 155 | ) 156 | )( 157 | Query(CurrencyRatesQuery, CurrencyRatesQuery.Variables("USD")) { d => 158 | if (d.data.isDefined) { 159 | gotDataPromise.success(assert(d.data.get.rates.exists(_.nonEmpty))) 160 | } 161 | 162 | div() 163 | } 164 | ), 165 | document.createElement("div") 166 | ) 167 | 168 | gotDataPromise.future 169 | } 170 | 171 | test("Can mount a Query component with a cache-only fetch policy") { 172 | val didntLoadPromise = Promise[Assertion] 173 | ReactDOM.render( 174 | ApolloProvider( 175 | client = new ApolloClient( 176 | ApolloClientOptions( 177 | uri = "https://this-does-not-exists.com", 178 | cache = new InMemoryCache() 179 | ) 180 | ) 181 | )( 182 | Query(CurrencyRatesQuery, CurrencyRatesQuery.Variables("USD"), ExtraQueryOptions( 183 | fetchPolicy = "cache-only" 184 | )) { d => 185 | // TODO: Check if this is the intended behavior 186 | if (!d.loading && d.error.isEmpty) { 187 | didntLoadPromise.trySuccess(assert(true)) 188 | } 189 | 190 | div() 191 | } 192 | ), 193 | document.createElement("div") 194 | ) 195 | 196 | didntLoadPromise.future 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /react/src/main/scala/com/apollographql/scalajs/react/Query.scala: -------------------------------------------------------------------------------- 1 | package com.apollographql.scalajs.react 2 | 3 | import com.apollographql.scalajs.{GraphQLQuery, DocumentNode} 4 | import slinky.core.ExternalComponent 5 | import slinky.core.facade.ReactElement 6 | import slinky.readwrite.{Reader, Writer} 7 | 8 | import scala.util.Try 9 | import scala.scalajs.js 10 | import scala.scalajs.js.| 11 | import scala.scalajs.js.annotation.JSImport 12 | 13 | case class QueryData[T](loading: Boolean, error: Option[js.Error], data: Option[T], refetch: () => Unit) 14 | object QueryData { 15 | implicit def reader[T](implicit tReader: Reader[T]): Reader[QueryData[T]] = { o => 16 | val dyn = o.asInstanceOf[js.Dynamic] 17 | val loading = Reader.booleanReader.read(dyn.loading.asInstanceOf[js.Object]) 18 | val error = Reader.optionReader[js.Error].read(dyn.error.asInstanceOf[js.Object]) 19 | QueryData( 20 | loading, 21 | error, 22 | if (!js.isUndefined(dyn.data) && js.Object.keys(dyn.data.asInstanceOf[js.Object]).nonEmpty) { 23 | Some(tReader.read(dyn.data.asInstanceOf[js.Object])) 24 | } else None, 25 | implicitly[Reader[() => Unit]].read(dyn.refetch.asInstanceOf[js.Object]) 26 | ) 27 | } 28 | } 29 | 30 | case class ExtraQueryOptions(pollInterval: js.UndefOr[Double] = js.undefined, 31 | notifyOnNetworkStatusChange: js.UndefOr[Boolean] = js.undefined, 32 | fetchPolicy: js.UndefOr[String] = js.undefined, 33 | errorPolicy: js.UndefOr[String] = js.undefined, 34 | ssr: js.UndefOr[Boolean] = js.undefined, 35 | displayName: js.UndefOr[String] = js.undefined, 36 | delay: js.UndefOr[Boolean] = js.undefined, 37 | context: js.UndefOr[js.Object] = js.undefined) 38 | 39 | object Query extends ExternalComponent { 40 | case class Props(query: DocumentNode, 41 | children: js.Object => ReactElement, 42 | variables: js.UndefOr[js.Object] = js.undefined, 43 | pollInterval: js.UndefOr[Double] = js.undefined, 44 | notifyOnNetworkStatusChange: js.UndefOr[Boolean] = js.undefined, 45 | fetchPolicy: js.UndefOr[String] = js.undefined, 46 | errorPolicy: js.UndefOr[String] = js.undefined, 47 | ssr: js.UndefOr[Boolean] = js.undefined, 48 | displayName: js.UndefOr[String] = js.undefined, 49 | delay: js.UndefOr[Boolean] = js.undefined, 50 | context: js.UndefOr[js.Object] = js.undefined) 51 | def apply[T, V](query: DocumentNode, variables: V, queryOptions: ExtraQueryOptions) 52 | (children: QueryData[T] => ReactElement) 53 | (implicit tReader: Reader[T], vWriter: Writer[V]): slinky.core.BuildingComponent[Element, js.Object] = { 54 | val queryDataReader = QueryData.reader(tReader) 55 | apply(Props( 56 | query = query, 57 | variables = vWriter.write(variables), 58 | children = d => { 59 | children(queryDataReader.read(d)) 60 | }, 61 | // queryOptions 62 | pollInterval = queryOptions.pollInterval, 63 | notifyOnNetworkStatusChange = queryOptions.notifyOnNetworkStatusChange, 64 | fetchPolicy = queryOptions.fetchPolicy, 65 | errorPolicy = queryOptions.errorPolicy, 66 | ssr = queryOptions.ssr, 67 | displayName = queryOptions.displayName, 68 | delay = queryOptions.delay, 69 | context = queryOptions.context 70 | )) 71 | } 72 | 73 | def apply[T, V](query: DocumentNode, variables: V) 74 | (children: QueryData[T] => ReactElement) 75 | (implicit tReader: Reader[T], vWriter: Writer[V]): slinky.core.BuildingComponent[Element, js.Object] = { 76 | apply[T, V]( 77 | query = query, 78 | variables = variables, 79 | queryOptions = ExtraQueryOptions() 80 | )(children) 81 | } 82 | 83 | def apply[T](query: DocumentNode, queryOptions: ExtraQueryOptions) 84 | (children: QueryData[T] => ReactElement) 85 | (implicit tReader: Reader[T]): slinky.core.BuildingComponent[Element, js.Object] = { 86 | apply[T, Unit]( 87 | query = query, 88 | (), 89 | queryOptions 90 | )(children) 91 | } 92 | 93 | def apply[T](query: DocumentNode) 94 | (children: QueryData[T] => ReactElement) 95 | (implicit tReader: Reader[T]): slinky.core.BuildingComponent[Element, js.Object] = { 96 | apply[T]( 97 | query = query, 98 | queryOptions = ExtraQueryOptions() 99 | )(children) 100 | } 101 | 102 | def apply[Q <: GraphQLQuery](query: Q, variables: Q#Variables, queryOptions: ExtraQueryOptions) 103 | (children: QueryData[query.Data] => ReactElement) 104 | (implicit dataReader: Reader[query.Data], 105 | variablesWriter: Writer[Q#Variables]): slinky.core.BuildingComponent[Element, js.Object] = { 106 | apply[query.Data, Q#Variables]( 107 | query = query.operation, 108 | variables = variables, 109 | queryOptions = queryOptions 110 | )(children) 111 | } 112 | 113 | def apply[Q <: GraphQLQuery](query: Q, variables: Q#Variables) 114 | (children: QueryData[query.Data] => ReactElement) 115 | (implicit dataReader: Reader[query.Data], 116 | variablesWriter: Writer[Q#Variables]): slinky.core.BuildingComponent[Element, js.Object] = { 117 | apply[Q]( 118 | query = query, 119 | variables = variables, 120 | queryOptions = ExtraQueryOptions() 121 | )(children) 122 | } 123 | 124 | def apply(query: GraphQLQuery { type Variables = Unit }, queryOptions: ExtraQueryOptions) 125 | (children: QueryData[query.Data] => ReactElement) 126 | (implicit dataReader: Reader[query.Data]): slinky.core.BuildingComponent[Element, js.Object] = { 127 | apply[GraphQLQuery {type Variables = Unit }]( 128 | query = query, 129 | variables = (), 130 | queryOptions = queryOptions 131 | )(children) 132 | } 133 | 134 | def apply(query: GraphQLQuery { type Variables = Unit }) 135 | (children: QueryData[query.Data] => ReactElement) 136 | (implicit dataReader: Reader[query.Data]): slinky.core.BuildingComponent[Element, js.Object] = { 137 | apply( 138 | query = query, 139 | queryOptions = ExtraQueryOptions() 140 | )(children) 141 | } 142 | 143 | @js.native 144 | @JSImport("@apollo/client/react/components", "Query") 145 | object QueryComponent extends js.Object 146 | 147 | override val component = QueryComponent 148 | } 149 | -------------------------------------------------------------------------------- /docs/source/essentials/queries.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Queries 3 | order: 2 4 | --- 5 | 6 | Now that you have your client set up, let's dive in deeper and see how to use Query components to load data into your application. 7 | 8 | ## The Query component 9 | Using the Query component from Scala.js is just like using it with the original JavaScript API. Simply pass in a parsed GraphQL query object (from `gql`) and a function that decides what to render given the state of the query. Because the Query component tracks all steps of making a query, you can easily render loading and error states by reading properties of the query status given to you. 10 | 11 | When you use the Query component, Apollo Client also performs caching to minimize server roundtrips. If the data requested is already in the cache, your Query component will immediately render with the cached data. If it isn't, the result of the query will be normalized and placed into the cache as soon as the data arrives. 12 | 13 | Let's see this in action! 14 | 15 | ```scala 16 | import com.apollographql.scalajs.react.Query 17 | 18 | Query[js.Object](gql( 19 | """{ 20 | | rates(currency: "USD") { 21 | | currency 22 | | } 23 | |}""".stripMargin 24 | )) { result => 25 | if (result.loading) { 26 | h1("Loading!") 27 | } else { 28 | div(result.data.get.toString) 29 | } 30 | } 31 | ``` 32 | 33 | Here, we specified the type of the result to be `js.Object`, essentially making it untyped. This data isn't super friendly to work with, though, so let's see how we can type our data. 34 | 35 | ## Typing Query Responses 36 | Apollo Scala.js allows you to specify models to parse the query results into. These models are usually case classes and can be handwritten or generated by Apollo CLI (see [automatic query types](#automatic-query-types) for how to set this up). 37 | 38 | Let's say we have a query 39 | ```graphql 40 | { 41 | dogs { 42 | id 43 | breed 44 | } 45 | } 46 | ``` 47 | 48 | The corresponding type for this would look something like this: 49 | ```scala 50 | case class Dog(id: String, breed: String) 51 | case class QueryData(dogs: Seq[Dog]) 52 | ``` 53 | 54 | Once we have this type written, all we have to do is replace `js.Object` with `QueryData` to get typed responses! Apollo Scala.js handles the process of parsing the result into Scala objects, so you can focus on using the data. 55 | 56 | ```scala 57 | Query[QueryData](gql( 58 | """{ 59 | | dogs { 60 | | id 61 | | breed 62 | | } 63 | |}""".stripMargin 64 | )) { queryStatus => 65 | if (queryStatus.loading) "Loading..." 66 | else if (queryStatus.error) s"Error! ${queryStatus.error.message}" 67 | else { 68 | div( 69 | queryStatus.data.get.dogs.map { dog => 70 | h1(key := dog.id)(dog.breed) 71 | } 72 | ) 73 | } 74 | } 75 | ``` 76 | 77 | ## Querying with Variables 78 | When performing a GraphQL query with a Query component, you can also pass in variables to be used by the query as an additional parameter. Just like the result, variables can be typed with an additional type parameter, or left untyped by using `js.Object` as the type. 79 | 80 | To perform a query with untyped variables, you can use `js.Dynamic.literal` to construct the variables object. 81 | ```scala 82 | case class Dog(id: String, breed: String) 83 | case class QueryData(dog: Dog) 84 | 85 | Query[QueryData, js.Object]( 86 | gql( 87 | """query dog($breed: String!) { 88 | | dog(breed: $breed) { 89 | | id 90 | | displayImage 91 | | } 92 | |}""".stripMargin 93 | ), 94 | js.Dynamic.literal( 95 | breed = "bulldog" 96 | ) 97 | ) { queryStatus => 98 | if (queryStatus.loading) "Loading..." 99 | else if (queryStatus.error) s"Error! ${queryStatus.error.message}" 100 | else { 101 | img(src := queryStatus.data.get.dog.displayImage) 102 | } 103 | } 104 | ``` 105 | 106 | If we want to type the variables being passed into the query, we can define a case class to replace `js.Object` in the type parameters. 107 | ```scala 108 | case class Variables(breed: String) 109 | 110 | Query[QueryData, Variables]( 111 | gql( 112 | """query dog($breed: String!) { 113 | | dog(breed: $breed) { 114 | | id 115 | | displayImage 116 | | } 117 | |}""".stripMargin 118 | ), 119 | Variables(breed = "bulldog") 120 | ) { queryStatus => 121 | if (queryStatus.loading) "Loading..." 122 | else if (queryStatus.error) s"Error! ${queryStatus.error.message}" 123 | else { 124 | img(src := queryStatus.data.get.dog.displayImage) 125 | } 126 | } 127 | ``` 128 | 129 | ## Automatic Query Types 130 | 131 | With `apollo`, we can automatically generate query objects that tie together GraphQL queries with response types based on the schema definition. First, make sure you have followed the [Apollo CLI Installation](/essentials/installation/#apollo-cli) steps and downloaded a schema by following the [instructions](https://github.com/apollographql/apollo-cli#apollo-schemadownload-output). For this example, we will be using the GraphQL server at `https://graphql-currency-rates.glitch.me`. 132 | 133 | With Apollo CLI installed, we can define our first query! Under `src/main/graphql`, you can define static queries that will be converted to Scala code by Apollo CLI. For example, we could define a query `usdrates.graphql`: 134 | 135 | ```graphql 136 | query USDRates { 137 | rates(currency: "USD") { 138 | currency 139 | } 140 | } 141 | ``` 142 | 143 | Which results in an object `UsdRatesQuery` being generated. To use this in a query component, we can simply pass the generated object in instead of a query string and Apollo Scala.js will automatically gather the result type based on the generated code. 144 | 145 | ```scala 146 | Query(UsdRatesQuery) { queryStatus => 147 | if (queryStatus.loading) "Loading..." 148 | else if (queryStatus.error) s"Error! ${queryStatus.error.message}" 149 | else { 150 | div(queryStatus.data.get.rates.mkString(", ")) 151 | } 152 | } 153 | ``` 154 | 155 | Apollo CLI also supports generating variables types, which are emitted as case classes inside the query object. To pass in variables to a query that requires some, simply pass in an instance of the variables class as a parameter after the query object. 156 | 157 | For a query: 158 | 159 | ```graphql 160 | query CurrencyRates($cur: String!) { 161 | rates(currency: $cur) { 162 | currency 163 | } 164 | } 165 | ``` 166 | 167 | We can pass in variables in a query component as: 168 | 169 | ```scala 170 | Query(CurrencyRatesQuery, CurrencyRatesQuery.Variables("USD")) { queryStatus => 171 | if (queryStatus.loading) "Loading..." 172 | else if (queryStatus.error) s"Error! ${queryStatus.error.message}" 173 | else { 174 | div(queryStatus.data.get.rates.mkString(", ")) 175 | } 176 | } 177 | ``` 178 | 179 | ## Extra Query Options 180 | If you want to pass in additional query options, such as the fetch policy, you can provide an instance of `ExtraQueryOptions` as an additional parameter. For example, to force the query to only load from the cache you can use: 181 | 182 | ```scala 183 | Query(CurrencyRatesQuery, CurrencyRatesQuery.Variables("USD"), ExtraQueryOptions( 184 | fetchPolicy = "cache-only" 185 | )) { ... } 186 | ``` 187 | 188 | ## Next steps 189 | Now that you've learned how to get data from your GraphQL server, it's time to learn to update that data with [Mutations](/essentials/mutations/)! 190 | -------------------------------------------------------------------------------- /tests/src/test/scala/com/apollographql/scalajs/react/MutationComponentTest.scala: -------------------------------------------------------------------------------- 1 | package com.apollographql.scalajs.react 2 | 3 | import com.apollographql.scalajs.AddTodoMutation 4 | import com.apollographql.scalajs.AllTodosIdQuery 5 | import com.apollographql.scalajs.UnfetchFetch 6 | import com.apollographql.scalajs.gql 7 | import org.scalajs.dom.document 8 | import org.scalatest.Assertion 9 | import org.scalatest.funsuite.AsyncFunSuite 10 | import org.scalatest.matchers.should.Matchers 11 | import slinky.web.ReactDOM 12 | import slinky.web.html.div 13 | 14 | import scala.concurrent.Future 15 | import scala.concurrent.Promise 16 | import scala.scalajs.js 17 | import scala.util.Failure 18 | import scala.util.Success 19 | import com.apollographql.scalajs.ApolloClient 20 | import com.apollographql.scalajs.ApolloClientOptions 21 | import com.apollographql.scalajs.cache.InMemoryCache 22 | 23 | class MutationComponentTest extends AsyncFunSuite with Matchers { 24 | js.Dynamic.global.window.fetch = UnfetchFetch 25 | 26 | implicit override def executionContext = 27 | scala.concurrent.ExecutionContext.Implicits.global 28 | 29 | trait Todo extends js.Object { 30 | val typ: String 31 | } 32 | 33 | trait TodoResult extends js.Object { 34 | val addTodo: Todo 35 | } 36 | 37 | test("Can mount a Mutation component and call the mutation") { 38 | val gotDataPromise = Promise[Assertion] 39 | 40 | var callMutation: () => Unit = null 41 | 42 | ReactDOM.render( 43 | ApolloProvider( 44 | client = new ApolloClient( 45 | ApolloClientOptions( 46 | uri = "https://graphql-todo-tracker.glitch.me", 47 | cache = new InMemoryCache() 48 | ) 49 | ) 50 | )( 51 | Mutation[TodoResult, Unit](gql( 52 | """mutation { 53 | | addTodo(type: "lol") { 54 | | typ: type 55 | | } 56 | |}""".stripMargin 57 | )) { (mut, d) => 58 | if (d.data.isDefined) { 59 | gotDataPromise.success(assert(d.data.get.addTodo.typ == "lol")) 60 | } 61 | 62 | callMutation = () => { 63 | mut(()) 64 | } 65 | 66 | div() 67 | } 68 | ), 69 | document.createElement("div") 70 | ) 71 | 72 | callMutation() 73 | 74 | gotDataPromise.future 75 | } 76 | 77 | test("Can mount a Mutation component and get an error when the mutation fails") { 78 | val gotFailurePromise = Promise[Assertion] 79 | 80 | var ranMutation = false 81 | 82 | ReactDOM.render( 83 | ApolloProvider( 84 | client = new ApolloClient( 85 | ApolloClientOptions( 86 | uri = "https://graphql-todo-tracker.glitch.me", 87 | cache = new InMemoryCache() 88 | ) 89 | ) 90 | )( 91 | Mutation[Unit, Unit](gql( 92 | """mutation { 93 | | randomMutationThatDoesntExist(type: "lol") { 94 | | typ: type 95 | | } 96 | |}""".stripMargin 97 | )) { (mut, d) => 98 | if (!ranMutation) { 99 | ranMutation = true 100 | mut(()).andThen { 101 | case Success(_) => 102 | gotFailurePromise.failure(new Exception("Succeeded when it shouldn't have")) 103 | case Failure(_) => 104 | gotFailurePromise.success(assert(true)) 105 | } 106 | } 107 | 108 | div() 109 | } 110 | ), 111 | document.createElement("div") 112 | ) 113 | 114 | gotFailurePromise.future 115 | } 116 | 117 | test("Can mount a Mutation component and call the mutation with variables") { 118 | val gotDataPromise = Promise[Assertion] 119 | 120 | var callMutation: () => Unit = null 121 | 122 | ReactDOM.render( 123 | ApolloProvider( 124 | client = new ApolloClient( 125 | ApolloClientOptions( 126 | uri = "https://graphql-todo-tracker.glitch.me", 127 | cache = new InMemoryCache() 128 | ) 129 | ) 130 | )( 131 | Mutation[TodoResult, js.Object](gql( 132 | """mutation AddTodo($typ: String!) { 133 | | addTodo(type: $typ) { 134 | | typ: type 135 | | } 136 | |}""".stripMargin 137 | )) { (mut, d) => 138 | if (d.data.isDefined) { 139 | gotDataPromise.success(assert(d.data.get.addTodo.typ == "bar")) 140 | } 141 | 142 | callMutation = () => { 143 | mut(js.Dynamic.literal(typ = "bar")) 144 | } 145 | 146 | div() 147 | } 148 | ), 149 | document.createElement("div") 150 | ) 151 | 152 | callMutation() 153 | 154 | gotDataPromise.future 155 | } 156 | 157 | test("Can mount a Mutation component, call the mutation, and get the result from the future") { 158 | var resultFuture: Future[Assertion] = null 159 | 160 | ReactDOM.render( 161 | ApolloProvider( 162 | client = new ApolloClient( 163 | ApolloClientOptions( 164 | uri = "https://graphql-todo-tracker.glitch.me", 165 | cache = new InMemoryCache() 166 | ) 167 | ) 168 | )( 169 | Mutation[TodoResult, js.Object](gql( 170 | """mutation AddTodo($typ: String!) { 171 | | addTodo(type: $typ) { 172 | | typ: type 173 | | } 174 | |}""".stripMargin 175 | )) { (mut, _) => 176 | if (resultFuture == null) { 177 | resultFuture = mut(js.Dynamic.literal(typ = "bar")).map { d => 178 | assert(d.data.get.addTodo.typ == "bar") 179 | } 180 | } 181 | 182 | div() 183 | } 184 | ), 185 | document.createElement("div") 186 | ) 187 | 188 | resultFuture 189 | } 190 | 191 | test("Can mount a Mutation component with a static mutation and call the mutation with variables") { 192 | val gotDataPromise = Promise[Assertion] 193 | 194 | var callMutation: () => Unit = null 195 | 196 | ReactDOM.render( 197 | ApolloProvider( 198 | client = new ApolloClient( 199 | ApolloClientOptions( 200 | uri = "https://graphql-todo-tracker.glitch.me", 201 | cache = new InMemoryCache() 202 | ) 203 | ) 204 | )( 205 | Mutation(AddTodoMutation) { (mut, res) => 206 | if (res.data.isDefined) { 207 | gotDataPromise.success(assert(res.data.get.addTodo.get.typ == "bar")) 208 | } 209 | 210 | callMutation = () => { 211 | mut(AddTodoMutation.Variables(typ = "bar")) 212 | } 213 | 214 | div() 215 | } 216 | ), 217 | document.createElement("div") 218 | ) 219 | 220 | callMutation() 221 | 222 | gotDataPromise.future 223 | } 224 | 225 | test("Can mount a Mutation component with refetchQueries and refreshes the query") { 226 | val multipleCallPromise = Promise[Assertion] 227 | 228 | var callMutation: () => Unit = null 229 | 230 | var todoSizeAccumulator: Seq[Int] = Seq() 231 | 232 | ReactDOM.render( 233 | ApolloProvider( 234 | client = new ApolloClient( 235 | ApolloClientOptions( 236 | uri = "https://graphql-todo-tracker.glitch.me", 237 | cache = new InMemoryCache() 238 | ) 239 | ) 240 | )( 241 | div( 242 | Query(AllTodosIdQuery) { result => 243 | 244 | if(result.data.isDefined) { 245 | val todos = result.data.get.todos.get.size 246 | todoSizeAccumulator = todoSizeAccumulator :+ todos 247 | 248 | // If the query has been called twice (i.e. refetch happened) 249 | if (todoSizeAccumulator.size == 2) { 250 | multipleCallPromise.success(todoSizeAccumulator.reverse.reduce(_ - _) shouldBe 1) 251 | } 252 | } 253 | 254 | div() 255 | }, 256 | Mutation(AddTodoMutation, UpdateStrategy(refetchQueries = Seq("AllTodosId"))) { (mut, res) => 257 | callMutation = () => { 258 | mut(AddTodoMutation.Variables(typ = "refresh")) 259 | } 260 | div() 261 | } 262 | ) 263 | ), 264 | document.createElement("div") 265 | ) 266 | 267 | callMutation() 268 | 269 | multipleCallPromise.future 270 | } 271 | } 272 | -------------------------------------------------------------------------------- /tests/queries.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "__schema": { 4 | "queryType": { 5 | "name": "Query" 6 | }, 7 | "mutationType": null, 8 | "subscriptionType": null, 9 | "types": [ 10 | { 11 | "kind": "OBJECT", 12 | "name": "Query", 13 | "description": "", 14 | "fields": [ 15 | { 16 | "name": "rates", 17 | "description": "", 18 | "args": [ 19 | { 20 | "name": "currency", 21 | "description": "", 22 | "type": { 23 | "kind": "NON_NULL", 24 | "name": null, 25 | "ofType": { 26 | "kind": "SCALAR", 27 | "name": "String", 28 | "ofType": null 29 | } 30 | }, 31 | "defaultValue": null 32 | } 33 | ], 34 | "type": { 35 | "kind": "LIST", 36 | "name": null, 37 | "ofType": { 38 | "kind": "OBJECT", 39 | "name": "ExchangeRate", 40 | "ofType": null 41 | } 42 | }, 43 | "isDeprecated": false, 44 | "deprecationReason": null 45 | } 46 | ], 47 | "inputFields": null, 48 | "interfaces": [], 49 | "enumValues": null, 50 | "possibleTypes": null 51 | }, 52 | { 53 | "kind": "SCALAR", 54 | "name": "String", 55 | "description": "The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text.", 56 | "fields": null, 57 | "inputFields": null, 58 | "interfaces": null, 59 | "enumValues": null, 60 | "possibleTypes": null 61 | }, 62 | { 63 | "kind": "OBJECT", 64 | "name": "ExchangeRate", 65 | "description": "", 66 | "fields": [ 67 | { 68 | "name": "currency", 69 | "description": "", 70 | "args": [], 71 | "type": { 72 | "kind": "SCALAR", 73 | "name": "String", 74 | "ofType": null 75 | }, 76 | "isDeprecated": false, 77 | "deprecationReason": null 78 | }, 79 | { 80 | "name": "rate", 81 | "description": "", 82 | "args": [], 83 | "type": { 84 | "kind": "SCALAR", 85 | "name": "String", 86 | "ofType": null 87 | }, 88 | "isDeprecated": false, 89 | "deprecationReason": null 90 | }, 91 | { 92 | "name": "name", 93 | "description": "", 94 | "args": [], 95 | "type": { 96 | "kind": "SCALAR", 97 | "name": "String", 98 | "ofType": null 99 | }, 100 | "isDeprecated": false, 101 | "deprecationReason": null 102 | } 103 | ], 104 | "inputFields": null, 105 | "interfaces": [], 106 | "enumValues": null, 107 | "possibleTypes": null 108 | }, 109 | { 110 | "kind": "OBJECT", 111 | "name": "__Schema", 112 | "description": "A GraphQL Schema defines the capabilities of a GraphQL server. It exposes all available types and directives on the server, as well as the entry points for query, mutation, and subscription operations.", 113 | "fields": [ 114 | { 115 | "name": "types", 116 | "description": "A list of all types supported by this server.", 117 | "args": [], 118 | "type": { 119 | "kind": "NON_NULL", 120 | "name": null, 121 | "ofType": { 122 | "kind": "LIST", 123 | "name": null, 124 | "ofType": { 125 | "kind": "NON_NULL", 126 | "name": null, 127 | "ofType": { 128 | "kind": "OBJECT", 129 | "name": "__Type", 130 | "ofType": null 131 | } 132 | } 133 | } 134 | }, 135 | "isDeprecated": false, 136 | "deprecationReason": null 137 | }, 138 | { 139 | "name": "queryType", 140 | "description": "The type that query operations will be rooted at.", 141 | "args": [], 142 | "type": { 143 | "kind": "NON_NULL", 144 | "name": null, 145 | "ofType": { 146 | "kind": "OBJECT", 147 | "name": "__Type", 148 | "ofType": null 149 | } 150 | }, 151 | "isDeprecated": false, 152 | "deprecationReason": null 153 | }, 154 | { 155 | "name": "mutationType", 156 | "description": "If this server supports mutation, the type that mutation operations will be rooted at.", 157 | "args": [], 158 | "type": { 159 | "kind": "OBJECT", 160 | "name": "__Type", 161 | "ofType": null 162 | }, 163 | "isDeprecated": false, 164 | "deprecationReason": null 165 | }, 166 | { 167 | "name": "subscriptionType", 168 | "description": "If this server support subscription, the type that subscription operations will be rooted at.", 169 | "args": [], 170 | "type": { 171 | "kind": "OBJECT", 172 | "name": "__Type", 173 | "ofType": null 174 | }, 175 | "isDeprecated": false, 176 | "deprecationReason": null 177 | }, 178 | { 179 | "name": "directives", 180 | "description": "A list of all directives supported by this server.", 181 | "args": [], 182 | "type": { 183 | "kind": "NON_NULL", 184 | "name": null, 185 | "ofType": { 186 | "kind": "LIST", 187 | "name": null, 188 | "ofType": { 189 | "kind": "NON_NULL", 190 | "name": null, 191 | "ofType": { 192 | "kind": "OBJECT", 193 | "name": "__Directive", 194 | "ofType": null 195 | } 196 | } 197 | } 198 | }, 199 | "isDeprecated": false, 200 | "deprecationReason": null 201 | } 202 | ], 203 | "inputFields": null, 204 | "interfaces": [], 205 | "enumValues": null, 206 | "possibleTypes": null 207 | }, 208 | { 209 | "kind": "OBJECT", 210 | "name": "__Type", 211 | "description": "The fundamental unit of any GraphQL Schema is the type. There are many kinds of types in GraphQL as represented by the `__TypeKind` enum.\n\nDepending on the kind of a type, certain fields describe information about that type. Scalar types provide no information beyond a name and description, while Enum types provide their values. Object and Interface types provide the fields they describe. Abstract types, Union and Interface, provide the Object types possible at runtime. List and NonNull types compose other types.", 212 | "fields": [ 213 | { 214 | "name": "kind", 215 | "description": null, 216 | "args": [], 217 | "type": { 218 | "kind": "NON_NULL", 219 | "name": null, 220 | "ofType": { 221 | "kind": "ENUM", 222 | "name": "__TypeKind", 223 | "ofType": null 224 | } 225 | }, 226 | "isDeprecated": false, 227 | "deprecationReason": null 228 | }, 229 | { 230 | "name": "name", 231 | "description": null, 232 | "args": [], 233 | "type": { 234 | "kind": "SCALAR", 235 | "name": "String", 236 | "ofType": null 237 | }, 238 | "isDeprecated": false, 239 | "deprecationReason": null 240 | }, 241 | { 242 | "name": "description", 243 | "description": null, 244 | "args": [], 245 | "type": { 246 | "kind": "SCALAR", 247 | "name": "String", 248 | "ofType": null 249 | }, 250 | "isDeprecated": false, 251 | "deprecationReason": null 252 | }, 253 | { 254 | "name": "fields", 255 | "description": null, 256 | "args": [ 257 | { 258 | "name": "includeDeprecated", 259 | "description": null, 260 | "type": { 261 | "kind": "SCALAR", 262 | "name": "Boolean", 263 | "ofType": null 264 | }, 265 | "defaultValue": "false" 266 | } 267 | ], 268 | "type": { 269 | "kind": "LIST", 270 | "name": null, 271 | "ofType": { 272 | "kind": "NON_NULL", 273 | "name": null, 274 | "ofType": { 275 | "kind": "OBJECT", 276 | "name": "__Field", 277 | "ofType": null 278 | } 279 | } 280 | }, 281 | "isDeprecated": false, 282 | "deprecationReason": null 283 | }, 284 | { 285 | "name": "interfaces", 286 | "description": null, 287 | "args": [], 288 | "type": { 289 | "kind": "LIST", 290 | "name": null, 291 | "ofType": { 292 | "kind": "NON_NULL", 293 | "name": null, 294 | "ofType": { 295 | "kind": "OBJECT", 296 | "name": "__Type", 297 | "ofType": null 298 | } 299 | } 300 | }, 301 | "isDeprecated": false, 302 | "deprecationReason": null 303 | }, 304 | { 305 | "name": "possibleTypes", 306 | "description": null, 307 | "args": [], 308 | "type": { 309 | "kind": "LIST", 310 | "name": null, 311 | "ofType": { 312 | "kind": "NON_NULL", 313 | "name": null, 314 | "ofType": { 315 | "kind": "OBJECT", 316 | "name": "__Type", 317 | "ofType": null 318 | } 319 | } 320 | }, 321 | "isDeprecated": false, 322 | "deprecationReason": null 323 | }, 324 | { 325 | "name": "enumValues", 326 | "description": null, 327 | "args": [ 328 | { 329 | "name": "includeDeprecated", 330 | "description": null, 331 | "type": { 332 | "kind": "SCALAR", 333 | "name": "Boolean", 334 | "ofType": null 335 | }, 336 | "defaultValue": "false" 337 | } 338 | ], 339 | "type": { 340 | "kind": "LIST", 341 | "name": null, 342 | "ofType": { 343 | "kind": "NON_NULL", 344 | "name": null, 345 | "ofType": { 346 | "kind": "OBJECT", 347 | "name": "__EnumValue", 348 | "ofType": null 349 | } 350 | } 351 | }, 352 | "isDeprecated": false, 353 | "deprecationReason": null 354 | }, 355 | { 356 | "name": "inputFields", 357 | "description": null, 358 | "args": [], 359 | "type": { 360 | "kind": "LIST", 361 | "name": null, 362 | "ofType": { 363 | "kind": "NON_NULL", 364 | "name": null, 365 | "ofType": { 366 | "kind": "OBJECT", 367 | "name": "__InputValue", 368 | "ofType": null 369 | } 370 | } 371 | }, 372 | "isDeprecated": false, 373 | "deprecationReason": null 374 | }, 375 | { 376 | "name": "ofType", 377 | "description": null, 378 | "args": [], 379 | "type": { 380 | "kind": "OBJECT", 381 | "name": "__Type", 382 | "ofType": null 383 | }, 384 | "isDeprecated": false, 385 | "deprecationReason": null 386 | } 387 | ], 388 | "inputFields": null, 389 | "interfaces": [], 390 | "enumValues": null, 391 | "possibleTypes": null 392 | }, 393 | { 394 | "kind": "ENUM", 395 | "name": "__TypeKind", 396 | "description": "An enum describing what kind of type a given `__Type` is.", 397 | "fields": null, 398 | "inputFields": null, 399 | "interfaces": null, 400 | "enumValues": [ 401 | { 402 | "name": "SCALAR", 403 | "description": "Indicates this type is a scalar.", 404 | "isDeprecated": false, 405 | "deprecationReason": null 406 | }, 407 | { 408 | "name": "OBJECT", 409 | "description": "Indicates this type is an object. `fields` and `interfaces` are valid fields.", 410 | "isDeprecated": false, 411 | "deprecationReason": null 412 | }, 413 | { 414 | "name": "INTERFACE", 415 | "description": "Indicates this type is an interface. `fields` and `possibleTypes` are valid fields.", 416 | "isDeprecated": false, 417 | "deprecationReason": null 418 | }, 419 | { 420 | "name": "UNION", 421 | "description": "Indicates this type is a union. `possibleTypes` is a valid field.", 422 | "isDeprecated": false, 423 | "deprecationReason": null 424 | }, 425 | { 426 | "name": "ENUM", 427 | "description": "Indicates this type is an enum. `enumValues` is a valid field.", 428 | "isDeprecated": false, 429 | "deprecationReason": null 430 | }, 431 | { 432 | "name": "INPUT_OBJECT", 433 | "description": "Indicates this type is an input object. `inputFields` is a valid field.", 434 | "isDeprecated": false, 435 | "deprecationReason": null 436 | }, 437 | { 438 | "name": "LIST", 439 | "description": "Indicates this type is a list. `ofType` is a valid field.", 440 | "isDeprecated": false, 441 | "deprecationReason": null 442 | }, 443 | { 444 | "name": "NON_NULL", 445 | "description": "Indicates this type is a non-null. `ofType` is a valid field.", 446 | "isDeprecated": false, 447 | "deprecationReason": null 448 | } 449 | ], 450 | "possibleTypes": null 451 | }, 452 | { 453 | "kind": "SCALAR", 454 | "name": "Boolean", 455 | "description": "The `Boolean` scalar type represents `true` or `false`.", 456 | "fields": null, 457 | "inputFields": null, 458 | "interfaces": null, 459 | "enumValues": null, 460 | "possibleTypes": null 461 | }, 462 | { 463 | "kind": "OBJECT", 464 | "name": "__Field", 465 | "description": "Object and Interface types are described by a list of Fields, each of which has a name, potentially a list of arguments, and a return type.", 466 | "fields": [ 467 | { 468 | "name": "name", 469 | "description": null, 470 | "args": [], 471 | "type": { 472 | "kind": "NON_NULL", 473 | "name": null, 474 | "ofType": { 475 | "kind": "SCALAR", 476 | "name": "String", 477 | "ofType": null 478 | } 479 | }, 480 | "isDeprecated": false, 481 | "deprecationReason": null 482 | }, 483 | { 484 | "name": "description", 485 | "description": null, 486 | "args": [], 487 | "type": { 488 | "kind": "SCALAR", 489 | "name": "String", 490 | "ofType": null 491 | }, 492 | "isDeprecated": false, 493 | "deprecationReason": null 494 | }, 495 | { 496 | "name": "args", 497 | "description": null, 498 | "args": [], 499 | "type": { 500 | "kind": "NON_NULL", 501 | "name": null, 502 | "ofType": { 503 | "kind": "LIST", 504 | "name": null, 505 | "ofType": { 506 | "kind": "NON_NULL", 507 | "name": null, 508 | "ofType": { 509 | "kind": "OBJECT", 510 | "name": "__InputValue", 511 | "ofType": null 512 | } 513 | } 514 | } 515 | }, 516 | "isDeprecated": false, 517 | "deprecationReason": null 518 | }, 519 | { 520 | "name": "type", 521 | "description": null, 522 | "args": [], 523 | "type": { 524 | "kind": "NON_NULL", 525 | "name": null, 526 | "ofType": { 527 | "kind": "OBJECT", 528 | "name": "__Type", 529 | "ofType": null 530 | } 531 | }, 532 | "isDeprecated": false, 533 | "deprecationReason": null 534 | }, 535 | { 536 | "name": "isDeprecated", 537 | "description": null, 538 | "args": [], 539 | "type": { 540 | "kind": "NON_NULL", 541 | "name": null, 542 | "ofType": { 543 | "kind": "SCALAR", 544 | "name": "Boolean", 545 | "ofType": null 546 | } 547 | }, 548 | "isDeprecated": false, 549 | "deprecationReason": null 550 | }, 551 | { 552 | "name": "deprecationReason", 553 | "description": null, 554 | "args": [], 555 | "type": { 556 | "kind": "SCALAR", 557 | "name": "String", 558 | "ofType": null 559 | }, 560 | "isDeprecated": false, 561 | "deprecationReason": null 562 | } 563 | ], 564 | "inputFields": null, 565 | "interfaces": [], 566 | "enumValues": null, 567 | "possibleTypes": null 568 | }, 569 | { 570 | "kind": "OBJECT", 571 | "name": "__InputValue", 572 | "description": "Arguments provided to Fields or Directives and the input fields of an InputObject are represented as Input Values which describe their type and optionally a default value.", 573 | "fields": [ 574 | { 575 | "name": "name", 576 | "description": null, 577 | "args": [], 578 | "type": { 579 | "kind": "NON_NULL", 580 | "name": null, 581 | "ofType": { 582 | "kind": "SCALAR", 583 | "name": "String", 584 | "ofType": null 585 | } 586 | }, 587 | "isDeprecated": false, 588 | "deprecationReason": null 589 | }, 590 | { 591 | "name": "description", 592 | "description": null, 593 | "args": [], 594 | "type": { 595 | "kind": "SCALAR", 596 | "name": "String", 597 | "ofType": null 598 | }, 599 | "isDeprecated": false, 600 | "deprecationReason": null 601 | }, 602 | { 603 | "name": "type", 604 | "description": null, 605 | "args": [], 606 | "type": { 607 | "kind": "NON_NULL", 608 | "name": null, 609 | "ofType": { 610 | "kind": "OBJECT", 611 | "name": "__Type", 612 | "ofType": null 613 | } 614 | }, 615 | "isDeprecated": false, 616 | "deprecationReason": null 617 | }, 618 | { 619 | "name": "defaultValue", 620 | "description": "A GraphQL-formatted string representing the default value for this input value.", 621 | "args": [], 622 | "type": { 623 | "kind": "SCALAR", 624 | "name": "String", 625 | "ofType": null 626 | }, 627 | "isDeprecated": false, 628 | "deprecationReason": null 629 | } 630 | ], 631 | "inputFields": null, 632 | "interfaces": [], 633 | "enumValues": null, 634 | "possibleTypes": null 635 | }, 636 | { 637 | "kind": "OBJECT", 638 | "name": "__EnumValue", 639 | "description": "One possible value for a given Enum. Enum values are unique values, not a placeholder for a string or numeric value. However an Enum value is returned in a JSON response as a string.", 640 | "fields": [ 641 | { 642 | "name": "name", 643 | "description": null, 644 | "args": [], 645 | "type": { 646 | "kind": "NON_NULL", 647 | "name": null, 648 | "ofType": { 649 | "kind": "SCALAR", 650 | "name": "String", 651 | "ofType": null 652 | } 653 | }, 654 | "isDeprecated": false, 655 | "deprecationReason": null 656 | }, 657 | { 658 | "name": "description", 659 | "description": null, 660 | "args": [], 661 | "type": { 662 | "kind": "SCALAR", 663 | "name": "String", 664 | "ofType": null 665 | }, 666 | "isDeprecated": false, 667 | "deprecationReason": null 668 | }, 669 | { 670 | "name": "isDeprecated", 671 | "description": null, 672 | "args": [], 673 | "type": { 674 | "kind": "NON_NULL", 675 | "name": null, 676 | "ofType": { 677 | "kind": "SCALAR", 678 | "name": "Boolean", 679 | "ofType": null 680 | } 681 | }, 682 | "isDeprecated": false, 683 | "deprecationReason": null 684 | }, 685 | { 686 | "name": "deprecationReason", 687 | "description": null, 688 | "args": [], 689 | "type": { 690 | "kind": "SCALAR", 691 | "name": "String", 692 | "ofType": null 693 | }, 694 | "isDeprecated": false, 695 | "deprecationReason": null 696 | } 697 | ], 698 | "inputFields": null, 699 | "interfaces": [], 700 | "enumValues": null, 701 | "possibleTypes": null 702 | }, 703 | { 704 | "kind": "OBJECT", 705 | "name": "__Directive", 706 | "description": "A Directive provides a way to describe alternate runtime execution and type validation behavior in a GraphQL document.\n\nIn some cases, you need to provide options to alter GraphQL's execution behavior in ways field arguments will not suffice, such as conditionally including or skipping a field. Directives provide this by describing additional information to the executor.", 707 | "fields": [ 708 | { 709 | "name": "name", 710 | "description": null, 711 | "args": [], 712 | "type": { 713 | "kind": "NON_NULL", 714 | "name": null, 715 | "ofType": { 716 | "kind": "SCALAR", 717 | "name": "String", 718 | "ofType": null 719 | } 720 | }, 721 | "isDeprecated": false, 722 | "deprecationReason": null 723 | }, 724 | { 725 | "name": "description", 726 | "description": null, 727 | "args": [], 728 | "type": { 729 | "kind": "SCALAR", 730 | "name": "String", 731 | "ofType": null 732 | }, 733 | "isDeprecated": false, 734 | "deprecationReason": null 735 | }, 736 | { 737 | "name": "locations", 738 | "description": null, 739 | "args": [], 740 | "type": { 741 | "kind": "NON_NULL", 742 | "name": null, 743 | "ofType": { 744 | "kind": "LIST", 745 | "name": null, 746 | "ofType": { 747 | "kind": "NON_NULL", 748 | "name": null, 749 | "ofType": { 750 | "kind": "ENUM", 751 | "name": "__DirectiveLocation", 752 | "ofType": null 753 | } 754 | } 755 | } 756 | }, 757 | "isDeprecated": false, 758 | "deprecationReason": null 759 | }, 760 | { 761 | "name": "args", 762 | "description": null, 763 | "args": [], 764 | "type": { 765 | "kind": "NON_NULL", 766 | "name": null, 767 | "ofType": { 768 | "kind": "LIST", 769 | "name": null, 770 | "ofType": { 771 | "kind": "NON_NULL", 772 | "name": null, 773 | "ofType": { 774 | "kind": "OBJECT", 775 | "name": "__InputValue", 776 | "ofType": null 777 | } 778 | } 779 | } 780 | }, 781 | "isDeprecated": false, 782 | "deprecationReason": null 783 | }, 784 | { 785 | "name": "onOperation", 786 | "description": null, 787 | "args": [], 788 | "type": { 789 | "kind": "NON_NULL", 790 | "name": null, 791 | "ofType": { 792 | "kind": "SCALAR", 793 | "name": "Boolean", 794 | "ofType": null 795 | } 796 | }, 797 | "isDeprecated": true, 798 | "deprecationReason": "Use `locations`." 799 | }, 800 | { 801 | "name": "onFragment", 802 | "description": null, 803 | "args": [], 804 | "type": { 805 | "kind": "NON_NULL", 806 | "name": null, 807 | "ofType": { 808 | "kind": "SCALAR", 809 | "name": "Boolean", 810 | "ofType": null 811 | } 812 | }, 813 | "isDeprecated": true, 814 | "deprecationReason": "Use `locations`." 815 | }, 816 | { 817 | "name": "onField", 818 | "description": null, 819 | "args": [], 820 | "type": { 821 | "kind": "NON_NULL", 822 | "name": null, 823 | "ofType": { 824 | "kind": "SCALAR", 825 | "name": "Boolean", 826 | "ofType": null 827 | } 828 | }, 829 | "isDeprecated": true, 830 | "deprecationReason": "Use `locations`." 831 | } 832 | ], 833 | "inputFields": null, 834 | "interfaces": [], 835 | "enumValues": null, 836 | "possibleTypes": null 837 | }, 838 | { 839 | "kind": "ENUM", 840 | "name": "__DirectiveLocation", 841 | "description": "A Directive can be adjacent to many parts of the GraphQL language, a __DirectiveLocation describes one such possible adjacencies.", 842 | "fields": null, 843 | "inputFields": null, 844 | "interfaces": null, 845 | "enumValues": [ 846 | { 847 | "name": "QUERY", 848 | "description": "Location adjacent to a query operation.", 849 | "isDeprecated": false, 850 | "deprecationReason": null 851 | }, 852 | { 853 | "name": "MUTATION", 854 | "description": "Location adjacent to a mutation operation.", 855 | "isDeprecated": false, 856 | "deprecationReason": null 857 | }, 858 | { 859 | "name": "SUBSCRIPTION", 860 | "description": "Location adjacent to a subscription operation.", 861 | "isDeprecated": false, 862 | "deprecationReason": null 863 | }, 864 | { 865 | "name": "FIELD", 866 | "description": "Location adjacent to a field.", 867 | "isDeprecated": false, 868 | "deprecationReason": null 869 | }, 870 | { 871 | "name": "FRAGMENT_DEFINITION", 872 | "description": "Location adjacent to a fragment definition.", 873 | "isDeprecated": false, 874 | "deprecationReason": null 875 | }, 876 | { 877 | "name": "FRAGMENT_SPREAD", 878 | "description": "Location adjacent to a fragment spread.", 879 | "isDeprecated": false, 880 | "deprecationReason": null 881 | }, 882 | { 883 | "name": "INLINE_FRAGMENT", 884 | "description": "Location adjacent to an inline fragment.", 885 | "isDeprecated": false, 886 | "deprecationReason": null 887 | }, 888 | { 889 | "name": "SCHEMA", 890 | "description": "Location adjacent to a schema definition.", 891 | "isDeprecated": false, 892 | "deprecationReason": null 893 | }, 894 | { 895 | "name": "SCALAR", 896 | "description": "Location adjacent to a scalar definition.", 897 | "isDeprecated": false, 898 | "deprecationReason": null 899 | }, 900 | { 901 | "name": "OBJECT", 902 | "description": "Location adjacent to an object type definition.", 903 | "isDeprecated": false, 904 | "deprecationReason": null 905 | }, 906 | { 907 | "name": "FIELD_DEFINITION", 908 | "description": "Location adjacent to a field definition.", 909 | "isDeprecated": false, 910 | "deprecationReason": null 911 | }, 912 | { 913 | "name": "ARGUMENT_DEFINITION", 914 | "description": "Location adjacent to an argument definition.", 915 | "isDeprecated": false, 916 | "deprecationReason": null 917 | }, 918 | { 919 | "name": "INTERFACE", 920 | "description": "Location adjacent to an interface definition.", 921 | "isDeprecated": false, 922 | "deprecationReason": null 923 | }, 924 | { 925 | "name": "UNION", 926 | "description": "Location adjacent to a union definition.", 927 | "isDeprecated": false, 928 | "deprecationReason": null 929 | }, 930 | { 931 | "name": "ENUM", 932 | "description": "Location adjacent to an enum definition.", 933 | "isDeprecated": false, 934 | "deprecationReason": null 935 | }, 936 | { 937 | "name": "ENUM_VALUE", 938 | "description": "Location adjacent to an enum value definition.", 939 | "isDeprecated": false, 940 | "deprecationReason": null 941 | }, 942 | { 943 | "name": "INPUT_OBJECT", 944 | "description": "Location adjacent to an input object type definition.", 945 | "isDeprecated": false, 946 | "deprecationReason": null 947 | }, 948 | { 949 | "name": "INPUT_FIELD_DEFINITION", 950 | "description": "Location adjacent to an input object field definition.", 951 | "isDeprecated": false, 952 | "deprecationReason": null 953 | } 954 | ], 955 | "possibleTypes": null 956 | } 957 | ], 958 | "directives": [ 959 | { 960 | "name": "skip", 961 | "description": "Directs the executor to skip this field or fragment when the `if` argument is true.", 962 | "locations": [ 963 | "FIELD", 964 | "FRAGMENT_SPREAD", 965 | "INLINE_FRAGMENT" 966 | ], 967 | "args": [ 968 | { 969 | "name": "if", 970 | "description": "Skipped when true.", 971 | "type": { 972 | "kind": "NON_NULL", 973 | "name": null, 974 | "ofType": { 975 | "kind": "SCALAR", 976 | "name": "Boolean", 977 | "ofType": null 978 | } 979 | }, 980 | "defaultValue": null 981 | } 982 | ] 983 | }, 984 | { 985 | "name": "include", 986 | "description": "Directs the executor to include this field or fragment only when the `if` argument is true.", 987 | "locations": [ 988 | "FIELD", 989 | "FRAGMENT_SPREAD", 990 | "INLINE_FRAGMENT" 991 | ], 992 | "args": [ 993 | { 994 | "name": "if", 995 | "description": "Included when true.", 996 | "type": { 997 | "kind": "NON_NULL", 998 | "name": null, 999 | "ofType": { 1000 | "kind": "SCALAR", 1001 | "name": "Boolean", 1002 | "ofType": null 1003 | } 1004 | }, 1005 | "defaultValue": null 1006 | } 1007 | ] 1008 | }, 1009 | { 1010 | "name": "deprecated", 1011 | "description": "Marks an element of a GraphQL schema as no longer supported.", 1012 | "locations": [ 1013 | "FIELD_DEFINITION", 1014 | "ENUM_VALUE" 1015 | ], 1016 | "args": [ 1017 | { 1018 | "name": "reason", 1019 | "description": "Explains why this element was deprecated, usually also including a suggestion for how to access supported similar data. Formatted in [Markdown](https://daringfireball.net/projects/markdown/).", 1020 | "type": { 1021 | "kind": "SCALAR", 1022 | "name": "String", 1023 | "ofType": null 1024 | }, 1025 | "defaultValue": "\"No longer supported\"" 1026 | } 1027 | ] 1028 | } 1029 | ] 1030 | } 1031 | }, 1032 | "extensions": { 1033 | "tracing": { 1034 | "version": 1, 1035 | "startTime": "2018-03-24T21:01:39.679Z", 1036 | "endTime": "2018-03-24T21:01:39.722Z", 1037 | "duration": 42687883, 1038 | "execution": { 1039 | "resolvers": [] 1040 | } 1041 | } 1042 | } 1043 | } -------------------------------------------------------------------------------- /example/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "__schema": { 3 | "description": null, 4 | "queryType": { 5 | "name": "Query" 6 | }, 7 | "mutationType": { 8 | "name": "Mutation" 9 | }, 10 | "subscriptionType": null, 11 | "types": [ 12 | { 13 | "kind": "OBJECT", 14 | "name": "Query", 15 | "description": "", 16 | "fields": [ 17 | { 18 | "name": "todos", 19 | "description": "", 20 | "args": [], 21 | "type": { 22 | "kind": "LIST", 23 | "name": null, 24 | "ofType": { 25 | "kind": "OBJECT", 26 | "name": "Todo", 27 | "ofType": null 28 | } 29 | }, 30 | "isDeprecated": false, 31 | "deprecationReason": null 32 | }, 33 | { 34 | "name": "todo", 35 | "description": "", 36 | "args": [ 37 | { 38 | "name": "id", 39 | "description": "", 40 | "type": { 41 | "kind": "NON_NULL", 42 | "name": null, 43 | "ofType": { 44 | "kind": "SCALAR", 45 | "name": "String", 46 | "ofType": null 47 | } 48 | }, 49 | "defaultValue": null 50 | } 51 | ], 52 | "type": { 53 | "kind": "OBJECT", 54 | "name": "Todo", 55 | "ofType": null 56 | }, 57 | "isDeprecated": false, 58 | "deprecationReason": null 59 | } 60 | ], 61 | "inputFields": null, 62 | "interfaces": [], 63 | "enumValues": null, 64 | "possibleTypes": null 65 | }, 66 | { 67 | "kind": "OBJECT", 68 | "name": "Todo", 69 | "description": "", 70 | "fields": [ 71 | { 72 | "name": "id", 73 | "description": "", 74 | "args": [], 75 | "type": { 76 | "kind": "NON_NULL", 77 | "name": null, 78 | "ofType": { 79 | "kind": "SCALAR", 80 | "name": "String", 81 | "ofType": null 82 | } 83 | }, 84 | "isDeprecated": false, 85 | "deprecationReason": null 86 | }, 87 | { 88 | "name": "type", 89 | "description": "", 90 | "args": [], 91 | "type": { 92 | "kind": "NON_NULL", 93 | "name": null, 94 | "ofType": { 95 | "kind": "SCALAR", 96 | "name": "String", 97 | "ofType": null 98 | } 99 | }, 100 | "isDeprecated": false, 101 | "deprecationReason": null 102 | } 103 | ], 104 | "inputFields": null, 105 | "interfaces": [], 106 | "enumValues": null, 107 | "possibleTypes": null 108 | }, 109 | { 110 | "kind": "SCALAR", 111 | "name": "String", 112 | "description": "The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text.", 113 | "fields": null, 114 | "inputFields": null, 115 | "interfaces": null, 116 | "enumValues": null, 117 | "possibleTypes": null 118 | }, 119 | { 120 | "kind": "OBJECT", 121 | "name": "Mutation", 122 | "description": "", 123 | "fields": [ 124 | { 125 | "name": "addTodo", 126 | "description": "", 127 | "args": [ 128 | { 129 | "name": "type", 130 | "description": "", 131 | "type": { 132 | "kind": "NON_NULL", 133 | "name": null, 134 | "ofType": { 135 | "kind": "SCALAR", 136 | "name": "String", 137 | "ofType": null 138 | } 139 | }, 140 | "defaultValue": null 141 | } 142 | ], 143 | "type": { 144 | "kind": "OBJECT", 145 | "name": "Todo", 146 | "ofType": null 147 | }, 148 | "isDeprecated": false, 149 | "deprecationReason": null 150 | }, 151 | { 152 | "name": "updateTodo", 153 | "description": "", 154 | "args": [ 155 | { 156 | "name": "id", 157 | "description": "", 158 | "type": { 159 | "kind": "NON_NULL", 160 | "name": null, 161 | "ofType": { 162 | "kind": "SCALAR", 163 | "name": "String", 164 | "ofType": null 165 | } 166 | }, 167 | "defaultValue": null 168 | }, 169 | { 170 | "name": "type", 171 | "description": "", 172 | "type": { 173 | "kind": "NON_NULL", 174 | "name": null, 175 | "ofType": { 176 | "kind": "SCALAR", 177 | "name": "String", 178 | "ofType": null 179 | } 180 | }, 181 | "defaultValue": null 182 | } 183 | ], 184 | "type": { 185 | "kind": "OBJECT", 186 | "name": "Todo", 187 | "ofType": null 188 | }, 189 | "isDeprecated": false, 190 | "deprecationReason": null 191 | } 192 | ], 193 | "inputFields": null, 194 | "interfaces": [], 195 | "enumValues": null, 196 | "possibleTypes": null 197 | }, 198 | { 199 | "kind": "OBJECT", 200 | "name": "__Schema", 201 | "description": "A GraphQL Schema defines the capabilities of a GraphQL server. It exposes all available types and directives on the server, as well as the entry points for query, mutation, and subscription operations.", 202 | "fields": [ 203 | { 204 | "name": "description", 205 | "description": null, 206 | "args": [], 207 | "type": { 208 | "kind": "SCALAR", 209 | "name": "String", 210 | "ofType": null 211 | }, 212 | "isDeprecated": false, 213 | "deprecationReason": null 214 | }, 215 | { 216 | "name": "types", 217 | "description": "A list of all types supported by this server.", 218 | "args": [], 219 | "type": { 220 | "kind": "NON_NULL", 221 | "name": null, 222 | "ofType": { 223 | "kind": "LIST", 224 | "name": null, 225 | "ofType": { 226 | "kind": "NON_NULL", 227 | "name": null, 228 | "ofType": { 229 | "kind": "OBJECT", 230 | "name": "__Type", 231 | "ofType": null 232 | } 233 | } 234 | } 235 | }, 236 | "isDeprecated": false, 237 | "deprecationReason": null 238 | }, 239 | { 240 | "name": "queryType", 241 | "description": "The type that query operations will be rooted at.", 242 | "args": [], 243 | "type": { 244 | "kind": "NON_NULL", 245 | "name": null, 246 | "ofType": { 247 | "kind": "OBJECT", 248 | "name": "__Type", 249 | "ofType": null 250 | } 251 | }, 252 | "isDeprecated": false, 253 | "deprecationReason": null 254 | }, 255 | { 256 | "name": "mutationType", 257 | "description": "If this server supports mutation, the type that mutation operations will be rooted at.", 258 | "args": [], 259 | "type": { 260 | "kind": "OBJECT", 261 | "name": "__Type", 262 | "ofType": null 263 | }, 264 | "isDeprecated": false, 265 | "deprecationReason": null 266 | }, 267 | { 268 | "name": "subscriptionType", 269 | "description": "If this server support subscription, the type that subscription operations will be rooted at.", 270 | "args": [], 271 | "type": { 272 | "kind": "OBJECT", 273 | "name": "__Type", 274 | "ofType": null 275 | }, 276 | "isDeprecated": false, 277 | "deprecationReason": null 278 | }, 279 | { 280 | "name": "directives", 281 | "description": "A list of all directives supported by this server.", 282 | "args": [], 283 | "type": { 284 | "kind": "NON_NULL", 285 | "name": null, 286 | "ofType": { 287 | "kind": "LIST", 288 | "name": null, 289 | "ofType": { 290 | "kind": "NON_NULL", 291 | "name": null, 292 | "ofType": { 293 | "kind": "OBJECT", 294 | "name": "__Directive", 295 | "ofType": null 296 | } 297 | } 298 | } 299 | }, 300 | "isDeprecated": false, 301 | "deprecationReason": null 302 | } 303 | ], 304 | "inputFields": null, 305 | "interfaces": [], 306 | "enumValues": null, 307 | "possibleTypes": null 308 | }, 309 | { 310 | "kind": "OBJECT", 311 | "name": "__Type", 312 | "description": "The fundamental unit of any GraphQL Schema is the type. There are many kinds of types in GraphQL as represented by the `__TypeKind` enum.\n\nDepending on the kind of a type, certain fields describe information about that type. Scalar types provide no information beyond a name, description and optional `specifiedByUrl`, while Enum types provide their values. Object and Interface types provide the fields they describe. Abstract types, Union and Interface, provide the Object types possible at runtime. List and NonNull types compose other types.", 313 | "fields": [ 314 | { 315 | "name": "kind", 316 | "description": null, 317 | "args": [], 318 | "type": { 319 | "kind": "NON_NULL", 320 | "name": null, 321 | "ofType": { 322 | "kind": "ENUM", 323 | "name": "__TypeKind", 324 | "ofType": null 325 | } 326 | }, 327 | "isDeprecated": false, 328 | "deprecationReason": null 329 | }, 330 | { 331 | "name": "name", 332 | "description": null, 333 | "args": [], 334 | "type": { 335 | "kind": "SCALAR", 336 | "name": "String", 337 | "ofType": null 338 | }, 339 | "isDeprecated": false, 340 | "deprecationReason": null 341 | }, 342 | { 343 | "name": "description", 344 | "description": null, 345 | "args": [], 346 | "type": { 347 | "kind": "SCALAR", 348 | "name": "String", 349 | "ofType": null 350 | }, 351 | "isDeprecated": false, 352 | "deprecationReason": null 353 | }, 354 | { 355 | "name": "specifiedByUrl", 356 | "description": null, 357 | "args": [], 358 | "type": { 359 | "kind": "SCALAR", 360 | "name": "String", 361 | "ofType": null 362 | }, 363 | "isDeprecated": false, 364 | "deprecationReason": null 365 | }, 366 | { 367 | "name": "fields", 368 | "description": null, 369 | "args": [ 370 | { 371 | "name": "includeDeprecated", 372 | "description": null, 373 | "type": { 374 | "kind": "SCALAR", 375 | "name": "Boolean", 376 | "ofType": null 377 | }, 378 | "defaultValue": "false" 379 | } 380 | ], 381 | "type": { 382 | "kind": "LIST", 383 | "name": null, 384 | "ofType": { 385 | "kind": "NON_NULL", 386 | "name": null, 387 | "ofType": { 388 | "kind": "OBJECT", 389 | "name": "__Field", 390 | "ofType": null 391 | } 392 | } 393 | }, 394 | "isDeprecated": false, 395 | "deprecationReason": null 396 | }, 397 | { 398 | "name": "interfaces", 399 | "description": null, 400 | "args": [], 401 | "type": { 402 | "kind": "LIST", 403 | "name": null, 404 | "ofType": { 405 | "kind": "NON_NULL", 406 | "name": null, 407 | "ofType": { 408 | "kind": "OBJECT", 409 | "name": "__Type", 410 | "ofType": null 411 | } 412 | } 413 | }, 414 | "isDeprecated": false, 415 | "deprecationReason": null 416 | }, 417 | { 418 | "name": "possibleTypes", 419 | "description": null, 420 | "args": [], 421 | "type": { 422 | "kind": "LIST", 423 | "name": null, 424 | "ofType": { 425 | "kind": "NON_NULL", 426 | "name": null, 427 | "ofType": { 428 | "kind": "OBJECT", 429 | "name": "__Type", 430 | "ofType": null 431 | } 432 | } 433 | }, 434 | "isDeprecated": false, 435 | "deprecationReason": null 436 | }, 437 | { 438 | "name": "enumValues", 439 | "description": null, 440 | "args": [ 441 | { 442 | "name": "includeDeprecated", 443 | "description": null, 444 | "type": { 445 | "kind": "SCALAR", 446 | "name": "Boolean", 447 | "ofType": null 448 | }, 449 | "defaultValue": "false" 450 | } 451 | ], 452 | "type": { 453 | "kind": "LIST", 454 | "name": null, 455 | "ofType": { 456 | "kind": "NON_NULL", 457 | "name": null, 458 | "ofType": { 459 | "kind": "OBJECT", 460 | "name": "__EnumValue", 461 | "ofType": null 462 | } 463 | } 464 | }, 465 | "isDeprecated": false, 466 | "deprecationReason": null 467 | }, 468 | { 469 | "name": "inputFields", 470 | "description": null, 471 | "args": [], 472 | "type": { 473 | "kind": "LIST", 474 | "name": null, 475 | "ofType": { 476 | "kind": "NON_NULL", 477 | "name": null, 478 | "ofType": { 479 | "kind": "OBJECT", 480 | "name": "__InputValue", 481 | "ofType": null 482 | } 483 | } 484 | }, 485 | "isDeprecated": false, 486 | "deprecationReason": null 487 | }, 488 | { 489 | "name": "ofType", 490 | "description": null, 491 | "args": [], 492 | "type": { 493 | "kind": "OBJECT", 494 | "name": "__Type", 495 | "ofType": null 496 | }, 497 | "isDeprecated": false, 498 | "deprecationReason": null 499 | } 500 | ], 501 | "inputFields": null, 502 | "interfaces": [], 503 | "enumValues": null, 504 | "possibleTypes": null 505 | }, 506 | { 507 | "kind": "ENUM", 508 | "name": "__TypeKind", 509 | "description": "An enum describing what kind of type a given `__Type` is.", 510 | "fields": null, 511 | "inputFields": null, 512 | "interfaces": null, 513 | "enumValues": [ 514 | { 515 | "name": "SCALAR", 516 | "description": "Indicates this type is a scalar.", 517 | "isDeprecated": false, 518 | "deprecationReason": null 519 | }, 520 | { 521 | "name": "OBJECT", 522 | "description": "Indicates this type is an object. `fields` and `interfaces` are valid fields.", 523 | "isDeprecated": false, 524 | "deprecationReason": null 525 | }, 526 | { 527 | "name": "INTERFACE", 528 | "description": "Indicates this type is an interface. `fields`, `interfaces`, and `possibleTypes` are valid fields.", 529 | "isDeprecated": false, 530 | "deprecationReason": null 531 | }, 532 | { 533 | "name": "UNION", 534 | "description": "Indicates this type is a union. `possibleTypes` is a valid field.", 535 | "isDeprecated": false, 536 | "deprecationReason": null 537 | }, 538 | { 539 | "name": "ENUM", 540 | "description": "Indicates this type is an enum. `enumValues` is a valid field.", 541 | "isDeprecated": false, 542 | "deprecationReason": null 543 | }, 544 | { 545 | "name": "INPUT_OBJECT", 546 | "description": "Indicates this type is an input object. `inputFields` is a valid field.", 547 | "isDeprecated": false, 548 | "deprecationReason": null 549 | }, 550 | { 551 | "name": "LIST", 552 | "description": "Indicates this type is a list. `ofType` is a valid field.", 553 | "isDeprecated": false, 554 | "deprecationReason": null 555 | }, 556 | { 557 | "name": "NON_NULL", 558 | "description": "Indicates this type is a non-null. `ofType` is a valid field.", 559 | "isDeprecated": false, 560 | "deprecationReason": null 561 | } 562 | ], 563 | "possibleTypes": null 564 | }, 565 | { 566 | "kind": "SCALAR", 567 | "name": "Boolean", 568 | "description": "The `Boolean` scalar type represents `true` or `false`.", 569 | "fields": null, 570 | "inputFields": null, 571 | "interfaces": null, 572 | "enumValues": null, 573 | "possibleTypes": null 574 | }, 575 | { 576 | "kind": "OBJECT", 577 | "name": "__Field", 578 | "description": "Object and Interface types are described by a list of Fields, each of which has a name, potentially a list of arguments, and a return type.", 579 | "fields": [ 580 | { 581 | "name": "name", 582 | "description": null, 583 | "args": [], 584 | "type": { 585 | "kind": "NON_NULL", 586 | "name": null, 587 | "ofType": { 588 | "kind": "SCALAR", 589 | "name": "String", 590 | "ofType": null 591 | } 592 | }, 593 | "isDeprecated": false, 594 | "deprecationReason": null 595 | }, 596 | { 597 | "name": "description", 598 | "description": null, 599 | "args": [], 600 | "type": { 601 | "kind": "SCALAR", 602 | "name": "String", 603 | "ofType": null 604 | }, 605 | "isDeprecated": false, 606 | "deprecationReason": null 607 | }, 608 | { 609 | "name": "args", 610 | "description": null, 611 | "args": [], 612 | "type": { 613 | "kind": "NON_NULL", 614 | "name": null, 615 | "ofType": { 616 | "kind": "LIST", 617 | "name": null, 618 | "ofType": { 619 | "kind": "NON_NULL", 620 | "name": null, 621 | "ofType": { 622 | "kind": "OBJECT", 623 | "name": "__InputValue", 624 | "ofType": null 625 | } 626 | } 627 | } 628 | }, 629 | "isDeprecated": false, 630 | "deprecationReason": null 631 | }, 632 | { 633 | "name": "type", 634 | "description": null, 635 | "args": [], 636 | "type": { 637 | "kind": "NON_NULL", 638 | "name": null, 639 | "ofType": { 640 | "kind": "OBJECT", 641 | "name": "__Type", 642 | "ofType": null 643 | } 644 | }, 645 | "isDeprecated": false, 646 | "deprecationReason": null 647 | }, 648 | { 649 | "name": "isDeprecated", 650 | "description": null, 651 | "args": [], 652 | "type": { 653 | "kind": "NON_NULL", 654 | "name": null, 655 | "ofType": { 656 | "kind": "SCALAR", 657 | "name": "Boolean", 658 | "ofType": null 659 | } 660 | }, 661 | "isDeprecated": false, 662 | "deprecationReason": null 663 | }, 664 | { 665 | "name": "deprecationReason", 666 | "description": null, 667 | "args": [], 668 | "type": { 669 | "kind": "SCALAR", 670 | "name": "String", 671 | "ofType": null 672 | }, 673 | "isDeprecated": false, 674 | "deprecationReason": null 675 | } 676 | ], 677 | "inputFields": null, 678 | "interfaces": [], 679 | "enumValues": null, 680 | "possibleTypes": null 681 | }, 682 | { 683 | "kind": "OBJECT", 684 | "name": "__InputValue", 685 | "description": "Arguments provided to Fields or Directives and the input fields of an InputObject are represented as Input Values which describe their type and optionally a default value.", 686 | "fields": [ 687 | { 688 | "name": "name", 689 | "description": null, 690 | "args": [], 691 | "type": { 692 | "kind": "NON_NULL", 693 | "name": null, 694 | "ofType": { 695 | "kind": "SCALAR", 696 | "name": "String", 697 | "ofType": null 698 | } 699 | }, 700 | "isDeprecated": false, 701 | "deprecationReason": null 702 | }, 703 | { 704 | "name": "description", 705 | "description": null, 706 | "args": [], 707 | "type": { 708 | "kind": "SCALAR", 709 | "name": "String", 710 | "ofType": null 711 | }, 712 | "isDeprecated": false, 713 | "deprecationReason": null 714 | }, 715 | { 716 | "name": "type", 717 | "description": null, 718 | "args": [], 719 | "type": { 720 | "kind": "NON_NULL", 721 | "name": null, 722 | "ofType": { 723 | "kind": "OBJECT", 724 | "name": "__Type", 725 | "ofType": null 726 | } 727 | }, 728 | "isDeprecated": false, 729 | "deprecationReason": null 730 | }, 731 | { 732 | "name": "defaultValue", 733 | "description": "A GraphQL-formatted string representing the default value for this input value.", 734 | "args": [], 735 | "type": { 736 | "kind": "SCALAR", 737 | "name": "String", 738 | "ofType": null 739 | }, 740 | "isDeprecated": false, 741 | "deprecationReason": null 742 | } 743 | ], 744 | "inputFields": null, 745 | "interfaces": [], 746 | "enumValues": null, 747 | "possibleTypes": null 748 | }, 749 | { 750 | "kind": "OBJECT", 751 | "name": "__EnumValue", 752 | "description": "One possible value for a given Enum. Enum values are unique values, not a placeholder for a string or numeric value. However an Enum value is returned in a JSON response as a string.", 753 | "fields": [ 754 | { 755 | "name": "name", 756 | "description": null, 757 | "args": [], 758 | "type": { 759 | "kind": "NON_NULL", 760 | "name": null, 761 | "ofType": { 762 | "kind": "SCALAR", 763 | "name": "String", 764 | "ofType": null 765 | } 766 | }, 767 | "isDeprecated": false, 768 | "deprecationReason": null 769 | }, 770 | { 771 | "name": "description", 772 | "description": null, 773 | "args": [], 774 | "type": { 775 | "kind": "SCALAR", 776 | "name": "String", 777 | "ofType": null 778 | }, 779 | "isDeprecated": false, 780 | "deprecationReason": null 781 | }, 782 | { 783 | "name": "isDeprecated", 784 | "description": null, 785 | "args": [], 786 | "type": { 787 | "kind": "NON_NULL", 788 | "name": null, 789 | "ofType": { 790 | "kind": "SCALAR", 791 | "name": "Boolean", 792 | "ofType": null 793 | } 794 | }, 795 | "isDeprecated": false, 796 | "deprecationReason": null 797 | }, 798 | { 799 | "name": "deprecationReason", 800 | "description": null, 801 | "args": [], 802 | "type": { 803 | "kind": "SCALAR", 804 | "name": "String", 805 | "ofType": null 806 | }, 807 | "isDeprecated": false, 808 | "deprecationReason": null 809 | } 810 | ], 811 | "inputFields": null, 812 | "interfaces": [], 813 | "enumValues": null, 814 | "possibleTypes": null 815 | }, 816 | { 817 | "kind": "OBJECT", 818 | "name": "__Directive", 819 | "description": "A Directive provides a way to describe alternate runtime execution and type validation behavior in a GraphQL document.\n\nIn some cases, you need to provide options to alter GraphQL's execution behavior in ways field arguments will not suffice, such as conditionally including or skipping a field. Directives provide this by describing additional information to the executor.", 820 | "fields": [ 821 | { 822 | "name": "name", 823 | "description": null, 824 | "args": [], 825 | "type": { 826 | "kind": "NON_NULL", 827 | "name": null, 828 | "ofType": { 829 | "kind": "SCALAR", 830 | "name": "String", 831 | "ofType": null 832 | } 833 | }, 834 | "isDeprecated": false, 835 | "deprecationReason": null 836 | }, 837 | { 838 | "name": "description", 839 | "description": null, 840 | "args": [], 841 | "type": { 842 | "kind": "SCALAR", 843 | "name": "String", 844 | "ofType": null 845 | }, 846 | "isDeprecated": false, 847 | "deprecationReason": null 848 | }, 849 | { 850 | "name": "isRepeatable", 851 | "description": null, 852 | "args": [], 853 | "type": { 854 | "kind": "NON_NULL", 855 | "name": null, 856 | "ofType": { 857 | "kind": "SCALAR", 858 | "name": "Boolean", 859 | "ofType": null 860 | } 861 | }, 862 | "isDeprecated": false, 863 | "deprecationReason": null 864 | }, 865 | { 866 | "name": "locations", 867 | "description": null, 868 | "args": [], 869 | "type": { 870 | "kind": "NON_NULL", 871 | "name": null, 872 | "ofType": { 873 | "kind": "LIST", 874 | "name": null, 875 | "ofType": { 876 | "kind": "NON_NULL", 877 | "name": null, 878 | "ofType": { 879 | "kind": "ENUM", 880 | "name": "__DirectiveLocation", 881 | "ofType": null 882 | } 883 | } 884 | } 885 | }, 886 | "isDeprecated": false, 887 | "deprecationReason": null 888 | }, 889 | { 890 | "name": "args", 891 | "description": null, 892 | "args": [], 893 | "type": { 894 | "kind": "NON_NULL", 895 | "name": null, 896 | "ofType": { 897 | "kind": "LIST", 898 | "name": null, 899 | "ofType": { 900 | "kind": "NON_NULL", 901 | "name": null, 902 | "ofType": { 903 | "kind": "OBJECT", 904 | "name": "__InputValue", 905 | "ofType": null 906 | } 907 | } 908 | } 909 | }, 910 | "isDeprecated": false, 911 | "deprecationReason": null 912 | } 913 | ], 914 | "inputFields": null, 915 | "interfaces": [], 916 | "enumValues": null, 917 | "possibleTypes": null 918 | }, 919 | { 920 | "kind": "ENUM", 921 | "name": "__DirectiveLocation", 922 | "description": "A Directive can be adjacent to many parts of the GraphQL language, a __DirectiveLocation describes one such possible adjacencies.", 923 | "fields": null, 924 | "inputFields": null, 925 | "interfaces": null, 926 | "enumValues": [ 927 | { 928 | "name": "QUERY", 929 | "description": "Location adjacent to a query operation.", 930 | "isDeprecated": false, 931 | "deprecationReason": null 932 | }, 933 | { 934 | "name": "MUTATION", 935 | "description": "Location adjacent to a mutation operation.", 936 | "isDeprecated": false, 937 | "deprecationReason": null 938 | }, 939 | { 940 | "name": "SUBSCRIPTION", 941 | "description": "Location adjacent to a subscription operation.", 942 | "isDeprecated": false, 943 | "deprecationReason": null 944 | }, 945 | { 946 | "name": "FIELD", 947 | "description": "Location adjacent to a field.", 948 | "isDeprecated": false, 949 | "deprecationReason": null 950 | }, 951 | { 952 | "name": "FRAGMENT_DEFINITION", 953 | "description": "Location adjacent to a fragment definition.", 954 | "isDeprecated": false, 955 | "deprecationReason": null 956 | }, 957 | { 958 | "name": "FRAGMENT_SPREAD", 959 | "description": "Location adjacent to a fragment spread.", 960 | "isDeprecated": false, 961 | "deprecationReason": null 962 | }, 963 | { 964 | "name": "INLINE_FRAGMENT", 965 | "description": "Location adjacent to an inline fragment.", 966 | "isDeprecated": false, 967 | "deprecationReason": null 968 | }, 969 | { 970 | "name": "VARIABLE_DEFINITION", 971 | "description": "Location adjacent to a variable definition.", 972 | "isDeprecated": false, 973 | "deprecationReason": null 974 | }, 975 | { 976 | "name": "SCHEMA", 977 | "description": "Location adjacent to a schema definition.", 978 | "isDeprecated": false, 979 | "deprecationReason": null 980 | }, 981 | { 982 | "name": "SCALAR", 983 | "description": "Location adjacent to a scalar definition.", 984 | "isDeprecated": false, 985 | "deprecationReason": null 986 | }, 987 | { 988 | "name": "OBJECT", 989 | "description": "Location adjacent to an object type definition.", 990 | "isDeprecated": false, 991 | "deprecationReason": null 992 | }, 993 | { 994 | "name": "FIELD_DEFINITION", 995 | "description": "Location adjacent to a field definition.", 996 | "isDeprecated": false, 997 | "deprecationReason": null 998 | }, 999 | { 1000 | "name": "ARGUMENT_DEFINITION", 1001 | "description": "Location adjacent to an argument definition.", 1002 | "isDeprecated": false, 1003 | "deprecationReason": null 1004 | }, 1005 | { 1006 | "name": "INTERFACE", 1007 | "description": "Location adjacent to an interface definition.", 1008 | "isDeprecated": false, 1009 | "deprecationReason": null 1010 | }, 1011 | { 1012 | "name": "UNION", 1013 | "description": "Location adjacent to a union definition.", 1014 | "isDeprecated": false, 1015 | "deprecationReason": null 1016 | }, 1017 | { 1018 | "name": "ENUM", 1019 | "description": "Location adjacent to an enum definition.", 1020 | "isDeprecated": false, 1021 | "deprecationReason": null 1022 | }, 1023 | { 1024 | "name": "ENUM_VALUE", 1025 | "description": "Location adjacent to an enum value definition.", 1026 | "isDeprecated": false, 1027 | "deprecationReason": null 1028 | }, 1029 | { 1030 | "name": "INPUT_OBJECT", 1031 | "description": "Location adjacent to an input object type definition.", 1032 | "isDeprecated": false, 1033 | "deprecationReason": null 1034 | }, 1035 | { 1036 | "name": "INPUT_FIELD_DEFINITION", 1037 | "description": "Location adjacent to an input object field definition.", 1038 | "isDeprecated": false, 1039 | "deprecationReason": null 1040 | } 1041 | ], 1042 | "possibleTypes": null 1043 | }, 1044 | { 1045 | "kind": "SCALAR", 1046 | "name": "Upload", 1047 | "description": "The `Upload` scalar type represents a file upload promise that resolves an object containing `stream`, `filename`, `mimetype` and `encoding`.", 1048 | "fields": null, 1049 | "inputFields": null, 1050 | "interfaces": null, 1051 | "enumValues": null, 1052 | "possibleTypes": null 1053 | } 1054 | ], 1055 | "directives": [ 1056 | { 1057 | "name": "skip", 1058 | "description": "Directs the executor to skip this field or fragment when the `if` argument is true.", 1059 | "isRepeatable": false, 1060 | "locations": [ 1061 | "FIELD", 1062 | "FRAGMENT_SPREAD", 1063 | "INLINE_FRAGMENT" 1064 | ], 1065 | "args": [ 1066 | { 1067 | "name": "if", 1068 | "description": "Skipped when true.", 1069 | "type": { 1070 | "kind": "NON_NULL", 1071 | "name": null, 1072 | "ofType": { 1073 | "kind": "SCALAR", 1074 | "name": "Boolean", 1075 | "ofType": null 1076 | } 1077 | }, 1078 | "defaultValue": null 1079 | } 1080 | ] 1081 | }, 1082 | { 1083 | "name": "include", 1084 | "description": "Directs the executor to include this field or fragment only when the `if` argument is true.", 1085 | "isRepeatable": false, 1086 | "locations": [ 1087 | "FIELD", 1088 | "FRAGMENT_SPREAD", 1089 | "INLINE_FRAGMENT" 1090 | ], 1091 | "args": [ 1092 | { 1093 | "name": "if", 1094 | "description": "Included when true.", 1095 | "type": { 1096 | "kind": "NON_NULL", 1097 | "name": null, 1098 | "ofType": { 1099 | "kind": "SCALAR", 1100 | "name": "Boolean", 1101 | "ofType": null 1102 | } 1103 | }, 1104 | "defaultValue": null 1105 | } 1106 | ] 1107 | }, 1108 | { 1109 | "name": "deprecated", 1110 | "description": "Marks an element of a GraphQL schema as no longer supported.", 1111 | "isRepeatable": false, 1112 | "locations": [ 1113 | "FIELD_DEFINITION", 1114 | "ENUM_VALUE" 1115 | ], 1116 | "args": [ 1117 | { 1118 | "name": "reason", 1119 | "description": "Explains why this element was deprecated, usually also including a suggestion for how to access supported similar data. Formatted in [Markdown](https://daringfireball.net/projects/markdown/).", 1120 | "type": { 1121 | "kind": "SCALAR", 1122 | "name": "String", 1123 | "ofType": null 1124 | }, 1125 | "defaultValue": "\"No longer supported\"" 1126 | } 1127 | ] 1128 | } 1129 | ] 1130 | } 1131 | } -------------------------------------------------------------------------------- /tests/mutations.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "__schema": { 4 | "queryType": { 5 | "name": "Query" 6 | }, 7 | "mutationType": { 8 | "name": "Mutation" 9 | }, 10 | "subscriptionType": null, 11 | "types": [ 12 | { 13 | "kind": "OBJECT", 14 | "name": "Query", 15 | "description": "", 16 | "fields": [ 17 | { 18 | "name": "todos", 19 | "description": "", 20 | "args": [], 21 | "type": { 22 | "kind": "LIST", 23 | "name": null, 24 | "ofType": { 25 | "kind": "OBJECT", 26 | "name": "Todo", 27 | "ofType": null 28 | } 29 | }, 30 | "isDeprecated": false, 31 | "deprecationReason": null 32 | }, 33 | { 34 | "name": "todo", 35 | "description": "", 36 | "args": [ 37 | { 38 | "name": "id", 39 | "description": "", 40 | "type": { 41 | "kind": "NON_NULL", 42 | "name": null, 43 | "ofType": { 44 | "kind": "SCALAR", 45 | "name": "String", 46 | "ofType": null 47 | } 48 | }, 49 | "defaultValue": null 50 | } 51 | ], 52 | "type": { 53 | "kind": "OBJECT", 54 | "name": "Todo", 55 | "ofType": null 56 | }, 57 | "isDeprecated": false, 58 | "deprecationReason": null 59 | } 60 | ], 61 | "inputFields": null, 62 | "interfaces": [], 63 | "enumValues": null, 64 | "possibleTypes": null 65 | }, 66 | { 67 | "kind": "OBJECT", 68 | "name": "Todo", 69 | "description": "", 70 | "fields": [ 71 | { 72 | "name": "id", 73 | "description": "", 74 | "args": [], 75 | "type": { 76 | "kind": "NON_NULL", 77 | "name": null, 78 | "ofType": { 79 | "kind": "SCALAR", 80 | "name": "String", 81 | "ofType": null 82 | } 83 | }, 84 | "isDeprecated": false, 85 | "deprecationReason": null 86 | }, 87 | { 88 | "name": "type", 89 | "description": "", 90 | "args": [], 91 | "type": { 92 | "kind": "NON_NULL", 93 | "name": null, 94 | "ofType": { 95 | "kind": "SCALAR", 96 | "name": "String", 97 | "ofType": null 98 | } 99 | }, 100 | "isDeprecated": false, 101 | "deprecationReason": null 102 | } 103 | ], 104 | "inputFields": null, 105 | "interfaces": [], 106 | "enumValues": null, 107 | "possibleTypes": null 108 | }, 109 | { 110 | "kind": "SCALAR", 111 | "name": "String", 112 | "description": "The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text.", 113 | "fields": null, 114 | "inputFields": null, 115 | "interfaces": null, 116 | "enumValues": null, 117 | "possibleTypes": null 118 | }, 119 | { 120 | "kind": "OBJECT", 121 | "name": "Mutation", 122 | "description": "", 123 | "fields": [ 124 | { 125 | "name": "addTodo", 126 | "description": "", 127 | "args": [ 128 | { 129 | "name": "type", 130 | "description": "", 131 | "type": { 132 | "kind": "NON_NULL", 133 | "name": null, 134 | "ofType": { 135 | "kind": "SCALAR", 136 | "name": "String", 137 | "ofType": null 138 | } 139 | }, 140 | "defaultValue": null 141 | } 142 | ], 143 | "type": { 144 | "kind": "OBJECT", 145 | "name": "Todo", 146 | "ofType": null 147 | }, 148 | "isDeprecated": false, 149 | "deprecationReason": null 150 | }, 151 | { 152 | "name": "updateTodo", 153 | "description": "", 154 | "args": [ 155 | { 156 | "name": "id", 157 | "description": "", 158 | "type": { 159 | "kind": "NON_NULL", 160 | "name": null, 161 | "ofType": { 162 | "kind": "SCALAR", 163 | "name": "String", 164 | "ofType": null 165 | } 166 | }, 167 | "defaultValue": null 168 | }, 169 | { 170 | "name": "type", 171 | "description": "", 172 | "type": { 173 | "kind": "NON_NULL", 174 | "name": null, 175 | "ofType": { 176 | "kind": "SCALAR", 177 | "name": "String", 178 | "ofType": null 179 | } 180 | }, 181 | "defaultValue": null 182 | } 183 | ], 184 | "type": { 185 | "kind": "OBJECT", 186 | "name": "Todo", 187 | "ofType": null 188 | }, 189 | "isDeprecated": false, 190 | "deprecationReason": null 191 | } 192 | ], 193 | "inputFields": null, 194 | "interfaces": [], 195 | "enumValues": null, 196 | "possibleTypes": null 197 | }, 198 | { 199 | "kind": "OBJECT", 200 | "name": "__Schema", 201 | "description": "A GraphQL Schema defines the capabilities of a GraphQL server. It exposes all available types and directives on the server, as well as the entry points for query, mutation, and subscription operations.", 202 | "fields": [ 203 | { 204 | "name": "types", 205 | "description": "A list of all types supported by this server.", 206 | "args": [], 207 | "type": { 208 | "kind": "NON_NULL", 209 | "name": null, 210 | "ofType": { 211 | "kind": "LIST", 212 | "name": null, 213 | "ofType": { 214 | "kind": "NON_NULL", 215 | "name": null, 216 | "ofType": { 217 | "kind": "OBJECT", 218 | "name": "__Type", 219 | "ofType": null 220 | } 221 | } 222 | } 223 | }, 224 | "isDeprecated": false, 225 | "deprecationReason": null 226 | }, 227 | { 228 | "name": "queryType", 229 | "description": "The type that query operations will be rooted at.", 230 | "args": [], 231 | "type": { 232 | "kind": "NON_NULL", 233 | "name": null, 234 | "ofType": { 235 | "kind": "OBJECT", 236 | "name": "__Type", 237 | "ofType": null 238 | } 239 | }, 240 | "isDeprecated": false, 241 | "deprecationReason": null 242 | }, 243 | { 244 | "name": "mutationType", 245 | "description": "If this server supports mutation, the type that mutation operations will be rooted at.", 246 | "args": [], 247 | "type": { 248 | "kind": "OBJECT", 249 | "name": "__Type", 250 | "ofType": null 251 | }, 252 | "isDeprecated": false, 253 | "deprecationReason": null 254 | }, 255 | { 256 | "name": "subscriptionType", 257 | "description": "If this server support subscription, the type that subscription operations will be rooted at.", 258 | "args": [], 259 | "type": { 260 | "kind": "OBJECT", 261 | "name": "__Type", 262 | "ofType": null 263 | }, 264 | "isDeprecated": false, 265 | "deprecationReason": null 266 | }, 267 | { 268 | "name": "directives", 269 | "description": "A list of all directives supported by this server.", 270 | "args": [], 271 | "type": { 272 | "kind": "NON_NULL", 273 | "name": null, 274 | "ofType": { 275 | "kind": "LIST", 276 | "name": null, 277 | "ofType": { 278 | "kind": "NON_NULL", 279 | "name": null, 280 | "ofType": { 281 | "kind": "OBJECT", 282 | "name": "__Directive", 283 | "ofType": null 284 | } 285 | } 286 | } 287 | }, 288 | "isDeprecated": false, 289 | "deprecationReason": null 290 | } 291 | ], 292 | "inputFields": null, 293 | "interfaces": [], 294 | "enumValues": null, 295 | "possibleTypes": null 296 | }, 297 | { 298 | "kind": "OBJECT", 299 | "name": "__Type", 300 | "description": "The fundamental unit of any GraphQL Schema is the type. There are many kinds of types in GraphQL as represented by the `__TypeKind` enum.\n\nDepending on the kind of a type, certain fields describe information about that type. Scalar types provide no information beyond a name and description, while Enum types provide their values. Object and Interface types provide the fields they describe. Abstract types, Union and Interface, provide the Object types possible at runtime. List and NonNull types compose other types.", 301 | "fields": [ 302 | { 303 | "name": "kind", 304 | "description": null, 305 | "args": [], 306 | "type": { 307 | "kind": "NON_NULL", 308 | "name": null, 309 | "ofType": { 310 | "kind": "ENUM", 311 | "name": "__TypeKind", 312 | "ofType": null 313 | } 314 | }, 315 | "isDeprecated": false, 316 | "deprecationReason": null 317 | }, 318 | { 319 | "name": "name", 320 | "description": null, 321 | "args": [], 322 | "type": { 323 | "kind": "SCALAR", 324 | "name": "String", 325 | "ofType": null 326 | }, 327 | "isDeprecated": false, 328 | "deprecationReason": null 329 | }, 330 | { 331 | "name": "description", 332 | "description": null, 333 | "args": [], 334 | "type": { 335 | "kind": "SCALAR", 336 | "name": "String", 337 | "ofType": null 338 | }, 339 | "isDeprecated": false, 340 | "deprecationReason": null 341 | }, 342 | { 343 | "name": "fields", 344 | "description": null, 345 | "args": [ 346 | { 347 | "name": "includeDeprecated", 348 | "description": null, 349 | "type": { 350 | "kind": "SCALAR", 351 | "name": "Boolean", 352 | "ofType": null 353 | }, 354 | "defaultValue": "false" 355 | } 356 | ], 357 | "type": { 358 | "kind": "LIST", 359 | "name": null, 360 | "ofType": { 361 | "kind": "NON_NULL", 362 | "name": null, 363 | "ofType": { 364 | "kind": "OBJECT", 365 | "name": "__Field", 366 | "ofType": null 367 | } 368 | } 369 | }, 370 | "isDeprecated": false, 371 | "deprecationReason": null 372 | }, 373 | { 374 | "name": "interfaces", 375 | "description": null, 376 | "args": [], 377 | "type": { 378 | "kind": "LIST", 379 | "name": null, 380 | "ofType": { 381 | "kind": "NON_NULL", 382 | "name": null, 383 | "ofType": { 384 | "kind": "OBJECT", 385 | "name": "__Type", 386 | "ofType": null 387 | } 388 | } 389 | }, 390 | "isDeprecated": false, 391 | "deprecationReason": null 392 | }, 393 | { 394 | "name": "possibleTypes", 395 | "description": null, 396 | "args": [], 397 | "type": { 398 | "kind": "LIST", 399 | "name": null, 400 | "ofType": { 401 | "kind": "NON_NULL", 402 | "name": null, 403 | "ofType": { 404 | "kind": "OBJECT", 405 | "name": "__Type", 406 | "ofType": null 407 | } 408 | } 409 | }, 410 | "isDeprecated": false, 411 | "deprecationReason": null 412 | }, 413 | { 414 | "name": "enumValues", 415 | "description": null, 416 | "args": [ 417 | { 418 | "name": "includeDeprecated", 419 | "description": null, 420 | "type": { 421 | "kind": "SCALAR", 422 | "name": "Boolean", 423 | "ofType": null 424 | }, 425 | "defaultValue": "false" 426 | } 427 | ], 428 | "type": { 429 | "kind": "LIST", 430 | "name": null, 431 | "ofType": { 432 | "kind": "NON_NULL", 433 | "name": null, 434 | "ofType": { 435 | "kind": "OBJECT", 436 | "name": "__EnumValue", 437 | "ofType": null 438 | } 439 | } 440 | }, 441 | "isDeprecated": false, 442 | "deprecationReason": null 443 | }, 444 | { 445 | "name": "inputFields", 446 | "description": null, 447 | "args": [], 448 | "type": { 449 | "kind": "LIST", 450 | "name": null, 451 | "ofType": { 452 | "kind": "NON_NULL", 453 | "name": null, 454 | "ofType": { 455 | "kind": "OBJECT", 456 | "name": "__InputValue", 457 | "ofType": null 458 | } 459 | } 460 | }, 461 | "isDeprecated": false, 462 | "deprecationReason": null 463 | }, 464 | { 465 | "name": "ofType", 466 | "description": null, 467 | "args": [], 468 | "type": { 469 | "kind": "OBJECT", 470 | "name": "__Type", 471 | "ofType": null 472 | }, 473 | "isDeprecated": false, 474 | "deprecationReason": null 475 | } 476 | ], 477 | "inputFields": null, 478 | "interfaces": [], 479 | "enumValues": null, 480 | "possibleTypes": null 481 | }, 482 | { 483 | "kind": "ENUM", 484 | "name": "__TypeKind", 485 | "description": "An enum describing what kind of type a given `__Type` is.", 486 | "fields": null, 487 | "inputFields": null, 488 | "interfaces": null, 489 | "enumValues": [ 490 | { 491 | "name": "SCALAR", 492 | "description": "Indicates this type is a scalar.", 493 | "isDeprecated": false, 494 | "deprecationReason": null 495 | }, 496 | { 497 | "name": "OBJECT", 498 | "description": "Indicates this type is an object. `fields` and `interfaces` are valid fields.", 499 | "isDeprecated": false, 500 | "deprecationReason": null 501 | }, 502 | { 503 | "name": "INTERFACE", 504 | "description": "Indicates this type is an interface. `fields` and `possibleTypes` are valid fields.", 505 | "isDeprecated": false, 506 | "deprecationReason": null 507 | }, 508 | { 509 | "name": "UNION", 510 | "description": "Indicates this type is a union. `possibleTypes` is a valid field.", 511 | "isDeprecated": false, 512 | "deprecationReason": null 513 | }, 514 | { 515 | "name": "ENUM", 516 | "description": "Indicates this type is an enum. `enumValues` is a valid field.", 517 | "isDeprecated": false, 518 | "deprecationReason": null 519 | }, 520 | { 521 | "name": "INPUT_OBJECT", 522 | "description": "Indicates this type is an input object. `inputFields` is a valid field.", 523 | "isDeprecated": false, 524 | "deprecationReason": null 525 | }, 526 | { 527 | "name": "LIST", 528 | "description": "Indicates this type is a list. `ofType` is a valid field.", 529 | "isDeprecated": false, 530 | "deprecationReason": null 531 | }, 532 | { 533 | "name": "NON_NULL", 534 | "description": "Indicates this type is a non-null. `ofType` is a valid field.", 535 | "isDeprecated": false, 536 | "deprecationReason": null 537 | } 538 | ], 539 | "possibleTypes": null 540 | }, 541 | { 542 | "kind": "SCALAR", 543 | "name": "Boolean", 544 | "description": "The `Boolean` scalar type represents `true` or `false`.", 545 | "fields": null, 546 | "inputFields": null, 547 | "interfaces": null, 548 | "enumValues": null, 549 | "possibleTypes": null 550 | }, 551 | { 552 | "kind": "OBJECT", 553 | "name": "__Field", 554 | "description": "Object and Interface types are described by a list of Fields, each of which has a name, potentially a list of arguments, and a return type.", 555 | "fields": [ 556 | { 557 | "name": "name", 558 | "description": null, 559 | "args": [], 560 | "type": { 561 | "kind": "NON_NULL", 562 | "name": null, 563 | "ofType": { 564 | "kind": "SCALAR", 565 | "name": "String", 566 | "ofType": null 567 | } 568 | }, 569 | "isDeprecated": false, 570 | "deprecationReason": null 571 | }, 572 | { 573 | "name": "description", 574 | "description": null, 575 | "args": [], 576 | "type": { 577 | "kind": "SCALAR", 578 | "name": "String", 579 | "ofType": null 580 | }, 581 | "isDeprecated": false, 582 | "deprecationReason": null 583 | }, 584 | { 585 | "name": "args", 586 | "description": null, 587 | "args": [], 588 | "type": { 589 | "kind": "NON_NULL", 590 | "name": null, 591 | "ofType": { 592 | "kind": "LIST", 593 | "name": null, 594 | "ofType": { 595 | "kind": "NON_NULL", 596 | "name": null, 597 | "ofType": { 598 | "kind": "OBJECT", 599 | "name": "__InputValue", 600 | "ofType": null 601 | } 602 | } 603 | } 604 | }, 605 | "isDeprecated": false, 606 | "deprecationReason": null 607 | }, 608 | { 609 | "name": "type", 610 | "description": null, 611 | "args": [], 612 | "type": { 613 | "kind": "NON_NULL", 614 | "name": null, 615 | "ofType": { 616 | "kind": "OBJECT", 617 | "name": "__Type", 618 | "ofType": null 619 | } 620 | }, 621 | "isDeprecated": false, 622 | "deprecationReason": null 623 | }, 624 | { 625 | "name": "isDeprecated", 626 | "description": null, 627 | "args": [], 628 | "type": { 629 | "kind": "NON_NULL", 630 | "name": null, 631 | "ofType": { 632 | "kind": "SCALAR", 633 | "name": "Boolean", 634 | "ofType": null 635 | } 636 | }, 637 | "isDeprecated": false, 638 | "deprecationReason": null 639 | }, 640 | { 641 | "name": "deprecationReason", 642 | "description": null, 643 | "args": [], 644 | "type": { 645 | "kind": "SCALAR", 646 | "name": "String", 647 | "ofType": null 648 | }, 649 | "isDeprecated": false, 650 | "deprecationReason": null 651 | } 652 | ], 653 | "inputFields": null, 654 | "interfaces": [], 655 | "enumValues": null, 656 | "possibleTypes": null 657 | }, 658 | { 659 | "kind": "OBJECT", 660 | "name": "__InputValue", 661 | "description": "Arguments provided to Fields or Directives and the input fields of an InputObject are represented as Input Values which describe their type and optionally a default value.", 662 | "fields": [ 663 | { 664 | "name": "name", 665 | "description": null, 666 | "args": [], 667 | "type": { 668 | "kind": "NON_NULL", 669 | "name": null, 670 | "ofType": { 671 | "kind": "SCALAR", 672 | "name": "String", 673 | "ofType": null 674 | } 675 | }, 676 | "isDeprecated": false, 677 | "deprecationReason": null 678 | }, 679 | { 680 | "name": "description", 681 | "description": null, 682 | "args": [], 683 | "type": { 684 | "kind": "SCALAR", 685 | "name": "String", 686 | "ofType": null 687 | }, 688 | "isDeprecated": false, 689 | "deprecationReason": null 690 | }, 691 | { 692 | "name": "type", 693 | "description": null, 694 | "args": [], 695 | "type": { 696 | "kind": "NON_NULL", 697 | "name": null, 698 | "ofType": { 699 | "kind": "OBJECT", 700 | "name": "__Type", 701 | "ofType": null 702 | } 703 | }, 704 | "isDeprecated": false, 705 | "deprecationReason": null 706 | }, 707 | { 708 | "name": "defaultValue", 709 | "description": "A GraphQL-formatted string representing the default value for this input value.", 710 | "args": [], 711 | "type": { 712 | "kind": "SCALAR", 713 | "name": "String", 714 | "ofType": null 715 | }, 716 | "isDeprecated": false, 717 | "deprecationReason": null 718 | } 719 | ], 720 | "inputFields": null, 721 | "interfaces": [], 722 | "enumValues": null, 723 | "possibleTypes": null 724 | }, 725 | { 726 | "kind": "OBJECT", 727 | "name": "__EnumValue", 728 | "description": "One possible value for a given Enum. Enum values are unique values, not a placeholder for a string or numeric value. However an Enum value is returned in a JSON response as a string.", 729 | "fields": [ 730 | { 731 | "name": "name", 732 | "description": null, 733 | "args": [], 734 | "type": { 735 | "kind": "NON_NULL", 736 | "name": null, 737 | "ofType": { 738 | "kind": "SCALAR", 739 | "name": "String", 740 | "ofType": null 741 | } 742 | }, 743 | "isDeprecated": false, 744 | "deprecationReason": null 745 | }, 746 | { 747 | "name": "description", 748 | "description": null, 749 | "args": [], 750 | "type": { 751 | "kind": "SCALAR", 752 | "name": "String", 753 | "ofType": null 754 | }, 755 | "isDeprecated": false, 756 | "deprecationReason": null 757 | }, 758 | { 759 | "name": "isDeprecated", 760 | "description": null, 761 | "args": [], 762 | "type": { 763 | "kind": "NON_NULL", 764 | "name": null, 765 | "ofType": { 766 | "kind": "SCALAR", 767 | "name": "Boolean", 768 | "ofType": null 769 | } 770 | }, 771 | "isDeprecated": false, 772 | "deprecationReason": null 773 | }, 774 | { 775 | "name": "deprecationReason", 776 | "description": null, 777 | "args": [], 778 | "type": { 779 | "kind": "SCALAR", 780 | "name": "String", 781 | "ofType": null 782 | }, 783 | "isDeprecated": false, 784 | "deprecationReason": null 785 | } 786 | ], 787 | "inputFields": null, 788 | "interfaces": [], 789 | "enumValues": null, 790 | "possibleTypes": null 791 | }, 792 | { 793 | "kind": "OBJECT", 794 | "name": "__Directive", 795 | "description": "A Directive provides a way to describe alternate runtime execution and type validation behavior in a GraphQL document.\n\nIn some cases, you need to provide options to alter GraphQL's execution behavior in ways field arguments will not suffice, such as conditionally including or skipping a field. Directives provide this by describing additional information to the executor.", 796 | "fields": [ 797 | { 798 | "name": "name", 799 | "description": null, 800 | "args": [], 801 | "type": { 802 | "kind": "NON_NULL", 803 | "name": null, 804 | "ofType": { 805 | "kind": "SCALAR", 806 | "name": "String", 807 | "ofType": null 808 | } 809 | }, 810 | "isDeprecated": false, 811 | "deprecationReason": null 812 | }, 813 | { 814 | "name": "description", 815 | "description": null, 816 | "args": [], 817 | "type": { 818 | "kind": "SCALAR", 819 | "name": "String", 820 | "ofType": null 821 | }, 822 | "isDeprecated": false, 823 | "deprecationReason": null 824 | }, 825 | { 826 | "name": "locations", 827 | "description": null, 828 | "args": [], 829 | "type": { 830 | "kind": "NON_NULL", 831 | "name": null, 832 | "ofType": { 833 | "kind": "LIST", 834 | "name": null, 835 | "ofType": { 836 | "kind": "NON_NULL", 837 | "name": null, 838 | "ofType": { 839 | "kind": "ENUM", 840 | "name": "__DirectiveLocation", 841 | "ofType": null 842 | } 843 | } 844 | } 845 | }, 846 | "isDeprecated": false, 847 | "deprecationReason": null 848 | }, 849 | { 850 | "name": "args", 851 | "description": null, 852 | "args": [], 853 | "type": { 854 | "kind": "NON_NULL", 855 | "name": null, 856 | "ofType": { 857 | "kind": "LIST", 858 | "name": null, 859 | "ofType": { 860 | "kind": "NON_NULL", 861 | "name": null, 862 | "ofType": { 863 | "kind": "OBJECT", 864 | "name": "__InputValue", 865 | "ofType": null 866 | } 867 | } 868 | } 869 | }, 870 | "isDeprecated": false, 871 | "deprecationReason": null 872 | }, 873 | { 874 | "name": "onOperation", 875 | "description": null, 876 | "args": [], 877 | "type": { 878 | "kind": "NON_NULL", 879 | "name": null, 880 | "ofType": { 881 | "kind": "SCALAR", 882 | "name": "Boolean", 883 | "ofType": null 884 | } 885 | }, 886 | "isDeprecated": true, 887 | "deprecationReason": "Use `locations`." 888 | }, 889 | { 890 | "name": "onFragment", 891 | "description": null, 892 | "args": [], 893 | "type": { 894 | "kind": "NON_NULL", 895 | "name": null, 896 | "ofType": { 897 | "kind": "SCALAR", 898 | "name": "Boolean", 899 | "ofType": null 900 | } 901 | }, 902 | "isDeprecated": true, 903 | "deprecationReason": "Use `locations`." 904 | }, 905 | { 906 | "name": "onField", 907 | "description": null, 908 | "args": [], 909 | "type": { 910 | "kind": "NON_NULL", 911 | "name": null, 912 | "ofType": { 913 | "kind": "SCALAR", 914 | "name": "Boolean", 915 | "ofType": null 916 | } 917 | }, 918 | "isDeprecated": true, 919 | "deprecationReason": "Use `locations`." 920 | } 921 | ], 922 | "inputFields": null, 923 | "interfaces": [], 924 | "enumValues": null, 925 | "possibleTypes": null 926 | }, 927 | { 928 | "kind": "ENUM", 929 | "name": "__DirectiveLocation", 930 | "description": "A Directive can be adjacent to many parts of the GraphQL language, a __DirectiveLocation describes one such possible adjacencies.", 931 | "fields": null, 932 | "inputFields": null, 933 | "interfaces": null, 934 | "enumValues": [ 935 | { 936 | "name": "QUERY", 937 | "description": "Location adjacent to a query operation.", 938 | "isDeprecated": false, 939 | "deprecationReason": null 940 | }, 941 | { 942 | "name": "MUTATION", 943 | "description": "Location adjacent to a mutation operation.", 944 | "isDeprecated": false, 945 | "deprecationReason": null 946 | }, 947 | { 948 | "name": "SUBSCRIPTION", 949 | "description": "Location adjacent to a subscription operation.", 950 | "isDeprecated": false, 951 | "deprecationReason": null 952 | }, 953 | { 954 | "name": "FIELD", 955 | "description": "Location adjacent to a field.", 956 | "isDeprecated": false, 957 | "deprecationReason": null 958 | }, 959 | { 960 | "name": "FRAGMENT_DEFINITION", 961 | "description": "Location adjacent to a fragment definition.", 962 | "isDeprecated": false, 963 | "deprecationReason": null 964 | }, 965 | { 966 | "name": "FRAGMENT_SPREAD", 967 | "description": "Location adjacent to a fragment spread.", 968 | "isDeprecated": false, 969 | "deprecationReason": null 970 | }, 971 | { 972 | "name": "INLINE_FRAGMENT", 973 | "description": "Location adjacent to an inline fragment.", 974 | "isDeprecated": false, 975 | "deprecationReason": null 976 | }, 977 | { 978 | "name": "SCHEMA", 979 | "description": "Location adjacent to a schema definition.", 980 | "isDeprecated": false, 981 | "deprecationReason": null 982 | }, 983 | { 984 | "name": "SCALAR", 985 | "description": "Location adjacent to a scalar definition.", 986 | "isDeprecated": false, 987 | "deprecationReason": null 988 | }, 989 | { 990 | "name": "OBJECT", 991 | "description": "Location adjacent to an object type definition.", 992 | "isDeprecated": false, 993 | "deprecationReason": null 994 | }, 995 | { 996 | "name": "FIELD_DEFINITION", 997 | "description": "Location adjacent to a field definition.", 998 | "isDeprecated": false, 999 | "deprecationReason": null 1000 | }, 1001 | { 1002 | "name": "ARGUMENT_DEFINITION", 1003 | "description": "Location adjacent to an argument definition.", 1004 | "isDeprecated": false, 1005 | "deprecationReason": null 1006 | }, 1007 | { 1008 | "name": "INTERFACE", 1009 | "description": "Location adjacent to an interface definition.", 1010 | "isDeprecated": false, 1011 | "deprecationReason": null 1012 | }, 1013 | { 1014 | "name": "UNION", 1015 | "description": "Location adjacent to a union definition.", 1016 | "isDeprecated": false, 1017 | "deprecationReason": null 1018 | }, 1019 | { 1020 | "name": "ENUM", 1021 | "description": "Location adjacent to an enum definition.", 1022 | "isDeprecated": false, 1023 | "deprecationReason": null 1024 | }, 1025 | { 1026 | "name": "ENUM_VALUE", 1027 | "description": "Location adjacent to an enum value definition.", 1028 | "isDeprecated": false, 1029 | "deprecationReason": null 1030 | }, 1031 | { 1032 | "name": "INPUT_OBJECT", 1033 | "description": "Location adjacent to an input object type definition.", 1034 | "isDeprecated": false, 1035 | "deprecationReason": null 1036 | }, 1037 | { 1038 | "name": "INPUT_FIELD_DEFINITION", 1039 | "description": "Location adjacent to an input object field definition.", 1040 | "isDeprecated": false, 1041 | "deprecationReason": null 1042 | } 1043 | ], 1044 | "possibleTypes": null 1045 | } 1046 | ], 1047 | "directives": [ 1048 | { 1049 | "name": "skip", 1050 | "description": "Directs the executor to skip this field or fragment when the `if` argument is true.", 1051 | "locations": [ 1052 | "FIELD", 1053 | "FRAGMENT_SPREAD", 1054 | "INLINE_FRAGMENT" 1055 | ], 1056 | "args": [ 1057 | { 1058 | "name": "if", 1059 | "description": "Skipped when true.", 1060 | "type": { 1061 | "kind": "NON_NULL", 1062 | "name": null, 1063 | "ofType": { 1064 | "kind": "SCALAR", 1065 | "name": "Boolean", 1066 | "ofType": null 1067 | } 1068 | }, 1069 | "defaultValue": null 1070 | } 1071 | ] 1072 | }, 1073 | { 1074 | "name": "include", 1075 | "description": "Directs the executor to include this field or fragment only when the `if` argument is true.", 1076 | "locations": [ 1077 | "FIELD", 1078 | "FRAGMENT_SPREAD", 1079 | "INLINE_FRAGMENT" 1080 | ], 1081 | "args": [ 1082 | { 1083 | "name": "if", 1084 | "description": "Included when true.", 1085 | "type": { 1086 | "kind": "NON_NULL", 1087 | "name": null, 1088 | "ofType": { 1089 | "kind": "SCALAR", 1090 | "name": "Boolean", 1091 | "ofType": null 1092 | } 1093 | }, 1094 | "defaultValue": null 1095 | } 1096 | ] 1097 | }, 1098 | { 1099 | "name": "deprecated", 1100 | "description": "Marks an element of a GraphQL schema as no longer supported.", 1101 | "locations": [ 1102 | "FIELD_DEFINITION", 1103 | "ENUM_VALUE" 1104 | ], 1105 | "args": [ 1106 | { 1107 | "name": "reason", 1108 | "description": "Explains why this element was deprecated, usually also including a suggestion for how to access supported similar data. Formatted in [Markdown](https://daringfireball.net/projects/markdown/).", 1109 | "type": { 1110 | "kind": "SCALAR", 1111 | "name": "String", 1112 | "ofType": null 1113 | }, 1114 | "defaultValue": "\"No longer supported\"" 1115 | } 1116 | ] 1117 | } 1118 | ] 1119 | } 1120 | }, 1121 | "extensions": { 1122 | "tracing": { 1123 | "version": 1, 1124 | "startTime": "2018-03-24T22:29:59.753Z", 1125 | "endTime": "2018-03-24T22:29:59.794Z", 1126 | "duration": 41261309, 1127 | "execution": { 1128 | "resolvers": [] 1129 | } 1130 | } 1131 | } 1132 | } --------------------------------------------------------------------------------