├── .gitignore ├── .npmignore ├── .travis.yml ├── ARCHITECTURE.md ├── LICENSE ├── README.md ├── docs ├── Makefile ├── conf.py ├── incremental-adoption.rst ├── index.rst ├── quickstart.rst ├── relay │ ├── connection.rst │ ├── index.rst │ ├── mutations.rst │ └── nodes.rst ├── requirements.txt └── types │ ├── enums.rst │ ├── index.rst │ ├── interfaces.rst │ ├── list-and-nonnull.rst │ ├── objecttypes.rst │ ├── scalars.rst │ ├── schema.rst │ └── unions.rst ├── examples ├── basic.js ├── complex-ts.ts ├── starwars-raw │ ├── data.js │ ├── schema.js │ └── test │ │ ├── __snapshots__ │ │ └── index.js.snap │ │ └── index.js ├── starwars-ts │ ├── data.ts │ ├── schema.ts │ └── test │ │ ├── __snapshots__ │ │ └── index.ts.snap │ │ └── index.ts └── starwars │ ├── data.js │ ├── schema.js │ └── test │ ├── __snapshots__ │ └── index.js.snap │ └── index.js ├── package.json ├── scripts └── preprocessor.js ├── src ├── __tests__ │ └── reflection.ts ├── definitions │ ├── __tests__ │ │ ├── __snapshots__ │ │ │ ├── inputobjecttype.ts.snap │ │ │ ├── objecttype.ts.snap │ │ │ └── schema.ts.snap │ │ ├── enum.ts │ │ ├── inputobjecttype.ts │ │ ├── interface.ts │ │ ├── objecttype.ts │ │ ├── scalars.ts │ │ ├── schema.ts │ │ ├── structures.ts │ │ └── unionttype.ts │ ├── enum.ts │ ├── field.ts │ ├── index.ts │ ├── inputfield.ts │ ├── inputobjecttype.ts │ ├── interfacetype.ts │ ├── objecttype.ts │ ├── scalars.ts │ ├── schema.ts │ ├── structures.ts │ ├── uniontype.ts │ └── utils.ts ├── graphql-iso-date.d.ts ├── index.ts └── reflection.ts ├── tsconfig.json ├── tslint.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | lib 3 | 4 | # Visual Studio Code workspace settings 5 | .vscode/* 6 | !.vscode/settings.json 7 | !.vscode/tasks.json 8 | !.vscode/launch.json 9 | !.vscode/extensions.json 10 | /coverage 11 | /.vscode 12 | /yarn-error.log 13 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | test/ 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | # Allow Travis tests to run in containers. 4 | sudo: false 5 | 6 | node_js: 7 | # - "4" 8 | - "6" 9 | - "8" 10 | 11 | cache: 12 | directories: 13 | - $HOME/.npm 14 | 15 | install: 16 | - npm config set spin=false 17 | - npm install -g coveralls 18 | - npm install 19 | - npm install graphql@$GRAPHQL_VERSION 20 | 21 | script: 22 | - npm run lint 23 | - npm test -- --coverage 24 | - coveralls < ./coverage/lcov.info || true # if coveralls doesn't have it covered 25 | 26 | env: 27 | # - GRAPHQL_VERSION='^0.10' 28 | # - GRAPHQL_VERSION='^0.11' 29 | - GRAPHQL_VERSION='^0.12' 30 | -------------------------------------------------------------------------------- /ARCHITECTURE.md: -------------------------------------------------------------------------------- 1 | # A sound GraphQL type system. 2 | 3 | Since I started working in Graphene/GraphQL I always wondered what would be the 4 | framework I wish to use to write my schemas with. To accomplish this mission I 5 | sneaked into almost all different GraphQL server implementations to see what 6 | Graphene can learn from them. 7 | 8 | In each of the different GraphQL server implementations there is a clear 9 | separation between GraphQL types and the corresponding data types, including 10 | Graphene-Python (except on Juniper, Rust GraphQL framework). 11 | 12 | ```js 13 | // The data type 14 | type User = { 15 | name: string 16 | }; 17 | 18 | // The GraphQL type 19 | var UserType = new GraphQLObjectType({ 20 | name: "UserType", 21 | fields: { 22 | name: { 23 | type: GraphQLString 24 | } 25 | } 26 | }); 27 | ``` 28 | 29 | After talking in the GraphQL Summit (anonuncing Graphene-JS) and watching some 30 | [insightful talks](https://www.youtube.com/watch?v=9czIsWUoQJY) it started to 31 | become obvious **how important was to close the gap between the GraphQL 32 | and the data types**. 33 | 34 | Not just because of the willing of a more stable long-term solution, but also 35 | because it will lead to ease the integration and use of GraphQL. 36 | 37 | # Introducing Graphene-JS 38 | 39 | Today, I'm presenting a revamped version of Graphene-JS. 40 | Designed to ease the integration of GraphQL in your current js architecture, 41 | by just... **by just using decorators**. 42 | 43 | This way you can **reuse the data types** you were using before, in the 44 | same fashion you do with your models on MobX or TypeORM. 45 | 46 | Is. That. Simple. 47 | 48 | ```js 49 | import { Schema, ObjectType, Field } from "graphene-js"; 50 | 51 | @ObjectType() 52 | class Query { 53 | @Field(String) 54 | hello() { 55 | return "World"; 56 | } 57 | } 58 | 59 | // Bonus: You can use the schema as any other instance of GraphQLSchema. 60 | var schema = new Schema({ query: Query }); 61 | ``` 62 | 63 | Thanks to this, you will be able to use GraphQL in a much more friendly 64 | way, with less code and improved readability. 65 | 66 | In our examples, we moved from a [170 LOC schema](https://github.com/graphql-js/graphene/blob/master/examples/starwars-raw/schema.js) to a very easy to read [100 LOC schema](https://github.com/graphql-js/graphene/blob/master/examples/starwars-ts/schema.ts). 67 | 68 | ## Incremental adoption 69 | 70 | For us is very important that GraphQL developers can adopt Graphene in a 71 | incremental way. 72 | 73 | Because of that, Graphene-JS has **full interoperability** with GraphQL types. 74 | 75 | ### Want to use GraphQL-js types in Graphene? 76 | 77 | Easy, just reference the GraphQL types: 78 | 79 | ```js 80 | @ObjectType() 81 | class User { 82 | @Field(GraphQLString) // Note we are using GraphQLString instead of string 83 | name; 84 | } 85 | ``` 86 | 87 | Same applies for interfaces of ObjectTypes, types of InputFields, arguments... 88 | 89 | ### Want to use Graphene types in GraphQL-js? 90 | 91 | Simple, just use `getGraphQLType`: 92 | 93 | ```js 94 | import { getGraphQLType } from "graphene-js"; 95 | 96 | var QueryType = new GraphQLObjectType({ 97 | name: "QueryType", 98 | fields: { 99 | viewer: { 100 | type: getGraphQLType(User) 101 | } 102 | } 103 | }); 104 | ``` 105 | 106 | ## Future improvements 107 | 108 | Graphene-JS is designed with types in mind. 109 | We foresee using the type reflection API from Typescript in the future, 110 | so we we can directly skip the need of indicating the Field types manually 111 | to let Graphene detect them automatically. 112 | 113 | So instead of typing: 114 | 115 | ```typescript 116 | @ObjectType() 117 | class User { 118 | @Field(String) name: string; 119 | } 120 | ``` 121 | 122 | To simply do: 123 | 124 | ```typescript 125 | @ObjectType() 126 | class User { 127 | @Field() name: string; 128 | } 129 | ``` 130 | 131 | Something similar could be achieved in Flow by using a babel plugin. 132 | 133 | ## Nice side effects 134 | 135 | Thanks to [var hoisting](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/var#var_hoisting), we can self-reference a type very easily with Graphene-JS. 136 | 137 | Like: 138 | 139 | ```js 140 | @ObjectType() 141 | class User { 142 | // Child: What kind of trickery is this one, mum? 143 | // Mum: is called variable hoisting, my son. Don't thank me, thank JS. 144 | @Field([User]) 145 | friends() { 146 | return [lee, oleg, johannes, sashko]; 147 | } 148 | } 149 | ``` 150 | 151 | Want to try it? You are just a `npm install graphene-js` away! :) 152 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017-present Graphene. 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [Graphene-JS](http://graphene-js.org) [![Build Status](https://travis-ci.org/graphql-js/graphene.svg?branch=master)](https://travis-ci.org/graphql-js/graphene) [![PyPI version](https://badge.fury.io/js/graphene-js.svg)](https://badge.fury.io/js/graphene-js) [![Coverage Status](https://coveralls.io/repos/graphql-js/graphene/badge.svg?branch=master&service=github)](https://coveralls.io/github/graphql-js/graphene?branch=master) 2 | 3 | [Graphene-JS](http://graphene-js.org) is a JS framework for building GraphQL schemas/types fast and easily. 4 | 5 | * **Easy to use:** Graphene helps you use GraphQL in Javascript without effort. 6 | * **Relay:** Graphene has builtin support for Relay. (_on the works_) 7 | * **Data agnostic:** Graphene supports any kind of data source: SQL (Sequelize), NoSQL, custom objects, etc. 8 | We believe that by providing a complete API you could plug Graphene-JS anywhere your data lives and make your data available 9 | through GraphQL. 10 | 11 | Check also the [architecture docs](https://github.com/graphql-js/graphene/blob/master/ARCHITECTURE.md) to see how Graphene-JS is architected to ease the development of GraphQL in JS. 12 | 13 | ## Integrations 14 | 15 | Graphene has multiple integrations with different frameworks: 16 | 17 | | integration | Package | 18 | | ----------- | ----------------------------------------------------------------------- | 19 | | Sequelize | [graphene-sequelize](https://github.com/graphql-js/graphene-sequelize/) | 20 | | TypeORM | **on the works** | 21 | 22 | Also, Graphene is fully compatible with the GraphQL spec, working seamlessly with all GraphQL clients, such as [Relay](https://github.com/facebook/relay), [Apollo](https://github.com/apollographql/apollo-client) and [urql](https://github.com/FormidableLabs/urql). 23 | 24 | ## Installation 25 | 26 | For instaling graphene, just run this command in your shell 27 | 28 | ```bash 29 | npm install --save graphene-js 30 | # or 31 | yarn add graphene-js 32 | ``` 33 | 34 | ## Examples 35 | 36 | Here is one example for you to get started: 37 | 38 | ```js 39 | import { ObjectType, Field, Schema } from "graphene-js"; 40 | 41 | @ObjectType() 42 | class Query { 43 | @Field(String) 44 | hello() { 45 | return "Hello world!"; 46 | } 47 | } 48 | 49 | const schema = new Schema({ query: Query }); 50 | ``` 51 | 52 | Then Querying `graphene.Schema` is as simple as: 53 | 54 | ```js 55 | query = ` 56 | query SayHello { 57 | hello 58 | } 59 | `; 60 | 61 | var result = schema.execute(query); 62 | ``` 63 | 64 | If you want to learn more, you can also check the [documentation](http://docs.graphene-js.org/) or check the provided [examples](examples/): 65 | 66 | * **Basic Schema**: [Starwars example](examples/starwars) 67 | * **Basic Schema (Typescript)**: [Starwars Relay example](examples/starwars-ts) 68 | 69 | ## Contributing 70 | 71 | After cloning this repo, ensure dependencies are installed by running: 72 | 73 | ```sh 74 | yarn 75 | ``` 76 | 77 | After developing, the full test suite can be evaluated by running: 78 | 79 | ```sh 80 | yarn test 81 | ``` 82 | 83 | You can also get the coverage with: 84 | 85 | ```sh 86 | yarn test --coverage 87 | ``` 88 | 89 | ### Documentation 90 | 91 | The documentation is generated using the excellent [Sphinx](http://www.sphinx-doc.org/) and a custom theme. 92 | 93 | The documentation dependencies are installed by running: 94 | 95 | ```sh 96 | cd docs 97 | pip install -r requirements.txt 98 | ``` 99 | 100 | Then to produce a HTML version of the documentation: 101 | 102 | ```sh 103 | make html 104 | ``` 105 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 14 | # the i18n builder cannot share the environment and doctrees with the others 15 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 16 | 17 | .PHONY: help 18 | help: 19 | @echo "Please use \`make ' where is one of" 20 | @echo " html to make standalone HTML files" 21 | @echo " dirhtml to make HTML files named index.html in directories" 22 | @echo " singlehtml to make a single large HTML file" 23 | @echo " pickle to make pickle files" 24 | @echo " json to make JSON files" 25 | @echo " htmlhelp to make HTML files and a HTML help project" 26 | @echo " qthelp to make HTML files and a qthelp project" 27 | @echo " applehelp to make an Apple Help Book" 28 | @echo " devhelp to make HTML files and a Devhelp project" 29 | @echo " epub to make an epub" 30 | @echo " epub3 to make an epub3" 31 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 32 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 33 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 34 | @echo " text to make text files" 35 | @echo " man to make manual pages" 36 | @echo " texinfo to make Texinfo files" 37 | @echo " info to make Texinfo files and run them through makeinfo" 38 | @echo " gettext to make PO message catalogs" 39 | @echo " changes to make an overview of all changed/added/deprecated items" 40 | @echo " xml to make Docutils-native XML files" 41 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 42 | @echo " linkcheck to check all external links for integrity" 43 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 44 | @echo " coverage to run coverage check of the documentation (if enabled)" 45 | @echo " dummy to check syntax errors of document sources" 46 | 47 | .PHONY: clean 48 | clean: 49 | rm -rf $(BUILDDIR)/* 50 | 51 | .PHONY: html 52 | html: 53 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 54 | @echo 55 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 56 | 57 | .PHONY: dirhtml 58 | dirhtml: 59 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 60 | @echo 61 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 62 | 63 | .PHONY: singlehtml 64 | singlehtml: 65 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 66 | @echo 67 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 68 | 69 | .PHONY: pickle 70 | pickle: 71 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 72 | @echo 73 | @echo "Build finished; now you can process the pickle files." 74 | 75 | .PHONY: json 76 | json: 77 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 78 | @echo 79 | @echo "Build finished; now you can process the JSON files." 80 | 81 | .PHONY: htmlhelp 82 | htmlhelp: 83 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 84 | @echo 85 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 86 | ".hhp project file in $(BUILDDIR)/htmlhelp." 87 | 88 | .PHONY: qthelp 89 | qthelp: 90 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 91 | @echo 92 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 93 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 94 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Graphene.qhcp" 95 | @echo "To view the help file:" 96 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Graphene.qhc" 97 | 98 | .PHONY: applehelp 99 | applehelp: 100 | $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp 101 | @echo 102 | @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." 103 | @echo "N.B. You won't be able to view it unless you put it in" \ 104 | "~/Library/Documentation/Help or install it in your application" \ 105 | "bundle." 106 | 107 | .PHONY: devhelp 108 | devhelp: 109 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 110 | @echo 111 | @echo "Build finished." 112 | @echo "To view the help file:" 113 | @echo "# mkdir -p $$HOME/.local/share/devhelp/Graphene" 114 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Graphene" 115 | @echo "# devhelp" 116 | 117 | .PHONY: epub 118 | epub: 119 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 120 | @echo 121 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 122 | 123 | .PHONY: epub3 124 | epub3: 125 | $(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3 126 | @echo 127 | @echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3." 128 | 129 | .PHONY: latex 130 | latex: 131 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 132 | @echo 133 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 134 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 135 | "(use \`make latexpdf' here to do that automatically)." 136 | 137 | .PHONY: latexpdf 138 | latexpdf: 139 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 140 | @echo "Running LaTeX files through pdflatex..." 141 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 142 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 143 | 144 | .PHONY: latexpdfja 145 | latexpdfja: 146 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 147 | @echo "Running LaTeX files through platex and dvipdfmx..." 148 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 149 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 150 | 151 | .PHONY: text 152 | text: 153 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 154 | @echo 155 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 156 | 157 | .PHONY: man 158 | man: 159 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 160 | @echo 161 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 162 | 163 | .PHONY: texinfo 164 | texinfo: 165 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 166 | @echo 167 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 168 | @echo "Run \`make' in that directory to run these through makeinfo" \ 169 | "(use \`make info' here to do that automatically)." 170 | 171 | .PHONY: info 172 | info: 173 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 174 | @echo "Running Texinfo files through makeinfo..." 175 | make -C $(BUILDDIR)/texinfo info 176 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 177 | 178 | .PHONY: gettext 179 | gettext: 180 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 181 | @echo 182 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 183 | 184 | .PHONY: changes 185 | changes: 186 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 187 | @echo 188 | @echo "The overview file is in $(BUILDDIR)/changes." 189 | 190 | .PHONY: linkcheck 191 | linkcheck: 192 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 193 | @echo 194 | @echo "Link check complete; look for any errors in the above output " \ 195 | "or in $(BUILDDIR)/linkcheck/output.txt." 196 | 197 | .PHONY: doctest 198 | doctest: 199 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 200 | @echo "Testing of doctests in the sources finished, look at the " \ 201 | "results in $(BUILDDIR)/doctest/output.txt." 202 | 203 | .PHONY: coverage 204 | coverage: 205 | $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage 206 | @echo "Testing of coverage in the sources finished, look at the " \ 207 | "results in $(BUILDDIR)/coverage/python.txt." 208 | 209 | .PHONY: xml 210 | xml: 211 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 212 | @echo 213 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 214 | 215 | .PHONY: pseudoxml 216 | pseudoxml: 217 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 218 | @echo 219 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 220 | 221 | .PHONY: dummy 222 | dummy: 223 | $(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy 224 | @echo 225 | @echo "Build finished. Dummy builder generates no files." 226 | 227 | .PHONY: livehtml 228 | livehtml: 229 | sphinx-autobuild -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 230 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | on_rtd = os.environ.get('READTHEDOCS', None) == 'True' 4 | 5 | # -*- coding: utf-8 -*- 6 | # 7 | # Graphene documentation build configuration file, created by 8 | # sphinx-quickstart on Sun Sep 11 18:30:51 2016. 9 | # 10 | # This file is execfile()d with the current directory set to its 11 | # containing dir. 12 | # 13 | # Note that not all possible configuration values are present in this 14 | # autogenerated file. 15 | # 16 | # All configuration values have a default; values that are commented out 17 | # serve to show the default. 18 | 19 | # If extensions (or modules to document with autodoc) are in another directory, 20 | # add these directories to sys.path here. If the directory is relative to the 21 | # documentation root, use os.path.abspath to make it absolute, like shown here. 22 | # 23 | # import os 24 | # import sys 25 | # sys.path.insert(0, os.path.abspath('.')) 26 | 27 | # -- General configuration ------------------------------------------------ 28 | 29 | # If your documentation needs a minimal Sphinx version, state it here. 30 | # 31 | # needs_sphinx = '1.0' 32 | 33 | # Add any Sphinx extension module names here, as strings. They can be 34 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 35 | # ones. 36 | extensions = [ 37 | 'sphinx.ext.autodoc', 38 | 'sphinx.ext.intersphinx', 39 | 'sphinx.ext.todo', 40 | 'sphinx.ext.coverage', 41 | 'sphinx.ext.viewcode', 42 | ] 43 | if not on_rtd: 44 | extensions += [ 45 | 'sphinx.ext.githubpages', 46 | ] 47 | 48 | # Add any paths that contain templates here, relative to this directory. 49 | templates_path = ['_templates'] 50 | 51 | # The suffix(es) of source filenames. 52 | # You can specify multiple suffix as a list of string: 53 | # 54 | # source_suffix = ['.rst', '.md'] 55 | source_suffix = '.rst' 56 | 57 | # The encoding of source files. 58 | # 59 | # source_encoding = 'utf-8-sig' 60 | 61 | # The master toctree document. 62 | master_doc = 'index' 63 | 64 | # General information about the project. 65 | project = u'Graphene' 66 | copyright = u'Graphene-JS 2018' 67 | author = u'Syrus Akbary' 68 | 69 | # The version info for the project you're documenting, acts as replacement for 70 | # |version| and |release|, also used in various other places throughout the 71 | # built documents. 72 | # 73 | # The short X.Y version. 74 | version = u'1.0' 75 | # The full version, including alpha/beta/rc tags. 76 | release = u'1.0' 77 | 78 | # The language for content autogenerated by Sphinx. Refer to documentation 79 | # for a list of supported languages. 80 | # 81 | # This is also used if you do content translation via gettext catalogs. 82 | # Usually you set "language" from the command line for these cases. 83 | language = None 84 | 85 | # There are two options for replacing |today|: either, you set today to some 86 | # non-false value, then it is used: 87 | # 88 | # today = '' 89 | # 90 | # Else, today_fmt is used as the format for a strftime call. 91 | # 92 | # today_fmt = '%B %d, %Y' 93 | 94 | # List of patterns, relative to source directory, that match files and 95 | # directories to ignore when looking for source files. 96 | # This patterns also effect to html_static_path and html_extra_path 97 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 98 | 99 | # The reST default role (used for this markup: `text`) to use for all 100 | # documents. 101 | # 102 | # default_role = None 103 | 104 | # If true, '()' will be appended to :func: etc. cross-reference text. 105 | # 106 | # add_function_parentheses = True 107 | 108 | # If true, the current module name will be prepended to all description 109 | # unit titles (such as .. function::). 110 | # 111 | # add_module_names = True 112 | 113 | # If true, sectionauthor and moduleauthor directives will be shown in the 114 | # output. They are ignored by default. 115 | # 116 | # show_authors = False 117 | 118 | # The name of the Pygments (syntax highlighting) style to use. 119 | pygments_style = 'sphinx' 120 | 121 | # A list of ignored prefixes for module index sorting. 122 | # modindex_common_prefix = [] 123 | 124 | # If true, keep warnings as "system message" paragraphs in the built documents. 125 | # keep_warnings = False 126 | 127 | # If true, `todo` and `todoList` produce output, else they produce nothing. 128 | todo_include_todos = True 129 | 130 | 131 | # -- Options for HTML output ---------------------------------------------- 132 | 133 | # The theme to use for HTML and HTML Help pages. See the documentation for 134 | # a list of builtin themes. 135 | # 136 | # html_theme = 'alabaster' 137 | # if on_rtd: 138 | # html_theme = 'sphinx_rtd_theme' 139 | import sphinx_graphene_theme 140 | 141 | html_theme = "sphinx_graphene_theme" 142 | 143 | html_theme_path = [sphinx_graphene_theme.get_html_theme_path()] 144 | 145 | # Theme options are theme-specific and customize the look and feel of a theme 146 | # further. For a list of options available for each theme, see the 147 | # documentation. 148 | # 149 | # html_theme_options = {} 150 | 151 | # Add any paths that contain custom themes here, relative to this directory. 152 | # html_theme_path = [] 153 | 154 | # The name for this set of Sphinx documents. 155 | # " v documentation" by default. 156 | # 157 | # html_title = u'Graphene v1.0' 158 | 159 | # A shorter title for the navigation bar. Default is the same as html_title. 160 | # 161 | # html_short_title = None 162 | 163 | # The name of an image file (relative to this directory) to place at the top 164 | # of the sidebar. 165 | # 166 | # html_logo = None 167 | 168 | # The name of an image file (relative to this directory) to use as a favicon of 169 | # the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 170 | # pixels large. 171 | # 172 | # html_favicon = None 173 | 174 | # Add any paths that contain custom static files (such as style sheets) here, 175 | # relative to this directory. They are copied after the builtin static files, 176 | # so a file named "default.css" will overwrite the builtin "default.css". 177 | html_static_path = ['_static'] 178 | 179 | # Add any extra paths that contain custom files (such as robots.txt or 180 | # .htaccess) here, relative to this directory. These files are copied 181 | # directly to the root of the documentation. 182 | # 183 | # html_extra_path = [] 184 | 185 | # If not None, a 'Last updated on:' timestamp is inserted at every page 186 | # bottom, using the given strftime format. 187 | # The empty string is equivalent to '%b %d, %Y'. 188 | # 189 | # html_last_updated_fmt = None 190 | 191 | # If true, SmartyPants will be used to convert quotes and dashes to 192 | # typographically correct entities. 193 | # 194 | # html_use_smartypants = True 195 | 196 | # Custom sidebar templates, maps document names to template names. 197 | # 198 | # html_sidebars = {} 199 | 200 | # Additional templates that should be rendered to pages, maps page names to 201 | # template names. 202 | # 203 | # html_additional_pages = {} 204 | 205 | # If false, no module index is generated. 206 | # 207 | # html_domain_indices = True 208 | 209 | # If false, no index is generated. 210 | # 211 | # html_use_index = True 212 | 213 | # If true, the index is split into individual pages for each letter. 214 | # 215 | # html_split_index = False 216 | 217 | # If true, links to the reST sources are added to the pages. 218 | # 219 | # html_show_sourcelink = True 220 | 221 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 222 | # 223 | # html_show_sphinx = True 224 | 225 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 226 | # 227 | # html_show_copyright = True 228 | 229 | # If true, an OpenSearch description file will be output, and all pages will 230 | # contain a tag referring to it. The value of this option must be the 231 | # base URL from which the finished HTML is served. 232 | # 233 | # html_use_opensearch = '' 234 | 235 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 236 | # html_file_suffix = None 237 | 238 | # Language to be used for generating the HTML full-text search index. 239 | # Sphinx supports the following languages: 240 | # 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' 241 | # 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr', 'zh' 242 | # 243 | # html_search_language = 'en' 244 | 245 | # A dictionary with options for the search language support, empty by default. 246 | # 'ja' uses this config value. 247 | # 'zh' user can custom change `jieba` dictionary path. 248 | # 249 | # html_search_options = {'type': 'default'} 250 | 251 | # The name of a javascript file (relative to the configuration directory) that 252 | # implements a search results scorer. If empty, the default will be used. 253 | # 254 | # html_search_scorer = 'scorer.js' 255 | 256 | # Output file base name for HTML help builder. 257 | htmlhelp_basename = 'Graphenedoc' 258 | 259 | # -- Options for LaTeX output --------------------------------------------- 260 | 261 | latex_elements = { 262 | # The paper size ('letterpaper' or 'a4paper'). 263 | # 264 | # 'papersize': 'letterpaper', 265 | 266 | # The font size ('10pt', '11pt' or '12pt'). 267 | # 268 | # 'pointsize': '10pt', 269 | 270 | # Additional stuff for the LaTeX preamble. 271 | # 272 | # 'preamble': '', 273 | 274 | # Latex figure (float) alignment 275 | # 276 | # 'figure_align': 'htbp', 277 | } 278 | 279 | # Grouping the document tree into LaTeX files. List of tuples 280 | # (source start file, target name, title, 281 | # author, documentclass [howto, manual, or own class]). 282 | latex_documents = [ 283 | (master_doc, 'Graphene.tex', u'Graphene Documentation', 284 | u'Syrus Akbary', 'manual'), 285 | ] 286 | 287 | # The name of an image file (relative to this directory) to place at the top of 288 | # the title page. 289 | # 290 | # latex_logo = None 291 | 292 | # For "manual" documents, if this is true, then toplevel headings are parts, 293 | # not chapters. 294 | # 295 | # latex_use_parts = False 296 | 297 | # If true, show page references after internal links. 298 | # 299 | # latex_show_pagerefs = False 300 | 301 | # If true, show URL addresses after external links. 302 | # 303 | # latex_show_urls = False 304 | 305 | # Documents to append as an appendix to all manuals. 306 | # 307 | # latex_appendices = [] 308 | 309 | # It false, will not define \strong, \code, itleref, \crossref ... but only 310 | # \sphinxstrong, ..., \sphinxtitleref, ... To help avoid clash with user added 311 | # packages. 312 | # 313 | # latex_keep_old_macro_names = True 314 | 315 | # If false, no module index is generated. 316 | # 317 | # latex_domain_indices = True 318 | 319 | 320 | # -- Options for manual page output --------------------------------------- 321 | 322 | # One entry per manual page. List of tuples 323 | # (source start file, name, description, authors, manual section). 324 | man_pages = [ 325 | (master_doc, 'graphene', u'Graphene Documentation', 326 | [author], 1) 327 | ] 328 | 329 | # If true, show URL addresses after external links. 330 | # 331 | # man_show_urls = False 332 | 333 | 334 | # -- Options for Texinfo output ------------------------------------------- 335 | 336 | # Grouping the document tree into Texinfo files. List of tuples 337 | # (source start file, target name, title, author, 338 | # dir menu entry, description, category) 339 | texinfo_documents = [ 340 | (master_doc, 'Graphene', u'Graphene Documentation', 341 | author, 'Graphene', 'One line description of project.', 342 | 'Miscellaneous'), 343 | ] 344 | 345 | # Documents to append as an appendix to all manuals. 346 | # 347 | # texinfo_appendices = [] 348 | 349 | # If false, no module index is generated. 350 | # 351 | # texinfo_domain_indices = True 352 | 353 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 354 | # 355 | # texinfo_show_urls = 'footnote' 356 | 357 | # If true, do not generate a @detailmenu in the "Top" node's menu. 358 | # 359 | # texinfo_no_detailmenu = False 360 | 361 | 362 | # -- Options for Epub output ---------------------------------------------- 363 | 364 | # Bibliographic Dublin Core info. 365 | epub_title = project 366 | epub_author = author 367 | epub_publisher = author 368 | epub_copyright = copyright 369 | 370 | # The basename for the epub file. It defaults to the project name. 371 | # epub_basename = project 372 | 373 | # The HTML theme for the epub output. Since the default themes are not 374 | # optimized for small screen space, using the same theme for HTML and epub 375 | # output is usually not wise. This defaults to 'epub', a theme designed to save 376 | # visual space. 377 | # 378 | # epub_theme = 'epub' 379 | 380 | # The language of the text. It defaults to the language option 381 | # or 'en' if the language is not set. 382 | # 383 | # epub_language = '' 384 | 385 | # The scheme of the identifier. Typical schemes are ISBN or URL. 386 | # epub_scheme = '' 387 | 388 | # The unique identifier of the text. This can be a ISBN number 389 | # or the project homepage. 390 | # 391 | # epub_identifier = '' 392 | 393 | # A unique identification for the text. 394 | # 395 | # epub_uid = '' 396 | 397 | # A tuple containing the cover image and cover page html template filenames. 398 | # 399 | # epub_cover = () 400 | 401 | # A sequence of (type, uri, title) tuples for the guide element of content.opf. 402 | # 403 | # epub_guide = () 404 | 405 | # HTML files that should be inserted before the pages created by sphinx. 406 | # The format is a list of tuples containing the path and title. 407 | # 408 | # epub_pre_files = [] 409 | 410 | # HTML files that should be inserted after the pages created by sphinx. 411 | # The format is a list of tuples containing the path and title. 412 | # 413 | # epub_post_files = [] 414 | 415 | # A list of files that should not be packed into the epub file. 416 | epub_exclude_files = ['search.html'] 417 | 418 | # The depth of the table of contents in toc.ncx. 419 | # 420 | # epub_tocdepth = 3 421 | 422 | # Allow duplicate toc entries. 423 | # 424 | # epub_tocdup = True 425 | 426 | # Choose between 'default' and 'includehidden'. 427 | # 428 | # epub_tocscope = 'default' 429 | 430 | # Fix unsupported image types using the Pillow. 431 | # 432 | # epub_fix_images = False 433 | 434 | # Scale large images. 435 | # 436 | # epub_max_image_width = 0 437 | 438 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 439 | # 440 | # epub_show_urls = 'inline' 441 | 442 | # If false, no index is generated. 443 | # 444 | # epub_use_index = True 445 | 446 | 447 | # Example configuration for intersphinx: refer to the Python standard library. 448 | intersphinx_mapping = { 449 | 'https://docs.python.org/': None, 450 | 'python': ('https://docs.python.org/', None), 451 | 'graphene_django': ('http://docs.graphene-python.org/projects/django/en/latest/', None), 452 | 'graphene_sqlalchemy': ('http://docs.graphene-python.org/projects/sqlalchemy/en/latest/', None), 453 | 'graphene_gae': ('http://docs.graphene-python.org/projects/gae/en/latest/', None), 454 | } 455 | -------------------------------------------------------------------------------- /docs/incremental-adoption.rst: -------------------------------------------------------------------------------- 1 | Incremental adoption 2 | ==================== 3 | 4 | Graphene-JS is designed to be adopted incrementally, that means that you will 5 | be able to use Graphene types inside of your already existing schema and 6 | viceversa. 7 | 8 | Graphene-JS types in GraphQL 9 | ---------------------------- 10 | 11 | Using Graphene types with your existing GraphQL types is very easy. 12 | The module have a utility function `getGraphQLType` that you can use to retrieve 13 | the native GraphQL type behind a Graphene type. 14 | 15 | For example: 16 | 17 | .. code:: js 18 | 19 | import { GraphQLSchema, GraphQLObjectType } from "graphql"; 20 | import { ObjectType, Field, getGraphQLType } from "graphene-js"; 21 | 22 | // Your graphene definition 23 | @ObjectType() 24 | class User { 25 | @Field(String) name 26 | } 27 | 28 | // Your normal GraphLQL types 29 | var query = new GraphQLObjectType({ 30 | name: 'Query', 31 | fields: { 32 | viewer: { 33 | // Note getGraphQLType(User) will return a GraphQLObjectType 34 | // that can be safely used in GraphQL types 35 | type: getGraphQLType(User), 36 | } 37 | } 38 | }); 39 | 40 | 41 | GraphQL types in Graphene 42 | ------------------------- 43 | 44 | Graphene can operate with native GraphQL types seamlessly, with no extra effort 45 | for the developer. You can use GraphQL native types directly in Graphene 46 | 47 | For example: 48 | 49 | .. code:: js 50 | 51 | import { ObjectType, Field } from "graphene-js"; 52 | 53 | var User = GraphQLObjectType({ 54 | name: 'User', 55 | fields: { 56 | name: { 57 | type: GraphQLString, 58 | } 59 | } 60 | }); 61 | 62 | @ObjectType() 63 | class Query { 64 | // User is a native GraphQL type 65 | @Field(User) user; 66 | } 67 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | Graphene-JS 2 | =========== 3 | 4 | Contents: 5 | 6 | .. toctree:: 7 | :maxdepth: 2 8 | 9 | quickstart 10 | types/index 11 | relay/index 12 | incremental-adoption 13 | 14 | Integrations 15 | ----- 16 | 17 | * `Graphene-sequelize `_ (`source `_) 18 | -------------------------------------------------------------------------------- /docs/quickstart.rst: -------------------------------------------------------------------------------- 1 | Getting started 2 | =============== 3 | 4 | For an introduction to GraphQL and an overview of its concepts, please refer 5 | to `the official introduction `_. 6 | 7 | Let’s build a basic GraphQL schema from scratch. 8 | 9 | Requirements 10 | ------------ 11 | 12 | - Node.js 13 | - Graphene-JS 14 | 15 | Project setup 16 | ------------- 17 | 18 | .. code:: bash 19 | 20 | npm install graphene-js 21 | # or 22 | yarn add graphene-js 23 | 24 | 25 | Creating a basic Schema 26 | ----------------------- 27 | 28 | A GraphQL schema describes your data model, and provides a GraphQL 29 | server with an associated set of resolve methods that know how to fetch 30 | data. 31 | 32 | We are going to create a very simple schema, with a ``Query`` with only 33 | one field: ``hello`` and an input name. And when we query it, it should return ``"Hello {name}"``. 34 | 35 | .. code:: js 36 | 37 | import { ObjectType, Field, Schema } from "graphene-js"; 38 | 39 | @ObjectType() 40 | class Query { 41 | @Field(String, {args: {name: String}}) 42 | hello({name}) { 43 | return `Hello ${name || "stranger"}`; 44 | } 45 | } 46 | 47 | schema = new Schema({query: Query}) 48 | 49 | 50 | Querying 51 | -------- 52 | 53 | Then we can start querying our schema: 54 | 55 | .. code:: js 56 | 57 | var result = await schema.execute('{ hello }') 58 | console.log(result.data.hello) # "Hello stranger" 59 | 60 | Congrats! You got your first graphene schema working! 61 | -------------------------------------------------------------------------------- /docs/relay/connection.rst: -------------------------------------------------------------------------------- 1 | Connection 2 | ========== 3 | 4 | A connection is a vitaminized version of a List that provides ways of 5 | slicing and paginating through it. The way you create Connection types 6 | in ``graphene`` is using ``relay.Connection`` and ``relay.ConnectionField``. 7 | 8 | Quick example 9 | ------------- 10 | 11 | If we want to create a custom Connection on a given node, we have to subclass the 12 | ``Connection`` class. 13 | 14 | In the following example, ``extra`` will be an extra field in the connection, 15 | and ``other`` an extra field in the Connection Edge. 16 | 17 | .. code:: python 18 | 19 | @ConnectionType({ 20 | Edge: Edge, 21 | node: Ship, 22 | }) 23 | class ShipConnection { 24 | @Field(String) extra; 25 | } 26 | 27 | The ``ShipConnection`` connection class, will have automatically a ``pageInfo`` field, 28 | and a ``edges`` field (which is a list of ``ShipConnection.Edge``). 29 | This ``Edge`` will have a ``node`` field linking to the specified node 30 | (in ``ShipConnection.Meta``) and the field ``other`` that we defined in the class. 31 | 32 | Connection Field 33 | ---------------- 34 | You can create connection fields in any Connection, in case any ObjectType 35 | that implements ``Node`` will have a default Connection. 36 | 37 | .. code:: python 38 | 39 | class Faction(graphene.ObjectType): 40 | name = graphene.String() 41 | ships = relay.ConnectionField(ShipConnection) 42 | 43 | def resolve_ships(self, info): 44 | return [] 45 | -------------------------------------------------------------------------------- /docs/relay/index.rst: -------------------------------------------------------------------------------- 1 | Relay 2 | ===== 3 | 4 | Graphene `Relay`_ integration is on the works. 5 | 6 | 7 | Useful links 8 | ------------ 9 | 10 | - `Getting started with Relay`_ 11 | - `Relay Global Identification Specification`_ 12 | - `Relay Cursor Connection Specification`_ 13 | - `Relay input Object Mutation`_ 14 | 15 | .. _Relay: https://facebook.github.io/relay/docs/graphql-relay-specification.html 16 | .. _Relay specification: https://facebook.github.io/relay/graphql/objectidentification.htm#sec-Node-root-field 17 | .. _Getting started with Relay: https://facebook.github.io/relay/docs/graphql-relay-specification.html 18 | .. _Relay Global Identification Specification: https://facebook.github.io/relay/graphql/objectidentification.htm 19 | .. _Relay Cursor Connection Specification: https://facebook.github.io/relay/graphql/connections.htm 20 | .. _Relay input Object Mutation: https://facebook.github.io/relay/graphql/mutations.htm 21 | -------------------------------------------------------------------------------- /docs/relay/mutations.rst: -------------------------------------------------------------------------------- 1 | Mutations 2 | ========= 3 | 4 | Most APIs don’t just allow you to read data, they also allow you to 5 | write. 6 | 7 | In GraphQL, this is done using mutations. Just like queries, 8 | Relay puts some additional requirements on mutations, but Graphene 9 | nicely manages that for you. All you need to do is make your mutation a 10 | subclass of ``relay.ClientIDMutation``. 11 | 12 | .. code:: python 13 | 14 | class IntroduceShip(relay.ClientIDMutation): 15 | 16 | class Input: 17 | ship_name = graphene.String(required=True) 18 | faction_id = graphene.String(required=True) 19 | 20 | ship = graphene.Field(Ship) 21 | faction = graphene.Field(Faction) 22 | 23 | @classmethod 24 | def mutate_and_get_payload(cls, root, info, **input): 25 | ship_name = input.ship_name 26 | faction_id = input.faction_id 27 | ship = create_ship(ship_name, faction_id) 28 | faction = get_faction(faction_id) 29 | return IntroduceShip(ship=ship, faction=faction) 30 | 31 | 32 | 33 | Accepting Files 34 | --------------- 35 | 36 | Mutations can also accept files, that's how it will work with different integrations: 37 | 38 | .. code:: python 39 | 40 | class UploadFile(graphene.ClientIDMutation): 41 | class Input: 42 | pass 43 | # nothing needed for uploading file 44 | 45 | # your return fields 46 | success = graphene.String() 47 | 48 | @classmethod 49 | def mutate_and_get_payload(cls, root, info, **input): 50 | # When using it in Django, context will be the request 51 | files = info.context.FILES 52 | # Or, if used in Flask, context will be the flask global request 53 | # files = context.files 54 | 55 | # do something with files 56 | 57 | return UploadFile(success=True) 58 | -------------------------------------------------------------------------------- /docs/relay/nodes.rst: -------------------------------------------------------------------------------- 1 | Nodes 2 | ===== 3 | 4 | A ``Node`` is an Interface provided by ``graphene.relay`` that contains 5 | a single field ``id`` (which is a ``ID!``). Any object that inherits 6 | from it has to implement a ``get_node`` method for retrieving a 7 | ``Node`` by an *id*. 8 | 9 | 10 | Quick example 11 | ------------- 12 | 13 | Example usage (taken from the `Starwars Relay example`_): 14 | 15 | .. code:: python 16 | 17 | class Ship(graphene.ObjectType): 18 | '''A ship in the Star Wars saga''' 19 | class Meta: 20 | interfaces = (relay.Node, ) 21 | 22 | name = graphene.String(description='The name of the ship.') 23 | 24 | @classmethod 25 | def get_node(cls, info, id): 26 | return get_ship(id) 27 | 28 | The ``id`` returned by the ``Ship`` type when you query it will be a 29 | scalar which contains enough info for the server to know its type and 30 | its id. 31 | 32 | For example, the instance ``Ship(id=1)`` will return ``U2hpcDox`` as the 33 | id when you query it (which is the base64 encoding of ``Ship:1``), and 34 | which could be useful later if we want to query a node by its id. 35 | 36 | 37 | Custom Nodes 38 | ------------ 39 | 40 | You can use the predefined ``relay.Node`` or you can subclass it, defining 41 | custom ways of how a node id is encoded (using the ``to_global_id`` method in the class) 42 | or how we can retrieve a Node given a encoded id (with the ``get_node_from_global_id`` method). 43 | 44 | Example of a custom node: 45 | 46 | .. code:: python 47 | 48 | class CustomNode(Node): 49 | 50 | class Meta: 51 | name = 'Node' 52 | 53 | @staticmethod 54 | def to_global_id(type, id): 55 | return '{}:{}'.format(type, id) 56 | 57 | @staticmethod 58 | def get_node_from_global_id(info global_id, only_type=None): 59 | type, id = global_id.split(':') 60 | if only_node: 61 | # We assure that the node type that we want to retrieve 62 | # is the same that was indicated in the field type 63 | assert type == only_node._meta.name, 'Received not compatible node.' 64 | 65 | if type == 'User': 66 | return get_user(id) 67 | elif type == 'Photo': 68 | return get_photo(id) 69 | 70 | 71 | The ``get_node_from_global_id`` method will be called when ``CustomNode.Field`` is resolved. 72 | 73 | 74 | Accessing node types 75 | -------------------- 76 | 77 | If we want to retrieve node instances from a ``global_id`` (scalar that identifies an instance by it's type name and id), 78 | we can simply do ``Node.get_node_from_global_id(info, global_id)``. 79 | 80 | In the case we want to restrict the instance retrieval to a specific type, we can do: 81 | ``Node.get_node_from_global_id(info, global_id, only_type=Ship)``. This will raise an error 82 | if the ``global_id`` doesn't correspond to a Ship type. 83 | 84 | 85 | Node Root field 86 | --------------- 87 | 88 | As is required in the `Relay specification`_, the server must implement 89 | a root field called ``node`` that returns a ``Node`` Interface. 90 | 91 | For this reason, ``graphene`` provides the field ``relay.Node.Field``, 92 | which links to any type in the Schema which implements ``Node``. 93 | Example usage: 94 | 95 | .. code:: python 96 | 97 | class Query(graphene.ObjectType): 98 | # Should be CustomNode.Field() if we want to use our custom Node 99 | node = relay.Node.Field() 100 | 101 | .. _Relay specification: https://facebook.github.io/relay/docs/graphql-relay-specification.html 102 | .. _Starwars Relay example: https://github.com/graphql-python/graphene/blob/master/examples/starwars_relay/schema.py 103 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | # Required library 2 | Sphinx==1.5.3 3 | # Docs template 4 | http://graphene-js.org/sphinx_graphene_theme.zip 5 | -------------------------------------------------------------------------------- /docs/types/enums.rst: -------------------------------------------------------------------------------- 1 | Enums 2 | ===== 3 | 4 | A ``Enum`` is a special ``GraphQL`` type that represents a set of 5 | symbolic names (members) bound to unique, constant values. 6 | 7 | Definition 8 | ---------- 9 | 10 | You can create an ``Enum`` using classes: 11 | 12 | .. code:: js 13 | 14 | import { EnumType } from "graphene-js"; 15 | 16 | @EnumType() 17 | class Episode { 18 | static NEWHOPE = 4 19 | static EMPIRE = 5 20 | static JEDI = 6 21 | } 22 | 23 | 24 | Graphene will automatically search for the static variables in the Enum and expose 25 | them as the enum values. 26 | 27 | Value descriptions 28 | ------------------ 29 | 30 | It's possible to add a description to an enum value, for that the enum value 31 | needs to have the ``description``decorator on it. 32 | 33 | .. code:: js 34 | 35 | @EnumType() 36 | class Episode { 37 | @description("New hope episode") 38 | static NEWHOPE = 4 39 | 40 | @description("Empire episode") 41 | static EMPIRE = 5 42 | 43 | @description("JEDI episode") 44 | static JEDI = 6 45 | } 46 | -------------------------------------------------------------------------------- /docs/types/index.rst: -------------------------------------------------------------------------------- 1 | =============== 2 | Types Reference 3 | =============== 4 | 5 | .. toctree:: 6 | :maxdepth: 1 7 | 8 | enums 9 | scalars 10 | list-and-nonnull 11 | interfaces 12 | unions 13 | objecttypes 14 | schema 15 | -------------------------------------------------------------------------------- /docs/types/interfaces.rst: -------------------------------------------------------------------------------- 1 | Interfaces 2 | ========== 3 | 4 | An Interface contains the essential fields that will be implemented by 5 | multiple ObjectTypes. 6 | 7 | The basics: 8 | 9 | - Each Interface is class decorated with ``InterfaceType``. 10 | - Each attribute decorated with ``@Field`` represents a GraphQL Field in the 11 | Interface. 12 | 13 | Quick example 14 | ------------- 15 | 16 | This example model defines a ``Character`` interface with a name. ``Human`` 17 | and ``Droid`` are two implementations of that interface. 18 | 19 | .. code:: js 20 | 21 | import { InterfaceType, ObjectType, Field } from "graphene-js"; 22 | 23 | @InterfaceType() 24 | class Character { 25 | @Field(String) name; 26 | } 27 | 28 | // Human is a Character implementation 29 | @ObjectType({ 30 | interfaces: [Character] 31 | }) 32 | class Human { 33 | @Field(String) bornIn; 34 | } 35 | 36 | // Droid is a Character implementation 37 | @ObjectType({ 38 | interfaces: [Character] 39 | }) 40 | class Human { 41 | @Field(String) function; 42 | } 43 | 44 | 45 | ``name`` is a field on the ``Character`` interface that will also exist on both 46 | the ``Human`` and ``Droid`` ObjectTypes (as those implement the ``Character`` 47 | interface). Each ObjectType may define additional fields. 48 | 49 | The above types have the following representation in a schema: 50 | 51 | .. code:: 52 | 53 | interface Character { 54 | name: String 55 | } 56 | 57 | type Droid implements Character { 58 | name: String 59 | function: String 60 | } 61 | 62 | type Human implements Character { 63 | name: String 64 | bornIn: String 65 | } 66 | -------------------------------------------------------------------------------- /docs/types/list-and-nonnull.rst: -------------------------------------------------------------------------------- 1 | Lists and Non-Null 2 | ================== 3 | 4 | Object types, scalars, and enums are the only kinds of types you can 5 | define in Graphene. But when you use the types in other parts of the 6 | schema, or in your query variable declarations, you can apply additional 7 | type modifiers that affect validation of those values. 8 | 9 | 10 | List 11 | ---- 12 | 13 | .. code:: js 14 | 15 | import { ObjectType, List } from "graphene-js"; 16 | 17 | @ObjectType() 18 | class Character { 19 | @Field(List(String)) appearsIn; 20 | } 21 | 22 | Lists work in a similar way: We can use a type modifier to mark a type as a 23 | ``List``, which indicates that this field will return a list of that type. 24 | It works the same for arguments, where the validation step will expect a list 25 | for that value. 26 | 27 | For ease of development, we can directly use js lists with one element ``[]``. 28 | 29 | Like: 30 | 31 | 32 | .. code:: js 33 | 34 | import { ObjectType, Field } from "graphene-js"; 35 | 36 | @ObjectType() 37 | class Character { 38 | @Field([String]) appearsIn; 39 | } 40 | 41 | 42 | NonNull 43 | ------- 44 | 45 | .. code:: js 46 | 47 | import { ObjectType, Field, NonNull } from "graphene-js"; 48 | 49 | @ObjectType() 50 | class Character { 51 | @Field(NonNull(String)) name; 52 | } 53 | 54 | 55 | Here, we're using a ``String`` type and marking it as Non-Null by wrapping 56 | it using the ``NonNull`` class. This means that our server always expects 57 | to return a non-null value for this field, and if it ends up getting a 58 | null value that will actually trigger a GraphQL execution error, 59 | letting the client know that something has gone wrong. 60 | -------------------------------------------------------------------------------- /docs/types/objecttypes.rst: -------------------------------------------------------------------------------- 1 | ObjectTypes 2 | =========== 3 | 4 | An ObjectType is the single, definitive source of information about your 5 | data. It contains the essential fields and behaviors of the data you’re 6 | querying. 7 | 8 | The basics: 9 | 10 | - Each ObjectType is a Python class that inherits from 11 | ``graphene.ObjectType``. 12 | - Each attribute of the ObjectType represents a ``Field``. 13 | 14 | Quick example 15 | ------------- 16 | 17 | This example model defines a Person, with a first and a last name: 18 | 19 | .. code:: js 20 | 21 | import { ObjectType, Field } from "graphene-js"; 22 | 23 | @ObjectType() 24 | class Person { 25 | @Field(String) firstName; 26 | @Field(String) lastName; 27 | @Field(String) 28 | fullName() { 29 | return `${this.firstName} ${this.lastName}`; 30 | } 31 | } 32 | 33 | **firstName** and **lastName** are fields of the ObjectType. Each 34 | field is specified as a class attribute, and each attribute maps to a 35 | Field. 36 | 37 | The above ``Person`` ObjectType has the following schema representation: 38 | 39 | .. code:: 40 | 41 | type Person { 42 | firstName: String 43 | lastName: String 44 | fullName: String 45 | } 46 | 47 | 48 | Resolvers 49 | --------- 50 | 51 | A resolver is a method that resolves certain fields within a 52 | ``ObjectType``. If not specififed otherwise, the resolver of a 53 | field is the ``resolve_{field_name}`` method on the ``ObjectType``. 54 | 55 | By default resolvers take the arguments ``args``, ``context`` and ``info``. 56 | 57 | 58 | Quick example 59 | ~~~~~~~~~~~~~ 60 | 61 | This example model defines a ``Query`` type, which has a reverse field 62 | that reverses the given ``word`` argument using the ``resolve_reverse`` 63 | method in the class. 64 | 65 | .. code:: js 66 | 67 | import { ObjectType, Field } from "graphene-js"; 68 | 69 | @ObjectType() 70 | class Query { 71 | @Field(String, {args: {word: String}}) 72 | reverse({word}) { 73 | return (word || "").split("").reverse().join("") 74 | } 75 | } 76 | 77 | 78 | Instances as data containers 79 | ---------------------------- 80 | 81 | Graphene ``ObjectType``\ s can act as containers too. So with the 82 | previous example you could do: 83 | 84 | .. code:: js 85 | 86 | peter = new Person({firstName: "", lastName: ""}) 87 | 88 | peter.firstName # prints "Peter" 89 | peter.lastName # prints "Griffin" 90 | 91 | .. _Interface: /docs/interfaces/ 92 | -------------------------------------------------------------------------------- /docs/types/scalars.rst: -------------------------------------------------------------------------------- 1 | Scalars 2 | ======= 3 | 4 | All Scalar types accept the following arguments. All are optional: 5 | 6 | 7 | Base scalars 8 | ------------ 9 | 10 | ``String`` 11 | 12 | Represents textual data, represented as UTF-8 13 | character sequences. The String type is most often used by GraphQL to 14 | represent free-form human-readable text. 15 | 16 | ``Int`` 17 | 18 | Represents non-fractional signed whole numeric 19 | values. Int can represent values between `-(2^53 - 1)` and `2^53 - 1` since 20 | represented in JSON as double-precision floating point numbers specified 21 | by `IEEE 754 `_. 22 | 23 | ``Float`` 24 | 25 | Represents signed double-precision fractional 26 | values as specified by 27 | `IEEE 754 `_. 28 | 29 | ``Boolean`` 30 | 31 | Represents `true` or `false`. 32 | 33 | ``ID`` 34 | 35 | Represents a unique identifier, often used to 36 | refetch an object or as key for a cache. The ID type appears in a JSON 37 | response as a String; however, it is not intended to be human-readable. 38 | When expected as an input type, any string (such as `"4"`) or integer 39 | (such as `4`) input value will be accepted as an ID. 40 | 41 | Graphene also provides custom scalars for Dates, Times, and JSON: 42 | 43 | ``graphene.Date`` 44 | 45 | Represents a Date value as specified by `iso8601 `_. 46 | 47 | ``graphene.DateTime`` 48 | 49 | Represents a DateTime value as specified by `iso8601 `_. 50 | 51 | ``graphene.Time`` 52 | 53 | Represents a Time value as specified by `iso8601 `_. 54 | 55 | 56 | Custom scalars 57 | -------------- 58 | 59 | You can create custom scalars for your schema. 60 | The following is an example for creating a DateTime scalar: 61 | 62 | .. code:: js 63 | 64 | import { GraphQLScalarType } from "graphql"; 65 | 66 | const Date = new GraphQLScalarType({ 67 | name: 'Date', 68 | description: 'Date custom scalar type', 69 | parseValue(value) { 70 | return new Date(value); // value from the client 71 | }, 72 | serialize(value) { 73 | return value.getTime(); // value sent to the client 74 | }, 75 | parseLiteral(ast) { 76 | if (ast.kind === Kind.INT) { 77 | return parseInt(ast.value, 10); // ast value is always in string format 78 | } 79 | return null; 80 | }, 81 | }); 82 | -------------------------------------------------------------------------------- /docs/types/schema.rst: -------------------------------------------------------------------------------- 1 | Schema 2 | ====== 3 | 4 | A Schema is created by supplying the root types of each type of operation, query, mutation and subscription. 5 | A schema definition is then supplied to the validator and executor. 6 | 7 | .. code:: js 8 | 9 | import { Schema } from "graphene-js"; 10 | 11 | const schema = new Schema({ 12 | query: MyRootQuery, 13 | mutation: MyRootMutation, 14 | }) 15 | 16 | Types 17 | ----- 18 | 19 | There are some cases where the schema cannot access all of the types that we plan to have. 20 | For example, when a field returns an ``Interface``, the schema doesn't know about any of the 21 | implementations. 22 | 23 | In this case, we need to use the ``types`` argument when creating the Schema. 24 | 25 | 26 | .. code:: js 27 | 28 | const schema = new Schema({ 29 | query: MyRootQuery, 30 | types=[SomeExtraType, ], 31 | }) 32 | 33 | 34 | Querying 35 | -------- 36 | 37 | To query a schema, call the ``execute`` method on it. 38 | 39 | 40 | .. code:: js 41 | 42 | await schema.execute('{ hello }') 43 | 44 | -------------------------------------------------------------------------------- /docs/types/unions.rst: -------------------------------------------------------------------------------- 1 | Unions 2 | ====== 3 | 4 | Union types are very similar to interfaces, but they don't get 5 | to specify any common fields between the types. 6 | 7 | The basics: 8 | 9 | - Each Union is a JS class decorated with ``UnionType``. 10 | - Unions don't have any fields on it, just links to the possible objecttypes. 11 | 12 | Quick example 13 | ------------- 14 | 15 | This example model defines several ObjectTypes with their own fields. 16 | ``SearchResult`` is the implementation of ``Union`` of this object types. 17 | 18 | .. code:: js 19 | 20 | import { ObjectType, UnionType, Field } from "graphene-js"; 21 | 22 | class Human { 23 | @Field(String) name; 24 | @Field(String) bornIn; 25 | } 26 | 27 | class Droid { 28 | @Field(String) name; 29 | @Field(String) primaryFunction; 30 | } 31 | 32 | class Starship { 33 | @Field(String) name; 34 | @Field(Number) length; 35 | } 36 | 37 | const SearchResult = new UnionType({ 38 | name: 'SearchResult', 39 | types: [Human, Droid, Starship] 40 | }) 41 | 42 | 43 | Wherever we return a SearchResult type in our schema, we might get a Human, a Droid, or a Starship. 44 | Note that members of a union type need to be concrete object types; 45 | you can't create a union type out of interfaces or other unions. 46 | 47 | The above types have the following representation in a schema: 48 | 49 | .. code:: 50 | 51 | type Droid { 52 | name: String 53 | primaryFunction: String 54 | } 55 | 56 | type Human { 57 | name: String 58 | bornIn: String 59 | } 60 | 61 | type Ship { 62 | name: String 63 | length: Int 64 | } 65 | 66 | union SearchResult = Human | Droid | Starship 67 | 68 | -------------------------------------------------------------------------------- /examples/basic.js: -------------------------------------------------------------------------------- 1 | import { ObjectType, Field } from '../../src'; 2 | 3 | @ObjectType() 4 | class Query { 5 | @Field(String) 6 | hello() { 7 | return 'World'; 8 | } 9 | } 10 | 11 | const schema = new Schema({ query: Query }); 12 | 13 | schema.execute(`query { 14 | hello 15 | }`); 16 | 17 | export default schema; 18 | -------------------------------------------------------------------------------- /examples/complex-ts.ts: -------------------------------------------------------------------------------- 1 | import { ObjectType, InterfaceType, Field, Schema } from '../src'; 2 | 3 | @InterfaceType() 4 | class BasePerson { 5 | @Field(String) public id: string; 6 | } 7 | 8 | @ObjectType({ 9 | interfaces: [BasePerson] 10 | }) 11 | class Person implements BasePerson { 12 | @Field(String) public id: string; 13 | 14 | @Field(String) public name: string; 15 | 16 | @Field(String) public lastName: string; 17 | 18 | @Field(String) 19 | public fullName(): string { 20 | return `${this.name} ${this.lastName}`; 21 | } 22 | 23 | private bestFriendId?: string; 24 | 25 | @Field(Person) 26 | public bestFriend(): Person | void { 27 | if (this.bestFriendId) { 28 | return persons[this.bestFriendId]; 29 | } 30 | } 31 | 32 | @Field([Person]) 33 | public allFriends: Person[]; 34 | 35 | constructor( 36 | id: string, 37 | name: string, 38 | lastName: string, 39 | bestFriendId?: string 40 | ) { 41 | this.id = id; 42 | this.name = name; 43 | this.lastName = lastName; 44 | this.bestFriendId = bestFriendId; 45 | } 46 | } 47 | 48 | var persons: { 49 | [key: string]: Person; 50 | } = { 51 | '1': new Person('1', 'Peter', 'Griffin', '2'), 52 | '2': new Person('2', 'Lisa', 'Simpson', '1') 53 | }; 54 | 55 | @ObjectType() 56 | class Query { 57 | @Field(Person, { args: { id: String } }) 58 | public getPerson(args: { id: string }): Person { 59 | return persons[args.id]; 60 | } 61 | } 62 | 63 | var schema = new Schema({ 64 | query: Query 65 | }); 66 | 67 | console.log(schema.toString()); 68 | schema 69 | .execute( 70 | `{ getPerson(id: "1") { 71 | id 72 | fullName 73 | bestFriend { 74 | id 75 | name 76 | } 77 | } 78 | }` 79 | ) 80 | .then(resp => console.log(resp.data)); 81 | export default schema; 82 | -------------------------------------------------------------------------------- /examples/starwars-raw/data.js: -------------------------------------------------------------------------------- 1 | const luke = { 2 | id: '1000', 3 | name: 'Luke Skywalker', 4 | friendIds: ['1002', '1003', '2000', '2001'], 5 | appearsIn: [4, 5, 6], 6 | homePlanet: 'Tatooine' 7 | }; 8 | 9 | const vader = { 10 | id: '1001', 11 | name: 'Darth Vader', 12 | friendIds: ['1004'], 13 | appearsIn: [4, 5, 6], 14 | homePlanet: 'Tatooine' 15 | }; 16 | 17 | const han = { 18 | id: '1002', 19 | name: 'Han Solo', 20 | friendIds: ['1000', '1003', '2001'], 21 | appearsIn: [4, 5, 6] 22 | }; 23 | 24 | const leia = { 25 | id: '1003', 26 | name: 'Leia Organa', 27 | friendIds: ['1000', '1002', '2000', '2001'], 28 | appearsIn: [4, 5, 6], 29 | homePlanet: 'Alderaan' 30 | }; 31 | 32 | const tarkin = { 33 | id: '1004', 34 | name: 'Wilhuff Tarkin', 35 | friendIds: ['1001'], 36 | appearsIn: [4] 37 | }; 38 | 39 | const humanData = { 40 | 1000: luke, 41 | 1001: vader, 42 | 1002: han, 43 | 1003: leia, 44 | 1004: tarkin 45 | }; 46 | 47 | const threepio = { 48 | id: '2000', 49 | name: 'C-3PO', 50 | friendIds: ['1000', '1002', '1003', '2001'], 51 | appearsIn: [4, 5, 6], 52 | primaryFunction: 'Protocol' 53 | }; 54 | 55 | const artoo = { 56 | id: '2001', 57 | name: 'R2-D2', 58 | friendIds: ['1000', '1002', '1003'], 59 | appearsIn: [4, 5, 6], 60 | primaryFunction: 'Astromech' 61 | }; 62 | 63 | const droidData = { 64 | 2000: threepio, 65 | 2001: artoo 66 | }; 67 | 68 | /** 69 | * Helper function to get a character by ID. 70 | */ 71 | function getCharacter(id) { 72 | // Returning a promise just to illustrate GraphQL.js's support. 73 | return Promise.resolve(humanData[id] || droidData[id]); 74 | } 75 | 76 | /** 77 | * Allows us to query for a character's friendIds. 78 | */ 79 | export function getFriends(character) { 80 | return character.friendIds.map(id => getCharacter(id)); 81 | } 82 | 83 | /** 84 | * Allows us to fetch the undisputed hero of the Star Wars trilogy, R2-D2. 85 | */ 86 | export function getHero(episode) { 87 | if (episode === 5) { 88 | // Luke is the hero of Episode V. 89 | return luke; 90 | } 91 | // Artoo is the hero otherwise. 92 | return artoo; 93 | } 94 | 95 | /** 96 | * Allows us to query for the human with the given id. 97 | */ 98 | export function getHuman(id) { 99 | return humanData[id]; 100 | } 101 | 102 | /** 103 | * Allows us to query for the droid with the given id. 104 | */ 105 | export function getDroid(id) { 106 | return droidData[id]; 107 | } 108 | -------------------------------------------------------------------------------- /examples/starwars-raw/schema.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | import { 11 | GraphQLEnumType, 12 | GraphQLInterfaceType, 13 | GraphQLObjectType, 14 | GraphQLList, 15 | GraphQLNonNull, 16 | GraphQLSchema, 17 | GraphQLString 18 | } from 'graphql'; 19 | 20 | import { getFriends, getHero, getHuman, getDroid } from './data.js'; 21 | 22 | const episodeEnum = new GraphQLEnumType({ 23 | name: 'Episode', 24 | description: 'One of the films in the Star Wars Trilogy', 25 | values: { 26 | NEWHOPE: { 27 | value: 4, 28 | description: 'Released in 1977.' 29 | }, 30 | EMPIRE: { 31 | value: 5, 32 | description: 'Released in 1980.' 33 | }, 34 | JEDI: { 35 | value: 6, 36 | description: 'Released in 1983.' 37 | } 38 | } 39 | }); 40 | 41 | const characterInterface = new GraphQLInterfaceType({ 42 | name: 'Character', 43 | description: 'A character in the Star Wars Trilogy', 44 | fields: () => ({ 45 | id: { 46 | type: new GraphQLNonNull(GraphQLString), 47 | description: 'The id of the character.' 48 | }, 49 | name: { 50 | type: GraphQLString, 51 | description: 'The name of the character.' 52 | }, 53 | friends: { 54 | type: new GraphQLList(characterInterface), 55 | description: 56 | 'The friends of the character, or an empty list if they ' + 'have none.' 57 | }, 58 | appearsIn: { 59 | type: new GraphQLList(episodeEnum), 60 | description: 'Which movies they appear in.' 61 | } 62 | }), 63 | resolveType: character => { 64 | return getHuman(character.id) ? humanType : droidType; 65 | } 66 | }); 67 | 68 | const humanType = new GraphQLObjectType({ 69 | name: 'Human', 70 | description: 'A humanoid creature in the Star Wars universe.', 71 | fields: () => ({ 72 | id: { 73 | type: new GraphQLNonNull(GraphQLString), 74 | description: 'The id of the human.' 75 | }, 76 | name: { 77 | type: GraphQLString, 78 | description: 'The name of the human.' 79 | }, 80 | friends: { 81 | type: new GraphQLList(characterInterface), 82 | description: 83 | 'The friends of the human, or an empty list if they ' + 'have none.', 84 | resolve: human => getFriends(human) 85 | }, 86 | appearsIn: { 87 | type: new GraphQLList(episodeEnum), 88 | description: 'Which movies they appear in.' 89 | }, 90 | homePlanet: { 91 | type: GraphQLString, 92 | description: 'The home planet of the human, or null if unknown.' 93 | } 94 | }), 95 | interfaces: [characterInterface] 96 | }); 97 | 98 | const droidType = new GraphQLObjectType({ 99 | name: 'Droid', 100 | description: 'A mechanical creature in the Star Wars universe.', 101 | fields: () => ({ 102 | id: { 103 | type: new GraphQLNonNull(GraphQLString), 104 | description: 'The id of the droid.' 105 | }, 106 | name: { 107 | type: GraphQLString, 108 | description: 'The name of the droid.' 109 | }, 110 | friends: { 111 | type: new GraphQLList(characterInterface), 112 | description: 113 | 'The friends of the droid, or an empty list if they ' + 'have none.', 114 | resolve: droid => getFriends(droid) 115 | }, 116 | appearsIn: { 117 | type: new GraphQLList(episodeEnum), 118 | description: 'Which movies they appear in.' 119 | }, 120 | primaryFunction: { 121 | type: GraphQLString, 122 | description: 'The primary function of the droid.' 123 | } 124 | }), 125 | interfaces: [characterInterface] 126 | }); 127 | 128 | const queryType = new GraphQLObjectType({ 129 | name: 'Query', 130 | fields: () => ({ 131 | hero: { 132 | type: characterInterface, 133 | args: { 134 | episode: { 135 | description: 136 | 'If omitted, returns the hero of the whole saga. If ' + 137 | 'provided, returns the hero of that particular episode.', 138 | type: episodeEnum 139 | } 140 | }, 141 | resolve: (root, { episode }) => getHero(episode) 142 | }, 143 | human: { 144 | type: humanType, 145 | args: { 146 | id: { 147 | description: 'id of the human', 148 | type: new GraphQLNonNull(GraphQLString) 149 | } 150 | }, 151 | resolve: (root, { id }) => getHuman(id) 152 | }, 153 | droid: { 154 | type: droidType, 155 | args: { 156 | id: { 157 | description: 'id of the droid', 158 | type: new GraphQLNonNull(GraphQLString) 159 | } 160 | }, 161 | resolve: (root, { id }) => getDroid(id) 162 | } 163 | }) 164 | }); 165 | 166 | const schema = new GraphQLSchema({ 167 | query: queryType 168 | }); 169 | 170 | export default schema; 171 | -------------------------------------------------------------------------------- /examples/starwars-raw/test/__snapshots__/index.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Interface create Interface 1`] = ` 4 | "\\"\\"\\"A character in the Star Wars Trilogy\\"\\"\\" 5 | interface Character { 6 | \\"\\"\\"The id of the character.\\"\\"\\" 7 | id: String! 8 | 9 | \\"\\"\\"The name of the character.\\"\\"\\" 10 | name: String 11 | 12 | \\"\\"\\"The friends of the character, or an empty list if they have none.\\"\\"\\" 13 | friends: [Character] 14 | 15 | \\"\\"\\"Which movies they appear in.\\"\\"\\" 16 | appearsIn: [Episode] 17 | } 18 | 19 | \\"\\"\\"A mechanical creature in the Star Wars universe.\\"\\"\\" 20 | type Droid implements Character { 21 | \\"\\"\\"The id of the droid.\\"\\"\\" 22 | id: String! 23 | 24 | \\"\\"\\"The name of the droid.\\"\\"\\" 25 | name: String 26 | 27 | \\"\\"\\"The friends of the droid, or an empty list if they have none.\\"\\"\\" 28 | friends: [Character] 29 | 30 | \\"\\"\\"Which movies they appear in.\\"\\"\\" 31 | appearsIn: [Episode] 32 | 33 | \\"\\"\\"The primary function of the droid.\\"\\"\\" 34 | primaryFunction: String 35 | } 36 | 37 | \\"\\"\\"One of the films in the Star Wars Trilogy\\"\\"\\" 38 | enum Episode { 39 | \\"\\"\\"Released in 1977.\\"\\"\\" 40 | NEWHOPE 41 | 42 | \\"\\"\\"Released in 1980.\\"\\"\\" 43 | EMPIRE 44 | 45 | \\"\\"\\"Released in 1983.\\"\\"\\" 46 | JEDI 47 | } 48 | 49 | \\"\\"\\"A humanoid creature in the Star Wars universe.\\"\\"\\" 50 | type Human implements Character { 51 | \\"\\"\\"The id of the human.\\"\\"\\" 52 | id: String! 53 | 54 | \\"\\"\\"The name of the human.\\"\\"\\" 55 | name: String 56 | 57 | \\"\\"\\"The friends of the human, or an empty list if they have none.\\"\\"\\" 58 | friends: [Character] 59 | 60 | \\"\\"\\"Which movies they appear in.\\"\\"\\" 61 | appearsIn: [Episode] 62 | 63 | \\"\\"\\"The home planet of the human, or null if unknown.\\"\\"\\" 64 | homePlanet: String 65 | } 66 | 67 | type Query { 68 | hero( 69 | \\"\\"\\" 70 | If omitted, returns the hero of the whole saga. If provided, returns the hero of that particular episode. 71 | \\"\\"\\" 72 | episode: Episode 73 | ): Character 74 | human( 75 | \\"\\"\\"id of the human\\"\\"\\" 76 | id: String! 77 | ): Human 78 | droid( 79 | \\"\\"\\"id of the droid\\"\\"\\" 80 | id: String! 81 | ): Droid 82 | } 83 | " 84 | `; 85 | -------------------------------------------------------------------------------- /examples/starwars-raw/test/index.js: -------------------------------------------------------------------------------- 1 | import { graphql, printSchema } from 'graphql'; 2 | import schema, { Episode } from '../schema'; 3 | 4 | describe('Interface', () => { 5 | test(`create Interface`, () => { 6 | expect(printSchema(schema)).toMatchSnapshot(); 7 | }); 8 | test('try query', async () => { 9 | var result = await graphql( 10 | schema, 11 | ` 12 | { 13 | hero { 14 | id 15 | name 16 | friends { 17 | id 18 | name 19 | } 20 | } 21 | } 22 | ` 23 | ); 24 | expect(result).toMatchObject({ 25 | data: { 26 | hero: { 27 | friends: [ 28 | { id: '1000', name: 'Luke Skywalker' }, 29 | { id: '1002', name: 'Han Solo' }, 30 | { id: '1003', name: 'Leia Organa' } 31 | ], 32 | id: '2001', 33 | name: 'R2-D2' 34 | } 35 | } 36 | }); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /examples/starwars-ts/data.ts: -------------------------------------------------------------------------------- 1 | import { Character, Human, Droid } from './schema'; 2 | 3 | const luke: Human = { 4 | id: '1000', 5 | name: 'Luke Skywalker', 6 | friendIds: ['1002', '1003', '2000', '2001'], 7 | appearsIn: [4, 5, 6], 8 | homePlanet: 'Tatooine' 9 | }; 10 | 11 | const vader: Human = { 12 | id: '1001', 13 | name: 'Darth Vader', 14 | friendIds: ['1004'], 15 | appearsIn: [4, 5, 6], 16 | homePlanet: 'Tatooine' 17 | }; 18 | 19 | const han: Human = { 20 | id: '1002', 21 | name: 'Han Solo', 22 | friendIds: ['1000', '1003', '2001'], 23 | appearsIn: [4, 5, 6] 24 | }; 25 | 26 | const leia: Human = { 27 | id: '1003', 28 | name: 'Leia Organa', 29 | friendIds: ['1000', '1002', '2000', '2001'], 30 | appearsIn: [4, 5, 6], 31 | homePlanet: 'Alderaan' 32 | }; 33 | 34 | const tarkin: Human = { 35 | id: '1004', 36 | name: 'Wilhuff Tarkin', 37 | friendIds: ['1001'], 38 | appearsIn: [4] 39 | }; 40 | 41 | const humanData: { 42 | [key: string]: Human; 43 | } = { 44 | 1000: luke, 45 | 1001: vader, 46 | 1002: han, 47 | 1003: leia, 48 | 1004: tarkin 49 | }; 50 | 51 | const threepio: Droid = { 52 | id: '2000', 53 | name: 'C-3PO', 54 | friendIds: ['1000', '1002', '1003', '2001'], 55 | appearsIn: [4, 5, 6], 56 | primaryFunction: 'Protocol' 57 | }; 58 | 59 | const artoo: Droid = { 60 | id: '2001', 61 | name: 'R2-D2', 62 | friendIds: ['1000', '1002', '1003'], 63 | appearsIn: [4, 5, 6], 64 | primaryFunction: 'Astromech' 65 | }; 66 | 67 | const droidData: { 68 | [key: string]: Droid; 69 | } = { 70 | 2000: threepio, 71 | 2001: artoo 72 | }; 73 | 74 | /** 75 | * Helper function to get a character by ID. 76 | */ 77 | function getCharacter(id: string): Human | Droid { 78 | // Returning a promise just to illustrate GraphQL.js's support. 79 | return humanData[id] || droidData[id]; 80 | } 81 | // function getCharacter(id: string): Promise { 82 | // // Returning a promise just to illustrate GraphQL.js's support. 83 | // return Promise.resolve(humanData[id] || droidData[id]); 84 | // } 85 | 86 | /** 87 | * Allows us to query for a character's friendIds. 88 | */ 89 | export function getFriends(character: Character): (Human | Droid)[] { 90 | return character.friendIds.map(id => getCharacter(id)); 91 | } 92 | 93 | /** 94 | * Allows us to fetch the undisputed hero of the Star Wars trilogy, R2-D2. 95 | */ 96 | export function getHero(episode: number): Human | Droid { 97 | if (episode === 5) { 98 | // Luke is the hero of Episode V. 99 | return luke; 100 | } 101 | // Artoo is the hero otherwise. 102 | return artoo; 103 | } 104 | 105 | /** 106 | * Allows us to query for the human with the given id. 107 | */ 108 | export function getHuman(id: string): Human { 109 | return humanData[id]; 110 | } 111 | 112 | /** 113 | * Allows us to query for the droid with the given id. 114 | */ 115 | export function getDroid(id: string): Droid { 116 | return droidData[id]; 117 | } 118 | -------------------------------------------------------------------------------- /examples/starwars-ts/schema.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ObjectType, 3 | InterfaceType, 4 | Schema, 5 | Field, 6 | ID, 7 | description, 8 | EnumType, 9 | NonNull 10 | } from '../../src'; 11 | import { getHero, getFriends, getHuman, getDroid } from './data'; 12 | 13 | @EnumType() 14 | @description('The description') 15 | export class Episode { 16 | @description('Released in 1977.') static NEWHOPE = 4; 17 | @description('Released in 1980.') static EMPIRE = 5; 18 | @description('Released in 1983.') static JEDI = 6; 19 | } 20 | 21 | @InterfaceType({ 22 | resolveType: (root: Character) => { 23 | return getHuman(root.id) ? Human : Droid; 24 | } 25 | }) 26 | @description('A character in the Star Wars Trilogy') 27 | export class Character { 28 | @Field(NonNull(ID)) 29 | @description('The id of the character.') 30 | public id: string; 31 | 32 | @Field(String) 33 | @description('The name of the character.') 34 | public name: string; 35 | 36 | @Field([Character]) 37 | @description( 38 | 'The friends of the character, or an empty list if they have none.' 39 | ) 40 | friends?(): Character[] { 41 | return getFriends(this); 42 | } 43 | 44 | @Field([Episode]) 45 | @description('Which movies they appear in.') 46 | public appearsIn: number[]; 47 | 48 | public friendIds: string[]; 49 | } 50 | 51 | @ObjectType({ 52 | interfaces: [Character] 53 | }) 54 | @description('A humanoid creature in the Star Wars universe.') 55 | export class Human implements Character { 56 | @Field(String) 57 | @description('The home planet of the human, or null if unknown..') 58 | public homePlanet?: string; 59 | 60 | id: string; 61 | name: string; 62 | friends?: () => Character[]; 63 | friendIds: string[]; 64 | appearsIn: number[]; 65 | } 66 | 67 | @ObjectType({ 68 | interfaces: [Character] 69 | }) 70 | @description('A mechanical creature in the Star Wars universe.') 71 | export class Droid implements Character { 72 | @Field(String) 73 | @description('The primary function of the droid, or null if unknown..') 74 | public primaryFunction: string; 75 | 76 | id: string; 77 | name: string; 78 | friends?: () => Character[]; 79 | friendIds: string[]; 80 | appearsIn: number[]; 81 | } 82 | 83 | @ObjectType() 84 | class Query { 85 | @Field(Character, { args: { episode: Episode } }) 86 | hero(args: { episode: number }): Character { 87 | return getHero(args.episode); 88 | } 89 | 90 | @Field(Human, { args: { id: String } }) 91 | human(args: { id: string }): Human { 92 | return getHuman(args.id); 93 | } 94 | 95 | @Field(Droid, { args: { id: String } }) 96 | droid(args: { id: string }): Droid { 97 | return getDroid(args.id); 98 | } 99 | } 100 | 101 | const schema = new Schema({ 102 | query: Query 103 | }); 104 | 105 | export default schema; 106 | -------------------------------------------------------------------------------- /examples/starwars-ts/test/__snapshots__/index.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Interface create Interface 1`] = ` 4 | "\\"\\"\\"A character in the Star Wars Trilogy\\"\\"\\" 5 | interface Character { 6 | \\"\\"\\"The id of the character.\\"\\"\\" 7 | id: ID! 8 | 9 | \\"\\"\\"The name of the character.\\"\\"\\" 10 | name: String 11 | 12 | \\"\\"\\"The friends of the character, or an empty list if they have none.\\"\\"\\" 13 | friends: [Character] 14 | 15 | \\"\\"\\"Which movies they appear in.\\"\\"\\" 16 | appearsIn: [Episode] 17 | } 18 | 19 | \\"\\"\\"A mechanical creature in the Star Wars universe.\\"\\"\\" 20 | type Droid implements Character { 21 | \\"\\"\\"The id of the character.\\"\\"\\" 22 | id: ID! 23 | 24 | \\"\\"\\"The name of the character.\\"\\"\\" 25 | name: String 26 | 27 | \\"\\"\\"The friends of the character, or an empty list if they have none.\\"\\"\\" 28 | friends: [Character] 29 | 30 | \\"\\"\\"Which movies they appear in.\\"\\"\\" 31 | appearsIn: [Episode] 32 | 33 | \\"\\"\\"The primary function of the droid, or null if unknown..\\"\\"\\" 34 | primaryFunction: String 35 | } 36 | 37 | \\"\\"\\"The description\\"\\"\\" 38 | enum Episode { 39 | \\"\\"\\"Released in 1977.\\"\\"\\" 40 | NEWHOPE 41 | 42 | \\"\\"\\"Released in 1980.\\"\\"\\" 43 | EMPIRE 44 | 45 | \\"\\"\\"Released in 1983.\\"\\"\\" 46 | JEDI 47 | } 48 | 49 | \\"\\"\\"A humanoid creature in the Star Wars universe.\\"\\"\\" 50 | type Human implements Character { 51 | \\"\\"\\"The id of the character.\\"\\"\\" 52 | id: ID! 53 | 54 | \\"\\"\\"The name of the character.\\"\\"\\" 55 | name: String 56 | 57 | \\"\\"\\"The friends of the character, or an empty list if they have none.\\"\\"\\" 58 | friends: [Character] 59 | 60 | \\"\\"\\"Which movies they appear in.\\"\\"\\" 61 | appearsIn: [Episode] 62 | 63 | \\"\\"\\"The home planet of the human, or null if unknown..\\"\\"\\" 64 | homePlanet: String 65 | } 66 | 67 | type Query { 68 | hero(episode: Episode): Character 69 | human(id: String): Human 70 | droid(id: String): Droid 71 | } 72 | " 73 | `; 74 | -------------------------------------------------------------------------------- /examples/starwars-ts/test/index.ts: -------------------------------------------------------------------------------- 1 | import schema, { Episode } from '../schema'; 2 | 3 | describe('Interface', () => { 4 | test(`create Interface`, () => { 5 | expect(schema.toString()).toMatchSnapshot(); 6 | }); 7 | test('try query', async () => { 8 | var result = await schema.execute(` 9 | { 10 | hero { 11 | id 12 | name 13 | friends { 14 | id 15 | name 16 | } 17 | } 18 | } 19 | `); 20 | expect(result).toMatchObject({ 21 | data: { 22 | hero: { 23 | friends: [ 24 | { id: '1000', name: 'Luke Skywalker' }, 25 | { id: '1002', name: 'Han Solo' }, 26 | { id: '1003', name: 'Leia Organa' } 27 | ], 28 | id: '2001', 29 | name: 'R2-D2' 30 | } 31 | } 32 | }); 33 | }); 34 | test('Episode enum', () => { 35 | expect(Episode.NEWHOPE).toBe(4); 36 | expect(Episode.EMPIRE).toBe(5); 37 | expect(Episode.JEDI).toBe(6); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /examples/starwars/data.js: -------------------------------------------------------------------------------- 1 | 2 | const luke = { 3 | id: "1000", 4 | name: "Luke Skywalker", 5 | friendIds: ["1002", "1003", "2000", "2001"], 6 | appearsIn: [4, 5, 6], 7 | homePlanet: "Tatooine" 8 | }; 9 | 10 | const vader = { 11 | id: "1001", 12 | name: "Darth Vader", 13 | friendIds: ["1004"], 14 | appearsIn: [4, 5, 6], 15 | homePlanet: "Tatooine" 16 | }; 17 | 18 | const han = { 19 | id: "1002", 20 | name: "Han Solo", 21 | friendIds: ["1000", "1003", "2001"], 22 | appearsIn: [4, 5, 6] 23 | }; 24 | 25 | const leia = { 26 | id: "1003", 27 | name: "Leia Organa", 28 | friendIds: ["1000", "1002", "2000", "2001"], 29 | appearsIn: [4, 5, 6], 30 | homePlanet: "Alderaan" 31 | }; 32 | 33 | const tarkin = { 34 | id: "1004", 35 | name: "Wilhuff Tarkin", 36 | friendIds: ["1001"], 37 | appearsIn: [4] 38 | }; 39 | 40 | const humanData = { 41 | 1000: luke, 42 | 1001: vader, 43 | 1002: han, 44 | 1003: leia, 45 | 1004: tarkin 46 | }; 47 | 48 | const threepio = { 49 | id: "2000", 50 | name: "C-3PO", 51 | friendIds: ["1000", "1002", "1003", "2001"], 52 | appearsIn: [4, 5, 6], 53 | primaryFunction: "Protocol" 54 | }; 55 | 56 | const artoo = { 57 | id: "2001", 58 | name: "R2-D2", 59 | friendIds: ["1000", "1002", "1003"], 60 | appearsIn: [4, 5, 6], 61 | primaryFunction: "Astromech" 62 | }; 63 | 64 | const droidData = { 65 | 2000: threepio, 66 | 2001: artoo 67 | }; 68 | 69 | /** 70 | * Helper function to get a character by ID. 71 | */ 72 | function getCharacter(id) { 73 | // Returning a promise just to illustrate GraphQL.js's support. 74 | return Promise.resolve(humanData[id] || droidData[id]); 75 | } 76 | 77 | /** 78 | * Allows us to query for a character's friendIds. 79 | */ 80 | export function getFriends(character) { 81 | return character.friendIds.map(id => getCharacter(id)); 82 | } 83 | 84 | /** 85 | * Allows us to fetch the undisputed hero of the Star Wars trilogy, R2-D2. 86 | */ 87 | export function getHero(episode) { 88 | if (episode === 5) { 89 | // Luke is the hero of Episode V. 90 | return luke; 91 | } 92 | // Artoo is the hero otherwise. 93 | return artoo; 94 | } 95 | 96 | /** 97 | * Allows us to query for the human with the given id. 98 | */ 99 | export function getHuman(id) { 100 | return humanData[id]; 101 | } 102 | 103 | /** 104 | * Allows us to query for the droid with the given id. 105 | */ 106 | export function getDroid(id) { 107 | return droidData[id]; 108 | } 109 | -------------------------------------------------------------------------------- /examples/starwars/schema.js: -------------------------------------------------------------------------------- 1 | import { 2 | ObjectType, 3 | InterfaceType, 4 | Schema, 5 | Field, 6 | ID, 7 | EnumType, 8 | EnumValue 9 | } from '../../src'; 10 | import { getHero, getFriends, getHuman, getDroid } from './data'; 11 | 12 | @EnumType() 13 | export class Episode { 14 | static NEWHOPE = 4; 15 | static EMPIRE = 5; 16 | static JEDI = 6; 17 | } 18 | 19 | @InterfaceType({ 20 | resolveType: root => { 21 | return getHuman(root.id) ? Human : Droid; 22 | } 23 | }) 24 | export class Character { 25 | @Field(ID) id; 26 | @Field(String) name; 27 | @Field([Character]) 28 | friends() { 29 | return getFriends(this); 30 | } 31 | 32 | @Field([Episode]) 33 | appearsIn; 34 | } 35 | 36 | @ObjectType({ 37 | interfaces: [Character] 38 | }) 39 | export class Human { 40 | @Field(String) homePlanet; 41 | } 42 | 43 | @ObjectType({ 44 | interfaces: [Character] 45 | }) 46 | export class Droid { 47 | @Field(String) primaryFunction; 48 | } 49 | 50 | @ObjectType() 51 | class Query { 52 | @Field(Character, { args: { episode: Number } }) 53 | hero({ episode }) { 54 | return getHero(episode); 55 | } 56 | 57 | @Field(Human, { args: { id: String } }) 58 | human({ id }) { 59 | return getHuman(id); 60 | } 61 | 62 | @Field(Droid, { args: { id: String } }) 63 | droid({ id }) { 64 | return getDroid(id); 65 | } 66 | } 67 | 68 | const schema = new Schema({ 69 | query: Query 70 | }); 71 | 72 | export default schema; 73 | -------------------------------------------------------------------------------- /examples/starwars/test/__snapshots__/index.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Interface create Interface 1`] = ` 4 | "interface Character { 5 | id: ID 6 | name: String 7 | friends: [Character] 8 | appearsIn: [Episode] 9 | } 10 | 11 | type Droid implements Character { 12 | id: ID 13 | name: String 14 | friends: [Character] 15 | appearsIn: [Episode] 16 | primaryFunction: String 17 | } 18 | 19 | enum Episode { 20 | NEWHOPE 21 | EMPIRE 22 | JEDI 23 | } 24 | 25 | type Human implements Character { 26 | id: ID 27 | name: String 28 | friends: [Character] 29 | appearsIn: [Episode] 30 | homePlanet: String 31 | } 32 | 33 | type Query { 34 | hero(episode: Float): Character 35 | human(id: String): Human 36 | droid(id: String): Droid 37 | } 38 | " 39 | `; 40 | -------------------------------------------------------------------------------- /examples/starwars/test/index.js: -------------------------------------------------------------------------------- 1 | import schema, { Episode } from '../schema'; 2 | 3 | describe('Interface', () => { 4 | test(`create Interface`, () => { 5 | expect(schema.toString()).toMatchSnapshot(); 6 | }); 7 | test('try query', async () => { 8 | var result = await schema.execute(` 9 | { 10 | hero { 11 | id 12 | name 13 | friends { 14 | id 15 | name 16 | } 17 | } 18 | } 19 | `); 20 | expect(result).toMatchObject({ 21 | data: { 22 | hero: { 23 | friends: [ 24 | { id: '1000', name: 'Luke Skywalker' }, 25 | { id: '1002', name: 'Han Solo' }, 26 | { id: '1003', name: 'Leia Organa' } 27 | ], 28 | id: '2001', 29 | name: 'R2-D2' 30 | } 31 | } 32 | }); 33 | }); 34 | test('Episode enum', () => { 35 | expect(Episode.NEWHOPE).toBe(4); 36 | expect(Episode.EMPIRE).toBe(5); 37 | expect(Episode.JEDI).toBe(6); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "graphene-js", 3 | "version": "0.5.2", 4 | "description": "GraphQL Framework for Javascript", 5 | "main": "lib/index.js", 6 | "typescript": { 7 | "definition": "lib/index.d.ts" 8 | }, 9 | "typings": "lib/index.d.ts", 10 | "scripts": { 11 | "clean": "rm -rf lib", 12 | "compile": "tsc", 13 | "watch": "tsc -w", 14 | "prepublish": "npm run clean && npm run compile", 15 | "test": "./node_modules/.bin/jest", 16 | "posttest": "npm run lint", 17 | "lint": "tslint \"src/**/*.ts\" -e \"**/__tests__/**\"" 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "graphql-js/graphene-js" 22 | }, 23 | "keywords": [ 24 | "GraphQL", 25 | "Graphene", 26 | "JavaScript", 27 | "TypeScript", 28 | "Schema", 29 | "Schema Language", 30 | "Tools" 31 | ], 32 | "author": "Syrus Akbary ", 33 | "license": "MIT", 34 | "homepage": "https://github.com/graphql-js/graphene/", 35 | "bugs": { 36 | "url": "https://github.com/graphql-js/graphene/issues" 37 | }, 38 | "engines": { 39 | "node": ">=6.0" 40 | }, 41 | "devDependencies": { 42 | "@types/common-tags": "^1.2.5", 43 | "@types/glob": "^5.0.30", 44 | "@types/graphql": "^0.9.4", 45 | "@types/inflected": "^1.1.29", 46 | "@types/jest": "^20.0.6", 47 | "@types/mkdirp": "^0.5.0", 48 | "@types/node": "^8.0.51", 49 | "@types/node-fetch": "^1.6.7", 50 | "@types/yargs": "^8.0.2", 51 | "ansi-regex": "^3.0.0", 52 | "common-tags": "^1.4.0", 53 | "jest": "^20.0.4", 54 | "jest-matcher-utils": "^20.0.3", 55 | "tslint": "^5.9.1", 56 | "typescript": "^2.7.1" 57 | }, 58 | "dependencies": { 59 | "graphql": "^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0", 60 | "graphql-iso-date": "^3.5.0", 61 | "reflect-metadata": "^0.1.12" 62 | }, 63 | "jest": { 64 | "testEnvironment": "node", 65 | "setupFiles": [], 66 | "testMatch": [ 67 | "**/__tests__/**/*.(js|ts)", 68 | "**/__tests__/*.(js|ts)", 69 | "**/test/**/*.(js|ts)", 70 | "**/test/*.(js|ts)" 71 | ], 72 | "testPathIgnorePatterns": [ 73 | "/node_modules/", 74 | "/lib/", 75 | "/test/fixtures/", 76 | "/scripts/preprocessor.js" 77 | ], 78 | "collectCoverageFrom": [ 79 | "src/**" 80 | ], 81 | "coveragePathIgnorePatterns": [ 82 | "/node_modules/", 83 | "/__tests__/", 84 | ".*.d.ts" 85 | ], 86 | "transform": { 87 | ".(js|ts)": "/scripts/preprocessor.js" 88 | }, 89 | "moduleFileExtensions": [ 90 | "ts", 91 | "js" 92 | ] 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /scripts/preprocessor.js: -------------------------------------------------------------------------------- 1 | const tsc = require('typescript'); 2 | const tsConfig = require('../tsconfig.json'); 3 | 4 | module.exports = { 5 | process(src, path) { 6 | return tsc.transpile(src, tsConfig.compilerOptions, path, []); 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /src/__tests__/reflection.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLString, GraphQLType, GraphQLList, GraphQLInt } from "graphql"; 2 | import { 3 | setGraphQLType, 4 | getGraphQLType, 5 | setDescription, 6 | getDescription, 7 | description, 8 | convertArrayToGraphQLList, 9 | setDeprecationReason, 10 | getDeprecationReason, 11 | deprecated 12 | } from "../reflection"; 13 | 14 | describe("Reflection integration", () => { 15 | describe("setGraphQLType", () => { 16 | var type: any; 17 | beforeEach(() => { 18 | class MyType {} 19 | type = MyType; 20 | }); 21 | 22 | test(`setGraphQLType works if provided type if GraphQLType`, () => { 23 | setGraphQLType(type, GraphQLString); 24 | expect(getGraphQLType(type)).toBe(GraphQLString); 25 | }); 26 | 27 | test(`setGraphQLType fails if provided type is not a GraphQLType`, () => { 28 | expect(() => { 29 | setGraphQLType(type, {}); 30 | }).toThrowError(/Expected .* to be a GraphQL type\./); 31 | }); 32 | 33 | test(`setGraphQLType fails if it already have a type associated`, () => { 34 | setGraphQLType(type, GraphQLString); 35 | expect(() => { 36 | setGraphQLType(type, GraphQLInt); 37 | }).toThrowError(/already have a Graphene type attached\./m); 38 | }); 39 | }); 40 | 41 | describe("getGraphQLType", () => { 42 | var type: any; 43 | beforeEach(() => { 44 | class MyType {} 45 | type = MyType; 46 | }); 47 | 48 | test("getGraphQLType throws if have no type attached", () => { 49 | expect(() => { 50 | getGraphQLType(type); 51 | }).toThrowError(/have no GraphQL type associated to it\./m); 52 | }); 53 | 54 | test("getGraphQLType works if provided a GraphQLType", () => { 55 | expect(getGraphQLType(GraphQLString)).toBe(GraphQLString); 56 | var listOfString = new GraphQLList(GraphQLString); 57 | expect(getGraphQLType(listOfString)).toBe(listOfString); 58 | }); 59 | 60 | test("getGraphQLType calls convertArrayToGraphQLList if the provided is an Array.", () => { 61 | jest.unmock("../reflection"); 62 | var reflection = require("../reflection"); 63 | var prev = reflection.convertArrayToGraphQLList; 64 | var funcResult = new GraphQLList(GraphQLString); 65 | reflection.convertArrayToGraphQLList = jest.fn(() => funcResult); 66 | var result = getGraphQLType([type]); 67 | expect(reflection.convertArrayToGraphQLList).toBeCalled(); 68 | expect(result).toBe(funcResult); 69 | reflection.convertArrayToGraphQLList = prev; 70 | }); 71 | }); 72 | 73 | describe("convertArrayToGraphQLList", () => { 74 | var type: any; 75 | beforeEach(() => { 76 | class MyType {} 77 | type = MyType; 78 | setGraphQLType(type, GraphQLString); 79 | }); 80 | 81 | test("convertArrayToGraphQLList should work if array have length 1", () => { 82 | var converted: GraphQLList = convertArrayToGraphQLList([type]); 83 | expect(converted).toBeInstanceOf(GraphQLList); 84 | expect(converted.ofType).toBe(GraphQLString); 85 | }); 86 | 87 | test("convertArrayToGraphQLList should fail if length of array is > 1", () => { 88 | expect(() => { 89 | convertArrayToGraphQLList([type, type]); 90 | }).toThrowError( 91 | /Graphene Array definitions should contain exactly one element\./m 92 | ); 93 | }); 94 | 95 | test("convertArrayToGraphQLList should fail if length of array is == 0", () => { 96 | expect(() => { 97 | convertArrayToGraphQLList([]); 98 | }).toThrowError( 99 | /Graphene Array definitions should contain exactly one element\./m 100 | ); 101 | }); 102 | }); 103 | 104 | describe("description", () => { 105 | var type: any; 106 | beforeEach(() => { 107 | class MyType { 108 | myMethod() {} 109 | } 110 | type = MyType; 111 | }); 112 | 113 | test("description on class", () => { 114 | setDescription(type, "The description"); 115 | expect(getDescription(type)).toBe("The description"); 116 | }); 117 | 118 | test("description on class method", () => { 119 | setDescription(type, "myMethod", "Method description"); 120 | expect(getDescription(type, "myMethod")).toBe("Method description"); 121 | }); 122 | 123 | test("description as class decorator", () => { 124 | @description("The description") 125 | class MyType { 126 | myMethod() {} 127 | } 128 | 129 | expect(getDescription(MyType)).toBe("The description"); 130 | }); 131 | 132 | test("description as method decorator", () => { 133 | class MyType { 134 | @description("Method description") 135 | myMethod() {} 136 | } 137 | 138 | expect(getDescription(MyType.prototype, "myMethod")).toBe( 139 | "Method description" 140 | ); 141 | }); 142 | 143 | test("description on static property", () => { 144 | class MyType { 145 | static statickey = 1; 146 | } 147 | setDescription(MyType, "statickey", "key description"); 148 | expect(getDescription(MyType, "statickey")).toBe("key description"); 149 | }); 150 | 151 | test("description as decorator on static property", () => { 152 | class MyType { 153 | @description("key description") static statickey = 1; 154 | } 155 | 156 | expect(getDescription(MyType, "statickey")).toBe("key description"); 157 | }); 158 | 159 | test("description as decorator on property", () => { 160 | class MyType { 161 | @description("key description") key = 1; 162 | } 163 | 164 | expect(getDescription(MyType.prototype, "key")).toBe("key description"); 165 | }); 166 | }); 167 | 168 | describe("deprecated", () => { 169 | var type: any; 170 | beforeEach(() => { 171 | class MyType { 172 | myMethod() {} 173 | } 174 | type = MyType; 175 | }); 176 | 177 | test("deprecated on class method", () => { 178 | setDeprecationReason(type, "myMethod", "Method deprecated"); 179 | expect(getDeprecationReason(type, "myMethod")).toBe("Method deprecated"); 180 | }); 181 | 182 | test("deprecated decorator in class", () => { 183 | expect(() => { 184 | @deprecated("is deprecated") 185 | class MyType { 186 | myMethod() {} 187 | } 188 | }).toThrowError( 189 | `Classes can't be decorated with the @deprecated decorator.` 190 | ); 191 | }); 192 | 193 | test("deprecated decorator in static method", () => { 194 | class MyType { 195 | @deprecated("Method deprecated") 196 | static myMethod() {} 197 | } 198 | 199 | expect(getDeprecationReason(MyType, "myMethod")).toBe( 200 | "Method deprecated" 201 | ); 202 | }); 203 | }); 204 | }); 205 | -------------------------------------------------------------------------------- /src/definitions/__tests__/__snapshots__/inputobjecttype.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`InputObjectType create InputObjectType with no fields 1`] = ` 4 | "Type class MyInputObject { 5 | } must have fields defined on it." 6 | `; 7 | -------------------------------------------------------------------------------- /src/definitions/__tests__/__snapshots__/objecttype.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`ObjectType setup create ObjectType with no fields 1`] = ` 4 | "Type class MyObject { 5 | } must have fields defined on it." 6 | `; 7 | 8 | exports[`ObjectType setup create ObjectType with wrong interfaces 1`] = `"Provided interface String is not valid"`; 9 | -------------------------------------------------------------------------------- /src/definitions/__tests__/__snapshots__/schema.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Schema can create a schema 1`] = ` 4 | Object { 5 | "data": Object { 6 | "hello": "World", 7 | }, 8 | } 9 | `; 10 | 11 | exports[`Schema can create a schema 2`] = ` 12 | "type Mutation { 13 | mutate: String 14 | } 15 | 16 | type Query { 17 | hello: String 18 | viewer: UserBase 19 | } 20 | 21 | type Subscription { 22 | subscribe: String 23 | } 24 | 25 | type User implements UserBase { 26 | name: String 27 | } 28 | 29 | interface UserBase { 30 | name: String 31 | } 32 | " 33 | `; 34 | -------------------------------------------------------------------------------- /src/definitions/__tests__/enum.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLEnumType } from "graphql"; 2 | import { getGraphQLType } from "../../reflection"; 3 | import { EnumType } from "../"; 4 | 5 | describe("Enum creation", () => { 6 | test(`create Enum`, () => { 7 | @EnumType({}) 8 | class MyEnum { 9 | static FOO = "bar"; 10 | } 11 | 12 | var graphqlType: GraphQLEnumType = getGraphQLType(MyEnum); 13 | expect(graphqlType.name).toBe("MyEnum"); 14 | expect(graphqlType.description).toBe(undefined); 15 | }); 16 | 17 | test(`create Enum custom settings`, () => { 18 | @EnumType({ 19 | name: "MyCustomEnum", 20 | description: "MyCustomEnum Description" 21 | }) 22 | class MyEnum { 23 | static FOO = "bar"; 24 | } 25 | 26 | var graphqlType: GraphQLEnumType = getGraphQLType(MyEnum); 27 | expect(graphqlType.name).toBe("MyCustomEnum"); 28 | expect(graphqlType.description).toBe("MyCustomEnum Description"); 29 | }); 30 | 31 | test(`create Enum with Values`, () => { 32 | @EnumType({}) 33 | class MyEnum { 34 | static VALUE1 = 1; 35 | static VALUE2 = 2; 36 | } 37 | expect(MyEnum.VALUE1).toBe(1); 38 | expect(MyEnum.VALUE2).toBe(2); 39 | }); 40 | 41 | test(`create Enum graphql type`, () => { 42 | @EnumType({ 43 | description: "Desc" 44 | }) 45 | class MyEnum { 46 | static VALUE1 = 1; 47 | static VALUE2 = 2; 48 | } 49 | var graphqlType: GraphQLEnumType = getGraphQLType(MyEnum); 50 | expect(graphqlType).toBeInstanceOf(GraphQLEnumType); 51 | expect(graphqlType.name).toBe("MyEnum"); 52 | expect(graphqlType.description).toBe("Desc"); 53 | expect(graphqlType.getValues()).toMatchObject([ 54 | { 55 | // "deprecationReason": undefined, 56 | // "description": "Value1 description", 57 | // "isDeprecated": false, 58 | name: "VALUE1", 59 | value: 1 60 | }, 61 | { 62 | // "deprecationReason": "Value2 is deprecated", 63 | // "description": "Value2 description", 64 | // "isDeprecated": true, 65 | name: "VALUE2", 66 | value: 2 67 | } 68 | ]); 69 | }); 70 | }); 71 | -------------------------------------------------------------------------------- /src/definitions/__tests__/inputobjecttype.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLInputObjectType } from "graphql"; 2 | import { getGraphQLType } from "../../reflection"; 3 | import { InputObjectType, InputField } from "../"; 4 | 5 | describe("InputObjectType", () => { 6 | test(`create InputObjectType`, () => { 7 | @InputObjectType() 8 | class MyInputObject { 9 | @InputField(String) hello: string; 10 | } 11 | 12 | var graphqlType: GraphQLInputObjectType = getGraphQLType( 13 | MyInputObject 14 | ); 15 | expect(graphqlType.name).toBe("MyInputObject"); 16 | expect(graphqlType.description).toBe(undefined); 17 | }); 18 | 19 | test(`create InputObjectType custom settings`, () => { 20 | @InputObjectType({ 21 | name: "MyInputObjectType", 22 | description: "MyInputObjectType description" 23 | }) 24 | class MyInputObject { 25 | @InputField(String) hello: string; 26 | } 27 | 28 | var graphqlType: GraphQLInputObjectType = getGraphQLType( 29 | MyInputObject 30 | ); 31 | expect(graphqlType.name).toBe("MyInputObjectType"); 32 | expect(graphqlType.description).toBe("MyInputObjectType description"); 33 | }); 34 | 35 | test(`create InputObjectType with fields`, () => { 36 | @InputObjectType({ 37 | name: "MyInputObjectType", 38 | description: "MyInputObjectType description" 39 | }) 40 | class MyInputObject { 41 | @InputField(String) hello: string; 42 | } 43 | 44 | var graphqlType: GraphQLInputObjectType = getGraphQLType( 45 | MyInputObject 46 | ); 47 | expect(graphqlType.name).toBe("MyInputObjectType"); 48 | expect(graphqlType.description).toBe("MyInputObjectType description"); 49 | expect(Object.keys(graphqlType.getFields())).toEqual(["hello"]); 50 | }); 51 | 52 | test(`create InputObjectType with no fields`, () => { 53 | expect(() => { 54 | @InputObjectType() 55 | class MyInputObject {} 56 | }).toThrowErrorMatchingSnapshot(); 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /src/definitions/__tests__/interface.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLInterfaceType } from "graphql"; 2 | import { getGraphQLType } from "../../reflection"; 3 | import { InterfaceType, Field } from "../"; 4 | 5 | describe("Interface", () => { 6 | test(`create Interface`, () => { 7 | @InterfaceType() 8 | class MyInterface { 9 | @Field(String) hello: string; 10 | } 11 | 12 | var graphqlType: GraphQLInterfaceType = getGraphQLType( 13 | MyInterface 14 | ); 15 | expect(graphqlType.name).toBe("MyInterface"); 16 | expect(graphqlType.description).toBe(undefined); 17 | }); 18 | 19 | test(`create Interface custom settings`, () => { 20 | @InterfaceType({ 21 | name: "MyInterfaceType", 22 | description: "MyInterfaceType Description" 23 | }) 24 | class MyInterface { 25 | @Field(String) hello: string; 26 | } 27 | 28 | var graphqlType: GraphQLInterfaceType = getGraphQLType( 29 | MyInterface 30 | ); 31 | expect(graphqlType.name).toBe("MyInterfaceType"); 32 | expect(graphqlType.description).toBe("MyInterfaceType Description"); 33 | }); 34 | 35 | test(`create Interface with fields`, () => { 36 | @InterfaceType({ 37 | name: "MyInterfaceType", 38 | description: "My Description" 39 | }) 40 | class MyInterface { 41 | @Field(String) hello: string; 42 | } 43 | 44 | var graphqlType: GraphQLInterfaceType = getGraphQLType( 45 | MyInterface 46 | ); 47 | expect(graphqlType).toBeInstanceOf(GraphQLInterfaceType); 48 | expect(graphqlType.name).toBe("MyInterfaceType"); 49 | expect(graphqlType.description).toBe("My Description"); 50 | expect(Object.keys(graphqlType.getFields())).toEqual(["hello"]); 51 | }); 52 | }); 53 | -------------------------------------------------------------------------------- /src/definitions/__tests__/objecttype.ts: -------------------------------------------------------------------------------- 1 | import { FieldConfig } from "./../field"; 2 | import { GraphQLObjectType, GraphQLInterfaceType } from "graphql"; 3 | import { getGraphQLType, description } from "../../reflection"; 4 | import { ObjectType, Field, DynamicField, InterfaceType } from "./../"; 5 | 6 | describe("ObjectType setup", () => { 7 | test(`create ObjectType properly`, () => { 8 | @ObjectType() 9 | class MyObjectType { 10 | @Field(String) hello: string; 11 | } 12 | 13 | var graphqlType: GraphQLObjectType = getGraphQLType( 14 | MyObjectType 15 | ); 16 | expect(graphqlType.name).toBe("MyObjectType"); 17 | expect(graphqlType.description).toBe(undefined); 18 | }); 19 | 20 | test(`create ObjectType field config`, () => { 21 | @ObjectType() 22 | class MyObjectType { 23 | @Field({ type: String }) 24 | hello: string; 25 | } 26 | 27 | var graphqlType: GraphQLObjectType = getGraphQLType( 28 | MyObjectType 29 | ); 30 | expect(graphqlType.name).toBe("MyObjectType"); 31 | expect(graphqlType.description).toBe(undefined); 32 | expect(graphqlType.getFields()).toHaveProperty("hello"); 33 | }); 34 | 35 | test(`create ObjectType description decorator`, () => { 36 | @ObjectType() 37 | @description("My Description") 38 | class MyObjectType { 39 | @Field(String) hello: string; 40 | } 41 | var graphqlType: GraphQLObjectType = getGraphQLType( 42 | MyObjectType 43 | ); 44 | 45 | expect(graphqlType.description).toBe("My Description"); 46 | }); 47 | 48 | test(`create ObjectType properly`, () => { 49 | @ObjectType({ 50 | name: "MyCustomObjectType", 51 | description: "My Description" 52 | }) 53 | class MyObjectType { 54 | @Field(String) 55 | hello(): string { 56 | return "World"; 57 | } 58 | } 59 | var graphqlType: GraphQLObjectType = getGraphQLType( 60 | MyObjectType 61 | ); 62 | 63 | expect(graphqlType.name).toBe("MyCustomObjectType"); 64 | expect(graphqlType.description).toBe("My Description"); 65 | expect(graphqlType.getFields()).toHaveProperty("hello"); 66 | 67 | // We preserve the function 68 | expect(new MyObjectType().hello()).toBe("World"); 69 | }); 70 | 71 | test(`create ObjectType with no fields`, () => { 72 | expect(() => { 73 | @ObjectType() 74 | class MyObject {} 75 | }).toThrowErrorMatchingSnapshot(); 76 | }); 77 | 78 | test(`create ObjectType with wrong interfaces`, () => { 79 | expect(() => { 80 | @ObjectType({ 81 | interfaces: [String] 82 | }) 83 | class MyObjectType { 84 | @Field(String) 85 | bar?(): string { 86 | return "Bar"; 87 | } 88 | } 89 | }).toThrowErrorMatchingSnapshot(); 90 | }); 91 | 92 | test(`create ObjectType with interfaces`, () => { 93 | @InterfaceType() 94 | class MyInterface1 { 95 | @Field(String) 96 | foo1?(): string { 97 | return "Foo1"; 98 | } 99 | } 100 | 101 | @InterfaceType() 102 | class MyInterface2 { 103 | @Field(String) 104 | foo2?(): string { 105 | return "Foo1"; 106 | } 107 | } 108 | 109 | @ObjectType({ 110 | interfaces: [MyInterface1, MyInterface2] 111 | }) 112 | class MyObjectType { 113 | @Field(String) 114 | bar?(): string { 115 | return "Bar"; 116 | } 117 | } 118 | 119 | var graphqlType: GraphQLObjectType = getGraphQLType( 120 | MyObjectType 121 | ); 122 | 123 | expect(graphqlType.name).toBe("MyObjectType"); 124 | 125 | var graphqlInterfaceType1: GraphQLInterfaceType = getGraphQLType( 126 | MyInterface1 127 | ); 128 | var graphqlInterfaceType2: GraphQLInterfaceType = getGraphQLType( 129 | MyInterface2 130 | ); 131 | 132 | expect(graphqlType.getInterfaces()).toEqual([ 133 | graphqlInterfaceType1, 134 | graphqlInterfaceType2 135 | ]); 136 | 137 | expect(Object.keys(graphqlType.getFields())).toEqual([ 138 | "foo1", 139 | "foo2", 140 | "bar" 141 | ]); 142 | }); 143 | 144 | test(`create ObjectType with dynamic field (skip)`, () => { 145 | @ObjectType() 146 | class MyObjectType { 147 | @DynamicField(() => null) 148 | skip: any; 149 | 150 | @Field(String) 151 | hello(): string { 152 | return "World"; 153 | } 154 | } 155 | var graphqlType: GraphQLObjectType = getGraphQLType( 156 | MyObjectType 157 | ); 158 | 159 | const fields = graphqlType.getFields(); 160 | expect(fields).toHaveProperty("hello"); 161 | expect(fields).not.toHaveProperty("skip"); 162 | }); 163 | 164 | test(`create ObjectType with dynamic field (include)`, () => { 165 | @ObjectType() 166 | class MyObjectType { 167 | @DynamicField((): FieldConfig => ({ type: String })) 168 | skip(): string { 169 | return "World"; 170 | } 171 | 172 | @Field(String) hello: string; 173 | } 174 | var graphqlType: GraphQLObjectType = getGraphQLType( 175 | MyObjectType 176 | ); 177 | 178 | const fields = graphqlType.getFields(); 179 | expect(fields).toHaveProperty("hello"); 180 | expect(fields).toHaveProperty("skip"); 181 | 182 | // We preserve the function 183 | expect(new MyObjectType().skip()).toBe("World"); 184 | }); 185 | 186 | test(`create ObjectType with field with extra config`, () => { 187 | @ObjectType() 188 | class MyObjectType { 189 | @Field(String, { complexity: 10 } as any) 190 | hello: string; 191 | } 192 | var graphqlType: GraphQLObjectType = getGraphQLType( 193 | MyObjectType 194 | ); 195 | 196 | const fields = graphqlType.getFields(); 197 | expect(fields).toHaveProperty("hello"); 198 | expect((fields["hello"] as any).complexity).toBe(10); 199 | }); 200 | }); 201 | -------------------------------------------------------------------------------- /src/definitions/__tests__/scalars.ts: -------------------------------------------------------------------------------- 1 | import { 2 | // GraphQLScalarType, 3 | // GraphQLID, 4 | GraphQLString, 5 | GraphQLBoolean, 6 | // GraphQLInt, 7 | GraphQLFloat 8 | } from "graphql"; 9 | import { GraphQLDateTime } from "graphql-iso-date"; 10 | import { getGraphQLType, setupNativeTypes } from "../../reflection"; 11 | 12 | // import { Field, InputField, Argument } from '../src'; 13 | // import { Scalar, ID, Str, Boolean, Int, Float } from './../src/types/scalars'; 14 | 15 | // describe('Scalar creation', () => { 16 | // test(`create new Scalar`, () => { 17 | // class MyScalar extends Scalar {} 18 | // expect(MyScalar.typeName).toBe('MyScalar'); 19 | // expect(MyScalar.description).toBe(undefined); 20 | // }); 21 | 22 | // test(`create Scalar custom settings`, () => { 23 | // class MyScalar extends Scalar { 24 | // static typeName = 'MyCustomScalar'; 25 | // static description = 'MyCustomScalar Description'; 26 | // } 27 | // expect(MyScalar.typeName).toBe('MyCustomScalar'); 28 | // expect(MyScalar.description).toBe('MyCustomScalar Description'); 29 | // }); 30 | 31 | // test(`create Scalar graphql type`, () => { 32 | // class MyScalar extends Scalar { 33 | // static description = 'Desc'; 34 | // static serialize() { 35 | // return 'serialize'; 36 | // } 37 | // static parseLiteral() { 38 | // return 'parseLiteral'; 39 | // } 40 | // static parseValue() { 41 | // return 'parseValue'; 42 | // } 43 | // } 44 | // const gql = MyScalar.gql; 45 | // expect(gql).toBeInstanceOf(GraphQLScalarType); 46 | // expect(gql.name).toBe('MyScalar'); 47 | // expect(gql.description).toBe('Desc'); 48 | // expect(gql.serialize()).toBe('serialize'); 49 | // expect(gql.parseLiteral()).toBe('parseLiteral'); 50 | // expect(gql.parseValue()).toBe('parseValue'); 51 | // }); 52 | // }); 53 | 54 | describe("Scalar can be mounted", () => { 55 | // class MyScalar extends Scalar { 56 | // static description = 'Desc'; 57 | // static serialize(value: any) { 58 | // value; 59 | // } 60 | // static parseLiteral(value: any) { 61 | // value; 62 | // } 63 | // static parseValue(value: any) { 64 | // value; 65 | // } 66 | // } 67 | 68 | // test(`as a Field`, () => { 69 | // let unmounted = new MyScalar({ description: 'MyScalar field' }); 70 | // let field = unmounted.toField(); 71 | // expect(field).toBeInstanceOf(Field); 72 | // expect(field.type).toBe(MyScalar); 73 | // expect(field.options).toMatchObject({ description: 'MyScalar field' }); 74 | // }); 75 | 76 | // test(`as a Argument`, () => { 77 | // let unmounted = new MyScalar({ description: 'MyScalar argument' }); 78 | // let arg = unmounted.toArgument(); 79 | // expect(arg).toBeInstanceOf(Argument); 80 | // expect(arg.type).toBe(MyScalar); 81 | // expect(arg.options).toMatchObject({ description: 'MyScalar argument' }); 82 | // }); 83 | 84 | // test(`as a InputField`, () => { 85 | // let unmounted = new MyScalar({ description: 'MyScalar input field' }); 86 | // let inputfield = unmounted.toInputField(); 87 | // expect(inputfield).toBeInstanceOf(InputField); 88 | // expect(inputfield.type).toBe(MyScalar); 89 | // expect(inputfield.options).toMatchObject({ 90 | // description: 'MyScalar input field' 91 | // }); 92 | // }); 93 | 94 | describe("Pre-defined scalars", () => { 95 | setupNativeTypes(); 96 | 97 | test(`String`, () => { 98 | expect(getGraphQLType(String)).toBe(GraphQLString); 99 | }); 100 | 101 | test(`Boolean`, () => { 102 | expect(getGraphQLType(Boolean)).toBe(GraphQLBoolean); 103 | }); 104 | 105 | test(`Float`, () => { 106 | expect(getGraphQLType(Number)).toBe(GraphQLFloat); 107 | }); 108 | 109 | test(`Date`, () => { 110 | expect(getGraphQLType(Date)).toBe(GraphQLDateTime); 111 | }); 112 | }); 113 | }); 114 | -------------------------------------------------------------------------------- /src/definitions/__tests__/schema.ts: -------------------------------------------------------------------------------- 1 | import { 2 | GraphQLSchema, 3 | GraphQLList, 4 | GraphQLString, 5 | GraphQLNonNull, 6 | GraphQLObjectType 7 | } from "graphql"; 8 | import { getGraphQLType } from "../../reflection"; 9 | import { Schema, ObjectType, InterfaceType, Field, List, NonNull } from "./../"; 10 | 11 | describe("Schema", () => { 12 | @InterfaceType() 13 | class UserBase { 14 | @Field(String) name: string; 15 | } 16 | 17 | @ObjectType({ 18 | interfaces: [UserBase] 19 | }) 20 | class User { 21 | @Field(String) name: string; 22 | } 23 | 24 | @ObjectType() 25 | class Query { 26 | @Field(String) 27 | hello() { 28 | return "World"; 29 | } 30 | @Field(UserBase) viewer: User; 31 | } 32 | 33 | @ObjectType() 34 | class Mutation { 35 | @Field(String) mutate: string; 36 | } 37 | 38 | @ObjectType() 39 | class Subscription { 40 | @Field(String) subscribe: string; 41 | } 42 | 43 | var userType: GraphQLObjectType = getGraphQLType(User); 44 | var queryType: GraphQLObjectType = getGraphQLType(Query); 45 | 46 | var mutationType: GraphQLObjectType = getGraphQLType( 47 | Mutation 48 | ); 49 | 50 | var subscriptionType: GraphQLObjectType = getGraphQLType( 51 | Subscription 52 | ); 53 | 54 | test(`works with query`, () => { 55 | var schema = new Schema({ 56 | query: Query 57 | }); 58 | 59 | expect(schema.getQueryType()).toBe(queryType); 60 | }); 61 | 62 | test(`works with mutation`, () => { 63 | var schema = new Schema({ 64 | query: Query, 65 | mutation: Mutation 66 | }); 67 | 68 | expect(schema.getMutationType()).toBe(mutationType); 69 | }); 70 | 71 | test(`works with subscription`, () => { 72 | var schema = new Schema({ 73 | query: Query, 74 | subscription: Subscription 75 | }); 76 | 77 | expect(schema.getSubscriptionType()).toBe(subscriptionType); 78 | }); 79 | 80 | test(`can create a schema`, async () => { 81 | var schema = new Schema({ 82 | query: Query, 83 | mutation: Mutation, 84 | subscription: Subscription, 85 | types: [User] 86 | }); 87 | 88 | expect(schema.getQueryType()).toBe(queryType); 89 | expect(schema.getMutationType()).toBe(mutationType); 90 | expect(schema.getSubscriptionType()).toBe(subscriptionType); 91 | 92 | expect(schema.getType("User")).toBe(userType); 93 | 94 | var result = await schema.execute(`{hello}`); 95 | expect(result).toMatchSnapshot(); 96 | 97 | var repr = await schema.toString(); 98 | expect(repr).toMatchSnapshot(); 99 | }); 100 | 101 | // test(`create List correct graphql type`, () => { 102 | // var myList = new List(ID); 103 | // expect(myList.gql).toBeInstanceOf(GraphQLList); 104 | // expect(myList.gql.ofType).toBe(GraphQLID); 105 | // expect(myList.ofType).toBe(ID); 106 | // }); 107 | 108 | // test(`create List gql fails if have arguments`, () => { 109 | // var myList = new List(ID, { required: true }); 110 | // var error; 111 | // try { 112 | // myList.gql; 113 | // } catch (e) { 114 | // error = e; 115 | // } 116 | // expect(error.toString()).toBe( 117 | // `Error: The List options: [object Object] will be dismissed.` + 118 | // `You will need to mount as a Field with using INSTANCE.toField()` 119 | // ); 120 | // }); 121 | 122 | // test(`as a Field`, () => { 123 | // let unmounted = new List(ID, { description: 'List ID field' }); 124 | // let field = unmounted.toField(); 125 | // expect(field).toBeInstanceOf(Field); 126 | // expect(field.type).toBeInstanceOf(List); 127 | // expect(field.type.ofType).toBe(ID); 128 | // expect(field.options).toMatchObject({ description: 'List ID field' }); 129 | // }); 130 | 131 | // test(`as a Argument`, () => { 132 | // let unmounted = new List(ID, { description: 'List ID argument' }); 133 | // let arg = unmounted.toArgument(); 134 | // expect(arg).toBeInstanceOf(Argument); 135 | // expect(arg.type).toBeInstanceOf(List); 136 | // expect(arg.type.ofType).toBe(ID); 137 | // expect(arg.options).toMatchObject({ description: 'List ID argument' }); 138 | // }); 139 | 140 | // test(`as a InputField`, () => { 141 | // let unmounted = new List(ID, { description: 'List ID input field' }); 142 | // let inputfield = unmounted.toInputField(); 143 | // expect(inputfield).toBeInstanceOf(InputField); 144 | // expect(inputfield.type).toBeInstanceOf(List); 145 | // expect(inputfield.type.ofType).toBe(ID); 146 | // expect(inputfield.options).toMatchObject({ 147 | // description: 'List ID input field' 148 | // }); 149 | // }); 150 | }); 151 | 152 | // describe('NonNull structure', () => { 153 | // test(`create new NonNull`, () => { 154 | // var myList = new NonNull(ID); 155 | // expect(myList.ofType).toBe(ID); 156 | // }); 157 | 158 | // test(`create NonNull correct graphql type`, () => { 159 | // var myList = new NonNull(ID); 160 | // expect(myList.gql).toBeInstanceOf(GraphQLNonNull); 161 | // expect(myList.gql.ofType).toBe(GraphQLID); 162 | // expect(myList.ofType).toBe(ID); 163 | // }); 164 | 165 | // test(`create NonNull gql fails if have arguments`, () => { 166 | // var myList = new NonNull(ID, { required: true }); 167 | // var error; 168 | // try { 169 | // myList.gql; 170 | // } catch (e) { 171 | // error = e; 172 | // } 173 | // expect(error.toString()).toBe( 174 | // `Error: The NonNull options: [object Object] will be dismissed.` + 175 | // `You will need to mount as a Field with using INSTANCE.toField()` 176 | // ); 177 | // }); 178 | 179 | // test(`as a Field`, () => { 180 | // let unmounted = new NonNull(ID, { description: 'NonNull ID field' }); 181 | // let field = unmounted.toField(); 182 | // expect(field).toBeInstanceOf(Field); 183 | // expect(field.type).toBeInstanceOf(NonNull); 184 | // expect(field.type.ofType).toBe(ID); 185 | // expect(field.options).toMatchObject({ description: 'NonNull ID field' }); 186 | // }); 187 | 188 | // test(`as a Argument`, () => { 189 | // let unmounted = new NonNull(ID, { description: 'NonNull ID argument' }); 190 | // let arg = unmounted.toArgument(); 191 | // expect(arg).toBeInstanceOf(Argument); 192 | // expect(arg.type).toBeInstanceOf(NonNull); 193 | // expect(arg.type.ofType).toBe(ID); 194 | // expect(arg.options).toMatchObject({ description: 'NonNull ID argument' }); 195 | // }); 196 | 197 | // test(`as a InputField`, () => { 198 | // let unmounted = new NonNull(ID, { description: 'NonNull ID input field' }); 199 | // let inputfield = unmounted.toInputField(); 200 | // expect(inputfield).toBeInstanceOf(InputField); 201 | // expect(inputfield.type).toBeInstanceOf(NonNull); 202 | // expect(inputfield.type.ofType).toBe(ID); 203 | // expect(inputfield.options).toMatchObject({ 204 | // description: 'NonNull ID input field' 205 | // }); 206 | // }); 207 | // }); 208 | -------------------------------------------------------------------------------- /src/definitions/__tests__/structures.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLList, GraphQLString, GraphQLNonNull } from "graphql"; 2 | import { getGraphQLType } from "../../reflection"; 3 | import { List, NonNull } from "./../"; 4 | // import { GraphQLID, GraphQLList, GraphQLNonNull } from 'graphql'; 5 | // import { Field, InputField, Argument } from '../src'; 6 | // import { ID } from './../src/types/scalars'; 7 | 8 | describe("List structure", () => { 9 | test(`create new List`, () => { 10 | var myList = List(String); 11 | var graphqlType: GraphQLList = >getGraphQLType( 12 | myList 13 | ); 14 | expect(graphqlType).toBeInstanceOf(GraphQLList); 15 | expect(graphqlType.ofType).toBe(GraphQLString); 16 | }); 17 | 18 | test(`create new NonNull`, () => { 19 | var myList = NonNull(String); 20 | var graphqlType: GraphQLNonNull = >getGraphQLType( 21 | myList 22 | ); 23 | expect(graphqlType).toBeInstanceOf(GraphQLNonNull); 24 | expect(graphqlType.ofType).toBe(GraphQLString); 25 | }); 26 | 27 | // test(`create List correct graphql type`, () => { 28 | // var myList = new List(ID); 29 | // expect(myList.gql).toBeInstanceOf(GraphQLList); 30 | // expect(myList.gql.ofType).toBe(GraphQLID); 31 | // expect(myList.ofType).toBe(ID); 32 | // }); 33 | 34 | // test(`create List gql fails if have arguments`, () => { 35 | // var myList = new List(ID, { required: true }); 36 | // var error; 37 | // try { 38 | // myList.gql; 39 | // } catch (e) { 40 | // error = e; 41 | // } 42 | // expect(error.toString()).toBe( 43 | // `Error: The List options: [object Object] will be dismissed.` + 44 | // `You will need to mount as a Field with using INSTANCE.toField()` 45 | // ); 46 | // }); 47 | 48 | // test(`as a Field`, () => { 49 | // let unmounted = new List(ID, { description: 'List ID field' }); 50 | // let field = unmounted.toField(); 51 | // expect(field).toBeInstanceOf(Field); 52 | // expect(field.type).toBeInstanceOf(List); 53 | // expect(field.type.ofType).toBe(ID); 54 | // expect(field.options).toMatchObject({ description: 'List ID field' }); 55 | // }); 56 | 57 | // test(`as a Argument`, () => { 58 | // let unmounted = new List(ID, { description: 'List ID argument' }); 59 | // let arg = unmounted.toArgument(); 60 | // expect(arg).toBeInstanceOf(Argument); 61 | // expect(arg.type).toBeInstanceOf(List); 62 | // expect(arg.type.ofType).toBe(ID); 63 | // expect(arg.options).toMatchObject({ description: 'List ID argument' }); 64 | // }); 65 | 66 | // test(`as a InputField`, () => { 67 | // let unmounted = new List(ID, { description: 'List ID input field' }); 68 | // let inputfield = unmounted.toInputField(); 69 | // expect(inputfield).toBeInstanceOf(InputField); 70 | // expect(inputfield.type).toBeInstanceOf(List); 71 | // expect(inputfield.type.ofType).toBe(ID); 72 | // expect(inputfield.options).toMatchObject({ 73 | // description: 'List ID input field' 74 | // }); 75 | // }); 76 | }); 77 | 78 | // describe('NonNull structure', () => { 79 | // test(`create new NonNull`, () => { 80 | // var myList = new NonNull(ID); 81 | // expect(myList.ofType).toBe(ID); 82 | // }); 83 | 84 | // test(`create NonNull correct graphql type`, () => { 85 | // var myList = new NonNull(ID); 86 | // expect(myList.gql).toBeInstanceOf(GraphQLNonNull); 87 | // expect(myList.gql.ofType).toBe(GraphQLID); 88 | // expect(myList.ofType).toBe(ID); 89 | // }); 90 | 91 | // test(`create NonNull gql fails if have arguments`, () => { 92 | // var myList = new NonNull(ID, { required: true }); 93 | // var error; 94 | // try { 95 | // myList.gql; 96 | // } catch (e) { 97 | // error = e; 98 | // } 99 | // expect(error.toString()).toBe( 100 | // `Error: The NonNull options: [object Object] will be dismissed.` + 101 | // `You will need to mount as a Field with using INSTANCE.toField()` 102 | // ); 103 | // }); 104 | 105 | // test(`as a Field`, () => { 106 | // let unmounted = new NonNull(ID, { description: 'NonNull ID field' }); 107 | // let field = unmounted.toField(); 108 | // expect(field).toBeInstanceOf(Field); 109 | // expect(field.type).toBeInstanceOf(NonNull); 110 | // expect(field.type.ofType).toBe(ID); 111 | // expect(field.options).toMatchObject({ description: 'NonNull ID field' }); 112 | // }); 113 | 114 | // test(`as a Argument`, () => { 115 | // let unmounted = new NonNull(ID, { description: 'NonNull ID argument' }); 116 | // let arg = unmounted.toArgument(); 117 | // expect(arg).toBeInstanceOf(Argument); 118 | // expect(arg.type).toBeInstanceOf(NonNull); 119 | // expect(arg.type.ofType).toBe(ID); 120 | // expect(arg.options).toMatchObject({ description: 'NonNull ID argument' }); 121 | // }); 122 | 123 | // test(`as a InputField`, () => { 124 | // let unmounted = new NonNull(ID, { description: 'NonNull ID input field' }); 125 | // let inputfield = unmounted.toInputField(); 126 | // expect(inputfield).toBeInstanceOf(InputField); 127 | // expect(inputfield.type).toBeInstanceOf(NonNull); 128 | // expect(inputfield.type.ofType).toBe(ID); 129 | // expect(inputfield.options).toMatchObject({ 130 | // description: 'NonNull ID input field' 131 | // }); 132 | // }); 133 | // }); 134 | -------------------------------------------------------------------------------- /src/definitions/__tests__/unionttype.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLUnionType, GraphQLObjectType } from "graphql"; 2 | import { getGraphQLType } from "../../reflection"; 3 | import { ObjectType, Field, UnionType } from "../"; 4 | 5 | describe("UnionType creation", () => { 6 | @ObjectType() 7 | class Foo { 8 | @Field(String) foo: string; 9 | } 10 | 11 | @ObjectType() 12 | class Bar { 13 | @Field(String) bar: string; 14 | } 15 | 16 | const fooGraphQLType: GraphQLObjectType = getGraphQLType( 17 | Foo 18 | ); 19 | const barGraphQLType: GraphQLObjectType = getGraphQLType( 20 | Bar 21 | ); 22 | 23 | test(`create UnionType`, () => { 24 | var myUnion = new UnionType({ 25 | name: "FooOrBar", 26 | description: "description", 27 | types: [Foo, Bar] 28 | }); 29 | 30 | var graphqlType: GraphQLUnionType = getGraphQLType( 31 | myUnion 32 | ); 33 | expect(graphqlType.getTypes()).toEqual([fooGraphQLType, barGraphQLType]); 34 | expect(graphqlType.name).toBe("FooOrBar"); 35 | expect(graphqlType.description).toBe("description"); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /src/definitions/enum.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017-present, Graphene. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | */ 8 | import { GraphQLEnumValueConfigMap, GraphQLEnumType } from "graphql"; 9 | import { 10 | getDescription, 11 | getDeprecationReason, 12 | setGraphQLType 13 | } from "./../reflection"; 14 | import { getStaticProperties } from "./utils"; 15 | 16 | // The provided configuration type when creating an EnumType. 17 | export type EnumTypeConfig = { 18 | name?: string; 19 | description?: string; 20 | }; 21 | 22 | export const EnumType = (opts: EnumTypeConfig = {}) => < 23 | T extends { new (...args: any[]): {}; [key: string]: any } 24 | >( 25 | target: T 26 | ): T => { 27 | let values: GraphQLEnumValueConfigMap = {}; 28 | getStaticProperties(target).forEach(name => { 29 | values[name] = { 30 | value: target[name], 31 | description: getDescription(target, name), 32 | deprecationReason: getDeprecationReason(target, name) 33 | }; 34 | }); 35 | setGraphQLType( 36 | target, 37 | new GraphQLEnumType({ 38 | name: opts.name || target.name, 39 | description: opts.description || getDescription(target), 40 | values: values 41 | }) 42 | ); 43 | return target; 44 | }; 45 | -------------------------------------------------------------------------------- /src/definitions/field.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017-present, Graphene. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | */ 8 | import { 9 | GraphQLArgumentConfig, 10 | GraphQLInputType, 11 | isOutputType, 12 | isInputType, 13 | GraphQLFieldResolver, 14 | defaultFieldResolver, 15 | GraphQLFieldConfig, 16 | GraphQLResolveInfo 17 | } from "graphql"; 18 | 19 | import { 20 | UnmountedFieldMap, 21 | getFields, 22 | getGraphQLType, 23 | getDescription, 24 | getDeprecationReason 25 | } from "./../reflection"; 26 | 27 | // Helper function that ease the creation of Arguments 28 | // This: 29 | // Argument(String, "The description", "value") 30 | // Will be converted to: 31 | // { 32 | // type: String, 33 | // description: "The description", 34 | // defaultValue: "value" 35 | // } 36 | export const Argument = ( 37 | type: InputType, 38 | description?: string, 39 | defaultValue?: any 40 | ): ArgumentType => { 41 | return { 42 | type, 43 | description, 44 | defaultValue 45 | }; 46 | }; 47 | 48 | type ArgumentMap = { 49 | [key: string]: GraphQLArgumentConfig; 50 | }; 51 | 52 | export type InputType = 53 | | GraphQLInputType 54 | | any 55 | | typeof String 56 | | typeof Number 57 | | typeof Boolean; 58 | 59 | export type ArgumentType = { 60 | type: InputType; 61 | description?: string; 62 | defaultValue?: any; 63 | }; 64 | 65 | // The provided configuration type when creating a Field. 66 | export type FieldPartialConfig = { 67 | args?: { 68 | [key: string]: ArgumentType | InputType; 69 | }; 70 | description?: string; 71 | deprecationReason?: string; 72 | resolver?: GraphQLFieldResolver; 73 | }; 74 | 75 | export type FieldConfig = FieldPartialConfig & { 76 | type?: any; 77 | }; 78 | 79 | export type FieldDecorator = (target: any, key: string) => void; 80 | 81 | export interface FieldSignatures { 82 | // Signature 1 83 | (fullConfig: FieldConfig): FieldDecorator; 84 | // Signature 2 85 | (type: any, config?: FieldPartialConfig): FieldDecorator; 86 | // (thunkConfig: () => FieldConfig): FieldDecorator; 87 | } 88 | 89 | export type Thunk = (() => T) | T; 90 | 91 | const isFunction = (thunk: any): boolean => { 92 | return typeof thunk === "function"; 93 | }; 94 | 95 | const resolveThunk = (thunk: Thunk): T => { 96 | return typeof thunk === "function" ? thunk() : thunk; 97 | }; 98 | 99 | const isObject = (o: any) => { 100 | return o instanceof Object && o.constructor === Object; 101 | }; 102 | 103 | const generateField = (config: FieldConfig): GraphQLFieldConfig => { 104 | let { 105 | type, 106 | description, 107 | deprecationReason, 108 | resolver, 109 | ...extraFieldConfig 110 | } = config; 111 | const _type = getGraphQLType(type); 112 | if (!isOutputType(_type)) { 113 | throw new Error("Type is not output"); 114 | } 115 | let args = config.args || {}; 116 | let fieldArgs: ArgumentMap = {}; 117 | for (let argKey in args) { 118 | let arg: ArgumentType | InputType = args[argKey]; 119 | let extra: {}; 120 | let argType: any; 121 | if ( 122 | typeof (arg).type !== "undefined" && 123 | !isInputType(arg) 124 | ) { 125 | let { type, ...extraOpts } = arg; 126 | argType = type; 127 | extra = extraOpts; 128 | } else { 129 | extra = {}; 130 | argType = arg; 131 | } 132 | 133 | const newType = getGraphQLType(argType); 134 | if (!isInputType(newType)) { 135 | throw new Error( 136 | `Field argument ${argKey} expected to be Input type. Received: ${argType}.` 137 | ); 138 | } 139 | fieldArgs[argKey] = { 140 | type: newType, 141 | ...extra 142 | }; 143 | } 144 | return { 145 | ...extraFieldConfig, 146 | args: fieldArgs, 147 | type: _type, 148 | description: description, 149 | deprecationReason: deprecationReason, 150 | resolve: resolver 151 | }; 152 | }; 153 | 154 | const transformResolverWithThis = (resolver: Function) => ( 155 | root: any, 156 | args: { [argName: string]: any }, 157 | context: any, 158 | info: GraphQLResolveInfo 159 | ) => { 160 | return resolver.call(root, args, context, info); 161 | }; 162 | 163 | const configFromTargetField = (target: any, key: string): FieldConfig => { 164 | const targetResolver = target[key]; 165 | const resolver = 166 | typeof targetResolver === "function" 167 | ? transformResolverWithThis(targetResolver) 168 | : defaultFieldResolver; 169 | 170 | return { 171 | description: getDescription(target, key), 172 | deprecationReason: getDeprecationReason(target, key), 173 | resolver: resolver 174 | }; 175 | }; 176 | 177 | export const Field: FieldSignatures = ( 178 | typeOrConfig: any, 179 | baseConfig?: FieldPartialConfig 180 | ) => (target: any, key: string) => { 181 | let config: FieldConfig; 182 | // First, we normalize the arguments into fieldConfig 183 | if (baseConfig) { 184 | // Signature 2 185 | config = { 186 | ...baseConfig, 187 | type: typeOrConfig 188 | }; 189 | } else { 190 | if (isObject(typeOrConfig)) { 191 | // Signature 1 192 | config = typeOrConfig; 193 | } else { 194 | // Signature 2 195 | config = { 196 | type: typeOrConfig 197 | }; 198 | } 199 | } 200 | // We check if we can get any description, deprecationReason or 201 | // resolver from the target[key] 202 | const extendedConfig: FieldConfig = { 203 | ...configFromTargetField(target, key), 204 | ...config 205 | }; 206 | const _class = target.constructor; 207 | let fields: UnmountedFieldMap = getFields(_class); 208 | if (key in fields) { 209 | throw new Error(`Field ${key} is already defined in ${_class}.`); 210 | } 211 | fields[key] = (): GraphQLFieldConfig => { 212 | return generateField(extendedConfig); 213 | }; 214 | }; 215 | 216 | export const DynamicField = (thunkConfig: () => FieldConfig | null) => ( 217 | target: any, 218 | key: string 219 | ) => { 220 | const _class = target.constructor; 221 | let fields: UnmountedFieldMap = getFields(_class); 222 | if (key in fields) { 223 | throw new Error(`Field ${key} is already defined in ${_class}.`); 224 | } 225 | fields[key] = (): GraphQLFieldConfig | null => { 226 | const config: FieldConfig | null = thunkConfig(); 227 | if (config === null) { 228 | return null; 229 | } 230 | const extendedConfig: FieldConfig = { 231 | ...configFromTargetField(target, key), 232 | ...config 233 | }; 234 | return generateField(extendedConfig); 235 | }; 236 | }; 237 | -------------------------------------------------------------------------------- /src/definitions/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017-present, Graphene. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | */ 8 | export { SchemaConfig, Schema } from "./schema"; 9 | export { 10 | String, 11 | Float, 12 | Boolean, 13 | ID, 14 | Int, 15 | Date, 16 | DateTime, 17 | Time 18 | } from "./scalars"; 19 | export { List, NonNull } from "./structures"; 20 | export { 21 | Argument, 22 | ArgumentType, 23 | FieldConfig, 24 | Field, 25 | DynamicField, 26 | InputType 27 | } from "./field"; 28 | export { InputFieldConfig, InputField } from "./inputfield"; 29 | export { ObjectTypeConfig, ObjectType } from "./objecttype"; 30 | export { InterfaceTypeConfig, InterfaceType } from "./interfacetype"; 31 | export { UnionTypeConfig, UnionType } from "./uniontype"; 32 | export { InputObjectTypeConfig, InputObjectType } from "./inputobjecttype"; 33 | export { EnumTypeConfig, EnumType } from "./enum"; 34 | -------------------------------------------------------------------------------- /src/definitions/inputfield.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017-present, Graphene. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | */ 8 | import { isInputType } from "graphql"; 9 | import { 10 | UnmountedInputFieldMap, 11 | getInputFields, 12 | getGraphQLType, 13 | getDescription, 14 | getDeprecationReason 15 | } from "./../reflection"; 16 | 17 | // The provided configuration type when creating an InputField. 18 | export type InputFieldConfig = { 19 | defaultValue?: any; 20 | description?: string; 21 | deprecationReason?: string; 22 | }; 23 | 24 | export const InputField = (type?: any, config: InputFieldConfig = {}) => ( 25 | target: any, 26 | key: string 27 | ) => { 28 | const _class = target.constructor; 29 | let fields: UnmountedInputFieldMap = getInputFields(_class); 30 | if (key in fields) { 31 | throw new Error(`Field ${key} is already defined in ${_class}.`); 32 | } 33 | fields[key] = () => { 34 | const _type = getGraphQLType(type); 35 | if (!isInputType(_type)) { 36 | throw new Error("Type is not input"); 37 | } 38 | const defaultValue: any = target[key]; 39 | return { 40 | type: _type, 41 | description: config.description || getDescription(target, key), 42 | deprecationReason: 43 | config.deprecationReason || getDeprecationReason(target, key), 44 | defaultValue: config.defaultValue || defaultValue 45 | }; 46 | }; 47 | }; 48 | -------------------------------------------------------------------------------- /src/definitions/inputobjecttype.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017-present, Graphene. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | */ 8 | import { GraphQLInputObjectType } from "graphql"; 9 | import { 10 | UnmountedInputFieldMap, 11 | getInputFields, 12 | assertInputFields, 13 | setGraphQLType, 14 | getDescription, 15 | mountInputFields 16 | } from "./../reflection"; 17 | 18 | // The provided configuration type when creating an InputObjectType. 19 | export type InputObjectTypeConfig = { 20 | name?: string; 21 | description?: string; 22 | }; 23 | 24 | export const InputObjectType = (opts: InputObjectTypeConfig = {}) => < 25 | T extends { new (...args: any[]): any } 26 | >( 27 | target: T 28 | ): T => { 29 | const fields: UnmountedInputFieldMap = getInputFields(target); 30 | assertInputFields(target, fields); 31 | 32 | setGraphQLType( 33 | target, 34 | new GraphQLInputObjectType({ 35 | name: opts.name || target.name, 36 | description: opts.description || getDescription(target), 37 | fields: mountInputFields(fields) 38 | }) 39 | ); 40 | 41 | return target; 42 | }; 43 | -------------------------------------------------------------------------------- /src/definitions/interfacetype.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017-present, Graphene. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | */ 8 | import { 9 | GraphQLResolveInfo, 10 | GraphQLTypeResolver, 11 | GraphQLObjectType, 12 | GraphQLInterfaceType 13 | } from "graphql"; 14 | import { 15 | UnmountedFieldMap, 16 | getFields, 17 | assertFields, 18 | getGraphQLType, 19 | setGraphQLType, 20 | getDescription, 21 | mountFields 22 | } from "./../reflection"; 23 | 24 | // The provided configuration type when creating an Interface. 25 | export type InterfaceTypeConfig = { 26 | name?: string; 27 | description?: string; 28 | resolveType?: (root?: any, context?: any, info?: GraphQLResolveInfo) => any; 29 | }; 30 | 31 | export const InterfaceType = (opts: InterfaceTypeConfig = {}) => < 32 | T extends { new (...args: any[]): any } 33 | >( 34 | target: T 35 | ): T => { 36 | const fields: UnmountedFieldMap = getFields(target); 37 | assertFields(target, fields); 38 | 39 | const resolveType: GraphQLTypeResolver = ( 40 | root?: any, 41 | context?: any, 42 | info?: GraphQLResolveInfo 43 | ): string | GraphQLObjectType | Promise => { 44 | if (opts.resolveType) { 45 | root = opts.resolveType(root, context, info); 46 | } 47 | return getGraphQLType(root); 48 | }; 49 | 50 | setGraphQLType( 51 | target, 52 | new GraphQLInterfaceType({ 53 | name: opts.name || target.name, 54 | description: opts.description || getDescription(target), 55 | resolveType: resolveType, 56 | fields: mountFields(fields) 57 | }) 58 | ); 59 | 60 | return target; 61 | }; 62 | -------------------------------------------------------------------------------- /src/definitions/objecttype.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017-present, Graphene. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | */ 8 | import { GraphQLInterfaceType, GraphQLObjectType } from "graphql"; 9 | import { 10 | getGraphQLType, 11 | UnmountedFieldMap, 12 | getFields, 13 | assertFields, 14 | setGraphQLType, 15 | getDescription, 16 | mountFields 17 | } from "./../reflection"; 18 | 19 | // The provided configuration type when creating an ObjectType. 20 | export type ObjectTypeConfig = { 21 | name?: string; 22 | description?: string; 23 | interfaces?: any[]; 24 | }; 25 | 26 | export const ObjectType = (opts: ObjectTypeConfig = {}) => < 27 | T extends { new (...args: any[]): any } 28 | >( 29 | target: T 30 | ): T => { 31 | // save a reference to the original constructor 32 | const interfaces: GraphQLInterfaceType[] = (opts.interfaces || []).map( 33 | iface => { 34 | const ifaceType = getGraphQLType(iface); 35 | if (!(ifaceType instanceof GraphQLInterfaceType)) { 36 | throw new Error(`Provided interface ${ifaceType} is not valid`); 37 | } 38 | return ifaceType; 39 | } 40 | ); 41 | 42 | let allInterfaceFields: UnmountedFieldMap = {}; 43 | 44 | (opts.interfaces || []).forEach((_, index) => { 45 | const iface = (opts.interfaces || [])[index]; 46 | const ifaceFields: UnmountedFieldMap = getFields(iface); 47 | allInterfaceFields = { 48 | ...allInterfaceFields, 49 | ...ifaceFields 50 | }; 51 | }); 52 | 53 | const fields: UnmountedFieldMap = { 54 | // First we introduce the fields from the interfaces that we inherit 55 | ...allInterfaceFields, 56 | // Then we retrieve the fields for the current type 57 | ...getFields(target) 58 | }; 59 | 60 | assertFields(target, fields); 61 | 62 | setGraphQLType( 63 | target, 64 | new GraphQLObjectType({ 65 | name: opts.name || target.name, 66 | description: opts.description || getDescription(target), 67 | interfaces: interfaces, 68 | fields: mountFields(fields) 69 | }) 70 | ); 71 | 72 | return target; 73 | }; 74 | -------------------------------------------------------------------------------- /src/definitions/scalars.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017-present, Graphene. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | */ 8 | import { 9 | GraphQLScalarType, 10 | GraphQLID, 11 | GraphQLInt, 12 | GraphQLFloat, 13 | GraphQLBoolean, 14 | GraphQLString 15 | } from "graphql"; 16 | import { GraphQLDate, GraphQLDateTime, GraphQLTime } from "graphql-iso-date"; 17 | 18 | import { setupNativeTypes } from "./../reflection"; 19 | 20 | // Store the basic references to GraphQL types 21 | export const String: GraphQLScalarType = GraphQLString; 22 | export const ID: GraphQLScalarType = GraphQLID; 23 | export const Int: GraphQLScalarType = GraphQLInt; 24 | export const Float: GraphQLScalarType = GraphQLFloat; 25 | export const Boolean: GraphQLScalarType = GraphQLBoolean; 26 | 27 | // Date/time types 28 | export const Date: GraphQLScalarType = GraphQLDate; 29 | export const DateTime: GraphQLScalarType = GraphQLDateTime; 30 | export const Time: GraphQLScalarType = GraphQLTime; 31 | 32 | // We setup the native data types, so we can use 33 | // String, Number, Boolean... as GraphQL types 34 | setupNativeTypes(); 35 | -------------------------------------------------------------------------------- /src/definitions/schema.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017-present, Graphene. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | */ 8 | import { 9 | GraphQLObjectType, 10 | ExecutionResult, 11 | GraphQLDirective, 12 | GraphQLSchema, 13 | GraphQLNamedType, 14 | graphql, 15 | printSchema 16 | } from "graphql"; 17 | 18 | import { getGraphQLType } from "../reflection"; 19 | 20 | // The provided configuration type when creating a new Schema. 21 | export type SchemaConfig = { 22 | query: any; 23 | mutation?: any; 24 | subscription?: any; 25 | directives?: GraphQLDirective[]; 26 | types?: any[]; 27 | }; 28 | 29 | type GraphQLSchemaConfig = { 30 | query: GraphQLObjectType; 31 | mutation?: GraphQLObjectType; 32 | subscription?: GraphQLObjectType; 33 | directives?: GraphQLDirective[]; 34 | types?: GraphQLNamedType[]; 35 | }; 36 | 37 | export class Schema extends GraphQLSchema { 38 | constructor(config: SchemaConfig) { 39 | let schemaConfig: GraphQLSchemaConfig = { 40 | query: getGraphQLType(config.query), 41 | directives: config.directives 42 | }; 43 | if (config.mutation) { 44 | schemaConfig.mutation = getGraphQLType( 45 | config.mutation 46 | ); 47 | } 48 | if (config.subscription) { 49 | schemaConfig.subscription = getGraphQLType( 50 | config.subscription 51 | ); 52 | } 53 | if (config.types) { 54 | schemaConfig.types = config.types.map( 55 | type => getGraphQLType(type) 56 | ); 57 | } 58 | super(schemaConfig); 59 | } 60 | public execute(query: string, ...args: any[]): Promise { 61 | return graphql(this, query, ...args); 62 | } 63 | public toString() { 64 | return printSchema(this); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/definitions/structures.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017-present, Graphene. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | */ 8 | import { getGraphQLType } from "./../reflection"; 9 | import { GraphQLList, GraphQLType, GraphQLNonNull } from "graphql"; 10 | 11 | // Helper funciton that will convert something like: 12 | // List(String) 13 | // To: 14 | // GraphQLList(GraphQLString) 15 | export const List = (ofType: any): GraphQLList => { 16 | return new GraphQLList(getGraphQLType(ofType)); 17 | }; 18 | 19 | // Helper funciton that will convert something like: 20 | // NonNull(String) 21 | // To: 22 | // GraphQLNonNull(GraphQLString) 23 | export const NonNull = (ofType: any): GraphQLNonNull => { 24 | return new GraphQLNonNull(getGraphQLType(ofType)); 25 | }; 26 | -------------------------------------------------------------------------------- /src/definitions/uniontype.ts: -------------------------------------------------------------------------------- 1 | import { 2 | GraphQLUnionType, 3 | GraphQLUnionTypeConfig, 4 | GraphQLTypeResolver, 5 | GraphQLObjectType 6 | } from "graphql"; 7 | import { getGraphQLType } from ".."; 8 | 9 | export interface UnionTypeConfig { 10 | name: string; 11 | types: any[]; 12 | resolveType?: GraphQLTypeResolver; 13 | description?: string; 14 | } 15 | 16 | export class UnionType extends GraphQLUnionType { 17 | constructor(config: UnionTypeConfig) { 18 | super({ 19 | name: config.name, 20 | types: config.types.map(type => getGraphQLType(type)), 21 | resolveType: config.resolveType, 22 | description: config.description 23 | }); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/definitions/utils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017-present, Graphene. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | */ 8 | 9 | class BaseClass {} 10 | 11 | const BaseClassProperties = Object.getOwnPropertyNames(BaseClass); 12 | 13 | // We remove the properties automatically included in the BaseClass 14 | // Such as .length, .name and .prototype 15 | export const getStaticProperties = (_class: Object) => { 16 | return Object.getOwnPropertyNames(_class).filter( 17 | name => BaseClassProperties.indexOf(name) === -1 18 | ); 19 | }; 20 | -------------------------------------------------------------------------------- /src/graphql-iso-date.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017-present, Graphene. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | */ 8 | 9 | // We add the missing type definitions to the graphql-iso-date package. 10 | declare module "graphql-iso-date" { 11 | import { GraphQLScalarType } from "graphql"; 12 | 13 | export const GraphQLDate: GraphQLScalarType; 14 | export const GraphQLDateTime: GraphQLScalarType; 15 | export const GraphQLTime: GraphQLScalarType; 16 | } 17 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { Boolean } from "./definitions/scalars"; 2 | /** 3 | * Copyright (c) 2017-present, Graphene. 4 | * 5 | * This source code is licensed under the MIT license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | * 8 | */ 9 | export { 10 | setGraphQLType, 11 | getGraphQLType, 12 | setDescription, 13 | getDescription, 14 | description, 15 | setDeprecationReason, 16 | getDeprecationReason, 17 | deprecated, 18 | getFields, 19 | assertFields, 20 | getInputFields, 21 | assertInputFields 22 | } from "./reflection"; 23 | 24 | export { 25 | String, 26 | ID, 27 | Int, 28 | Float, 29 | Boolean, 30 | Date, 31 | DateTime, 32 | Time, 33 | List, 34 | NonNull, 35 | Argument, 36 | InputType, 37 | ArgumentType, 38 | FieldConfig, 39 | Field, 40 | DynamicField, 41 | InputFieldConfig, 42 | InputField, 43 | ObjectTypeConfig, 44 | ObjectType, 45 | InterfaceTypeConfig, 46 | InterfaceType, 47 | UnionTypeConfig, 48 | UnionType, 49 | InputObjectTypeConfig, 50 | InputObjectType, 51 | EnumTypeConfig, 52 | EnumType, 53 | SchemaConfig, 54 | Schema 55 | } from "./definitions/index"; 56 | -------------------------------------------------------------------------------- /src/reflection.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017-present, Graphene. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | */ 8 | import "reflect-metadata"; 9 | import { 10 | GraphQLType, 11 | GraphQLString, 12 | GraphQLFloat, 13 | GraphQLList, 14 | GraphQLBoolean, 15 | assertType, 16 | isType, 17 | GraphQLFieldConfig, 18 | GraphQLInputFieldConfig 19 | } from "graphql"; 20 | import { GraphQLDateTime } from "graphql-iso-date"; 21 | 22 | export const GRAPHENE_TYPE_METADATA_KEY = "graphene:type"; 23 | export const GRAPHENE_FIELDS_METADATA_KEY = "graphene:fields"; 24 | export const GRAPHENE_INPUTFIELDS_METADATA_KEY = "graphene:inputfields"; 25 | export const GRAPHENE_DESCRIPTION_KEY = "graphene:description"; 26 | export const GRAPHENE_DEPRECATED_KEY = "graphene:deprecated"; 27 | 28 | // A utility funciton to convert values like: 29 | // [String] 30 | // To: 31 | // GraphQLList(GraphQLString) 32 | export const convertArrayToGraphQLList = ( 33 | target: Array 34 | ): GraphQLList => { 35 | if (target.length !== 1) { 36 | throw new Error( 37 | `Graphene Array definitions should contain exactly one element. Received ${ 38 | target.length 39 | } elements.` 40 | ); 41 | } 42 | const innerType = getGraphQLType(target[0]); 43 | return new GraphQLList(innerType); 44 | }; 45 | 46 | // A utility funciton to convert values like: 47 | // String 48 | // To: 49 | // GraphQLString 50 | // Note: for String -> GraphQLString conversion to work we have to call 51 | // setupNativeTypes 52 | export const getGraphQLType = (target: any): GraphQLType => { 53 | if (Array.isArray(target)) { 54 | return convertArrayToGraphQLList(target); 55 | } 56 | if (isType(target)) { 57 | return target; 58 | } 59 | const metadataType: GraphQLType = Reflect.getMetadata( 60 | GRAPHENE_TYPE_METADATA_KEY, 61 | target 62 | ); 63 | if (metadataType) { 64 | return metadataType; 65 | } 66 | throw new Error( 67 | `The target ${target} have no GraphQL type associated to it.` 68 | ); 69 | }; 70 | 71 | // An utility function to associate a GraphQL type 72 | // with the specified target. 73 | // This method takes full advantage of the Reflection API. 74 | export const setGraphQLType = (target: any, type: GraphQLType): void => { 75 | // Is the provided type a GraphQLType? 76 | // Will fail if not. 77 | assertType(type); 78 | 79 | // First we check this type have no other type associated with it. 80 | if (Reflect.hasMetadata(GRAPHENE_TYPE_METADATA_KEY, target)) { 81 | throw new Error( 82 | `Type ${String(target)} already have a Graphene type attached.` 83 | ); 84 | } 85 | // We define the type metadata through reflection 86 | Reflect.defineMetadata(GRAPHENE_TYPE_METADATA_KEY, type, target); 87 | }; 88 | 89 | // Field definitions 90 | export type UnmountedFieldMap = { 91 | [key: string]: () => GraphQLFieldConfig | null; 92 | }; 93 | 94 | export type MountedFieldMap = { 95 | [key: string]: GraphQLFieldConfig; 96 | }; 97 | 98 | // Get the fields given a constructor 99 | export const getFields = (target: any): UnmountedFieldMap => { 100 | let fields: UnmountedFieldMap; 101 | if (!Reflect.hasMetadata(GRAPHENE_FIELDS_METADATA_KEY, target)) { 102 | fields = {}; 103 | Reflect.defineMetadata(GRAPHENE_FIELDS_METADATA_KEY, fields, target); 104 | } else { 105 | fields = Reflect.getMetadata(GRAPHENE_FIELDS_METADATA_KEY, target); 106 | } 107 | return fields; 108 | }; 109 | 110 | // mountFields 111 | export const mountFields = ( 112 | fields: UnmountedFieldMap 113 | ) => (): MountedFieldMap => { 114 | let finalFields: MountedFieldMap = {}; 115 | for (let key in fields) { 116 | let field = fields[key](); 117 | if (!field) { 118 | continue; 119 | } 120 | finalFields[key] = field; 121 | } 122 | return finalFields; 123 | }; 124 | 125 | export const assertFields = (target: any, fields: UnmountedFieldMap) => { 126 | if (Object.keys(fields).length === 0) { 127 | throw new Error(`Type ${target} must have fields defined on it.`); 128 | } 129 | }; 130 | 131 | // InputFieldDefinitions 132 | export type UnmountedInputFieldMap = { 133 | [key: string]: () => GraphQLInputFieldConfig; 134 | }; 135 | 136 | export type MountedInputFieldMap = { 137 | [key: string]: GraphQLInputFieldConfig; 138 | }; 139 | 140 | // Get the fields given a constructor 141 | export const getInputFields = (target: any): UnmountedInputFieldMap => { 142 | let fields: UnmountedInputFieldMap; 143 | if (!Reflect.hasMetadata(GRAPHENE_INPUTFIELDS_METADATA_KEY, target)) { 144 | fields = {}; 145 | Reflect.defineMetadata(GRAPHENE_INPUTFIELDS_METADATA_KEY, fields, target); 146 | } else { 147 | fields = Reflect.getMetadata(GRAPHENE_INPUTFIELDS_METADATA_KEY, target); 148 | } 149 | return fields; 150 | }; 151 | 152 | // mountInputFields 153 | export const mountInputFields = ( 154 | fields: UnmountedInputFieldMap 155 | ) => (): MountedInputFieldMap => { 156 | let finalFields: MountedInputFieldMap = {}; 157 | for (let key in fields) { 158 | finalFields[key] = fields[key](); 159 | } 160 | return finalFields; 161 | }; 162 | 163 | export const assertInputFields = ( 164 | target: any, 165 | fields: UnmountedInputFieldMap 166 | ) => { 167 | if (Object.keys(fields).length === 0) { 168 | throw new Error(`Type ${target} must have fields defined on it.`); 169 | } 170 | }; 171 | 172 | // The setup function that permit us to use 173 | // the native String and Number types. 174 | export const setupNativeTypes = () => { 175 | setGraphQLType(String, GraphQLString); 176 | setGraphQLType(Boolean, GraphQLBoolean); 177 | setGraphQLType(Number, GraphQLFloat); 178 | setGraphQLType(Date, GraphQLDateTime); 179 | }; 180 | 181 | // Description setter 182 | // Usage: 183 | // setDescription(MyClass, 'the description') 184 | // Or: 185 | // setDescription(MyClass, 'methodName', 'the description') 186 | export const setDescription = ( 187 | target: any, 188 | keyOrDescription: string, 189 | description?: string 190 | ) => { 191 | if (typeof description === "undefined") { 192 | Reflect.defineMetadata(GRAPHENE_DESCRIPTION_KEY, keyOrDescription, target); 193 | } else { 194 | Reflect.defineMetadata( 195 | GRAPHENE_DESCRIPTION_KEY, 196 | description, 197 | target, 198 | keyOrDescription 199 | ); 200 | } 201 | }; 202 | 203 | // Description getter 204 | // Usage: 205 | // getDescription(MyClass) 206 | // Or: 207 | // setDescription(MyClass, 'methodName') 208 | export const getDescription = (target: any, key?: string) => { 209 | if (key) { 210 | return Reflect.getMetadata(GRAPHENE_DESCRIPTION_KEY, target, key); 211 | } 212 | return Reflect.getMetadata(GRAPHENE_DESCRIPTION_KEY, target); 213 | }; 214 | 215 | // Deprecation reason setter 216 | // Usage: 217 | // setDeprecationReason(MyClass, 'methodName', 'this method is now deprecated') 218 | export const setDeprecationReason = ( 219 | target: any, 220 | key: string, 221 | reason: string 222 | ) => { 223 | Reflect.defineMetadata(GRAPHENE_DEPRECATED_KEY, reason, target, key); 224 | }; 225 | 226 | // Deprecation reason getter 227 | // Usage: 228 | // getDeprecationReason(MyClass, 'methodName') 229 | export const getDeprecationReason = (target: any, key: string) => { 230 | return Reflect.getMetadata(GRAPHENE_DEPRECATED_KEY, target, key); 231 | }; 232 | 233 | // Setup the decorators 234 | 235 | // Description decorator. 236 | // This decorator permits us to decorate classes, like: 237 | // 238 | // @description('The class description') 239 | // class X {} 240 | // 241 | // Or the class methods, like: 242 | // 243 | // class X{ 244 | // @description('The method description') 245 | // myMethod() {} 246 | // } 247 | export const description = (description: string) => ( 248 | target: any, 249 | key?: string, 250 | descriptor?: PropertyDescriptor 251 | ) => { 252 | if (typeof key !== "undefined") { 253 | // It's a decorated method 254 | setDescription(target, key, description); 255 | return descriptor; 256 | } 257 | // It's a decorated class 258 | setDescription(target, description); 259 | return target; 260 | }; 261 | 262 | // This decorator permits us to mark fields as deprecated, like: 263 | // 264 | // class X{ 265 | // @deprecated('This method is deprecated') 266 | // myMethod() {} 267 | // } 268 | export const deprecated = (reason: string) => ( 269 | target: any, 270 | key?: string, 271 | descriptor?: any 272 | ) => { 273 | if (typeof key !== "undefined") { 274 | // It's a decorated method 275 | setDeprecationReason(target, key, reason); 276 | return descriptor; 277 | } 278 | // It's a decorated class 279 | throw new Error( 280 | `Classes can't be decorated with the @deprecated decorator. Received ${target}.` 281 | ); 282 | }; 283 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2015", 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "sourceMap": true, 7 | "declaration": true, 8 | "rootDir": "./src", 9 | "outDir": "./lib", 10 | "removeComments": true, 11 | "strict": true, 12 | "noImplicitReturns": true, 13 | "noFallthroughCasesInSwitch": true, 14 | "noUnusedParameters": true, 15 | "allowJs": false, 16 | "allowSyntheticDefaultImports": true, 17 | "allowUnreachableCode": true, 18 | "strictPropertyInitialization": false, 19 | "experimentalDecorators": true, 20 | "emitDecoratorMetadata": true 21 | }, 22 | "include": ["src/**/*"], 23 | "exclude": ["node_modules", "dist"] 24 | } 25 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "align": [false, "parameters", "arguments", "statements"], 4 | "ban": false, 5 | "class-name": true, 6 | "curly": true, 7 | "eofline": true, 8 | "indent": [true, "spaces"], 9 | "interface-name": false, 10 | "jsdoc-format": true, 11 | "label-position": true, 12 | "max-line-length": [true, 140], 13 | "member-access": true, 14 | "member-ordering": [ 15 | true, 16 | "public-before-private", 17 | "static-before-instance", 18 | "variables-before-functions" 19 | ], 20 | "no-any": false, 21 | "no-arg": true, 22 | "no-bitwise": true, 23 | "no-conditional-assignment": true, 24 | "no-consecutive-blank-lines": false, 25 | "no-console": [true, "log", "debug", "info", "time", "timeEnd", "trace"], 26 | "no-construct": true, 27 | "no-parameter-properties": true, 28 | "no-debugger": true, 29 | "no-duplicate-variable": true, 30 | "no-empty": true, 31 | "no-eval": true, 32 | "no-inferrable-types": false, 33 | "no-internal-module": true, 34 | "no-null-keyword": false, 35 | "no-require-imports": false, 36 | "no-switch-case-fall-through": true, 37 | "no-trailing-whitespace": true, 38 | "no-unused-expression": true, 39 | "no-use-before-declare": false, 40 | "no-var-keyword": true, 41 | "object-literal-sort-keys": false, 42 | "one-line": [ 43 | true, 44 | "check-open-brace", 45 | "check-catch", 46 | "check-else", 47 | "check-finally", 48 | "check-whitespace" 49 | ], 50 | "quotemark": [true, "avoid-escape"], 51 | "radix": true, 52 | "semicolon": [true, "always"], 53 | "switch-default": true, 54 | "trailing-comma": [false], 55 | "triple-equals": [true, "allow-null-check"], 56 | "typedef": [ 57 | false, 58 | "call-signature", 59 | "parameter", 60 | "arrow-parameter", 61 | "property-declaration", 62 | "variable-declaration", 63 | "member-variable-declaration" 64 | ], 65 | "typedef-whitespace": [ 66 | true, 67 | { 68 | "call-signature": "nospace", 69 | "index-signature": "nospace", 70 | "parameter": "nospace", 71 | "property-declaration": "nospace", 72 | "variable-declaration": "nospace" 73 | }, 74 | { 75 | "call-signature": "space", 76 | "index-signature": "space", 77 | "parameter": "space", 78 | "property-declaration": "space", 79 | "variable-declaration": "space" 80 | } 81 | ], 82 | "variable-name": [ 83 | true, 84 | "check-format", 85 | "allow-leading-underscore", 86 | "allow-pascal-case" 87 | ], 88 | "whitespace": [ 89 | true, 90 | "check-branch", 91 | "check-decl", 92 | "check-operator", 93 | "check-separator", 94 | "check-type" 95 | ] 96 | } 97 | } 98 | --------------------------------------------------------------------------------