├── .circleci └── config.yml ├── .gitignore ├── .huskyrc ├── .npmrc ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── ava.config.js ├── examples ├── full-example │ ├── .gitignore │ ├── .graphqlconfig.yml │ ├── README.md │ ├── binding │ │ ├── binding.js │ │ ├── ts-binding.ts │ │ ├── ts-usage.ts │ │ └── usage.js │ ├── datamodel.graphql │ ├── package.json │ ├── post-transform.graphql │ ├── prisma.yml │ ├── prisma │ │ ├── ts-prisma.ts │ │ ├── ts-usage.ts │ │ └── usage.js │ ├── schema.graphql │ ├── schema.js │ ├── server.ts │ ├── typings.ts │ └── yarn.lock └── minimal-example │ ├── .gitignore │ ├── README.md │ ├── binding.ts │ ├── package.json │ ├── prisma-usage.ts │ ├── prisma.graphql │ ├── prisma.ts │ ├── schema.ts │ ├── usage.ts │ └── yarn.lock ├── package.json ├── prettier.config.js ├── renovate.json ├── src ├── Binding.ts ├── Delegate.ts ├── bin.ts ├── codegen │ ├── FlowGenerator.test.ts │ ├── FlowGenerator.test.ts.md │ ├── FlowGenerator.test.ts.snap │ ├── FlowGenerator.ts │ ├── Generator.test.ts │ ├── Generator.test.ts.md │ ├── Generator.test.ts.snap │ ├── Generator.ts │ ├── TypescriptGenerator.test.ts │ ├── TypescriptGenerator.test.ts.md │ ├── TypescriptGenerator.test.ts.snap │ ├── TypescriptGenerator.ts │ ├── fixtures │ │ └── schema.graphql │ ├── types.ts │ └── utils │ │ ├── flatten.ts │ │ └── interleave.ts ├── fragmentReplacements.ts ├── index.ts ├── info.test.ts ├── info.test.ts.md ├── info.test.ts.snap ├── info.ts ├── makeBindingClass.ts ├── types.ts └── utils │ ├── addFragmentToInfo.test.ts │ ├── addFragmentToInfo.test.ts.md │ ├── addFragmentToInfo.test.ts.snap │ ├── addFragmentToInfo.ts │ ├── index.ts │ └── removeKey.ts ├── tsconfig.json ├── tslint.json └── yarn.lock /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | build: 4 | docker: 5 | - image: circleci/node:8 6 | steps: 7 | - checkout 8 | - run: yarn 9 | - run: yarn test 10 | - run: yarn semantic-release 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .idea 3 | .vscode 4 | dist 5 | .DS_Store 6 | *.log 7 | 8 | src/*.test.js.md 9 | /.prettierrc.json 10 | package-lock.json 11 | -------------------------------------------------------------------------------- /.huskyrc: -------------------------------------------------------------------------------- 1 | { 2 | "hooks": { 3 | "pre-commit": "pretty-quick --staged" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | ## Commits 3 | Please make sure that you stick to the commit message convention by [semantic-release](https://github.com/semantic-release/semantic-release) 4 | So instead of having a message like this: 5 | `fixed typescript generator` 6 | You now have this: 7 | `fix: typescript generator` 8 | 9 | ## Tests 10 | Before making the PR also make sure that the tests are green. 11 | Just run `yarn test`. 12 | 13 | If it's a fundamental new feature (for example adding a new generator), please cover it with new tests. 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Graphcool, Inc 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 | # graphql-binding 2 | 3 | # Deprecation Notice! 4 | 5 | In the last few months, since [the transition of many libraries](https://www.prisma.io/blog/the-guild-takes-over-oss-libraries-vvluy2i4uevs) under [The Guild](http://the-guild.dev)'s leadership, We've reviewed and released many improvements and versions to [graphql-cli](https://github.com/Urigo/graphql-cli), [graphql-config](https://github.com/kamilkisiela/graphql-config) and [graphql-import](https://github.com/ardatan/graphql-import). 6 | 7 | We've reviewed `graphql-binding`, had many meetings with current users and engaged the community also through the [roadmap issue](https://github.com/dotansimha/graphql-binding/issues/325). 8 | 9 | What we've found is that the new [GraphQL Mesh](https://the-guild.dev/blog/graphql-mesh) library is covering not only all the current capabilities of GraphQL Binding, but also the future ideas that were introduced in the [original GraphQL Binding blog post](https://github.com/prisma-archive/prisma-blog-archive/blob/master/2018-01-12-reusing-and-composing-graphql-apis-with-graphql-bindings.mdx) and haven't come to life yet. 10 | 11 | And the best thing - [GraphQL Mesh](https://the-guild.dev/blog/graphql-mesh) gives you all those capabilities, even if your source is not a GraphQL service at all! 12 | it can be GraphQL, OpenAPI/Swagger, gRPC, SQL or any other source! 13 | And of course you can even merge all those sources into a single SDK. 14 | 15 | Just like GraphQL Binding, you get a fully typed SDK (thanks to the protocols SDKs and the [GraphQL Code Generator](https://github.com/dotansimha/graphql-code-generator)), but from any source, and that SDK can run anywhere, as a connector or as a full blown gateway. 16 | And you can share your own "Mesh Modules" (which you would probably call "your own binding") and our community already created many of those! 17 | Also, we decided to simply expose regular GraphQL, so you can choose how to consume it using all the awesome [fluent client SDKs out there](https://hasura.io/blog/fluent-graphql-clients-how-to-write-queries-like-a-boss/). 18 | 19 | If you think that we've missed anything from GraphQL Binding that is not supported in a better way in GraphQL Mesh, please let us know! 20 | 21 | 22 | 23 | ## Overview 24 | 25 | 🔗 GraphQL bindings are **modular building blocks** that allow to embed existing GraphQL APIs into your own GraphQL server. Think about it as turning (parts of) GraphQL APIs into reusable LEGO building blocks. Bindings may be generated in JavaScript, TypeScript, or Flow. 26 | 27 | > The idea of GraphQL bindings is introduced in detail in this blog post: [Reusing & Composing GraphQL APIs with GraphQL Bindings](https://www.prisma.io/blog/reusing-and-composing-graphql-apis-with-graphql-bindings-80a4aa37cff5/) 28 | 29 | ## Install 30 | 31 | ```sh 32 | yarn add graphql-binding 33 | ``` 34 | 35 | ## Public GraphQL bindings 36 | 37 | You can find practical, production-ready examples here: 38 | 39 | - [`graphql-binding-github`](https://github.com/graphql-binding/graphql-binding-github) 40 | - [`prisma-binding`](https://github.com/prisma/prisma-binding) 41 | 42 | > Note: If you've created your own GraphQL binding based on this package, please add it to this list via a PR 🙌 43 | 44 | If you have any questions, share some ideas or just want to chat about GraphQL bindings, join the [`#graphql-bindings`](https://prisma.slack.com/messages/graphql-bindings) channel in our [Slack](https://slack.prisma.io/). 45 | -------------------------------------------------------------------------------- /ava.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | compileEnhancements: false, 3 | verbose: true, 4 | extensions: ['ts'], 5 | require: ['ts-node/register'], 6 | files: ['src/**/*.test.ts'], 7 | } 8 | -------------------------------------------------------------------------------- /examples/full-example/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | export* -------------------------------------------------------------------------------- /examples/full-example/.graphqlconfig.yml: -------------------------------------------------------------------------------- 1 | projects: 2 | db: 3 | schemaPath: schema.graphql 4 | extensions: 5 | prisma: prisma.yml 6 | codegen: 7 | - generator: typegen 8 | language: typescript 9 | input: "{binding,prisma}/*.ts" 10 | output: 11 | typings: typings.ts 12 | - generator: prisma-binding 13 | language: typescript 14 | input: schema.js 15 | output: 16 | binding: prisma/ts-prisma.ts 17 | # - generator: prisma-binding 18 | # language: javascript 19 | # input: schema.js 20 | # output: 21 | # binding: prisma/prisma.js 22 | # - generator: graphql-binding 23 | # language: typescript 24 | # input: schema.js 25 | # output: 26 | # binding: binding/ts-binding.ts 27 | # - generator: graphql-binding 28 | # language: javascript 29 | # input: schema.js 30 | # output: 31 | # binding: binding/binding.js 32 | -------------------------------------------------------------------------------- /examples/full-example/README.md: -------------------------------------------------------------------------------- 1 | # graphql-config-example 2 | 3 | ## Usage 4 | ```bash 5 | $ yarn install 6 | $ npm install -g graphql-cli typescript 7 | $ graphql codegen 8 | 9 | ## Run Prisma Binding 10 | $ ts-node prisma/ts-usage.ts 11 | # or 12 | $ node prisma/usage.js 13 | 14 | ## Run Generic Binding 15 | $ ts-node binding/ts-usage.ts 16 | # or 17 | $ node binding/usage.js 18 | ``` 19 | -------------------------------------------------------------------------------- /examples/full-example/binding/binding.js: -------------------------------------------------------------------------------- 1 | const { makeBindingClass } = require('graphql-binding') 2 | const schema = require('../schema') 3 | 4 | module.exports = makeBindingClass({ schema }) -------------------------------------------------------------------------------- /examples/full-example/binding/ts-binding.ts: -------------------------------------------------------------------------------- 1 | import { makeBindingClass } from 'graphql-binding' 2 | import { GraphQLResolveInfo } from 'graphql' 3 | import * as schema from '../schema' 4 | 5 | interface BindingInstance { 6 | query: { 7 | users: (args: { where?: UserWhereInput, orderBy?: UserOrderByInput, skip?: Int, after?: String, before?: String, first?: Int, last?: Int }, info?: GraphQLResolveInfo | string, context?: { [key: string]: any }) => Promise , 8 | user: (args: { where: UserWhereUniqueInput }, info?: GraphQLResolveInfo | string, context?: { [key: string]: any }) => Promise , 9 | usersConnection: (args: { where?: UserWhereInput, orderBy?: UserOrderByInput, skip?: Int, after?: String, before?: String, first?: Int, last?: Int }, info?: GraphQLResolveInfo | string, context?: { [key: string]: any }) => Promise , 10 | node: (args: { id: ID_Output }, info?: GraphQLResolveInfo | string, context?: { [key: string]: any }) => Promise 11 | } 12 | mutation: { 13 | createUser: (args: { data: UserCreateInput }, info?: GraphQLResolveInfo | string, context?: { [key: string]: any }) => Promise , 14 | updateUser: (args: { data: UserUpdateInput, where: UserWhereUniqueInput }, info?: GraphQLResolveInfo | string, context?: { [key: string]: any }) => Promise , 15 | deleteUser: (args: { where: UserWhereUniqueInput }, info?: GraphQLResolveInfo | string, context?: { [key: string]: any }) => Promise , 16 | upsertUser: (args: { where: UserWhereUniqueInput, create: UserCreateInput, update: UserUpdateInput }, info?: GraphQLResolveInfo | string, context?: { [key: string]: any }) => Promise , 17 | updateManyUsers: (args: { data: UserUpdateInput, where?: UserWhereInput }, info?: GraphQLResolveInfo | string, context?: { [key: string]: any }) => Promise , 18 | deleteManyUsers: (args: { where?: UserWhereInput }, info?: GraphQLResolveInfo | string, context?: { [key: string]: any }) => Promise 19 | } 20 | subscription: { 21 | user: (args: { where?: UserSubscriptionWhereInput }, info?: GraphQLResolveInfo | string, context?: { [key: string]: any }) => Promise 22 | } 23 | request: (query: string, variables?: {[key: string]: any}) => Promise 24 | } 25 | 26 | interface BindingConstructor { 27 | new(...args): T 28 | } 29 | 30 | export const Binding = makeBindingClass>({ schema }) 31 | 32 | /** 33 | * Types 34 | */ 35 | 36 | export type UserOrderByInput = 'id_ASC' | 37 | 'id_DESC' | 38 | 'name_ASC' | 39 | 'name_DESC' | 40 | 'updatedAt_ASC' | 41 | 'updatedAt_DESC' | 42 | 'createdAt_ASC' | 43 | 'createdAt_DESC' 44 | 45 | export type MutationType = 'CREATED' | 46 | 'UPDATED' | 47 | 'DELETED' 48 | 49 | export interface UserCreateInput { 50 | name: String 51 | } 52 | 53 | export interface UserWhereUniqueInput { 54 | id?: ID_Input 55 | } 56 | 57 | export interface UserUpdateInput { 58 | name?: String 59 | } 60 | 61 | export interface UserSubscriptionWhereInput { 62 | AND?: UserSubscriptionWhereInput[] | UserSubscriptionWhereInput 63 | OR?: UserSubscriptionWhereInput[] | UserSubscriptionWhereInput 64 | NOT?: UserSubscriptionWhereInput[] | UserSubscriptionWhereInput 65 | mutation_in?: MutationType[] | MutationType 66 | updatedFields_contains?: String 67 | updatedFields_contains_every?: String[] | String 68 | updatedFields_contains_some?: String[] | String 69 | node?: UserWhereInput 70 | } 71 | 72 | export interface UserWhereInput { 73 | AND?: UserWhereInput[] | UserWhereInput 74 | OR?: UserWhereInput[] | UserWhereInput 75 | NOT?: UserWhereInput[] | UserWhereInput 76 | id?: ID_Input 77 | id_not?: ID_Input 78 | id_in?: ID_Input[] | ID_Input 79 | id_not_in?: ID_Input[] | ID_Input 80 | id_lt?: ID_Input 81 | id_lte?: ID_Input 82 | id_gt?: ID_Input 83 | id_gte?: ID_Input 84 | id_contains?: ID_Input 85 | id_not_contains?: ID_Input 86 | id_starts_with?: ID_Input 87 | id_not_starts_with?: ID_Input 88 | id_ends_with?: ID_Input 89 | id_not_ends_with?: ID_Input 90 | name?: String 91 | name_not?: String 92 | name_in?: String[] | String 93 | name_not_in?: String[] | String 94 | name_lt?: String 95 | name_lte?: String 96 | name_gt?: String 97 | name_gte?: String 98 | name_contains?: String 99 | name_not_contains?: String 100 | name_starts_with?: String 101 | name_not_starts_with?: String 102 | name_ends_with?: String 103 | name_not_ends_with?: String 104 | } 105 | 106 | /* 107 | * An object with an ID 108 | 109 | */ 110 | export interface Node { 111 | id: ID_Output 112 | } 113 | 114 | export interface UserPreviousValues { 115 | id: ID_Output 116 | name: String 117 | } 118 | 119 | export interface BatchPayload { 120 | count: Long 121 | } 122 | 123 | /* 124 | * Information about pagination in a connection. 125 | 126 | */ 127 | export interface PageInfo { 128 | hasNextPage: Boolean 129 | hasPreviousPage: Boolean 130 | startCursor?: String 131 | endCursor?: String 132 | } 133 | 134 | export interface UserSubscriptionPayload { 135 | mutation: MutationType 136 | node?: User 137 | updatedFields?: String[] 138 | previousValues?: UserPreviousValues 139 | } 140 | 141 | export interface User extends Node { 142 | id: ID_Output 143 | name: String 144 | } 145 | 146 | /* 147 | * A connection to a list of items. 148 | 149 | */ 150 | export interface UserConnection { 151 | pageInfo: PageInfo 152 | edges: UserEdge[] 153 | aggregate: AggregateUser 154 | } 155 | 156 | export interface AggregateUser { 157 | count: Int 158 | } 159 | 160 | /* 161 | * An edge in a connection. 162 | 163 | */ 164 | export interface UserEdge { 165 | node: User 166 | cursor: String 167 | } 168 | 169 | /* 170 | The `Boolean` scalar type represents `true` or `false`. 171 | */ 172 | export type Boolean = boolean 173 | 174 | /* 175 | The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text. 176 | */ 177 | export type String = string 178 | 179 | /* 180 | The `Int` scalar type represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1. 181 | */ 182 | export type Int = number 183 | 184 | /* 185 | The `Long` scalar type represents non-fractional signed whole numeric values. 186 | Long can represent values between -(2^63) and 2^63 - 1. 187 | */ 188 | export type Long = string 189 | 190 | /* 191 | The `ID` scalar type represents a unique identifier, often used to refetch an object or as key for a cache. The ID type appears in a JSON response as a String; however, it is not intended to be human-readable. When expected as an input type, any string (such as `"4"`) or integer (such as `4`) input value will be accepted as an ID. 192 | */ 193 | export type ID_Input = string | number 194 | export type ID_Output = string -------------------------------------------------------------------------------- /examples/full-example/binding/ts-usage.ts: -------------------------------------------------------------------------------- 1 | import { Binding } from './ts-binding' 2 | 3 | const binding = new Binding() 4 | 5 | // binding.mutation 6 | // .createUser({ 7 | // data: { 8 | // name: 'some user', 9 | // }, 10 | // }) 11 | // .catch(e => console.error(e)) 12 | 13 | binding.query 14 | .users( 15 | {}, 16 | ` 17 | fragment Frogo on User { 18 | id 19 | } 20 | `, 21 | ) 22 | .then(r => { 23 | console.log(r) 24 | }) 25 | .catch(e => console.error(e)) 26 | -------------------------------------------------------------------------------- /examples/full-example/binding/usage.js: -------------------------------------------------------------------------------- 1 | const Binding = require('./binding') 2 | 3 | const binding = new Binding() 4 | 5 | binding.mutation 6 | .createUser({ 7 | data: { 8 | name: 'some user', 9 | }, 10 | }, '{id}') 11 | .catch(e => console.error(e)) 12 | 13 | binding.query 14 | .users({}, '{id}') 15 | .then(r => { 16 | console.log(r) 17 | }) 18 | .catch(e => console.error(e)) 19 | -------------------------------------------------------------------------------- /examples/full-example/datamodel.graphql: -------------------------------------------------------------------------------- 1 | type User { 2 | id: ID! @unique 3 | name: String! 4 | } 5 | 6 | type A { 7 | id: ID! @unique 8 | name: String! 9 | } 10 | 11 | type B { 12 | id: ID! @unique 13 | name: String! 14 | } 15 | -------------------------------------------------------------------------------- /examples/full-example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "graphql-binding": "^2.1.1", 4 | "graphql-cli": "^2.16.3", 5 | "prisma-binding": "^2.1.0", 6 | "typescript": "^2.8.3" 7 | }, 8 | "dependencies": { 9 | "@types/zen-observable": "^0.8.0", 10 | "apollo-codegen": "^0.20.2", 11 | "graphql-tag": "^2.9.2", 12 | "graphql-tools": "^3.0.4", 13 | "graphql-yoga": "^1.14.10" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /examples/full-example/post-transform.graphql: -------------------------------------------------------------------------------- 1 | type AggregateUser { 2 | count: Int! 3 | } 4 | 5 | type BatchPayload { 6 | """The number of nodes that have been affected by the Batch operation.""" 7 | count: Long! 8 | } 9 | 10 | """ 11 | The `Long` scalar type represents non-fractional signed whole numeric values. 12 | Long can represent values between -(2^63) and 2^63 - 1. 13 | """ 14 | scalar Long 15 | 16 | type Mutation { 17 | createUser(data: UserCreateInput!): User! 18 | updateUser(data: UserUpdateInput!, where: UserWhereUniqueInput!): User 19 | deleteUser(where: UserWhereUniqueInput!): User 20 | upsertUser(where: UserWhereUniqueInput!, create: UserCreateInput!, update: UserUpdateInput!): User! 21 | updateManyUsers(data: UserUpdateInput!, where: UserWhereInput): BatchPayload! 22 | deleteManyUsers(where: UserWhereInput): BatchPayload! 23 | } 24 | 25 | enum MutationType { 26 | CREATED 27 | UPDATED 28 | DELETED 29 | } 30 | 31 | """An object with an ID""" 32 | interface Node { 33 | """The id of the object.""" 34 | id: ID! 35 | } 36 | 37 | """Information about pagination in a connection.""" 38 | type PageInfo { 39 | """When paginating forwards, are there more items?""" 40 | hasNextPage: Boolean! 41 | 42 | """When paginating backwards, are there more items?""" 43 | hasPreviousPage: Boolean! 44 | 45 | """When paginating backwards, the cursor to continue.""" 46 | startCursor: String 47 | 48 | """When paginating forwards, the cursor to continue.""" 49 | endCursor: String 50 | } 51 | 52 | type Query { 53 | users(where: UserWhereInput, orderBy: UserOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): [User]! 54 | user(where: UserWhereUniqueInput!): User 55 | usersConnection(where: UserWhereInput, orderBy: UserOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): UserConnection! 56 | 57 | """Fetches an object given its ID""" 58 | node( 59 | """The ID of an object""" 60 | id: ID! 61 | ): Node 62 | } 63 | 64 | type Subscription { 65 | user(where: UserSubscriptionWhereInput): UserSubscriptionPayload 66 | } 67 | 68 | type User implements Node { 69 | id: ID! 70 | name: String! 71 | } 72 | 73 | """A connection to a list of items.""" 74 | type UserConnection { 75 | """Information to aid in pagination.""" 76 | pageInfo: PageInfo! 77 | 78 | """A list of edges.""" 79 | edges: [UserEdge]! 80 | aggregate: AggregateUser! 81 | } 82 | 83 | input UserCreateInput { 84 | name: String! 85 | } 86 | 87 | """An edge in a connection.""" 88 | type UserEdge { 89 | """The item at the end of the edge.""" 90 | node: User! 91 | 92 | """A cursor for use in pagination.""" 93 | cursor: String! 94 | } 95 | 96 | enum UserOrderByInput { 97 | id_ASC 98 | id_DESC 99 | name_ASC 100 | name_DESC 101 | updatedAt_ASC 102 | updatedAt_DESC 103 | createdAt_ASC 104 | createdAt_DESC 105 | } 106 | 107 | type UserPreviousValues { 108 | id: ID! 109 | name: String! 110 | } 111 | 112 | type UserSubscriptionPayload { 113 | mutation: MutationType! 114 | node: User 115 | updatedFields: [String!] 116 | previousValues: UserPreviousValues 117 | } 118 | 119 | input UserSubscriptionWhereInput { 120 | """Logical AND on all given filters.""" 121 | AND: [UserSubscriptionWhereInput!] 122 | 123 | """Logical OR on all given filters.""" 124 | OR: [UserSubscriptionWhereInput!] 125 | 126 | """Logical NOT on all given filters combined by AND.""" 127 | NOT: [UserSubscriptionWhereInput!] 128 | 129 | """ 130 | The subscription event gets dispatched when it's listed in mutation_in 131 | """ 132 | mutation_in: [MutationType!] 133 | 134 | """ 135 | The subscription event gets only dispatched when one of the updated fields names is included in this list 136 | """ 137 | updatedFields_contains: String 138 | 139 | """ 140 | The subscription event gets only dispatched when all of the field names included in this list have been updated 141 | """ 142 | updatedFields_contains_every: [String!] 143 | 144 | """ 145 | The subscription event gets only dispatched when some of the field names included in this list have been updated 146 | """ 147 | updatedFields_contains_some: [String!] 148 | node: UserWhereInput 149 | } 150 | 151 | input UserUpdateInput { 152 | name: String 153 | } 154 | 155 | input UserWhereInput { 156 | """Logical AND on all given filters.""" 157 | AND: [UserWhereInput!] 158 | 159 | """Logical OR on all given filters.""" 160 | OR: [UserWhereInput!] 161 | 162 | """Logical NOT on all given filters combined by AND.""" 163 | NOT: [UserWhereInput!] 164 | id: ID 165 | 166 | """All values that are not equal to given value.""" 167 | id_not: ID 168 | 169 | """All values that are contained in given list.""" 170 | id_in: [ID!] 171 | 172 | """All values that are not contained in given list.""" 173 | id_not_in: [ID!] 174 | 175 | """All values less than the given value.""" 176 | id_lt: ID 177 | 178 | """All values less than or equal the given value.""" 179 | id_lte: ID 180 | 181 | """All values greater than the given value.""" 182 | id_gt: ID 183 | 184 | """All values greater than or equal the given value.""" 185 | id_gte: ID 186 | 187 | """All values containing the given string.""" 188 | id_contains: ID 189 | 190 | """All values not containing the given string.""" 191 | id_not_contains: ID 192 | 193 | """All values starting with the given string.""" 194 | id_starts_with: ID 195 | 196 | """All values not starting with the given string.""" 197 | id_not_starts_with: ID 198 | 199 | """All values ending with the given string.""" 200 | id_ends_with: ID 201 | 202 | """All values not ending with the given string.""" 203 | id_not_ends_with: ID 204 | name: String 205 | 206 | """All values that are not equal to given value.""" 207 | name_not: String 208 | 209 | """All values that are contained in given list.""" 210 | name_in: [String!] 211 | 212 | """All values that are not contained in given list.""" 213 | name_not_in: [String!] 214 | 215 | """All values less than the given value.""" 216 | name_lt: String 217 | 218 | """All values less than or equal the given value.""" 219 | name_lte: String 220 | 221 | """All values greater than the given value.""" 222 | name_gt: String 223 | 224 | """All values greater than or equal the given value.""" 225 | name_gte: String 226 | 227 | """All values containing the given string.""" 228 | name_contains: String 229 | 230 | """All values not containing the given string.""" 231 | name_not_contains: String 232 | 233 | """All values starting with the given string.""" 234 | name_starts_with: String 235 | 236 | """All values not starting with the given string.""" 237 | name_not_starts_with: String 238 | 239 | """All values ending with the given string.""" 240 | name_ends_with: String 241 | 242 | """All values not ending with the given string.""" 243 | name_not_ends_with: String 244 | } 245 | 246 | input UserWhereUniqueInput { 247 | id: ID 248 | } 249 | -------------------------------------------------------------------------------- /examples/full-example/prisma.yml: -------------------------------------------------------------------------------- 1 | endpoint: https://eu1.prisma.sh/lol/session65/dev 2 | datamodel: datamodel.graphql 3 | hooks: 4 | post-deploy: 5 | - graphql get-schema 6 | - graphql codegen 7 | -------------------------------------------------------------------------------- /examples/full-example/prisma/ts-usage.ts: -------------------------------------------------------------------------------- 1 | import { Prisma } from './ts-prisma' 2 | import gql from 'graphql-tag' 3 | import { CreateUserFragment } from '../typings' 4 | 5 | async function run() { 6 | const binding = new Prisma({ 7 | endpoint: 'https://eu1.prisma.sh/lol/session65/dev', 8 | }) 9 | 10 | setTimeout(async () => { 11 | const createResult = await binding.mutation.createUser( 12 | { 13 | data: { 14 | name: 'some user', 15 | }, 16 | }, 17 | gql` 18 | fragment CreateUser on User { 19 | id 20 | } 21 | `, 22 | ) 23 | console.log(createResult) 24 | }, 3000) 25 | 26 | const subscription = await binding.subscription.user( 27 | {}, 28 | gql` 29 | { 30 | node { 31 | id 32 | } 33 | } 34 | `, 35 | ) 36 | subscription.next().then(res => { 37 | console.log('got some value') 38 | console.log(res) 39 | }) 40 | } 41 | 42 | run() 43 | -------------------------------------------------------------------------------- /examples/full-example/prisma/usage.js: -------------------------------------------------------------------------------- 1 | const Prisma = require('./prisma') 2 | 3 | const binding = new Prisma({ 4 | endpoint: 'https://eu1.prisma.sh/lol/session65/dev', 5 | }) 6 | 7 | binding.mutation 8 | .createUser({ 9 | data: { 10 | name: 'some user', 11 | }, 12 | }, '{id}') 13 | .catch(e => console.error(e)) 14 | 15 | binding.query 16 | .users({}, '{id}') 17 | .then(r => { 18 | console.log(r) 19 | }) 20 | .catch(e => console.error(e)) 21 | -------------------------------------------------------------------------------- /examples/full-example/schema.graphql: -------------------------------------------------------------------------------- 1 | # source: https://eu1.prisma.sh/lol/session65/dev 2 | # timestamp: Wed May 09 2018 09:46:02 GMT+0200 (CEST) 3 | 4 | type A implements Node { 5 | id: ID! 6 | name: String! 7 | } 8 | 9 | """A connection to a list of items.""" 10 | type AConnection { 11 | """Information to aid in pagination.""" 12 | pageInfo: PageInfo! 13 | 14 | """A list of edges.""" 15 | edges: [AEdge]! 16 | aggregate: AggregateA! 17 | } 18 | 19 | input ACreateInput { 20 | name: String! 21 | } 22 | 23 | """An edge in a connection.""" 24 | type AEdge { 25 | """The item at the end of the edge.""" 26 | node: A! 27 | 28 | """A cursor for use in pagination.""" 29 | cursor: String! 30 | } 31 | 32 | type AggregateA { 33 | count: Int! 34 | } 35 | 36 | type AggregateB { 37 | count: Int! 38 | } 39 | 40 | type AggregateUser { 41 | count: Int! 42 | } 43 | 44 | enum AOrderByInput { 45 | id_ASC 46 | id_DESC 47 | name_ASC 48 | name_DESC 49 | updatedAt_ASC 50 | updatedAt_DESC 51 | createdAt_ASC 52 | createdAt_DESC 53 | } 54 | 55 | type APreviousValues { 56 | id: ID! 57 | name: String! 58 | } 59 | 60 | type ASubscriptionPayload { 61 | mutation: MutationType! 62 | node: A 63 | updatedFields: [String!] 64 | previousValues: APreviousValues 65 | } 66 | 67 | input ASubscriptionWhereInput { 68 | """Logical AND on all given filters.""" 69 | AND: [ASubscriptionWhereInput!] 70 | 71 | """Logical OR on all given filters.""" 72 | OR: [ASubscriptionWhereInput!] 73 | 74 | """Logical NOT on all given filters combined by AND.""" 75 | NOT: [ASubscriptionWhereInput!] 76 | 77 | """ 78 | The subscription event gets dispatched when it's listed in mutation_in 79 | """ 80 | mutation_in: [MutationType!] 81 | 82 | """ 83 | The subscription event gets only dispatched when one of the updated fields names is included in this list 84 | """ 85 | updatedFields_contains: String 86 | 87 | """ 88 | The subscription event gets only dispatched when all of the field names included in this list have been updated 89 | """ 90 | updatedFields_contains_every: [String!] 91 | 92 | """ 93 | The subscription event gets only dispatched when some of the field names included in this list have been updated 94 | """ 95 | updatedFields_contains_some: [String!] 96 | node: AWhereInput 97 | } 98 | 99 | input AUpdateInput { 100 | name: String 101 | } 102 | 103 | input AWhereInput { 104 | """Logical AND on all given filters.""" 105 | AND: [AWhereInput!] 106 | 107 | """Logical OR on all given filters.""" 108 | OR: [AWhereInput!] 109 | 110 | """Logical NOT on all given filters combined by AND.""" 111 | NOT: [AWhereInput!] 112 | id: ID 113 | 114 | """All values that are not equal to given value.""" 115 | id_not: ID 116 | 117 | """All values that are contained in given list.""" 118 | id_in: [ID!] 119 | 120 | """All values that are not contained in given list.""" 121 | id_not_in: [ID!] 122 | 123 | """All values less than the given value.""" 124 | id_lt: ID 125 | 126 | """All values less than or equal the given value.""" 127 | id_lte: ID 128 | 129 | """All values greater than the given value.""" 130 | id_gt: ID 131 | 132 | """All values greater than or equal the given value.""" 133 | id_gte: ID 134 | 135 | """All values containing the given string.""" 136 | id_contains: ID 137 | 138 | """All values not containing the given string.""" 139 | id_not_contains: ID 140 | 141 | """All values starting with the given string.""" 142 | id_starts_with: ID 143 | 144 | """All values not starting with the given string.""" 145 | id_not_starts_with: ID 146 | 147 | """All values ending with the given string.""" 148 | id_ends_with: ID 149 | 150 | """All values not ending with the given string.""" 151 | id_not_ends_with: ID 152 | name: String 153 | 154 | """All values that are not equal to given value.""" 155 | name_not: String 156 | 157 | """All values that are contained in given list.""" 158 | name_in: [String!] 159 | 160 | """All values that are not contained in given list.""" 161 | name_not_in: [String!] 162 | 163 | """All values less than the given value.""" 164 | name_lt: String 165 | 166 | """All values less than or equal the given value.""" 167 | name_lte: String 168 | 169 | """All values greater than the given value.""" 170 | name_gt: String 171 | 172 | """All values greater than or equal the given value.""" 173 | name_gte: String 174 | 175 | """All values containing the given string.""" 176 | name_contains: String 177 | 178 | """All values not containing the given string.""" 179 | name_not_contains: String 180 | 181 | """All values starting with the given string.""" 182 | name_starts_with: String 183 | 184 | """All values not starting with the given string.""" 185 | name_not_starts_with: String 186 | 187 | """All values ending with the given string.""" 188 | name_ends_with: String 189 | 190 | """All values not ending with the given string.""" 191 | name_not_ends_with: String 192 | } 193 | 194 | input AWhereUniqueInput { 195 | id: ID 196 | } 197 | 198 | type B implements Node { 199 | id: ID! 200 | name: String! 201 | } 202 | 203 | type BatchPayload { 204 | """The number of nodes that have been affected by the Batch operation.""" 205 | count: Long! 206 | } 207 | 208 | """A connection to a list of items.""" 209 | type BConnection { 210 | """Information to aid in pagination.""" 211 | pageInfo: PageInfo! 212 | 213 | """A list of edges.""" 214 | edges: [BEdge]! 215 | aggregate: AggregateB! 216 | } 217 | 218 | input BCreateInput { 219 | name: String! 220 | } 221 | 222 | """An edge in a connection.""" 223 | type BEdge { 224 | """The item at the end of the edge.""" 225 | node: B! 226 | 227 | """A cursor for use in pagination.""" 228 | cursor: String! 229 | } 230 | 231 | enum BOrderByInput { 232 | id_ASC 233 | id_DESC 234 | name_ASC 235 | name_DESC 236 | updatedAt_ASC 237 | updatedAt_DESC 238 | createdAt_ASC 239 | createdAt_DESC 240 | } 241 | 242 | type BPreviousValues { 243 | id: ID! 244 | name: String! 245 | } 246 | 247 | type BSubscriptionPayload { 248 | mutation: MutationType! 249 | node: B 250 | updatedFields: [String!] 251 | previousValues: BPreviousValues 252 | } 253 | 254 | input BSubscriptionWhereInput { 255 | """Logical AND on all given filters.""" 256 | AND: [BSubscriptionWhereInput!] 257 | 258 | """Logical OR on all given filters.""" 259 | OR: [BSubscriptionWhereInput!] 260 | 261 | """Logical NOT on all given filters combined by AND.""" 262 | NOT: [BSubscriptionWhereInput!] 263 | 264 | """ 265 | The subscription event gets dispatched when it's listed in mutation_in 266 | """ 267 | mutation_in: [MutationType!] 268 | 269 | """ 270 | The subscription event gets only dispatched when one of the updated fields names is included in this list 271 | """ 272 | updatedFields_contains: String 273 | 274 | """ 275 | The subscription event gets only dispatched when all of the field names included in this list have been updated 276 | """ 277 | updatedFields_contains_every: [String!] 278 | 279 | """ 280 | The subscription event gets only dispatched when some of the field names included in this list have been updated 281 | """ 282 | updatedFields_contains_some: [String!] 283 | node: BWhereInput 284 | } 285 | 286 | input BUpdateInput { 287 | name: String 288 | } 289 | 290 | input BWhereInput { 291 | """Logical AND on all given filters.""" 292 | AND: [BWhereInput!] 293 | 294 | """Logical OR on all given filters.""" 295 | OR: [BWhereInput!] 296 | 297 | """Logical NOT on all given filters combined by AND.""" 298 | NOT: [BWhereInput!] 299 | id: ID 300 | 301 | """All values that are not equal to given value.""" 302 | id_not: ID 303 | 304 | """All values that are contained in given list.""" 305 | id_in: [ID!] 306 | 307 | """All values that are not contained in given list.""" 308 | id_not_in: [ID!] 309 | 310 | """All values less than the given value.""" 311 | id_lt: ID 312 | 313 | """All values less than or equal the given value.""" 314 | id_lte: ID 315 | 316 | """All values greater than the given value.""" 317 | id_gt: ID 318 | 319 | """All values greater than or equal the given value.""" 320 | id_gte: ID 321 | 322 | """All values containing the given string.""" 323 | id_contains: ID 324 | 325 | """All values not containing the given string.""" 326 | id_not_contains: ID 327 | 328 | """All values starting with the given string.""" 329 | id_starts_with: ID 330 | 331 | """All values not starting with the given string.""" 332 | id_not_starts_with: ID 333 | 334 | """All values ending with the given string.""" 335 | id_ends_with: ID 336 | 337 | """All values not ending with the given string.""" 338 | id_not_ends_with: ID 339 | name: String 340 | 341 | """All values that are not equal to given value.""" 342 | name_not: String 343 | 344 | """All values that are contained in given list.""" 345 | name_in: [String!] 346 | 347 | """All values that are not contained in given list.""" 348 | name_not_in: [String!] 349 | 350 | """All values less than the given value.""" 351 | name_lt: String 352 | 353 | """All values less than or equal the given value.""" 354 | name_lte: String 355 | 356 | """All values greater than the given value.""" 357 | name_gt: String 358 | 359 | """All values greater than or equal the given value.""" 360 | name_gte: String 361 | 362 | """All values containing the given string.""" 363 | name_contains: String 364 | 365 | """All values not containing the given string.""" 366 | name_not_contains: String 367 | 368 | """All values starting with the given string.""" 369 | name_starts_with: String 370 | 371 | """All values not starting with the given string.""" 372 | name_not_starts_with: String 373 | 374 | """All values ending with the given string.""" 375 | name_ends_with: String 376 | 377 | """All values not ending with the given string.""" 378 | name_not_ends_with: String 379 | } 380 | 381 | input BWhereUniqueInput { 382 | id: ID 383 | } 384 | 385 | """ 386 | The `Long` scalar type represents non-fractional signed whole numeric values. 387 | Long can represent values between -(2^63) and 2^63 - 1. 388 | """ 389 | scalar Long 390 | 391 | type Mutation { 392 | createUser(data: UserCreateInput!): User! 393 | createA(data: ACreateInput!): A! 394 | createB(data: BCreateInput!): B! 395 | updateUser(data: UserUpdateInput!, where: UserWhereUniqueInput!): User 396 | updateA(data: AUpdateInput!, where: AWhereUniqueInput!): A 397 | updateB(data: BUpdateInput!, where: BWhereUniqueInput!): B 398 | deleteUser(where: UserWhereUniqueInput!): User 399 | deleteA(where: AWhereUniqueInput!): A 400 | deleteB(where: BWhereUniqueInput!): B 401 | upsertUser(where: UserWhereUniqueInput!, create: UserCreateInput!, update: UserUpdateInput!): User! 402 | upsertA(where: AWhereUniqueInput!, create: ACreateInput!, update: AUpdateInput!): A! 403 | upsertB(where: BWhereUniqueInput!, create: BCreateInput!, update: BUpdateInput!): B! 404 | updateManyUsers(data: UserUpdateInput!, where: UserWhereInput): BatchPayload! 405 | updateManyAs(data: AUpdateInput!, where: AWhereInput): BatchPayload! 406 | updateManyBs(data: BUpdateInput!, where: BWhereInput): BatchPayload! 407 | deleteManyUsers(where: UserWhereInput): BatchPayload! 408 | deleteManyAs(where: AWhereInput): BatchPayload! 409 | deleteManyBs(where: BWhereInput): BatchPayload! 410 | } 411 | 412 | enum MutationType { 413 | CREATED 414 | UPDATED 415 | DELETED 416 | } 417 | 418 | """An object with an ID""" 419 | interface Node { 420 | """The id of the object.""" 421 | id: ID! 422 | } 423 | 424 | """Information about pagination in a connection.""" 425 | type PageInfo { 426 | """When paginating forwards, are there more items?""" 427 | hasNextPage: Boolean! 428 | 429 | """When paginating backwards, are there more items?""" 430 | hasPreviousPage: Boolean! 431 | 432 | """When paginating backwards, the cursor to continue.""" 433 | startCursor: String 434 | 435 | """When paginating forwards, the cursor to continue.""" 436 | endCursor: String 437 | } 438 | 439 | type Query { 440 | users(where: UserWhereInput, orderBy: UserOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): [User]! 441 | as(where: AWhereInput, orderBy: AOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): [A]! 442 | bs(where: BWhereInput, orderBy: BOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): [B]! 443 | user(where: UserWhereUniqueInput!): User 444 | a(where: AWhereUniqueInput!): A 445 | b(where: BWhereUniqueInput!): B 446 | usersConnection(where: UserWhereInput, orderBy: UserOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): UserConnection! 447 | asConnection(where: AWhereInput, orderBy: AOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): AConnection! 448 | bsConnection(where: BWhereInput, orderBy: BOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): BConnection! 449 | 450 | """Fetches an object given its ID""" 451 | node( 452 | """The ID of an object""" 453 | id: ID! 454 | ): Node 455 | } 456 | 457 | type Subscription { 458 | user(where: UserSubscriptionWhereInput): UserSubscriptionPayload 459 | a(where: ASubscriptionWhereInput): ASubscriptionPayload 460 | b(where: BSubscriptionWhereInput): BSubscriptionPayload 461 | } 462 | 463 | type User implements Node { 464 | id: ID! 465 | name: String! 466 | } 467 | 468 | """A connection to a list of items.""" 469 | type UserConnection { 470 | """Information to aid in pagination.""" 471 | pageInfo: PageInfo! 472 | 473 | """A list of edges.""" 474 | edges: [UserEdge]! 475 | aggregate: AggregateUser! 476 | } 477 | 478 | input UserCreateInput { 479 | name: String! 480 | } 481 | 482 | """An edge in a connection.""" 483 | type UserEdge { 484 | """The item at the end of the edge.""" 485 | node: User! 486 | 487 | """A cursor for use in pagination.""" 488 | cursor: String! 489 | } 490 | 491 | enum UserOrderByInput { 492 | id_ASC 493 | id_DESC 494 | name_ASC 495 | name_DESC 496 | updatedAt_ASC 497 | updatedAt_DESC 498 | createdAt_ASC 499 | createdAt_DESC 500 | } 501 | 502 | type UserPreviousValues { 503 | id: ID! 504 | name: String! 505 | } 506 | 507 | type UserSubscriptionPayload { 508 | mutation: MutationType! 509 | node: User 510 | updatedFields: [String!] 511 | previousValues: UserPreviousValues 512 | } 513 | 514 | input UserSubscriptionWhereInput { 515 | """Logical AND on all given filters.""" 516 | AND: [UserSubscriptionWhereInput!] 517 | 518 | """Logical OR on all given filters.""" 519 | OR: [UserSubscriptionWhereInput!] 520 | 521 | """Logical NOT on all given filters combined by AND.""" 522 | NOT: [UserSubscriptionWhereInput!] 523 | 524 | """ 525 | The subscription event gets dispatched when it's listed in mutation_in 526 | """ 527 | mutation_in: [MutationType!] 528 | 529 | """ 530 | The subscription event gets only dispatched when one of the updated fields names is included in this list 531 | """ 532 | updatedFields_contains: String 533 | 534 | """ 535 | The subscription event gets only dispatched when all of the field names included in this list have been updated 536 | """ 537 | updatedFields_contains_every: [String!] 538 | 539 | """ 540 | The subscription event gets only dispatched when some of the field names included in this list have been updated 541 | """ 542 | updatedFields_contains_some: [String!] 543 | node: UserWhereInput 544 | } 545 | 546 | input UserUpdateInput { 547 | name: String 548 | } 549 | 550 | input UserWhereInput { 551 | """Logical AND on all given filters.""" 552 | AND: [UserWhereInput!] 553 | 554 | """Logical OR on all given filters.""" 555 | OR: [UserWhereInput!] 556 | 557 | """Logical NOT on all given filters combined by AND.""" 558 | NOT: [UserWhereInput!] 559 | id: ID 560 | 561 | """All values that are not equal to given value.""" 562 | id_not: ID 563 | 564 | """All values that are contained in given list.""" 565 | id_in: [ID!] 566 | 567 | """All values that are not contained in given list.""" 568 | id_not_in: [ID!] 569 | 570 | """All values less than the given value.""" 571 | id_lt: ID 572 | 573 | """All values less than or equal the given value.""" 574 | id_lte: ID 575 | 576 | """All values greater than the given value.""" 577 | id_gt: ID 578 | 579 | """All values greater than or equal the given value.""" 580 | id_gte: ID 581 | 582 | """All values containing the given string.""" 583 | id_contains: ID 584 | 585 | """All values not containing the given string.""" 586 | id_not_contains: ID 587 | 588 | """All values starting with the given string.""" 589 | id_starts_with: ID 590 | 591 | """All values not starting with the given string.""" 592 | id_not_starts_with: ID 593 | 594 | """All values ending with the given string.""" 595 | id_ends_with: ID 596 | 597 | """All values not ending with the given string.""" 598 | id_not_ends_with: ID 599 | name: String 600 | 601 | """All values that are not equal to given value.""" 602 | name_not: String 603 | 604 | """All values that are contained in given list.""" 605 | name_in: [String!] 606 | 607 | """All values that are not contained in given list.""" 608 | name_not_in: [String!] 609 | 610 | """All values less than the given value.""" 611 | name_lt: String 612 | 613 | """All values less than or equal the given value.""" 614 | name_lte: String 615 | 616 | """All values greater than the given value.""" 617 | name_gt: String 618 | 619 | """All values greater than or equal the given value.""" 620 | name_gte: String 621 | 622 | """All values containing the given string.""" 623 | name_contains: String 624 | 625 | """All values not containing the given string.""" 626 | name_not_contains: String 627 | 628 | """All values starting with the given string.""" 629 | name_starts_with: String 630 | 631 | """All values not starting with the given string.""" 632 | name_not_starts_with: String 633 | 634 | """All values ending with the given string.""" 635 | name_ends_with: String 636 | 637 | """All values not ending with the given string.""" 638 | name_not_ends_with: String 639 | } 640 | 641 | input UserWhereUniqueInput { 642 | id: ID 643 | } 644 | -------------------------------------------------------------------------------- /examples/full-example/schema.js: -------------------------------------------------------------------------------- 1 | const { buildSchema } = require('graphql') 2 | const fs = require('fs') 3 | const {addMockFunctionsToSchema} = require('graphql-tools') 4 | 5 | const schema = buildSchema(fs.readFileSync(__dirname + '/schema.graphql', 'utf-8')) 6 | 7 | addMockFunctionsToSchema({schema}) 8 | 9 | module.exports = schema 10 | -------------------------------------------------------------------------------- /examples/full-example/server.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLServer } from 'graphql-yoga' 2 | import { Prisma } from './prisma' 3 | import { addFragmentToInfo } from 'graphql-binding' 4 | 5 | const binding = new Prisma({ 6 | endpoint: 'https://eu1.prisma.sh/lol/session65/dev', 7 | }) 8 | 9 | const resolvers = { 10 | Query: { 11 | users: async (parent, args, ctx, info) => { 12 | const result = await binding.query.users(args, addFragmentToInfo( 13 | info, 14 | 'fragment X on User {id}', 15 | ) as any) 16 | console.log(result) 17 | return result 18 | }, 19 | }, 20 | } 21 | 22 | const typeDefs = ` 23 | type Query { 24 | users: [User!]! 25 | } 26 | 27 | type User { 28 | id: ID! 29 | name: String! 30 | } 31 | ` 32 | 33 | const server = new GraphQLServer({ typeDefs, resolvers }) 34 | server.start(() => console.log('Server is running on localhost:4000')) 35 | -------------------------------------------------------------------------------- /examples/full-example/typings.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | // This file was automatically generated and should not be edited. 3 | 4 | export interface MyQueryQuery { 5 | users: Array< { 6 | id: string, 7 | } | null >, 8 | }; 9 | 10 | export interface CreateUserFragment { 11 | id: string, 12 | }; 13 | -------------------------------------------------------------------------------- /examples/minimal-example/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /examples/minimal-example/README.md: -------------------------------------------------------------------------------- 1 | # codegen example 2 | 3 | ## Usage 4 | ``` 5 | npm install -g graphql-binding 6 | 7 | # Typescript 8 | graphql-binding --input schema.ts --language typescript --outputBinding binding.ts 9 | 10 | # Javascript 11 | graphql-binding --input schema.ts --language javascript --outputBinding binding.js 12 | 13 | # Running the binding 14 | ts-node usage.ts 15 | ``` 16 | -------------------------------------------------------------------------------- /examples/minimal-example/binding.ts: -------------------------------------------------------------------------------- 1 | import { makeBindingClass, Options } from 'graphql-binding' 2 | import { GraphQLResolveInfo, GraphQLSchema } from 'graphql' 3 | import { IResolvers } from 'graphql-tools/dist/Interfaces' 4 | import schema from './schema' 5 | 6 | export interface Query { 7 | hello: (args?: {}, info?: GraphQLResolveInfo | string, options?: Options) => Promise 8 | } 9 | 10 | export interface Mutation {} 11 | 12 | export interface Subscription {} 13 | 14 | export interface Binding { 15 | query: Query 16 | mutation: Mutation 17 | subscription: Subscription 18 | request: (query: string, variables?: {[key: string]: any}) => Promise 19 | delegate(operation: 'query' | 'mutation', fieldName: string, args: { 20 | [key: string]: any; 21 | }, infoOrQuery?: GraphQLResolveInfo | string, options?: Options): Promise; 22 | delegateSubscription(fieldName: string, args?: { 23 | [key: string]: any; 24 | }, infoOrQuery?: GraphQLResolveInfo | string, options?: Options): Promise>; 25 | getAbstractResolvers(filterSchema?: GraphQLSchema | string): IResolvers; 26 | } 27 | 28 | export interface BindingConstructor { 29 | new(...args): T 30 | } 31 | 32 | export const Binding = makeBindingClass>({ schema }) 33 | 34 | /** 35 | * Types 36 | */ 37 | 38 | /* 39 | The `Boolean` scalar type represents `true` or `false`. 40 | */ 41 | export type Boolean = boolean 42 | 43 | /* 44 | The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text. 45 | */ 46 | export type String = string -------------------------------------------------------------------------------- /examples/minimal-example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "schema-test", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "dependencies": { 7 | "graphql": "^0.13.2", 8 | "graphql-binding": "^2.1.1", 9 | "graphql-tools": "^3.0.0", 10 | "prisma-binding": "^2.1.0" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /examples/minimal-example/prisma-usage.ts: -------------------------------------------------------------------------------- 1 | import { Prisma } from './prisma' 2 | 3 | const binding = new Prisma({ 4 | endpoint: 'https://eu1.prisma.sh/public-puddlerazor-shrieker-889738/hh/dev', 5 | }) 6 | 7 | async function run() { 8 | const result = await binding.query.users({}) 9 | console.log(result) 10 | } 11 | 12 | run().catch(e => console.error(e)) 13 | -------------------------------------------------------------------------------- /examples/minimal-example/prisma.graphql: -------------------------------------------------------------------------------- 1 | # source: https://eu1.prisma.sh/public-puddlerazor-shrieker-889738/hh/dev 2 | # timestamp: Wed May 02 2018 18:42:41 GMT+0200 (CEST) 3 | 4 | type AggregateUser { 5 | count: Int! 6 | } 7 | 8 | type BatchPayload { 9 | """The number of nodes that have been affected by the Batch operation.""" 10 | count: Long! 11 | } 12 | 13 | """ 14 | The `Long` scalar type represents non-fractional signed whole numeric values. 15 | Long can represent values between -(2^63) and 2^63 - 1. 16 | """ 17 | scalar Long 18 | 19 | type Mutation { 20 | createUser(data: UserCreateInput!): User! 21 | updateUser(data: UserUpdateInput!, where: UserWhereUniqueInput!): User 22 | deleteUser(where: UserWhereUniqueInput!): User 23 | upsertUser(where: UserWhereUniqueInput!, create: UserCreateInput!, update: UserUpdateInput!): User! 24 | updateManyUsers(data: UserUpdateInput!, where: UserWhereInput): BatchPayload! 25 | deleteManyUsers(where: UserWhereInput): BatchPayload! 26 | } 27 | 28 | enum MutationType { 29 | CREATED 30 | UPDATED 31 | DELETED 32 | } 33 | 34 | """An object with an ID""" 35 | interface Node { 36 | """The id of the object.""" 37 | id: ID! 38 | } 39 | 40 | """Information about pagination in a connection.""" 41 | type PageInfo { 42 | """When paginating forwards, are there more items?""" 43 | hasNextPage: Boolean! 44 | 45 | """When paginating backwards, are there more items?""" 46 | hasPreviousPage: Boolean! 47 | 48 | """When paginating backwards, the cursor to continue.""" 49 | startCursor: String 50 | 51 | """When paginating forwards, the cursor to continue.""" 52 | endCursor: String 53 | } 54 | 55 | type Query { 56 | users(where: UserWhereInput, orderBy: UserOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): [User]! 57 | user(where: UserWhereUniqueInput!): User 58 | usersConnection(where: UserWhereInput, orderBy: UserOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): UserConnection! 59 | 60 | """Fetches an object given its ID""" 61 | node( 62 | """The ID of an object""" 63 | id: ID! 64 | ): Node 65 | } 66 | 67 | type Subscription { 68 | user(where: UserSubscriptionWhereInput): UserSubscriptionPayload 69 | } 70 | 71 | type User implements Node { 72 | id: ID! 73 | name: String! 74 | email: String! 75 | } 76 | 77 | """A connection to a list of items.""" 78 | type UserConnection { 79 | """Information to aid in pagination.""" 80 | pageInfo: PageInfo! 81 | 82 | """A list of edges.""" 83 | edges: [UserEdge]! 84 | aggregate: AggregateUser! 85 | } 86 | 87 | input UserCreateInput { 88 | name: String! 89 | email: String! 90 | } 91 | 92 | """An edge in a connection.""" 93 | type UserEdge { 94 | """The item at the end of the edge.""" 95 | node: User! 96 | 97 | """A cursor for use in pagination.""" 98 | cursor: String! 99 | } 100 | 101 | enum UserOrderByInput { 102 | id_ASC 103 | id_DESC 104 | name_ASC 105 | name_DESC 106 | email_ASC 107 | email_DESC 108 | updatedAt_ASC 109 | updatedAt_DESC 110 | createdAt_ASC 111 | createdAt_DESC 112 | } 113 | 114 | type UserPreviousValues { 115 | id: ID! 116 | name: String! 117 | email: String! 118 | } 119 | 120 | type UserSubscriptionPayload { 121 | mutation: MutationType! 122 | node: User 123 | updatedFields: [String!] 124 | previousValues: UserPreviousValues 125 | } 126 | 127 | input UserSubscriptionWhereInput { 128 | """Logical AND on all given filters.""" 129 | AND: [UserSubscriptionWhereInput!] 130 | 131 | """Logical OR on all given filters.""" 132 | OR: [UserSubscriptionWhereInput!] 133 | 134 | """Logical NOT on all given filters combined by AND.""" 135 | NOT: [UserSubscriptionWhereInput!] 136 | 137 | """ 138 | The subscription event gets dispatched when it's listed in mutation_in 139 | """ 140 | mutation_in: [MutationType!] 141 | 142 | """ 143 | The subscription event gets only dispatched when one of the updated fields names is included in this list 144 | """ 145 | updatedFields_contains: String 146 | 147 | """ 148 | The subscription event gets only dispatched when all of the field names included in this list have been updated 149 | """ 150 | updatedFields_contains_every: [String!] 151 | 152 | """ 153 | The subscription event gets only dispatched when some of the field names included in this list have been updated 154 | """ 155 | updatedFields_contains_some: [String!] 156 | node: UserWhereInput 157 | } 158 | 159 | input UserUpdateInput { 160 | name: String 161 | email: String 162 | } 163 | 164 | input UserWhereInput { 165 | """Logical AND on all given filters.""" 166 | AND: [UserWhereInput!] 167 | 168 | """Logical OR on all given filters.""" 169 | OR: [UserWhereInput!] 170 | 171 | """Logical NOT on all given filters combined by AND.""" 172 | NOT: [UserWhereInput!] 173 | id: ID 174 | 175 | """All values that are not equal to given value.""" 176 | id_not: ID 177 | 178 | """All values that are contained in given list.""" 179 | id_in: [ID!] 180 | 181 | """All values that are not contained in given list.""" 182 | id_not_in: [ID!] 183 | 184 | """All values less than the given value.""" 185 | id_lt: ID 186 | 187 | """All values less than or equal the given value.""" 188 | id_lte: ID 189 | 190 | """All values greater than the given value.""" 191 | id_gt: ID 192 | 193 | """All values greater than or equal the given value.""" 194 | id_gte: ID 195 | 196 | """All values containing the given string.""" 197 | id_contains: ID 198 | 199 | """All values not containing the given string.""" 200 | id_not_contains: ID 201 | 202 | """All values starting with the given string.""" 203 | id_starts_with: ID 204 | 205 | """All values not starting with the given string.""" 206 | id_not_starts_with: ID 207 | 208 | """All values ending with the given string.""" 209 | id_ends_with: ID 210 | 211 | """All values not ending with the given string.""" 212 | id_not_ends_with: ID 213 | name: String 214 | 215 | """All values that are not equal to given value.""" 216 | name_not: String 217 | 218 | """All values that are contained in given list.""" 219 | name_in: [String!] 220 | 221 | """All values that are not contained in given list.""" 222 | name_not_in: [String!] 223 | 224 | """All values less than the given value.""" 225 | name_lt: String 226 | 227 | """All values less than or equal the given value.""" 228 | name_lte: String 229 | 230 | """All values greater than the given value.""" 231 | name_gt: String 232 | 233 | """All values greater than or equal the given value.""" 234 | name_gte: String 235 | 236 | """All values containing the given string.""" 237 | name_contains: String 238 | 239 | """All values not containing the given string.""" 240 | name_not_contains: String 241 | 242 | """All values starting with the given string.""" 243 | name_starts_with: String 244 | 245 | """All values not starting with the given string.""" 246 | name_not_starts_with: String 247 | 248 | """All values ending with the given string.""" 249 | name_ends_with: String 250 | 251 | """All values not ending with the given string.""" 252 | name_not_ends_with: String 253 | email: String 254 | 255 | """All values that are not equal to given value.""" 256 | email_not: String 257 | 258 | """All values that are contained in given list.""" 259 | email_in: [String!] 260 | 261 | """All values that are not contained in given list.""" 262 | email_not_in: [String!] 263 | 264 | """All values less than the given value.""" 265 | email_lt: String 266 | 267 | """All values less than or equal the given value.""" 268 | email_lte: String 269 | 270 | """All values greater than the given value.""" 271 | email_gt: String 272 | 273 | """All values greater than or equal the given value.""" 274 | email_gte: String 275 | 276 | """All values containing the given string.""" 277 | email_contains: String 278 | 279 | """All values not containing the given string.""" 280 | email_not_contains: String 281 | 282 | """All values starting with the given string.""" 283 | email_starts_with: String 284 | 285 | """All values not starting with the given string.""" 286 | email_not_starts_with: String 287 | 288 | """All values ending with the given string.""" 289 | email_ends_with: String 290 | 291 | """All values not ending with the given string.""" 292 | email_not_ends_with: String 293 | } 294 | 295 | input UserWhereUniqueInput { 296 | id: ID 297 | email: String 298 | } 299 | -------------------------------------------------------------------------------- /examples/minimal-example/prisma.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLResolveInfo } from 'graphql' 2 | import { makeBinding, BasePrismaOptions } from 'prisma-binding' 3 | 4 | interface BindingInstance { 5 | query: { 6 | users: (args: { where?: UserWhereInput, orderBy?: UserOrderByInput, skip?: Int, after?: String, before?: String, first?: Int, last?: Int }, info?: GraphQLResolveInfo | string, context?: { [key: string]: any }) => Promise , 7 | user: (args: { where: UserWhereUniqueInput }, info?: GraphQLResolveInfo | string, context?: { [key: string]: any }) => Promise , 8 | usersConnection: (args: { where?: UserWhereInput, orderBy?: UserOrderByInput, skip?: Int, after?: String, before?: String, first?: Int, last?: Int }, info?: GraphQLResolveInfo | string, context?: { [key: string]: any }) => Promise , 9 | node: (args: { id: ID_Output }, info?: GraphQLResolveInfo | string, context?: { [key: string]: any }) => Promise 10 | } 11 | mutation: { 12 | createUser: (args: { data: UserCreateInput }, info?: GraphQLResolveInfo | string, context?: { [key: string]: any }) => Promise , 13 | updateUser: (args: { data: UserUpdateInput, where: UserWhereUniqueInput }, info?: GraphQLResolveInfo | string, context?: { [key: string]: any }) => Promise , 14 | deleteUser: (args: { where: UserWhereUniqueInput }, info?: GraphQLResolveInfo | string, context?: { [key: string]: any }) => Promise , 15 | upsertUser: (args: { where: UserWhereUniqueInput, create: UserCreateInput, update: UserUpdateInput }, info?: GraphQLResolveInfo | string, context?: { [key: string]: any }) => Promise , 16 | updateManyUsers: (args: { data: UserUpdateInput, where?: UserWhereInput }, info?: GraphQLResolveInfo | string, context?: { [key: string]: any }) => Promise , 17 | deleteManyUsers: (args: { where?: UserWhereInput }, info?: GraphQLResolveInfo | string, context?: { [key: string]: any }) => Promise 18 | } 19 | subscription: { 20 | user: (args: { where?: UserSubscriptionWhereInput }, info?: GraphQLResolveInfo | string, context?: { [key: string]: any }) => Promise 21 | } 22 | } 23 | 24 | interface BindingConstructor { 25 | new(options: BasePrismaOptions): T 26 | } 27 | /** 28 | * Type Defs 29 | */ 30 | 31 | const typeDefs = `type AggregateUser { 32 | count: Int! 33 | } 34 | 35 | type BatchPayload { 36 | """The number of nodes that have been affected by the Batch operation.""" 37 | count: Long! 38 | } 39 | 40 | """ 41 | The \`Long\` scalar type represents non-fractional signed whole numeric values. 42 | Long can represent values between -(2^63) and 2^63 - 1. 43 | """ 44 | scalar Long 45 | 46 | type Mutation { 47 | createUser(data: UserCreateInput!): User! 48 | updateUser(data: UserUpdateInput!, where: UserWhereUniqueInput!): User 49 | deleteUser(where: UserWhereUniqueInput!): User 50 | upsertUser(where: UserWhereUniqueInput!, create: UserCreateInput!, update: UserUpdateInput!): User! 51 | updateManyUsers(data: UserUpdateInput!, where: UserWhereInput): BatchPayload! 52 | deleteManyUsers(where: UserWhereInput): BatchPayload! 53 | } 54 | 55 | enum MutationType { 56 | CREATED 57 | UPDATED 58 | DELETED 59 | } 60 | 61 | """An object with an ID""" 62 | interface Node { 63 | """The id of the object.""" 64 | id: ID! 65 | } 66 | 67 | """Information about pagination in a connection.""" 68 | type PageInfo { 69 | """When paginating forwards, are there more items?""" 70 | hasNextPage: Boolean! 71 | 72 | """When paginating backwards, are there more items?""" 73 | hasPreviousPage: Boolean! 74 | 75 | """When paginating backwards, the cursor to continue.""" 76 | startCursor: String 77 | 78 | """When paginating forwards, the cursor to continue.""" 79 | endCursor: String 80 | } 81 | 82 | type Query { 83 | users(where: UserWhereInput, orderBy: UserOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): [User]! 84 | user(where: UserWhereUniqueInput!): User 85 | usersConnection(where: UserWhereInput, orderBy: UserOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): UserConnection! 86 | 87 | """Fetches an object given its ID""" 88 | node( 89 | """The ID of an object""" 90 | id: ID! 91 | ): Node 92 | } 93 | 94 | type Subscription { 95 | user(where: UserSubscriptionWhereInput): UserSubscriptionPayload 96 | } 97 | 98 | type User implements Node { 99 | id: ID! 100 | name: String! 101 | email: String! 102 | } 103 | 104 | """A connection to a list of items.""" 105 | type UserConnection { 106 | """Information to aid in pagination.""" 107 | pageInfo: PageInfo! 108 | 109 | """A list of edges.""" 110 | edges: [UserEdge]! 111 | aggregate: AggregateUser! 112 | } 113 | 114 | input UserCreateInput { 115 | name: String! 116 | email: String! 117 | } 118 | 119 | """An edge in a connection.""" 120 | type UserEdge { 121 | """The item at the end of the edge.""" 122 | node: User! 123 | 124 | """A cursor for use in pagination.""" 125 | cursor: String! 126 | } 127 | 128 | enum UserOrderByInput { 129 | id_ASC 130 | id_DESC 131 | name_ASC 132 | name_DESC 133 | email_ASC 134 | email_DESC 135 | updatedAt_ASC 136 | updatedAt_DESC 137 | createdAt_ASC 138 | createdAt_DESC 139 | } 140 | 141 | type UserPreviousValues { 142 | id: ID! 143 | name: String! 144 | email: String! 145 | } 146 | 147 | type UserSubscriptionPayload { 148 | mutation: MutationType! 149 | node: User 150 | updatedFields: [String!] 151 | previousValues: UserPreviousValues 152 | } 153 | 154 | input UserSubscriptionWhereInput { 155 | """Logical AND on all given filters.""" 156 | AND: [UserSubscriptionWhereInput!] 157 | 158 | """Logical OR on all given filters.""" 159 | OR: [UserSubscriptionWhereInput!] 160 | 161 | """Logical NOT on all given filters combined by AND.""" 162 | NOT: [UserSubscriptionWhereInput!] 163 | 164 | """ 165 | The subscription event gets dispatched when it's listed in mutation_in 166 | """ 167 | mutation_in: [MutationType!] 168 | 169 | """ 170 | The subscription event gets only dispatched when one of the updated fields names is included in this list 171 | """ 172 | updatedFields_contains: String 173 | 174 | """ 175 | The subscription event gets only dispatched when all of the field names included in this list have been updated 176 | """ 177 | updatedFields_contains_every: [String!] 178 | 179 | """ 180 | The subscription event gets only dispatched when some of the field names included in this list have been updated 181 | """ 182 | updatedFields_contains_some: [String!] 183 | node: UserWhereInput 184 | } 185 | 186 | input UserUpdateInput { 187 | name: String 188 | email: String 189 | } 190 | 191 | input UserWhereInput { 192 | """Logical AND on all given filters.""" 193 | AND: [UserWhereInput!] 194 | 195 | """Logical OR on all given filters.""" 196 | OR: [UserWhereInput!] 197 | 198 | """Logical NOT on all given filters combined by AND.""" 199 | NOT: [UserWhereInput!] 200 | id: ID 201 | 202 | """All values that are not equal to given value.""" 203 | id_not: ID 204 | 205 | """All values that are contained in given list.""" 206 | id_in: [ID!] 207 | 208 | """All values that are not contained in given list.""" 209 | id_not_in: [ID!] 210 | 211 | """All values less than the given value.""" 212 | id_lt: ID 213 | 214 | """All values less than or equal the given value.""" 215 | id_lte: ID 216 | 217 | """All values greater than the given value.""" 218 | id_gt: ID 219 | 220 | """All values greater than or equal the given value.""" 221 | id_gte: ID 222 | 223 | """All values containing the given string.""" 224 | id_contains: ID 225 | 226 | """All values not containing the given string.""" 227 | id_not_contains: ID 228 | 229 | """All values starting with the given string.""" 230 | id_starts_with: ID 231 | 232 | """All values not starting with the given string.""" 233 | id_not_starts_with: ID 234 | 235 | """All values ending with the given string.""" 236 | id_ends_with: ID 237 | 238 | """All values not ending with the given string.""" 239 | id_not_ends_with: ID 240 | name: String 241 | 242 | """All values that are not equal to given value.""" 243 | name_not: String 244 | 245 | """All values that are contained in given list.""" 246 | name_in: [String!] 247 | 248 | """All values that are not contained in given list.""" 249 | name_not_in: [String!] 250 | 251 | """All values less than the given value.""" 252 | name_lt: String 253 | 254 | """All values less than or equal the given value.""" 255 | name_lte: String 256 | 257 | """All values greater than the given value.""" 258 | name_gt: String 259 | 260 | """All values greater than or equal the given value.""" 261 | name_gte: String 262 | 263 | """All values containing the given string.""" 264 | name_contains: String 265 | 266 | """All values not containing the given string.""" 267 | name_not_contains: String 268 | 269 | """All values starting with the given string.""" 270 | name_starts_with: String 271 | 272 | """All values not starting with the given string.""" 273 | name_not_starts_with: String 274 | 275 | """All values ending with the given string.""" 276 | name_ends_with: String 277 | 278 | """All values not ending with the given string.""" 279 | name_not_ends_with: String 280 | email: String 281 | 282 | """All values that are not equal to given value.""" 283 | email_not: String 284 | 285 | """All values that are contained in given list.""" 286 | email_in: [String!] 287 | 288 | """All values that are not contained in given list.""" 289 | email_not_in: [String!] 290 | 291 | """All values less than the given value.""" 292 | email_lt: String 293 | 294 | """All values less than or equal the given value.""" 295 | email_lte: String 296 | 297 | """All values greater than the given value.""" 298 | email_gt: String 299 | 300 | """All values greater than or equal the given value.""" 301 | email_gte: String 302 | 303 | """All values containing the given string.""" 304 | email_contains: String 305 | 306 | """All values not containing the given string.""" 307 | email_not_contains: String 308 | 309 | """All values starting with the given string.""" 310 | email_starts_with: String 311 | 312 | """All values not starting with the given string.""" 313 | email_not_starts_with: String 314 | 315 | """All values ending with the given string.""" 316 | email_ends_with: String 317 | 318 | """All values not ending with the given string.""" 319 | email_not_ends_with: String 320 | } 321 | 322 | input UserWhereUniqueInput { 323 | id: ID 324 | email: String 325 | } 326 | ` 327 | 328 | export const Prisma = makeBinding>(typeDefs) 329 | 330 | /** 331 | * Types 332 | */ 333 | 334 | export type UserOrderByInput = 'id_ASC' | 335 | 'id_DESC' | 336 | 'name_ASC' | 337 | 'name_DESC' | 338 | 'email_ASC' | 339 | 'email_DESC' | 340 | 'updatedAt_ASC' | 341 | 'updatedAt_DESC' | 342 | 'createdAt_ASC' | 343 | 'createdAt_DESC' 344 | 345 | export type MutationType = 'CREATED' | 346 | 'UPDATED' | 347 | 'DELETED' 348 | 349 | export interface UserCreateInput { 350 | name: String 351 | email: String 352 | } 353 | 354 | export interface UserWhereUniqueInput { 355 | id?: ID_Input 356 | email?: String 357 | } 358 | 359 | export interface UserUpdateInput { 360 | name?: String 361 | email?: String 362 | } 363 | 364 | export interface UserSubscriptionWhereInput { 365 | AND?: UserSubscriptionWhereInput[] | UserSubscriptionWhereInput 366 | OR?: UserSubscriptionWhereInput[] | UserSubscriptionWhereInput 367 | NOT?: UserSubscriptionWhereInput[] | UserSubscriptionWhereInput 368 | mutation_in?: MutationType[] | MutationType 369 | updatedFields_contains?: String 370 | updatedFields_contains_every?: String[] | String 371 | updatedFields_contains_some?: String[] | String 372 | node?: UserWhereInput 373 | } 374 | 375 | export interface UserWhereInput { 376 | AND?: UserWhereInput[] | UserWhereInput 377 | OR?: UserWhereInput[] | UserWhereInput 378 | NOT?: UserWhereInput[] | UserWhereInput 379 | id?: ID_Input 380 | id_not?: ID_Input 381 | id_in?: ID_Input[] | ID_Input 382 | id_not_in?: ID_Input[] | ID_Input 383 | id_lt?: ID_Input 384 | id_lte?: ID_Input 385 | id_gt?: ID_Input 386 | id_gte?: ID_Input 387 | id_contains?: ID_Input 388 | id_not_contains?: ID_Input 389 | id_starts_with?: ID_Input 390 | id_not_starts_with?: ID_Input 391 | id_ends_with?: ID_Input 392 | id_not_ends_with?: ID_Input 393 | name?: String 394 | name_not?: String 395 | name_in?: String[] | String 396 | name_not_in?: String[] | String 397 | name_lt?: String 398 | name_lte?: String 399 | name_gt?: String 400 | name_gte?: String 401 | name_contains?: String 402 | name_not_contains?: String 403 | name_starts_with?: String 404 | name_not_starts_with?: String 405 | name_ends_with?: String 406 | name_not_ends_with?: String 407 | email?: String 408 | email_not?: String 409 | email_in?: String[] | String 410 | email_not_in?: String[] | String 411 | email_lt?: String 412 | email_lte?: String 413 | email_gt?: String 414 | email_gte?: String 415 | email_contains?: String 416 | email_not_contains?: String 417 | email_starts_with?: String 418 | email_not_starts_with?: String 419 | email_ends_with?: String 420 | email_not_ends_with?: String 421 | } 422 | 423 | /* 424 | * An object with an ID 425 | 426 | */ 427 | export interface Node { 428 | id: ID_Output 429 | } 430 | 431 | export interface UserPreviousValues { 432 | id: ID_Output 433 | name: String 434 | email: String 435 | } 436 | 437 | export interface BatchPayload { 438 | count: Long 439 | } 440 | 441 | /* 442 | * Information about pagination in a connection. 443 | 444 | */ 445 | export interface PageInfo { 446 | hasNextPage: Boolean 447 | hasPreviousPage: Boolean 448 | startCursor?: String 449 | endCursor?: String 450 | } 451 | 452 | export interface UserSubscriptionPayload { 453 | mutation: MutationType 454 | node?: User 455 | updatedFields?: String[] 456 | previousValues?: UserPreviousValues 457 | } 458 | 459 | export interface User extends Node { 460 | id: ID_Output 461 | name: String 462 | email: String 463 | } 464 | 465 | /* 466 | * A connection to a list of items. 467 | 468 | */ 469 | export interface UserConnection { 470 | pageInfo: PageInfo 471 | edges: UserEdge[] 472 | aggregate: AggregateUser 473 | } 474 | 475 | export interface AggregateUser { 476 | count: Int 477 | } 478 | 479 | /* 480 | * An edge in a connection. 481 | 482 | */ 483 | export interface UserEdge { 484 | node: User 485 | cursor: String 486 | } 487 | 488 | /* 489 | The `Boolean` scalar type represents `true` or `false`. 490 | */ 491 | export type Boolean = boolean 492 | 493 | /* 494 | The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text. 495 | */ 496 | export type String = string 497 | 498 | /* 499 | The `Int` scalar type represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1. 500 | */ 501 | export type Int = number 502 | 503 | /* 504 | The `Long` scalar type represents non-fractional signed whole numeric values. 505 | Long can represent values between -(2^63) and 2^63 - 1. 506 | */ 507 | export type Long = string 508 | 509 | /* 510 | The `ID` scalar type represents a unique identifier, often used to refetch an object or as key for a cache. The ID type appears in a JSON response as a String; however, it is not intended to be human-readable. When expected as an input type, any string (such as `"4"`) or integer (such as `4`) input value will be accepted as an ID. 511 | */ 512 | export type ID_Input = string | number 513 | export type ID_Output = string -------------------------------------------------------------------------------- /examples/minimal-example/schema.ts: -------------------------------------------------------------------------------- 1 | import {makeExecutableSchema} from 'graphql-tools' 2 | 3 | const schema = makeExecutableSchema({ 4 | typeDefs: ` 5 | type Query { 6 | hello: String 7 | } 8 | `, 9 | resolvers: { 10 | Query: { 11 | hello: () => 'world' 12 | } 13 | } 14 | }) 15 | 16 | export default schema 17 | -------------------------------------------------------------------------------- /examples/minimal-example/usage.ts: -------------------------------------------------------------------------------- 1 | import { Binding } from './binding' 2 | 3 | const binding = new Binding() 4 | 5 | async function run() { 6 | const result = await binding.query.hello() 7 | console.log(result) 8 | } 9 | 10 | run().catch(e => console.error(e)) 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "graphql-binding", 3 | "version": "0.0.0-semantic-release", 4 | "license": "MIT", 5 | "repository": "git@github.com:graphcool/graphql-binding.git", 6 | "author": "Johannes Schickling ", 7 | "contributors": [ 8 | "Kim Brandwijk ", 9 | "Tim Suchanek ", 10 | "Marcin Pacholec ", 11 | "Matic Zavadlal " 12 | ], 13 | "bin": { 14 | "graphql-binding": "./dist/bin.js" 15 | }, 16 | "files": [ 17 | "dist" 18 | ], 19 | "main": "dist/index.js", 20 | "types": "dist/index.d.ts", 21 | "scripts": { 22 | "prepublish": "npm-run-all build", 23 | "clean": "rimraf dist", 24 | "compile": "tsc -d", 25 | "build": "npm-run-all clean compile", 26 | "pretest": "npm-run-all build", 27 | "test-lint": "tslint --project tsconfig.json {src}/**/*.ts && prettier-check --ignore-path .gitignore src/**/*.ts", 28 | "test-ava": "ava", 29 | "test": "npm-run-all test-*" 30 | }, 31 | "dependencies": { 32 | "graphql": "^14.2.0", 33 | "graphql-import": "^0.7.1", 34 | "graphql-tools-fork": "^8.1.0", 35 | "iterall": "1.2.2", 36 | "object-path-immutable": "^3.0.0", 37 | "resolve-cwd": "^2.0.0", 38 | "ts-node": "^7.0.1", 39 | "yargs": "^12.0.2" 40 | }, 41 | "devDependencies": { 42 | "@types/mkdirp": "0.5.2", 43 | "@types/node": "10.14.17", 44 | "@types/yargs": "12.0.12", 45 | "apollo-link": "1.2.13", 46 | "ava": "1.4.1", 47 | "graphql-tag": "2.10.1", 48 | "husky": "1.3.1", 49 | "npm-run-all": "4.1.5", 50 | "prettier": "1.15.3", 51 | "prettier-check": "2.0.0", 52 | "pretty-quick": "1.11.1", 53 | "rimraf": "2.7.1", 54 | "semantic-release": "15.13.24", 55 | "tslint": "5.20.0", 56 | "tslint-config-prettier": "1.18.0", 57 | "tslint-config-standard": "8.0.1", 58 | "typescript": "3.6.2" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | semi: false, 3 | singleQuote: true, 4 | trailingComma: 'all', 5 | } 6 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["config:base", "docker:disable"], 3 | "automerge": true, 4 | "major": { 5 | "automerge": false 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/Binding.ts: -------------------------------------------------------------------------------- 1 | import { 2 | QueryMap, 3 | MutationMap, 4 | BindingOptions, 5 | SubscriptionMap /*, Operation*/, 6 | QueryOrMutation, 7 | } from './types' 8 | import { Delegate } from './Delegate' 9 | 10 | // to avoid recreation on each instantiation for the same schema, we cache the created methods 11 | const methodCache = new Map() 12 | 13 | export class Binding extends Delegate { 14 | query: QueryMap 15 | mutation: MutationMap 16 | subscription: SubscriptionMap 17 | 18 | constructor({ 19 | schema, 20 | fragmentReplacements, 21 | before, 22 | disableCache, 23 | }: BindingOptions) { 24 | super({ schema, fragmentReplacements, before, disableCache }) 25 | 26 | const { query, mutation, subscription } = this.buildMethods() 27 | this.query = query 28 | this.mutation = mutation 29 | this.subscription = subscription 30 | } 31 | 32 | buildMethods() { 33 | const cachedMethods = methodCache.get(this.schema) 34 | if (cachedMethods) { 35 | return cachedMethods 36 | } 37 | const methods = { 38 | query: this.buildQueryMethods('query'), 39 | mutation: this.buildQueryMethods('mutation'), 40 | subscription: this.buildSubscriptionMethods(), 41 | } 42 | if (!this.disableCache) { 43 | methodCache.set(this.schema, methods) 44 | } 45 | return methods 46 | } 47 | 48 | buildQueryMethods(operation: QueryOrMutation): QueryMap { 49 | const queryType = 50 | operation === 'query' 51 | ? this.schema.getQueryType() 52 | : this.schema.getMutationType() 53 | if (!queryType) { 54 | return {} 55 | } 56 | const fields = queryType.getFields() 57 | return Object.entries(fields) 58 | .map(([fieldName, field]) => { 59 | return { 60 | key: fieldName, 61 | value: (args, info, options) => { 62 | return this.delegate(operation, fieldName, args, info, options) 63 | }, 64 | } 65 | }) 66 | .reduce((acc, curr) => ({ ...acc, [curr.key]: curr.value }), {}) 67 | } 68 | 69 | buildSubscriptionMethods(): SubscriptionMap { 70 | const subscriptionType = this.schema.getSubscriptionType() 71 | if (!subscriptionType) { 72 | return {} 73 | } 74 | const fields = subscriptionType.getFields() 75 | return Object.entries(fields) 76 | .map(([fieldName, field]) => { 77 | return { 78 | key: fieldName, 79 | value: (args, info, options) => { 80 | return this.delegateSubscription(fieldName, args, info, options) 81 | }, 82 | } 83 | }) 84 | .reduce((acc, curr) => ({ ...acc, [curr.key]: curr.value }), {}) 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/Delegate.ts: -------------------------------------------------------------------------------- 1 | import { $$asyncIterator } from 'iterall' 2 | import { buildInfo } from './info' 3 | import { 4 | GraphQLResolveInfo, 5 | graphql, 6 | GraphQLSchema, 7 | buildSchema, 8 | GraphQLUnionType, 9 | GraphQLInterfaceType, 10 | } from 'graphql' 11 | import { 12 | delegateToSchema, 13 | ReplaceFieldWithFragment, 14 | IResolvers, 15 | } from 'graphql-tools-fork' 16 | import { 17 | BindingOptions, 18 | Options, 19 | QueryOrMutation, 20 | Operation, 21 | FragmentReplacement, 22 | } from './types' 23 | import { importSchema } from 'graphql-import' 24 | import * as fs from 'fs' 25 | import * as path from 'path' 26 | 27 | export class Delegate { 28 | schema: GraphQLSchema 29 | before: () => void 30 | disableCache: boolean 31 | 32 | private fragmentReplacements: FragmentReplacement[] 33 | 34 | constructor({ 35 | schema, 36 | fragmentReplacements, 37 | before, 38 | disableCache, 39 | }: BindingOptions) { 40 | this.fragmentReplacements = fragmentReplacements || [] 41 | this.schema = schema 42 | this.disableCache = disableCache || false 43 | 44 | this.before = before || (() => undefined) 45 | } 46 | 47 | public async request( 48 | query: string, 49 | variables?: { [key: string]: any }, 50 | ): Promise { 51 | this.before() 52 | return graphql(this.schema, query, null, null, variables).then( 53 | r => r as any, 54 | ) 55 | } 56 | 57 | public async delegate( 58 | operation: QueryOrMutation, 59 | fieldName: string, 60 | args: { 61 | [key: string]: any 62 | }, 63 | infoOrQuery?: GraphQLResolveInfo | string, 64 | options?: Options, 65 | ) { 66 | this.before() 67 | 68 | return this.delegateToSchema( 69 | operation, 70 | fieldName, 71 | args, 72 | infoOrQuery, 73 | options, 74 | ).result 75 | } 76 | 77 | public async delegateSubscription( 78 | fieldName: string, 79 | args?: { [key: string]: any }, 80 | infoOrQuery?: GraphQLResolveInfo | string, 81 | options?: Options, 82 | ): Promise> { 83 | this.before() 84 | 85 | const { result, info } = this.delegateToSchema( 86 | 'subscription', 87 | fieldName, 88 | args, 89 | infoOrQuery, 90 | options, 91 | ) 92 | 93 | const iterator = await result 94 | 95 | return { 96 | async next() { 97 | const { value } = await iterator.next() 98 | const data = { [info.fieldName]: value[fieldName] } 99 | return { value: data, done: false } 100 | }, 101 | return() { 102 | iterator.return() 103 | return Promise.resolve({ value: undefined, done: true }) 104 | }, 105 | throw(error) { 106 | return Promise.reject(error) 107 | }, 108 | [$$asyncIterator]() { 109 | return this 110 | }, 111 | } 112 | } 113 | 114 | public getAbstractResolvers( 115 | filterSchema?: GraphQLSchema | string, 116 | ): IResolvers { 117 | const typeMap = this.schema.getTypeMap() 118 | 119 | if (filterSchema && typeof filterSchema === 'string') { 120 | if (filterSchema.endsWith('graphql')) { 121 | const schemaPath = path.resolve(filterSchema) 122 | 123 | if (!fs.existsSync(schemaPath)) { 124 | throw new Error(`No schema found for path: ${schemaPath}`) 125 | } 126 | 127 | filterSchema = importSchema(schemaPath) 128 | } 129 | filterSchema = buildSchema(filterSchema) 130 | } 131 | const filterTypeMap = 132 | filterSchema instanceof GraphQLSchema 133 | ? filterSchema.getTypeMap() 134 | : typeMap 135 | const filterType = typeName => typeName in filterTypeMap 136 | 137 | const resolvers = {} 138 | Object.keys(typeMap) 139 | .filter(filterType) 140 | .forEach(typeName => { 141 | const type = typeMap[typeName] 142 | if ( 143 | type instanceof GraphQLUnionType || 144 | type instanceof GraphQLInterfaceType 145 | ) { 146 | resolvers[typeName] = { 147 | __resolveType: type.resolveType, 148 | } 149 | } 150 | }) 151 | 152 | return resolvers 153 | } 154 | 155 | private delegateToSchema( 156 | operation: Operation, 157 | fieldName: string, 158 | args?: { [key: string]: any }, 159 | infoOrQuery?: GraphQLResolveInfo | string, 160 | options?: Options, 161 | ): { info: any; result: Promise } { 162 | const info = buildInfo(fieldName, operation, this.schema, infoOrQuery) 163 | 164 | const transforms = options && options.transforms ? options.transforms : [] 165 | const result = delegateToSchema({ 166 | schema: this.schema, 167 | operation, 168 | fieldName, 169 | args: args || {}, 170 | context: options && options.context ? options.context : {}, 171 | info, 172 | transforms: [ 173 | ...transforms, 174 | new ReplaceFieldWithFragment(this.schema, this.fragmentReplacements), 175 | ], 176 | }) 177 | 178 | return { info, result } 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /src/bin.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import * as fs from 'fs' 4 | import * as yargs from 'yargs' 5 | import mkdirp from 'mkdirp' 6 | import * as path from 'path' 7 | import { Generator } from './codegen/Generator' 8 | import { TypescriptGenerator } from './codegen/TypescriptGenerator' 9 | import { buildSchema, printSchema } from 'graphql' 10 | 11 | const argv = yargs 12 | .usage( 13 | `Usage: $0 -i [input] -l [language] -b [outputBinding] -t [outputTypedefs]`, 14 | ) 15 | .options({ 16 | input: { 17 | alias: 'i', 18 | describe: 'Path to schema.js or schema.ts file', 19 | type: 'string', 20 | }, 21 | language: { 22 | alias: 'l', 23 | describe: 24 | 'Type of the generator. Available languages: typescript, javascript', 25 | type: 'string', 26 | }, 27 | outputBinding: { 28 | alias: 'b', 29 | describe: 'Output binding. Example: binding.ts', 30 | type: 'string', 31 | }, 32 | outputTypedefs: { 33 | alias: 't', 34 | describe: 'Output type defs. Example: typeDefs.graphql', 35 | type: 'string', 36 | }, 37 | }) 38 | .demandOption(['i', 'l', 'b']).argv 39 | 40 | run(argv).catch(e => console.error(e)) 41 | 42 | async function run(argv) { 43 | const { input, language, outputBinding, outputTypedefs } = argv 44 | 45 | const schema = getSchemaFromInput(input) 46 | const args = { 47 | ...schema, 48 | inputSchemaPath: path.resolve(input), 49 | outputBindingPath: path.resolve(outputBinding), 50 | } 51 | if (language === 'typescript') { 52 | require('ts-node').register() 53 | } 54 | const generatorInstance = 55 | language === 'typescript' 56 | ? new TypescriptGenerator(args) 57 | : new Generator(args) 58 | const code = generatorInstance.render() 59 | 60 | mkdirp.sync(path.dirname(outputBinding)) 61 | fs.writeFileSync(outputBinding, code) 62 | 63 | if (outputTypedefs) { 64 | mkdirp.sync(path.dirname(outputTypedefs)) 65 | fs.writeFileSync(outputTypedefs, printSchema(schema.schema)) 66 | } 67 | 68 | console.log('Done generating binding') 69 | } 70 | 71 | function getSchemaFromInput(input) { 72 | if (input.endsWith('.graphql') || input.endsWith('.gql')) { 73 | const sdl = fs.readFileSync(path.resolve(input), 'utf-8') 74 | const schema = buildSchema(sdl) 75 | return { 76 | isDefaultExport: false, 77 | schema, 78 | } 79 | } 80 | 81 | if (input.endsWith('.js') || input.endsWith('.ts')) { 82 | if (input.endsWith('.ts')) { 83 | require('ts-node').register() 84 | } 85 | const schema = require(path.resolve(input)) 86 | if (schema.default) { 87 | return { 88 | isDefaultExport: true, 89 | schema: schema.default, 90 | } 91 | } 92 | return { 93 | isDefaultExport: false, 94 | schema, 95 | } 96 | } 97 | 98 | throw new Error(`Can't handle schema ${input}`) 99 | } 100 | -------------------------------------------------------------------------------- /src/codegen/FlowGenerator.test.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs' 2 | import * as path from 'path' 3 | import { buildSchema } from 'graphql' 4 | import { FlowGenerator } from './FlowGenerator' 5 | import test from 'ava' 6 | 7 | const typeDefs = fs.readFileSync( 8 | path.join(__dirname, '../../src/codegen/fixtures/schema.graphql'), 9 | 'utf-8', 10 | ) 11 | test('flow generator', t => { 12 | const schema = buildSchema(typeDefs) 13 | const generator = new FlowGenerator({ 14 | schema, 15 | inputSchemaPath: 'src/schema.js', 16 | outputBindingPath: 'src/generated/binding.js', 17 | isDefaultExport: false, 18 | }) 19 | const result = generator.render() 20 | t.snapshot(result) 21 | }) 22 | -------------------------------------------------------------------------------- /src/codegen/FlowGenerator.test.ts.md: -------------------------------------------------------------------------------- 1 | # Snapshot report for `src/codegen/FlowGenerator.test.ts` 2 | 3 | The actual snapshot is saved in `FlowGenerator.test.ts.snap`. 4 | 5 | Generated by [AVA](https://ava.li). 6 | 7 | ## flow generator 8 | 9 | > Snapshot 1 10 | 11 | `// @flow␊ 12 | import { makeBindingClass, Options } from 'graphql-binding'␊ 13 | import { GraphQLResolveInfo, GraphQLSchema } from 'graphql'␊ 14 | import { IResolvers } from 'graphql-tools-fork'␊ 15 | import * as schema from '../schema'␊ 16 | ␊ 17 | export interface Query {␊ 18 | posts(args: { where?: PostWhereInput, orderBy?: PostOrderByInput, skip?: Int, after?: String, before?: String, first?: Int, last?: Int }, info?: GraphQLResolveInfo | string, options?: Options): Promise; ␊ 19 | users(args: { where?: UserWhereInput, orderBy?: UserOrderByInput, skip?: Int, after?: String, before?: String, first?: Int, last?: Int }, info?: GraphQLResolveInfo | string, options?: Options): Promise; ␊ 20 | post(args: { where: PostWhereUniqueInput }, info?: GraphQLResolveInfo | string, options?: Options): Promise; ␊ 21 | user(args: { where: UserWhereUniqueInput }, info?: GraphQLResolveInfo | string, options?: Options): Promise; ␊ 22 | postsConnection(args: { where?: PostWhereInput, orderBy?: PostOrderByInput, skip?: Int, after?: String, before?: String, first?: Int, last?: Int }, info?: GraphQLResolveInfo | string, options?: Options): Promise; ␊ 23 | usersConnection(args: { where?: UserWhereInput, orderBy?: UserOrderByInput, skip?: Int, after?: String, before?: String, first?: Int, last?: Int }, info?: GraphQLResolveInfo | string, options?: Options): Promise; ␊ 24 | node(args: { id: ID_Output }, info?: GraphQLResolveInfo | string, options?: Options): Promise; ␊ 25 | }␊ 26 | ␊ 27 | export interface Mutation {␊ 28 | createPost(args: { data: PostCreateInput }, info?: GraphQLResolveInfo | string, options?: Options): Promise; ␊ 29 | createUser(args: { data: UserCreateInput }, info?: GraphQLResolveInfo | string, options?: Options): Promise; ␊ 30 | updatePost(args: { data: PostUpdateInput, where: PostWhereUniqueInput }, info?: GraphQLResolveInfo | string, options?: Options): Promise; ␊ 31 | updateUser(args: { data: UserUpdateInput, where: UserWhereUniqueInput }, info?: GraphQLResolveInfo | string, options?: Options): Promise; ␊ 32 | deletePost(args: { where: PostWhereUniqueInput }, info?: GraphQLResolveInfo | string, options?: Options): Promise; ␊ 33 | deleteUser(args: { where: UserWhereUniqueInput }, info?: GraphQLResolveInfo | string, options?: Options): Promise; ␊ 34 | upsertPost(args: { where: PostWhereUniqueInput, create: PostCreateInput, update: PostUpdateInput }, info?: GraphQLResolveInfo | string, options?: Options): Promise; ␊ 35 | upsertUser(args: { where: UserWhereUniqueInput, create: UserCreateInput, update: UserUpdateInput }, info?: GraphQLResolveInfo | string, options?: Options): Promise; ␊ 36 | updateManyPosts(args: { data: PostUpdateInput, where: PostWhereInput }, info?: GraphQLResolveInfo | string, options?: Options): Promise; ␊ 37 | updateManyUsers(args: { data: UserUpdateInput, where: UserWhereInput }, info?: GraphQLResolveInfo | string, options?: Options): Promise; ␊ 38 | deleteManyPosts(args: { where: PostWhereInput }, info?: GraphQLResolveInfo | string, options?: Options): Promise; ␊ 39 | deleteManyUsers(args: { where: UserWhereInput }, info?: GraphQLResolveInfo | string, options?: Options): Promise; ␊ 40 | }␊ 41 | ␊ 42 | export interface Subscription {␊ 43 | post(args: { where?: PostSubscriptionWhereInput }, info?: GraphQLResolveInfo | string, options?: Options): Promise>; ␊ 44 | user(args: { where?: UserSubscriptionWhereInput }, info?: GraphQLResolveInfo | string, options?: Options): Promise>; ␊ 45 | }␊ 46 | ␊ 47 | export interface Binding {␊ 48 | query: Query;␊ 49 | mutation: Mutation;␊ 50 | subscription: Subscription;␊ 51 | request(query: string, variables?: {[key: string]: any}): Promise;␊ 52 | delegate(operation: 'query' | 'mutation', fieldName: string, args: {␊ 53 | [key: string]: any;␊ 54 | }, infoOrQuery?: GraphQLResolveInfo | string, options?: Options): Promise;␊ 55 | delegateSubscription(fieldName: string, args?: {␊ 56 | [key: string]: any;␊ 57 | }, infoOrQuery?: GraphQLResolveInfo | string, options?: Options): Promise>;␊ 58 | getAbstractResolvers(filterSchema?: GraphQLSchema | string): IResolvers;␊ 59 | }␊ 60 | ␊ 61 | export interface BindingConstructor {␊ 62 | new(...args): T;␊ 63 | }␊ 64 | ␊ 65 | export const Binding = makeBindingClass>({ schema })␊ 66 | ␊ 67 | /**␊ 68 | * Types␊ 69 | */␊ 70 | ␊ 71 | export type MutationType =␊ 72 | | 'CREATED'␊ 73 | | 'UPDATED'␊ 74 | | 'DELETED'␊ 75 | ␊ 76 | ␊ 77 | export type PostOrderByInput =␊ 78 | | 'id_ASC'␊ 79 | | 'id_DESC'␊ 80 | | 'createdAt_ASC'␊ 81 | | 'createdAt_DESC'␊ 82 | | 'updatedAt_ASC'␊ 83 | | 'updatedAt_DESC'␊ 84 | | 'isPublished_ASC'␊ 85 | | 'isPublished_DESC'␊ 86 | | 'title_ASC'␊ 87 | | 'title_DESC'␊ 88 | | 'text_ASC'␊ 89 | | 'text_DESC'␊ 90 | ␊ 91 | ␊ 92 | export type UserOrderByInput =␊ 93 | | 'id_ASC'␊ 94 | | 'id_DESC'␊ 95 | | 'email_ASC'␊ 96 | | 'email_DESC'␊ 97 | | 'password_ASC'␊ 98 | | 'password_DESC'␊ 99 | | 'name_ASC'␊ 100 | | 'name_DESC'␊ 101 | | 'updatedAt_ASC'␊ 102 | | 'updatedAt_DESC'␊ 103 | | 'createdAt_ASC'␊ 104 | | 'createdAt_DESC'␊ 105 | ␊ 106 | ␊ 107 | export type PostCreateInput = {| ␊ 108 | isPublished?: Boolean,␊ 109 | title: String,␊ 110 | text: String,␊ 111 | author: UserCreateOneWithoutPostsInput␊ 112 | |}␊ 113 | ␊ 114 | export type PostCreateManyWithoutAuthorInput = {| ␊ 115 | create?: Array< PostCreateWithoutAuthorInput > | PostCreateWithoutAuthorInput,␊ 116 | connect?: Array< PostWhereUniqueInput > | PostWhereUniqueInput␊ 117 | |}␊ 118 | ␊ 119 | export type PostCreateWithoutAuthorInput = {| ␊ 120 | isPublished?: Boolean,␊ 121 | title: String,␊ 122 | text: String␊ 123 | |}␊ 124 | ␊ 125 | export type PostSubscriptionWhereInput = {| ␊ 126 | AND?: Array< PostSubscriptionWhereInput > | PostSubscriptionWhereInput,␊ 127 | OR?: Array< PostSubscriptionWhereInput > | PostSubscriptionWhereInput,␊ 128 | mutation_in?: Array< MutationType > | MutationType,␊ 129 | updatedFields_contains?: String,␊ 130 | updatedFields_contains_every?: Array< String > | String,␊ 131 | updatedFields_contains_some?: Array< String > | String,␊ 132 | node?: PostWhereInput␊ 133 | |}␊ 134 | ␊ 135 | export type PostUpdateInput = {| ␊ 136 | isPublished?: Boolean,␊ 137 | title?: String,␊ 138 | text?: String,␊ 139 | author?: UserUpdateOneWithoutPostsInput␊ 140 | |}␊ 141 | ␊ 142 | export type PostUpdateManyWithoutAuthorInput = {| ␊ 143 | create?: Array< PostCreateWithoutAuthorInput > | PostCreateWithoutAuthorInput,␊ 144 | connect?: Array< PostWhereUniqueInput > | PostWhereUniqueInput,␊ 145 | disconnect?: Array< PostWhereUniqueInput > | PostWhereUniqueInput,␊ 146 | delete?: Array< PostWhereUniqueInput > | PostWhereUniqueInput,␊ 147 | update?: Array< PostUpdateWithWhereUniqueWithoutAuthorInput > | PostUpdateWithWhereUniqueWithoutAuthorInput,␊ 148 | upsert?: Array< PostUpsertWithWhereUniqueWithoutAuthorInput > | PostUpsertWithWhereUniqueWithoutAuthorInput␊ 149 | |}␊ 150 | ␊ 151 | export type PostUpdateWithoutAuthorDataInput = {| ␊ 152 | isPublished?: Boolean,␊ 153 | title?: String,␊ 154 | text?: String␊ 155 | |}␊ 156 | ␊ 157 | export type PostUpdateWithWhereUniqueWithoutAuthorInput = {| ␊ 158 | where: PostWhereUniqueInput,␊ 159 | data: PostUpdateWithoutAuthorDataInput␊ 160 | |}␊ 161 | ␊ 162 | export type PostUpsertWithWhereUniqueWithoutAuthorInput = {| ␊ 163 | where: PostWhereUniqueInput,␊ 164 | update: PostUpdateWithoutAuthorDataInput,␊ 165 | create: PostCreateWithoutAuthorInput␊ 166 | |}␊ 167 | ␊ 168 | export type PostWhereInput = {| ␊ 169 | AND?: Array< PostWhereInput > | PostWhereInput,␊ 170 | OR?: Array< PostWhereInput > | PostWhereInput,␊ 171 | id?: ID_Input,␊ 172 | id_not?: ID_Input,␊ 173 | id_in?: Array< ID_Input > | ID_Input,␊ 174 | id_not_in?: Array< ID_Input > | ID_Input,␊ 175 | id_lt?: ID_Input,␊ 176 | id_lte?: ID_Input,␊ 177 | id_gt?: ID_Input,␊ 178 | id_gte?: ID_Input,␊ 179 | id_contains?: ID_Input,␊ 180 | id_not_contains?: ID_Input,␊ 181 | id_starts_with?: ID_Input,␊ 182 | id_not_starts_with?: ID_Input,␊ 183 | id_ends_with?: ID_Input,␊ 184 | id_not_ends_with?: ID_Input,␊ 185 | createdAt?: DateTime,␊ 186 | createdAt_not?: DateTime,␊ 187 | createdAt_in?: Array< DateTime > | DateTime,␊ 188 | createdAt_not_in?: Array< DateTime > | DateTime,␊ 189 | createdAt_lt?: DateTime,␊ 190 | createdAt_lte?: DateTime,␊ 191 | createdAt_gt?: DateTime,␊ 192 | createdAt_gte?: DateTime,␊ 193 | updatedAt?: DateTime,␊ 194 | updatedAt_not?: DateTime,␊ 195 | updatedAt_in?: Array< DateTime > | DateTime,␊ 196 | updatedAt_not_in?: Array< DateTime > | DateTime,␊ 197 | updatedAt_lt?: DateTime,␊ 198 | updatedAt_lte?: DateTime,␊ 199 | updatedAt_gt?: DateTime,␊ 200 | updatedAt_gte?: DateTime,␊ 201 | isPublished?: Boolean,␊ 202 | isPublished_not?: Boolean,␊ 203 | title?: String,␊ 204 | title_not?: String,␊ 205 | title_in?: Array< String > | String,␊ 206 | title_not_in?: Array< String > | String,␊ 207 | title_lt?: String,␊ 208 | title_lte?: String,␊ 209 | title_gt?: String,␊ 210 | title_gte?: String,␊ 211 | title_contains?: String,␊ 212 | title_not_contains?: String,␊ 213 | title_starts_with?: String,␊ 214 | title_not_starts_with?: String,␊ 215 | title_ends_with?: String,␊ 216 | title_not_ends_with?: String,␊ 217 | text?: String,␊ 218 | text_not?: String,␊ 219 | text_in?: Array< String > | String,␊ 220 | text_not_in?: Array< String > | String,␊ 221 | text_lt?: String,␊ 222 | text_lte?: String,␊ 223 | text_gt?: String,␊ 224 | text_gte?: String,␊ 225 | text_contains?: String,␊ 226 | text_not_contains?: String,␊ 227 | text_starts_with?: String,␊ 228 | text_not_starts_with?: String,␊ 229 | text_ends_with?: String,␊ 230 | text_not_ends_with?: String,␊ 231 | author?: UserWhereInput␊ 232 | |}␊ 233 | ␊ 234 | export type PostWhereUniqueInput = {| ␊ 235 | id?: ID_Input␊ 236 | |}␊ 237 | ␊ 238 | export type UserCreateInput = {| ␊ 239 | email: String,␊ 240 | password: String,␊ 241 | name: String,␊ 242 | posts?: PostCreateManyWithoutAuthorInput␊ 243 | |}␊ 244 | ␊ 245 | export type UserCreateOneWithoutPostsInput = {| ␊ 246 | create?: UserCreateWithoutPostsInput,␊ 247 | connect?: UserWhereUniqueInput␊ 248 | |}␊ 249 | ␊ 250 | export type UserCreateWithoutPostsInput = {| ␊ 251 | email: String,␊ 252 | password: String,␊ 253 | name: String␊ 254 | |}␊ 255 | ␊ 256 | export type UserSubscriptionWhereInput = {| ␊ 257 | AND?: Array< UserSubscriptionWhereInput > | UserSubscriptionWhereInput,␊ 258 | OR?: Array< UserSubscriptionWhereInput > | UserSubscriptionWhereInput,␊ 259 | mutation_in?: Array< MutationType > | MutationType,␊ 260 | updatedFields_contains?: String,␊ 261 | updatedFields_contains_every?: Array< String > | String,␊ 262 | updatedFields_contains_some?: Array< String > | String,␊ 263 | node?: UserWhereInput␊ 264 | |}␊ 265 | ␊ 266 | export type UserUpdateInput = {| ␊ 267 | email?: String,␊ 268 | password?: String,␊ 269 | name?: String,␊ 270 | posts?: PostUpdateManyWithoutAuthorInput␊ 271 | |}␊ 272 | ␊ 273 | export type UserUpdateOneWithoutPostsInput = {| ␊ 274 | create?: UserCreateWithoutPostsInput,␊ 275 | connect?: UserWhereUniqueInput,␊ 276 | delete?: Boolean,␊ 277 | update?: UserUpdateWithoutPostsDataInput,␊ 278 | upsert?: UserUpsertWithoutPostsInput␊ 279 | |}␊ 280 | ␊ 281 | export type UserUpdateWithoutPostsDataInput = {| ␊ 282 | email?: String,␊ 283 | password?: String,␊ 284 | name?: String␊ 285 | |}␊ 286 | ␊ 287 | export type UserUpsertWithoutPostsInput = {| ␊ 288 | update: UserUpdateWithoutPostsDataInput,␊ 289 | create: UserCreateWithoutPostsInput␊ 290 | |}␊ 291 | ␊ 292 | export type UserWhereInput = {| ␊ 293 | AND?: Array< UserWhereInput > | UserWhereInput,␊ 294 | OR?: Array< UserWhereInput > | UserWhereInput,␊ 295 | id?: ID_Input,␊ 296 | id_not?: ID_Input,␊ 297 | id_in?: Array< ID_Input > | ID_Input,␊ 298 | id_not_in?: Array< ID_Input > | ID_Input,␊ 299 | id_lt?: ID_Input,␊ 300 | id_lte?: ID_Input,␊ 301 | id_gt?: ID_Input,␊ 302 | id_gte?: ID_Input,␊ 303 | id_contains?: ID_Input,␊ 304 | id_not_contains?: ID_Input,␊ 305 | id_starts_with?: ID_Input,␊ 306 | id_not_starts_with?: ID_Input,␊ 307 | id_ends_with?: ID_Input,␊ 308 | id_not_ends_with?: ID_Input,␊ 309 | email?: String,␊ 310 | email_not?: String,␊ 311 | email_in?: Array< String > | String,␊ 312 | email_not_in?: Array< String > | String,␊ 313 | email_lt?: String,␊ 314 | email_lte?: String,␊ 315 | email_gt?: String,␊ 316 | email_gte?: String,␊ 317 | email_contains?: String,␊ 318 | email_not_contains?: String,␊ 319 | email_starts_with?: String,␊ 320 | email_not_starts_with?: String,␊ 321 | email_ends_with?: String,␊ 322 | email_not_ends_with?: String,␊ 323 | password?: String,␊ 324 | password_not?: String,␊ 325 | password_in?: Array< String > | String,␊ 326 | password_not_in?: Array< String > | String,␊ 327 | password_lt?: String,␊ 328 | password_lte?: String,␊ 329 | password_gt?: String,␊ 330 | password_gte?: String,␊ 331 | password_contains?: String,␊ 332 | password_not_contains?: String,␊ 333 | password_starts_with?: String,␊ 334 | password_not_starts_with?: String,␊ 335 | password_ends_with?: String,␊ 336 | password_not_ends_with?: String,␊ 337 | name?: String,␊ 338 | name_not?: String,␊ 339 | name_in?: Array< String > | String,␊ 340 | name_not_in?: Array< String > | String,␊ 341 | name_lt?: String,␊ 342 | name_lte?: String,␊ 343 | name_gt?: String,␊ 344 | name_gte?: String,␊ 345 | name_contains?: String,␊ 346 | name_not_contains?: String,␊ 347 | name_starts_with?: String,␊ 348 | name_not_starts_with?: String,␊ 349 | name_ends_with?: String,␊ 350 | name_not_ends_with?: String,␊ 351 | posts_every?: PostWhereInput,␊ 352 | posts_some?: PostWhereInput,␊ 353 | posts_none?: PostWhereInput␊ 354 | |}␊ 355 | ␊ 356 | export type UserWhereUniqueInput = {| ␊ 357 | id?: ID_Input,␊ 358 | email?: String␊ 359 | |}␊ 360 | ␊ 361 | /*␊ 362 | * An object with an ID␊ 363 | ␊ 364 | */␊ 365 | export type Node = {| ␊ 366 | id: ID_Output,␊ 367 | |}␊ 368 | ␊ 369 | export type AggregatePost = {| ␊ 370 | count: Int,␊ 371 | |}␊ 372 | ␊ 373 | export type AggregateUser = {| ␊ 374 | count: Int,␊ 375 | |}␊ 376 | ␊ 377 | export type BatchPayload = {| ␊ 378 | count: Long,␊ 379 | |}␊ 380 | ␊ 381 | /*␊ 382 | * Information about pagination in a connection.␊ 383 | ␊ 384 | */␊ 385 | export type PageInfo = {| ␊ 386 | hasNextPage: Boolean,␊ 387 | hasPreviousPage: Boolean,␊ 388 | startCursor?: String,␊ 389 | endCursor?: String,␊ 390 | |}␊ 391 | ␊ 392 | export type Post = {| ...Node,␊ 393 | ␊ 394 | id: ID_Output,␊ 395 | createdAt: DateTime,␊ 396 | updatedAt: DateTime,␊ 397 | isPublished: Boolean,␊ 398 | title: String,␊ 399 | text: String,␊ 400 | author: User,␊ 401 | |}␊ 402 | ␊ 403 | /*␊ 404 | * A connection to a list of items.␊ 405 | ␊ 406 | */␊ 407 | export type PostConnection = {| ␊ 408 | pageInfo: PageInfo,␊ 409 | edges: PostEdge[],␊ 410 | aggregate: AggregatePost,␊ 411 | |}␊ 412 | ␊ 413 | /*␊ 414 | * An edge in a connection.␊ 415 | ␊ 416 | */␊ 417 | export type PostEdge = {| ␊ 418 | node: Post,␊ 419 | cursor: String,␊ 420 | |}␊ 421 | ␊ 422 | export type PostPreviousValues = {| ␊ 423 | id: ID_Output,␊ 424 | createdAt: DateTime,␊ 425 | updatedAt: DateTime,␊ 426 | isPublished: Boolean,␊ 427 | title: String,␊ 428 | text: String,␊ 429 | |}␊ 430 | ␊ 431 | export type PostSubscriptionPayload = {| ␊ 432 | mutation: MutationType,␊ 433 | node?: Post,␊ 434 | updatedFields?: String[],␊ 435 | previousValues?: PostPreviousValues,␊ 436 | |}␊ 437 | ␊ 438 | export type User = {| ...Node,␊ 439 | ␊ 440 | id: ID_Output,␊ 441 | email: String,␊ 442 | password: String,␊ 443 | name: String,␊ 444 | posts?: Post[],␊ 445 | |}␊ 446 | ␊ 447 | /*␊ 448 | * A connection to a list of items.␊ 449 | ␊ 450 | */␊ 451 | export type UserConnection = {| ␊ 452 | pageInfo: PageInfo,␊ 453 | edges: UserEdge[],␊ 454 | aggregate: AggregateUser,␊ 455 | |}␊ 456 | ␊ 457 | /*␊ 458 | * An edge in a connection.␊ 459 | ␊ 460 | */␊ 461 | export type UserEdge = {| ␊ 462 | node: User,␊ 463 | cursor: String,␊ 464 | |}␊ 465 | ␊ 466 | export type UserPreviousValues = {| ␊ 467 | id: ID_Output,␊ 468 | email: String,␊ 469 | password: String,␊ 470 | name: String,␊ 471 | |}␊ 472 | ␊ 473 | export type UserSubscriptionPayload = {| ␊ 474 | mutation: MutationType,␊ 475 | node?: User,␊ 476 | updatedFields?: String[],␊ 477 | previousValues?: UserPreviousValues,␊ 478 | |}␊ 479 | ␊ 480 | /*␊ 481 | The `Boolean` scalar type represents `true` or `false`.␊ 482 | */␊ 483 | export type Boolean = boolean ␊ 484 | ␊ 485 | export type DateTime = Date | string ␊ 486 | ␊ 487 | /*␊ 488 | The `ID` scalar type represents a unique identifier, often used to refetch an object or as key for a cache. The ID type appears in a JSON response as a String; however, it is not intended to be human-readable. When expected as an input type, any string (such as `"4"`) or integer (such as `4`) input value will be accepted as an ID.␊ 489 | */␊ 490 | export type ID_Input = string | number␊ 491 | export type ID_Output = string␊ 492 | ␊ 493 | /*␊ 494 | The `Int` scalar type represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1.␊ 495 | */␊ 496 | export type Int = number ␊ 497 | ␊ 498 | /*␊ 499 | The `Long` scalar type represents non-fractional signed whole numeric values.␊ 500 | Long can represent values between -(2^63) and 2^63 - 1.␊ 501 | */␊ 502 | export type Long = string ␊ 503 | ␊ 504 | /*␊ 505 | The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text.␊ 506 | */␊ 507 | export type String = string ` 508 | -------------------------------------------------------------------------------- /src/codegen/FlowGenerator.test.ts.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dotansimha/graphql-binding/60fce38680a7b0f6e50722e5fe8c95cc39e2ea9d/src/codegen/FlowGenerator.test.ts.snap -------------------------------------------------------------------------------- /src/codegen/FlowGenerator.ts: -------------------------------------------------------------------------------- 1 | import { Generator } from './Generator' 2 | 3 | import { 4 | GraphQLSchema, 5 | GraphQLUnionType, 6 | GraphQLInterfaceType, 7 | GraphQLInputObjectType, 8 | GraphQLInputField, 9 | GraphQLField, 10 | GraphQLInputType, 11 | GraphQLOutputType, 12 | GraphQLWrappingType, 13 | GraphQLNamedType, 14 | GraphQLScalarType, 15 | GraphQLEnumType, 16 | GraphQLFieldMap, 17 | GraphQLObjectType, 18 | isNonNullType, 19 | isListType, 20 | } from 'graphql' 21 | 22 | import { Maybe } from './types' 23 | 24 | export class FlowGenerator extends Generator { 25 | scalarMapping = { 26 | Int: 'number', 27 | String: 'string', 28 | ID: 'string | number', 29 | Float: 'number', 30 | Boolean: 'boolean', 31 | DateTime: 'Date | string', 32 | } 33 | 34 | graphqlRenderers = { 35 | GraphQLUnionType: (type: GraphQLUnionType): string => { 36 | return `${this.renderDescription(type.description)} export type ${ 37 | type.name 38 | } = {| ${type 39 | .getTypes() 40 | .map(t => t.name) 41 | .join(', \n')} 42 | |} 43 | ` 44 | }, 45 | GraphQLObjectType: ( 46 | type: GraphQLObjectType | GraphQLInputObjectType | GraphQLInterfaceType, 47 | ): string => this.renderInterfaceOrObject(type), 48 | GraphQLInterfaceType: ( 49 | type: GraphQLObjectType | GraphQLInputObjectType | GraphQLInterfaceType, 50 | ): string => this.renderInterfaceOrObject(type), 51 | GraphQLInputObjectType: ( 52 | type: GraphQLObjectType | GraphQLInputObjectType | GraphQLInterfaceType, 53 | ): string => { 54 | const fieldDefinition = Object.keys(type.getFields()) 55 | .map(f => { 56 | const field = type.getFields()[f] 57 | return ` ${this.renderFieldName(field)}: ${this.renderInputFieldType( 58 | field.type, 59 | )}` 60 | }) 61 | .join(',\n') 62 | 63 | let interfaces: GraphQLInterfaceType[] = [] 64 | if (type instanceof GraphQLObjectType) { 65 | interfaces = (type as any).getInterrfaces() 66 | } 67 | return this.renderInterfaceWrapper( 68 | type.name, 69 | type.description, 70 | interfaces, 71 | fieldDefinition, 72 | ) 73 | }, 74 | 75 | GraphQLScalarType: (type: GraphQLScalarType): string => { 76 | if (type.name === 'ID') { 77 | return this.graphqlRenderers.GraphQLIDType(type) 78 | } 79 | return `${ 80 | type.description 81 | ? `/* 82 | ${type.description} 83 | */ 84 | ` 85 | : '' 86 | } export type ${type.name} = ${this.scalarMapping[type.name] || 87 | 'string'} ` 88 | }, 89 | 90 | GraphQLIDType: (type: GraphQLScalarType): string => { 91 | return `${ 92 | type.description 93 | ? `/* 94 | ${type.description} 95 | */ 96 | ` 97 | : '' 98 | } export type ${type.name}_Input = ${this.scalarMapping[type.name] || 99 | 'string'} 100 | export type ${type.name}_Output = string` 101 | }, 102 | 103 | GraphQLEnumType: (type: GraphQLEnumType): string => { 104 | return `${this.renderDescription(type.description)} export type ${ 105 | type.name 106 | } = 107 | ${type 108 | .getValues() 109 | .map(e => ` | '${e.name}'`) 110 | .join('\n')} 111 | ` 112 | }, 113 | } 114 | constructor({ 115 | schema, 116 | inputSchemaPath, 117 | outputBindingPath, 118 | isDefaultExport, 119 | }: { 120 | schema: GraphQLSchema 121 | inputSchemaPath: string 122 | outputBindingPath: string 123 | isDefaultExport: boolean 124 | }) { 125 | super({ schema, inputSchemaPath, outputBindingPath, isDefaultExport }) 126 | } 127 | render() { 128 | return this.compile`\ 129 | // @flow 130 | ${this.renderImports()} 131 | 132 | export interface Query ${this.renderQueries()} 133 | 134 | export interface Mutation ${this.renderMutations()} 135 | 136 | export interface Subscription ${this.renderSubscriptions()} 137 | 138 | export interface Binding { 139 | query: Query; 140 | mutation: Mutation; 141 | subscription: Subscription; 142 | request(query: string, variables?: {[key: string]: any}): Promise; 143 | delegate(operation: 'query' | 'mutation', fieldName: string, args: { 144 | [key: string]: any; 145 | }, infoOrQuery?: GraphQLResolveInfo | string, options?: Options): Promise; 146 | delegateSubscription(fieldName: string, args?: { 147 | [key: string]: any; 148 | }, infoOrQuery?: GraphQLResolveInfo | string, options?: Options): Promise>; 149 | getAbstractResolvers(filterSchema?: GraphQLSchema | string): IResolvers; 150 | } 151 | 152 | export interface BindingConstructor { 153 | new(...args): T; 154 | } 155 | 156 | ${this.renderExports()} 157 | 158 | /** 159 | * Types 160 | */ 161 | 162 | ${this.renderTypes()}` 163 | } 164 | renderExports() { 165 | return `export const Binding = makeBindingClass>({ schema })` 166 | } 167 | 168 | renderQueries() { 169 | const queryType = this.schema.getQueryType() 170 | if (!queryType) { 171 | return '{}' 172 | } 173 | return this.renderMainMethodFields('query', queryType.getFields()) 174 | } 175 | 176 | renderMutations() { 177 | const mutationType = this.schema.getMutationType() 178 | if (!mutationType) { 179 | return '{}' 180 | } 181 | return this.renderMainMethodFields('mutation', mutationType.getFields()) 182 | } 183 | 184 | renderSubscriptions() { 185 | const subscriptionType = this.schema.getSubscriptionType() 186 | if (!subscriptionType) { 187 | return '{}' 188 | } 189 | return this.renderMainMethodFields( 190 | 'subscription', 191 | subscriptionType.getFields(), 192 | ) 193 | } 194 | 195 | getTypeNames() { 196 | const ast = this.schema 197 | // Create types 198 | return Object.keys(ast.getTypeMap()) 199 | .filter(typeName => !typeName.startsWith('__')) 200 | .filter(typeName => typeName !== (ast.getQueryType() as any).name) 201 | .filter(typeName => 202 | ast.getMutationType() 203 | ? typeName !== (ast.getMutationType()! as any).name 204 | : true, 205 | ) 206 | .filter(typeName => 207 | ast.getSubscriptionType() 208 | ? typeName !== (ast.getSubscriptionType()! as any).name 209 | : true, 210 | ) 211 | .sort((a, b) => { 212 | const typeA = ast.getType(a)! 213 | const typeB = ast.getType(b)! 214 | /** 215 | * Firstly sorted by constructor type alphabetically, 216 | * secondly sorted by their name alphabetically. 217 | */ 218 | const constructorOrder = typeA.constructor.name.localeCompare( 219 | typeB.constructor.name, 220 | ) 221 | switch (constructorOrder) { 222 | case 0: 223 | return typeA.name.localeCompare(typeB.name) 224 | 225 | default: 226 | return constructorOrder 227 | } 228 | }) 229 | } 230 | 231 | renderTypes() { 232 | const typeNames = this.getTypeNames() 233 | return typeNames 234 | .map(typeName => { 235 | const type = this.schema.getTypeMap()[typeName] 236 | return this.graphqlRenderers[type.constructor.name] 237 | ? this.graphqlRenderers[type.constructor.name](type) 238 | : null 239 | }) 240 | .join('\n\n') 241 | } 242 | 243 | renderMainMethodFields( 244 | operation: string, 245 | fields: GraphQLFieldMap, 246 | ): string { 247 | const methods = Object.keys(fields) 248 | .map(f => { 249 | const field = fields[f] 250 | const hasArgs = field.args.length > 0 251 | return ` ${field.name}(args${hasArgs ? '' : '?'}: {${ 252 | hasArgs ? ' ' : '' 253 | }${field.args 254 | .map( 255 | f => `${this.renderFieldName(f)}: ${this.renderFieldType(f.type)}`, 256 | ) 257 | .join(', ')}${ 258 | field.args.length > 0 ? ' ' : '' 259 | }}, info?: GraphQLResolveInfo | string, options?: Options): ${this.getPayloadType( 260 | operation, 261 | `${this.renderFieldType(field.type)}${ 262 | !isNonNullType(field.type) ? ' | null' : '' 263 | }`, 264 | )}; ` 265 | }) 266 | .join('\n') 267 | 268 | return `{\n${methods}\n }` 269 | } 270 | 271 | getPayloadType(operation: string, type: string) { 272 | if (operation === 'subscription') { 273 | return `Promise>` 274 | } 275 | return `Promise<${type}>` 276 | } 277 | 278 | renderInterfaceOrObject( 279 | type: GraphQLObjectType | GraphQLInputObjectType | GraphQLInterfaceType, 280 | ): string { 281 | const fieldDefinition: string = Object.keys(type.getFields()) 282 | .map(f => { 283 | const field = type.getFields()[f] 284 | return ` ${this.renderFieldName(field)}: ${this.renderFieldType( 285 | field.type, 286 | )},` 287 | }) 288 | .join('\n') 289 | 290 | let interfaces: GraphQLInterfaceType[] = [] 291 | if (type instanceof GraphQLObjectType) { 292 | interfaces = (type as any).getInterfaces() 293 | } 294 | 295 | return this.renderInterfaceWrapper( 296 | type.name, 297 | type.description, 298 | interfaces, 299 | fieldDefinition, 300 | ) 301 | } 302 | 303 | renderFieldName(field: GraphQLInputField | GraphQLField): string { 304 | return `${field.name}${isNonNullType(field.type) ? '' : '?'}` 305 | } 306 | 307 | renderFieldType(type: GraphQLInputType | GraphQLOutputType): string { 308 | if (isNonNullType(type)) { 309 | return `${this.renderFieldType((type as GraphQLWrappingType).ofType)}` 310 | } 311 | if (isListType(type)) { 312 | return `${this.renderFieldType((type as GraphQLWrappingType).ofType)}[]` 313 | } 314 | return `${(type as GraphQLNamedType).name}${ 315 | (type as GraphQLNamedType).name === 'ID' ? '_Output' : '' 316 | }` 317 | } 318 | 319 | renderInputFieldType(type: GraphQLInputType | GraphQLOutputType) { 320 | if (isNonNullType(type)) { 321 | return `${this.renderInputFieldType( 322 | (type as GraphQLWrappingType).ofType, 323 | )}` 324 | } 325 | if (isListType(type)) { 326 | const inputType = this.renderInputFieldType( 327 | (type as GraphQLWrappingType).ofType, 328 | ) 329 | return `Array< ${inputType} > | ${inputType}` 330 | } 331 | return `${(type as GraphQLNamedType).name}${ 332 | (type as GraphQLNamedType).name === 'ID' ? '_Input' : '' 333 | }` 334 | } 335 | 336 | renderTypeWrapper( 337 | typeName: string, 338 | typeDescription: Maybe, 339 | fieldDefinition: string, 340 | ): string { 341 | return `${this.renderDescription( 342 | typeDescription, 343 | )} export type ${typeName} = { ${fieldDefinition} }` 344 | } 345 | 346 | renderObjectWrapper( 347 | typeName: string, 348 | typeDescription: Maybe, 349 | objects: GraphQLObjectType[], 350 | fieldDefinition: string, 351 | ): string { 352 | return `${this.renderDescription( 353 | typeDescription, 354 | )} export type ${typeName} = { 355 | ` 356 | } 357 | 358 | renderInterfaceWrapper( 359 | typeName: string, 360 | typeDescription: Maybe, 361 | interfaces: GraphQLInterfaceType[], 362 | fieldDefinition: string, 363 | ): string { 364 | return `${this.renderDescription( 365 | typeDescription, 366 | )} export type ${typeName} = {| ${ 367 | interfaces.length > 0 368 | ? `...${interfaces.map(i => i.name).join(',\n')},\n ` 369 | : '' 370 | } 371 | ${fieldDefinition} 372 | |}` 373 | } 374 | 375 | renderDescription(description: Maybe): string { 376 | return `${ 377 | description 378 | ? `/* 379 | ${description.split('\n').map(l => ` * ${l}\n`)} 380 | */ 381 | ` 382 | : '' 383 | }` 384 | } 385 | renderImports() { 386 | return `\ 387 | import { makeBindingClass, Options } from 'graphql-binding' 388 | import { GraphQLResolveInfo, GraphQLSchema } from 'graphql' 389 | import { IResolvers } from 'graphql-tools-fork' 390 | import ${ 391 | this.isDefaultExport ? '' : '* as ' 392 | }schema from '${this.getRelativeSchemaPath()}'` 393 | } 394 | } 395 | -------------------------------------------------------------------------------- /src/codegen/Generator.test.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs' 2 | import * as path from 'path' 3 | import { buildSchema } from 'graphql' 4 | import { Generator } from './Generator' 5 | import test from 'ava' 6 | 7 | const typeDefs = fs.readFileSync( 8 | path.join(__dirname, '../../src/codegen/fixtures/schema.graphql'), 9 | 'utf-8', 10 | ) 11 | test('basic generator', t => { 12 | const schema = buildSchema(typeDefs) 13 | const generator = new Generator({ 14 | schema, 15 | inputSchemaPath: 'src/schema.js', 16 | outputBindingPath: 'src/generated/binding.js', 17 | isDefaultExport: false, 18 | }) 19 | const result = generator.render() 20 | t.snapshot(result) 21 | }) 22 | -------------------------------------------------------------------------------- /src/codegen/Generator.test.ts.md: -------------------------------------------------------------------------------- 1 | # Snapshot report for `src/codegen/Generator.test.ts` 2 | 3 | The actual snapshot is saved in `Generator.test.ts.snap`. 4 | 5 | Generated by [AVA](https://ava.li). 6 | 7 | ## basic generator 8 | 9 | > Snapshot 1 10 | 11 | `const { makeBindingClass } = require('graphql-binding')␊ 12 | const schema = require('../schema')␊ 13 | ␊ 14 | module.exports = makeBindingClass({ schema })` 15 | -------------------------------------------------------------------------------- /src/codegen/Generator.test.ts.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dotansimha/graphql-binding/60fce38680a7b0f6e50722e5fe8c95cc39e2ea9d/src/codegen/Generator.test.ts.snap -------------------------------------------------------------------------------- /src/codegen/Generator.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLSchema } from 'graphql' 2 | import flatten from './utils/flatten' 3 | import { interleave } from './utils/interleave' 4 | import { Interpolation } from './types' 5 | import * as path from 'path' 6 | 7 | export class Generator { 8 | schema: GraphQLSchema 9 | inputSchemaPath: string 10 | outputBindingPath: string 11 | isDefaultExport: boolean 12 | 13 | constructor({ 14 | schema, 15 | inputSchemaPath, 16 | outputBindingPath, 17 | isDefaultExport, 18 | }: { 19 | schema: GraphQLSchema 20 | inputSchemaPath: string 21 | outputBindingPath: string 22 | isDefaultExport: boolean 23 | }) { 24 | this.schema = schema 25 | this.inputSchemaPath = inputSchemaPath 26 | this.outputBindingPath = outputBindingPath 27 | this.isDefaultExport = isDefaultExport 28 | } 29 | render() { 30 | return this.compile`\ 31 | ${this.renderImports()} 32 | 33 | ${this.renderExports()}` 34 | } 35 | compile( 36 | strings: TemplateStringsArray, 37 | ...interpolations: Interpolation[] 38 | ) { 39 | return flatten(interleave(strings, interpolations), this).join( 40 | '', 41 | ) 42 | } 43 | getRelativeSchemaPath() { 44 | const result = path.posix 45 | .relative( 46 | path.dirname(this.outputBindingPath) + '/', 47 | this.inputSchemaPath, 48 | ) 49 | .replace(/\.(t|j)s$/, '') 50 | 51 | if (result.startsWith('.')) { 52 | return result 53 | } 54 | 55 | return `./` + result 56 | } 57 | renderImports() { 58 | return `\ 59 | const { makeBindingClass } = require('graphql-binding') 60 | const schema = require('${this.getRelativeSchemaPath()}')${ 61 | this.isDefaultExport ? '.default' : '' 62 | }` 63 | } 64 | renderExports() { 65 | return `module.exports = makeBindingClass({ schema })` 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/codegen/TypescriptGenerator.test.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs' 2 | import * as path from 'path' 3 | import { buildSchema } from 'graphql' 4 | import { TypescriptGenerator } from './TypescriptGenerator' 5 | import test from 'ava' 6 | 7 | const typeDefs = fs.readFileSync( 8 | path.join(__dirname, '../../src/codegen/fixtures/schema.graphql'), 9 | 'utf-8', 10 | ) 11 | test('typescript generator', t => { 12 | const schema = buildSchema(typeDefs) 13 | const generator = new TypescriptGenerator({ 14 | schema, 15 | inputSchemaPath: 'src/schema.js', 16 | outputBindingPath: 'src/generated/binding.js', 17 | isDefaultExport: false, 18 | }) 19 | const result = generator.render() 20 | t.snapshot(result) 21 | }) 22 | -------------------------------------------------------------------------------- /src/codegen/TypescriptGenerator.test.ts.md: -------------------------------------------------------------------------------- 1 | # Snapshot report for `src/codegen/TypescriptGenerator.test.ts` 2 | 3 | The actual snapshot is saved in `TypescriptGenerator.test.ts.snap`. 4 | 5 | Generated by [AVA](https://ava.li). 6 | 7 | ## typescript generator 8 | 9 | > Snapshot 1 10 | 11 | `import { makeBindingClass, Options } from 'graphql-binding'␊ 12 | import { GraphQLResolveInfo, GraphQLSchema } from 'graphql'␊ 13 | import { IResolvers } from 'graphql-tools-fork'␊ 14 | import * as schema from '../schema'␊ 15 | ␊ 16 | export interface Query {␊ 17 | posts: >(args: { where?: PostWhereInput | null, orderBy?: PostOrderByInput | null, skip?: Int | null, after?: String | null, before?: String | null, first?: Int | null, last?: Int | null }, info?: GraphQLResolveInfo | string, options?: Options) => Promise ,␊ 18 | users: >(args: { where?: UserWhereInput | null, orderBy?: UserOrderByInput | null, skip?: Int | null, after?: String | null, before?: String | null, first?: Int | null, last?: Int | null }, info?: GraphQLResolveInfo | string, options?: Options) => Promise ,␊ 19 | post: (args: { where: PostWhereUniqueInput }, info?: GraphQLResolveInfo | string, options?: Options) => Promise ,␊ 20 | user: (args: { where: UserWhereUniqueInput }, info?: GraphQLResolveInfo | string, options?: Options) => Promise ,␊ 21 | postsConnection: (args: { where?: PostWhereInput | null, orderBy?: PostOrderByInput | null, skip?: Int | null, after?: String | null, before?: String | null, first?: Int | null, last?: Int | null }, info?: GraphQLResolveInfo | string, options?: Options) => Promise ,␊ 22 | usersConnection: (args: { where?: UserWhereInput | null, orderBy?: UserOrderByInput | null, skip?: Int | null, after?: String | null, before?: String | null, first?: Int | null, last?: Int | null }, info?: GraphQLResolveInfo | string, options?: Options) => Promise ,␊ 23 | node: (args: { id: ID_Output }, info?: GraphQLResolveInfo | string, options?: Options) => Promise ␊ 24 | }␊ 25 | ␊ 26 | export interface Mutation {␊ 27 | createPost: (args: { data: PostCreateInput }, info?: GraphQLResolveInfo | string, options?: Options) => Promise ,␊ 28 | createUser: (args: { data: UserCreateInput }, info?: GraphQLResolveInfo | string, options?: Options) => Promise ,␊ 29 | updatePost: (args: { data: PostUpdateInput, where: PostWhereUniqueInput }, info?: GraphQLResolveInfo | string, options?: Options) => Promise ,␊ 30 | updateUser: (args: { data: UserUpdateInput, where: UserWhereUniqueInput }, info?: GraphQLResolveInfo | string, options?: Options) => Promise ,␊ 31 | deletePost: (args: { where: PostWhereUniqueInput }, info?: GraphQLResolveInfo | string, options?: Options) => Promise ,␊ 32 | deleteUser: (args: { where: UserWhereUniqueInput }, info?: GraphQLResolveInfo | string, options?: Options) => Promise ,␊ 33 | upsertPost: (args: { where: PostWhereUniqueInput, create: PostCreateInput, update: PostUpdateInput }, info?: GraphQLResolveInfo | string, options?: Options) => Promise ,␊ 34 | upsertUser: (args: { where: UserWhereUniqueInput, create: UserCreateInput, update: UserUpdateInput }, info?: GraphQLResolveInfo | string, options?: Options) => Promise ,␊ 35 | updateManyPosts: (args: { data: PostUpdateInput, where: PostWhereInput }, info?: GraphQLResolveInfo | string, options?: Options) => Promise ,␊ 36 | updateManyUsers: (args: { data: UserUpdateInput, where: UserWhereInput }, info?: GraphQLResolveInfo | string, options?: Options) => Promise ,␊ 37 | deleteManyPosts: (args: { where: PostWhereInput }, info?: GraphQLResolveInfo | string, options?: Options) => Promise ,␊ 38 | deleteManyUsers: (args: { where: UserWhereInput }, info?: GraphQLResolveInfo | string, options?: Options) => Promise ␊ 39 | }␊ 40 | ␊ 41 | export interface Subscription {␊ 42 | post: (args: { where?: PostSubscriptionWhereInput | null }, info?: GraphQLResolveInfo | string, options?: Options) => Promise> ,␊ 43 | user: (args: { where?: UserSubscriptionWhereInput | null }, info?: GraphQLResolveInfo | string, options?: Options) => Promise> ␊ 44 | }␊ 45 | ␊ 46 | export interface Binding {␊ 47 | query: Query␊ 48 | mutation: Mutation␊ 49 | subscription: Subscription␊ 50 | request: (query: string, variables?: {[key: string]: any}) => Promise␊ 51 | delegate(operation: 'query' | 'mutation', fieldName: string, args: {␊ 52 | [key: string]: any;␊ 53 | }, infoOrQuery?: GraphQLResolveInfo | string, options?: Options): Promise;␊ 54 | delegateSubscription(fieldName: string, args?: {␊ 55 | [key: string]: any;␊ 56 | }, infoOrQuery?: GraphQLResolveInfo | string, options?: Options): Promise>;␊ 57 | getAbstractResolvers(filterSchema?: GraphQLSchema | string): IResolvers;␊ 58 | }␊ 59 | ␊ 60 | export interface BindingConstructor {␊ 61 | new(...args: any[]): T␊ 62 | }␊ 63 | ␊ 64 | export const Binding = makeBindingClass>({ schema })␊ 65 | ␊ 66 | /**␊ 67 | * Types␊ 68 | */␊ 69 | ␊ 70 | export type MutationType = 'CREATED' |␊ 71 | 'UPDATED' |␊ 72 | 'DELETED'␊ 73 | ␊ 74 | export type PostOrderByInput = 'id_ASC' |␊ 75 | 'id_DESC' |␊ 76 | 'createdAt_ASC' |␊ 77 | 'createdAt_DESC' |␊ 78 | 'updatedAt_ASC' |␊ 79 | 'updatedAt_DESC' |␊ 80 | 'isPublished_ASC' |␊ 81 | 'isPublished_DESC' |␊ 82 | 'title_ASC' |␊ 83 | 'title_DESC' |␊ 84 | 'text_ASC' |␊ 85 | 'text_DESC'␊ 86 | ␊ 87 | export type UserOrderByInput = 'id_ASC' |␊ 88 | 'id_DESC' |␊ 89 | 'email_ASC' |␊ 90 | 'email_DESC' |␊ 91 | 'password_ASC' |␊ 92 | 'password_DESC' |␊ 93 | 'name_ASC' |␊ 94 | 'name_DESC' |␊ 95 | 'updatedAt_ASC' |␊ 96 | 'updatedAt_DESC' |␊ 97 | 'createdAt_ASC' |␊ 98 | 'createdAt_DESC'␊ 99 | ␊ 100 | export interface PostCreateInput {␊ 101 | isPublished?: Boolean | null␊ 102 | title: String␊ 103 | text: String␊ 104 | author: UserCreateOneWithoutPostsInput␊ 105 | }␊ 106 | ␊ 107 | export interface PostCreateManyWithoutAuthorInput {␊ 108 | create?: PostCreateWithoutAuthorInput[] | PostCreateWithoutAuthorInput | null␊ 109 | connect?: PostWhereUniqueInput[] | PostWhereUniqueInput | null␊ 110 | }␊ 111 | ␊ 112 | export interface PostCreateWithoutAuthorInput {␊ 113 | isPublished?: Boolean | null␊ 114 | title: String␊ 115 | text: String␊ 116 | }␊ 117 | ␊ 118 | export interface PostSubscriptionWhereInput {␊ 119 | AND?: PostSubscriptionWhereInput[] | PostSubscriptionWhereInput | null␊ 120 | OR?: PostSubscriptionWhereInput[] | PostSubscriptionWhereInput | null␊ 121 | mutation_in?: MutationType[] | MutationType | null␊ 122 | updatedFields_contains?: String | null␊ 123 | updatedFields_contains_every?: String[] | String | null␊ 124 | updatedFields_contains_some?: String[] | String | null␊ 125 | node?: PostWhereInput | null␊ 126 | }␊ 127 | ␊ 128 | export interface PostUpdateInput {␊ 129 | isPublished?: Boolean | null␊ 130 | title?: String | null␊ 131 | text?: String | null␊ 132 | author?: UserUpdateOneWithoutPostsInput | null␊ 133 | }␊ 134 | ␊ 135 | export interface PostUpdateManyWithoutAuthorInput {␊ 136 | create?: PostCreateWithoutAuthorInput[] | PostCreateWithoutAuthorInput | null␊ 137 | connect?: PostWhereUniqueInput[] | PostWhereUniqueInput | null␊ 138 | disconnect?: PostWhereUniqueInput[] | PostWhereUniqueInput | null␊ 139 | delete?: PostWhereUniqueInput[] | PostWhereUniqueInput | null␊ 140 | update?: PostUpdateWithWhereUniqueWithoutAuthorInput[] | PostUpdateWithWhereUniqueWithoutAuthorInput | null␊ 141 | upsert?: PostUpsertWithWhereUniqueWithoutAuthorInput[] | PostUpsertWithWhereUniqueWithoutAuthorInput | null␊ 142 | }␊ 143 | ␊ 144 | export interface PostUpdateWithoutAuthorDataInput {␊ 145 | isPublished?: Boolean | null␊ 146 | title?: String | null␊ 147 | text?: String | null␊ 148 | }␊ 149 | ␊ 150 | export interface PostUpdateWithWhereUniqueWithoutAuthorInput {␊ 151 | where: PostWhereUniqueInput␊ 152 | data: PostUpdateWithoutAuthorDataInput␊ 153 | }␊ 154 | ␊ 155 | export interface PostUpsertWithWhereUniqueWithoutAuthorInput {␊ 156 | where: PostWhereUniqueInput␊ 157 | update: PostUpdateWithoutAuthorDataInput␊ 158 | create: PostCreateWithoutAuthorInput␊ 159 | }␊ 160 | ␊ 161 | export interface PostWhereInput {␊ 162 | AND?: PostWhereInput[] | PostWhereInput | null␊ 163 | OR?: PostWhereInput[] | PostWhereInput | null␊ 164 | id?: ID_Input | null␊ 165 | id_not?: ID_Input | null␊ 166 | id_in?: ID_Output[] | ID_Output | null␊ 167 | id_not_in?: ID_Output[] | ID_Output | null␊ 168 | id_lt?: ID_Input | null␊ 169 | id_lte?: ID_Input | null␊ 170 | id_gt?: ID_Input | null␊ 171 | id_gte?: ID_Input | null␊ 172 | id_contains?: ID_Input | null␊ 173 | id_not_contains?: ID_Input | null␊ 174 | id_starts_with?: ID_Input | null␊ 175 | id_not_starts_with?: ID_Input | null␊ 176 | id_ends_with?: ID_Input | null␊ 177 | id_not_ends_with?: ID_Input | null␊ 178 | createdAt?: DateTime | null␊ 179 | createdAt_not?: DateTime | null␊ 180 | createdAt_in?: DateTime[] | DateTime | null␊ 181 | createdAt_not_in?: DateTime[] | DateTime | null␊ 182 | createdAt_lt?: DateTime | null␊ 183 | createdAt_lte?: DateTime | null␊ 184 | createdAt_gt?: DateTime | null␊ 185 | createdAt_gte?: DateTime | null␊ 186 | updatedAt?: DateTime | null␊ 187 | updatedAt_not?: DateTime | null␊ 188 | updatedAt_in?: DateTime[] | DateTime | null␊ 189 | updatedAt_not_in?: DateTime[] | DateTime | null␊ 190 | updatedAt_lt?: DateTime | null␊ 191 | updatedAt_lte?: DateTime | null␊ 192 | updatedAt_gt?: DateTime | null␊ 193 | updatedAt_gte?: DateTime | null␊ 194 | isPublished?: Boolean | null␊ 195 | isPublished_not?: Boolean | null␊ 196 | title?: String | null␊ 197 | title_not?: String | null␊ 198 | title_in?: String[] | String | null␊ 199 | title_not_in?: String[] | String | null␊ 200 | title_lt?: String | null␊ 201 | title_lte?: String | null␊ 202 | title_gt?: String | null␊ 203 | title_gte?: String | null␊ 204 | title_contains?: String | null␊ 205 | title_not_contains?: String | null␊ 206 | title_starts_with?: String | null␊ 207 | title_not_starts_with?: String | null␊ 208 | title_ends_with?: String | null␊ 209 | title_not_ends_with?: String | null␊ 210 | text?: String | null␊ 211 | text_not?: String | null␊ 212 | text_in?: String[] | String | null␊ 213 | text_not_in?: String[] | String | null␊ 214 | text_lt?: String | null␊ 215 | text_lte?: String | null␊ 216 | text_gt?: String | null␊ 217 | text_gte?: String | null␊ 218 | text_contains?: String | null␊ 219 | text_not_contains?: String | null␊ 220 | text_starts_with?: String | null␊ 221 | text_not_starts_with?: String | null␊ 222 | text_ends_with?: String | null␊ 223 | text_not_ends_with?: String | null␊ 224 | author?: UserWhereInput | null␊ 225 | }␊ 226 | ␊ 227 | export interface PostWhereUniqueInput {␊ 228 | id?: ID_Input | null␊ 229 | }␊ 230 | ␊ 231 | export interface UserCreateInput {␊ 232 | email: String␊ 233 | password: String␊ 234 | name: String␊ 235 | posts?: PostCreateManyWithoutAuthorInput | null␊ 236 | }␊ 237 | ␊ 238 | export interface UserCreateOneWithoutPostsInput {␊ 239 | create?: UserCreateWithoutPostsInput | null␊ 240 | connect?: UserWhereUniqueInput | null␊ 241 | }␊ 242 | ␊ 243 | export interface UserCreateWithoutPostsInput {␊ 244 | email: String␊ 245 | password: String␊ 246 | name: String␊ 247 | }␊ 248 | ␊ 249 | export interface UserSubscriptionWhereInput {␊ 250 | AND?: UserSubscriptionWhereInput[] | UserSubscriptionWhereInput | null␊ 251 | OR?: UserSubscriptionWhereInput[] | UserSubscriptionWhereInput | null␊ 252 | mutation_in?: MutationType[] | MutationType | null␊ 253 | updatedFields_contains?: String | null␊ 254 | updatedFields_contains_every?: String[] | String | null␊ 255 | updatedFields_contains_some?: String[] | String | null␊ 256 | node?: UserWhereInput | null␊ 257 | }␊ 258 | ␊ 259 | export interface UserUpdateInput {␊ 260 | email?: String | null␊ 261 | password?: String | null␊ 262 | name?: String | null␊ 263 | posts?: PostUpdateManyWithoutAuthorInput | null␊ 264 | }␊ 265 | ␊ 266 | export interface UserUpdateOneWithoutPostsInput {␊ 267 | create?: UserCreateWithoutPostsInput | null␊ 268 | connect?: UserWhereUniqueInput | null␊ 269 | delete?: Boolean | null␊ 270 | update?: UserUpdateWithoutPostsDataInput | null␊ 271 | upsert?: UserUpsertWithoutPostsInput | null␊ 272 | }␊ 273 | ␊ 274 | export interface UserUpdateWithoutPostsDataInput {␊ 275 | email?: String | null␊ 276 | password?: String | null␊ 277 | name?: String | null␊ 278 | }␊ 279 | ␊ 280 | export interface UserUpsertWithoutPostsInput {␊ 281 | update: UserUpdateWithoutPostsDataInput␊ 282 | create: UserCreateWithoutPostsInput␊ 283 | }␊ 284 | ␊ 285 | export interface UserWhereInput {␊ 286 | AND?: UserWhereInput[] | UserWhereInput | null␊ 287 | OR?: UserWhereInput[] | UserWhereInput | null␊ 288 | id?: ID_Input | null␊ 289 | id_not?: ID_Input | null␊ 290 | id_in?: ID_Output[] | ID_Output | null␊ 291 | id_not_in?: ID_Output[] | ID_Output | null␊ 292 | id_lt?: ID_Input | null␊ 293 | id_lte?: ID_Input | null␊ 294 | id_gt?: ID_Input | null␊ 295 | id_gte?: ID_Input | null␊ 296 | id_contains?: ID_Input | null␊ 297 | id_not_contains?: ID_Input | null␊ 298 | id_starts_with?: ID_Input | null␊ 299 | id_not_starts_with?: ID_Input | null␊ 300 | id_ends_with?: ID_Input | null␊ 301 | id_not_ends_with?: ID_Input | null␊ 302 | email?: String | null␊ 303 | email_not?: String | null␊ 304 | email_in?: String[] | String | null␊ 305 | email_not_in?: String[] | String | null␊ 306 | email_lt?: String | null␊ 307 | email_lte?: String | null␊ 308 | email_gt?: String | null␊ 309 | email_gte?: String | null␊ 310 | email_contains?: String | null␊ 311 | email_not_contains?: String | null␊ 312 | email_starts_with?: String | null␊ 313 | email_not_starts_with?: String | null␊ 314 | email_ends_with?: String | null␊ 315 | email_not_ends_with?: String | null␊ 316 | password?: String | null␊ 317 | password_not?: String | null␊ 318 | password_in?: String[] | String | null␊ 319 | password_not_in?: String[] | String | null␊ 320 | password_lt?: String | null␊ 321 | password_lte?: String | null␊ 322 | password_gt?: String | null␊ 323 | password_gte?: String | null␊ 324 | password_contains?: String | null␊ 325 | password_not_contains?: String | null␊ 326 | password_starts_with?: String | null␊ 327 | password_not_starts_with?: String | null␊ 328 | password_ends_with?: String | null␊ 329 | password_not_ends_with?: String | null␊ 330 | name?: String | null␊ 331 | name_not?: String | null␊ 332 | name_in?: String[] | String | null␊ 333 | name_not_in?: String[] | String | null␊ 334 | name_lt?: String | null␊ 335 | name_lte?: String | null␊ 336 | name_gt?: String | null␊ 337 | name_gte?: String | null␊ 338 | name_contains?: String | null␊ 339 | name_not_contains?: String | null␊ 340 | name_starts_with?: String | null␊ 341 | name_not_starts_with?: String | null␊ 342 | name_ends_with?: String | null␊ 343 | name_not_ends_with?: String | null␊ 344 | posts_every?: PostWhereInput | null␊ 345 | posts_some?: PostWhereInput | null␊ 346 | posts_none?: PostWhereInput | null␊ 347 | }␊ 348 | ␊ 349 | export interface UserWhereUniqueInput {␊ 350 | id?: ID_Input | null␊ 351 | email?: String | null␊ 352 | }␊ 353 | ␊ 354 | /*␊ 355 | * An object with an ID␊ 356 | ␊ 357 | */␊ 358 | export interface Node {␊ 359 | id: ID_Output␊ 360 | }␊ 361 | ␊ 362 | export interface AggregatePost {␊ 363 | count: Int␊ 364 | }␊ 365 | ␊ 366 | export interface AggregateUser {␊ 367 | count: Int␊ 368 | }␊ 369 | ␊ 370 | export interface BatchPayload {␊ 371 | count: Long␊ 372 | }␊ 373 | ␊ 374 | /*␊ 375 | * Information about pagination in a connection.␊ 376 | ␊ 377 | */␊ 378 | export interface PageInfo {␊ 379 | hasNextPage: Boolean␊ 380 | hasPreviousPage: Boolean␊ 381 | startCursor?: String | null␊ 382 | endCursor?: String | null␊ 383 | }␊ 384 | ␊ 385 | export interface Post extends Node {␊ 386 | id: ID_Output␊ 387 | createdAt: DateTime␊ 388 | updatedAt: DateTime␊ 389 | isPublished: Boolean␊ 390 | title: String␊ 391 | text: String␊ 392 | author: User␊ 393 | }␊ 394 | ␊ 395 | /*␊ 396 | * A connection to a list of items.␊ 397 | ␊ 398 | */␊ 399 | export interface PostConnection {␊ 400 | pageInfo: PageInfo␊ 401 | edges: Array␊ 402 | aggregate: AggregatePost␊ 403 | }␊ 404 | ␊ 405 | /*␊ 406 | * An edge in a connection.␊ 407 | ␊ 408 | */␊ 409 | export interface PostEdge {␊ 410 | node: Post␊ 411 | cursor: String␊ 412 | }␊ 413 | ␊ 414 | export interface PostPreviousValues {␊ 415 | id: ID_Output␊ 416 | createdAt: DateTime␊ 417 | updatedAt: DateTime␊ 418 | isPublished: Boolean␊ 419 | title: String␊ 420 | text: String␊ 421 | }␊ 422 | ␊ 423 | export interface PostSubscriptionPayload {␊ 424 | mutation: MutationType␊ 425 | node?: Post | null␊ 426 | updatedFields?: Array | null␊ 427 | previousValues?: PostPreviousValues | null␊ 428 | }␊ 429 | ␊ 430 | export interface User extends Node {␊ 431 | id: ID_Output␊ 432 | email: String␊ 433 | password: String␊ 434 | name: String␊ 435 | posts?: Array | null␊ 436 | }␊ 437 | ␊ 438 | /*␊ 439 | * A connection to a list of items.␊ 440 | ␊ 441 | */␊ 442 | export interface UserConnection {␊ 443 | pageInfo: PageInfo␊ 444 | edges: Array␊ 445 | aggregate: AggregateUser␊ 446 | }␊ 447 | ␊ 448 | /*␊ 449 | * An edge in a connection.␊ 450 | ␊ 451 | */␊ 452 | export interface UserEdge {␊ 453 | node: User␊ 454 | cursor: String␊ 455 | }␊ 456 | ␊ 457 | export interface UserPreviousValues {␊ 458 | id: ID_Output␊ 459 | email: String␊ 460 | password: String␊ 461 | name: String␊ 462 | }␊ 463 | ␊ 464 | export interface UserSubscriptionPayload {␊ 465 | mutation: MutationType␊ 466 | node?: User | null␊ 467 | updatedFields?: Array | null␊ 468 | previousValues?: UserPreviousValues | null␊ 469 | }␊ 470 | ␊ 471 | /*␊ 472 | The `Boolean` scalar type represents `true` or `false`.␊ 473 | */␊ 474 | export type Boolean = boolean␊ 475 | ␊ 476 | export type DateTime = Date | string␊ 477 | ␊ 478 | /*␊ 479 | The `ID` scalar type represents a unique identifier, often used to refetch an object or as key for a cache. The ID type appears in a JSON response as a String; however, it is not intended to be human-readable. When expected as an input type, any string (such as `"4"`) or integer (such as `4`) input value will be accepted as an ID.␊ 480 | */␊ 481 | export type ID_Input = string | number␊ 482 | export type ID_Output = string␊ 483 | ␊ 484 | /*␊ 485 | The `Int` scalar type represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1.␊ 486 | */␊ 487 | export type Int = number␊ 488 | ␊ 489 | /*␊ 490 | The `Long` scalar type represents non-fractional signed whole numeric values.␊ 491 | Long can represent values between -(2^63) and 2^63 - 1.␊ 492 | */␊ 493 | export type Long = string␊ 494 | ␊ 495 | /*␊ 496 | The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text.␊ 497 | */␊ 498 | export type String = string` 499 | -------------------------------------------------------------------------------- /src/codegen/TypescriptGenerator.test.ts.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dotansimha/graphql-binding/60fce38680a7b0f6e50722e5fe8c95cc39e2ea9d/src/codegen/TypescriptGenerator.test.ts.snap -------------------------------------------------------------------------------- /src/codegen/TypescriptGenerator.ts: -------------------------------------------------------------------------------- 1 | import { 2 | GraphQLSchema, 3 | GraphQLUnionType, 4 | GraphQLInterfaceType, 5 | GraphQLInputObjectType, 6 | GraphQLInputField, 7 | GraphQLField, 8 | GraphQLInputType, 9 | GraphQLOutputType, 10 | GraphQLScalarType, 11 | GraphQLEnumType, 12 | GraphQLFieldMap, 13 | GraphQLObjectType, 14 | getNamedType, 15 | isNonNullType, 16 | isListType, 17 | isObjectType, 18 | } from 'graphql' 19 | 20 | import { Generator } from './Generator' 21 | import { Maybe } from './types' 22 | 23 | export class TypescriptGenerator extends Generator { 24 | scalarMapping = { 25 | Int: 'number', 26 | String: 'string', 27 | ID: 'string | number', 28 | Float: 'number', 29 | Boolean: 'boolean', 30 | DateTime: 'Date | string', 31 | Json: 'any', 32 | } 33 | 34 | graphqlRenderers = { 35 | GraphQLUnionType: (type: GraphQLUnionType): string => { 36 | return `${this.renderDescription(type.description)}export type ${ 37 | type.name 38 | } = ${type 39 | .getTypes() 40 | .map(t => t.name) 41 | .join(' | ')}` 42 | }, 43 | 44 | GraphQLObjectType: ( 45 | type: GraphQLObjectType | GraphQLInputObjectType | GraphQLInterfaceType, 46 | ): string => this.renderInterfaceOrObject(type), 47 | 48 | GraphQLInterfaceType: ( 49 | type: GraphQLObjectType | GraphQLInputObjectType | GraphQLInterfaceType, 50 | ): string => this.renderInterfaceOrObject(type), 51 | 52 | GraphQLInputObjectType: ( 53 | type: GraphQLObjectType | GraphQLInputObjectType | GraphQLInterfaceType, 54 | ): string => { 55 | const fieldDefinition = Object.keys(type.getFields()) 56 | .map(f => { 57 | const field = type.getFields()[f] 58 | return ` ${this.renderFieldName(field)}: ${this.renderInputFieldType( 59 | field.type, 60 | )}` 61 | }) 62 | .join('\n') 63 | 64 | let interfaces: GraphQLInterfaceType[] = [] 65 | if (isObjectType(type)) { 66 | interfaces = type.getInterfaces() 67 | } 68 | 69 | return this.renderInterfaceWrapper( 70 | type.name, 71 | type.description, 72 | interfaces, 73 | fieldDefinition, 74 | ) 75 | }, 76 | 77 | GraphQLScalarType: (type: GraphQLScalarType): string => { 78 | if (type.name === 'ID') { 79 | return this.graphqlRenderers.GraphQLIDType(type) 80 | } 81 | return `${ 82 | type.description 83 | ? `/* 84 | ${type.description} 85 | */ 86 | ` 87 | : '' 88 | }export type ${type.name} = ${this.scalarMapping[type.name] || 'string'}` 89 | }, 90 | 91 | GraphQLIDType: (type: GraphQLScalarType): string => { 92 | return `${ 93 | type.description 94 | ? `/* 95 | ${type.description} 96 | */ 97 | ` 98 | : '' 99 | }export type ${type.name}_Input = ${this.scalarMapping[type.name] || 100 | 'string'} 101 | export type ${type.name}_Output = string` 102 | }, 103 | 104 | GraphQLEnumType: (type: GraphQLEnumType): string => { 105 | return `${this.renderDescription(type.description)}export type ${ 106 | type.name 107 | } = ${type 108 | .getValues() 109 | .map(e => ` '${e.name}'`) 110 | .join(' |\n')}` 111 | }, 112 | } 113 | constructor({ 114 | schema, 115 | inputSchemaPath, 116 | outputBindingPath, 117 | isDefaultExport, 118 | }: { 119 | schema: GraphQLSchema 120 | inputSchemaPath: string 121 | outputBindingPath: string 122 | isDefaultExport: boolean 123 | }) { 124 | super({ schema, inputSchemaPath, outputBindingPath, isDefaultExport }) 125 | } 126 | render() { 127 | return this.compile`\ 128 | ${this.renderImports()} 129 | 130 | export interface Query ${this.renderQueries()} 131 | 132 | export interface Mutation ${this.renderMutations()} 133 | 134 | export interface Subscription ${this.renderSubscriptions()} 135 | 136 | export interface Binding { 137 | query: Query 138 | mutation: Mutation 139 | subscription: Subscription 140 | request: (query: string, variables?: {[key: string]: any}) => Promise 141 | delegate(operation: 'query' | 'mutation', fieldName: string, args: { 142 | [key: string]: any; 143 | }, infoOrQuery?: GraphQLResolveInfo | string, options?: Options): Promise; 144 | delegateSubscription(fieldName: string, args?: { 145 | [key: string]: any; 146 | }, infoOrQuery?: GraphQLResolveInfo | string, options?: Options): Promise>; 147 | getAbstractResolvers(filterSchema?: GraphQLSchema | string): IResolvers; 148 | } 149 | 150 | export interface BindingConstructor { 151 | new(...args: any[]): T 152 | } 153 | 154 | ${this.renderExports()} 155 | 156 | /** 157 | * Types 158 | */ 159 | 160 | ${this.renderTypes()}` 161 | } 162 | renderExports() { 163 | return `export const Binding = makeBindingClass>({ schema })` 164 | } 165 | renderQueries() { 166 | const queryType = this.schema.getQueryType() 167 | if (!queryType) { 168 | return '{}' 169 | } 170 | return this.renderMainMethodFields('query', queryType.getFields()) 171 | } 172 | renderMutations() { 173 | const mutationType = this.schema.getMutationType() 174 | if (!mutationType) { 175 | return '{}' 176 | } 177 | return this.renderMainMethodFields('mutation', mutationType.getFields()) 178 | } 179 | renderSubscriptions() { 180 | const subscriptionType = this.schema.getSubscriptionType() 181 | if (!subscriptionType) { 182 | return '{}' 183 | } 184 | return this.renderMainMethodFields( 185 | 'subscription', 186 | subscriptionType.getFields(), 187 | ) 188 | } 189 | getTypeNames() { 190 | const ast = this.schema 191 | // Create types 192 | return Object.keys(ast.getTypeMap()) 193 | .filter(typeName => !typeName.startsWith('__')) 194 | .filter(typeName => 195 | ast.getQueryType() ? typeName !== ast.getQueryType()!.name : true, 196 | ) 197 | .filter(typeName => 198 | ast.getMutationType() ? typeName !== ast.getMutationType()!.name : true, 199 | ) 200 | .filter(typeName => 201 | ast.getSubscriptionType() 202 | ? typeName !== ast.getSubscriptionType()!.name 203 | : true, 204 | ) 205 | .sort((a, b) => { 206 | const typeA = ast.getType(a)! 207 | const typeB = ast.getType(b)! 208 | /** 209 | * Firstly sorted by constructor type alphabetically, 210 | * secondly sorted by their name alphabetically. 211 | */ 212 | const constructorOrder = typeA.constructor.name.localeCompare( 213 | typeB.constructor.name, 214 | ) 215 | switch (constructorOrder) { 216 | case 0: 217 | return typeA.name.localeCompare(typeB.name) 218 | 219 | default: 220 | return constructorOrder 221 | } 222 | }) 223 | } 224 | renderTypes() { 225 | const typeNames = this.getTypeNames() 226 | return typeNames 227 | .map(typeName => { 228 | const type = this.schema.getTypeMap()[typeName] 229 | return this.graphqlRenderers[type.constructor.name] 230 | ? this.graphqlRenderers[type.constructor.name](type) 231 | : null 232 | }) 233 | .join('\n\n') 234 | } 235 | 236 | renderMainMethodFields( 237 | operation: string, 238 | fields: GraphQLFieldMap, 239 | ): string { 240 | const methods = Object.keys(fields) 241 | .map(f => { 242 | const field = fields[f] 243 | const hasArgs = field.args.length > 0 244 | 245 | return ` ${field.name}: (args${hasArgs ? '' : '?'}: {${hasArgs ? ' ' : ''}${field.args 248 | .map( 249 | f => `${this.renderFieldName(f)}: ${this.renderFieldType(f.type)}`, 250 | ) 251 | .join(', ')}${ 252 | field.args.length > 0 ? ' ' : '' 253 | }}, info?: GraphQLResolveInfo | string, options?: Options) => ${this.getPayloadType( 254 | operation, 255 | isNonNullType(field.type), 256 | )} ` 257 | }) 258 | .join(',\n') 259 | 260 | return `{\n${methods}\n }` 261 | } 262 | 263 | getPayloadType(operation: string, nonNullType: boolean) { 264 | if (operation === 'subscription') { 265 | return `Promise>` 266 | } 267 | 268 | return `Promise` 269 | } 270 | 271 | renderInterfaceOrObject( 272 | type: GraphQLObjectType | GraphQLInputObjectType | GraphQLInterfaceType, 273 | ): string { 274 | const fieldDefinition = Object.keys(type.getFields()) 275 | .map(f => { 276 | const field = type.getFields()[f] 277 | return ` ${this.renderFieldName(field)}: ${this.renderFieldType( 278 | field.type, 279 | )}` 280 | }) 281 | .join('\n') 282 | 283 | let interfaces: GraphQLInterfaceType[] = [] 284 | if (isObjectType(type)) { 285 | interfaces = type.getInterfaces() 286 | } 287 | 288 | return this.renderInterfaceWrapper( 289 | type.name, 290 | type.description, 291 | interfaces, 292 | fieldDefinition, 293 | ) 294 | } 295 | 296 | renderFieldName(field: GraphQLInputField | GraphQLField) { 297 | return isNonNullType(field.type) ? field.name : `${field.name}?` 298 | } 299 | 300 | renderFieldType(type: GraphQLInputType | GraphQLOutputType): string { 301 | /* Render list type */ 302 | if (isListType(type)) { 303 | return `Array<${this.renderFieldType(type.ofType)}> | null` 304 | } 305 | 306 | /* Render non nullable type */ 307 | if (isNonNullType(type)) { 308 | return `${this.renderFieldType(type.ofType)}`.replace(/ \| null$/, '') 309 | } 310 | 311 | /* Render nullable type */ 312 | const ofType = getNamedType(type) 313 | return `${ofType.name === 'ID' ? 'ID_Output' : ofType.name} | null` 314 | } 315 | 316 | renderInputFieldType(type: GraphQLInputType | GraphQLOutputType): string { 317 | /* Render list type */ 318 | if (isListType(type)) { 319 | const typeName = this.renderFieldType(type.ofType) 320 | return `${typeName}[] | ${typeName} | null` 321 | } 322 | 323 | /* Render non nullable type */ 324 | if (isNonNullType(type)) { 325 | return `${this.renderFieldType(type.ofType)}`.replace(/ \| null$/, '') 326 | } 327 | 328 | /* Render nullable type */ 329 | const ofType = getNamedType(type) 330 | return `${ofType.name === 'ID' ? 'ID_Input' : ofType.name} | null` 331 | } 332 | 333 | renderTypeWrapper( 334 | typeName: string, 335 | typeDescription: Maybe, 336 | fieldDefinition: string, 337 | ): string { 338 | return `${this.renderDescription( 339 | typeDescription, 340 | )}export type ${typeName} = { 341 | ${fieldDefinition} 342 | }` 343 | } 344 | 345 | renderInterfaceWrapper( 346 | typeName: string, 347 | typeDescription: Maybe, 348 | interfaces: GraphQLInterfaceType[], 349 | fieldDefinition: string, 350 | ): string { 351 | return `${this.renderDescription( 352 | typeDescription, 353 | )}export interface ${typeName}${ 354 | interfaces.length > 0 355 | ? ` extends ${interfaces.map(i => i.name).join(', ')}` 356 | : '' 357 | } { 358 | ${fieldDefinition} 359 | }` 360 | } 361 | 362 | renderDescription(description: Maybe) { 363 | return `${ 364 | description 365 | ? `/* 366 | ${description.split('\n').map(l => ` * ${l}\n`)} 367 | */ 368 | ` 369 | : '' 370 | }` 371 | } 372 | renderImports() { 373 | return `\ 374 | import { makeBindingClass, Options } from 'graphql-binding' 375 | import { GraphQLResolveInfo, GraphQLSchema } from 'graphql' 376 | import { IResolvers } from 'graphql-tools-fork' 377 | import ${ 378 | this.isDefaultExport ? '' : '* as ' 379 | }schema from '${this.getRelativeSchemaPath()}'` 380 | } 381 | } 382 | -------------------------------------------------------------------------------- /src/codegen/fixtures/schema.graphql: -------------------------------------------------------------------------------- 1 | # THIS FILE HAS BEEN AUTO-GENERATED BY "PRISMA DEPLOY" 2 | # DO NOT EDIT THIS FILE DIRECTLY 3 | 4 | # 5 | # Model Types 6 | # 7 | 8 | type Post implements Node { 9 | id: ID! 10 | createdAt: DateTime! 11 | updatedAt: DateTime! 12 | isPublished: Boolean! 13 | title: String! 14 | text: String! 15 | author(where: UserWhereInput): User! 16 | } 17 | 18 | type User implements Node { 19 | id: ID! 20 | email: String! 21 | password: String! 22 | name: String! 23 | posts( 24 | where: PostWhereInput 25 | orderBy: PostOrderByInput 26 | skip: Int 27 | after: String 28 | before: String 29 | first: Int 30 | last: Int 31 | ): [Post!] 32 | } 33 | 34 | # 35 | # Other Types 36 | # 37 | 38 | type AggregatePost { 39 | count: Int! 40 | } 41 | 42 | type AggregateUser { 43 | count: Int! 44 | } 45 | 46 | type BatchPayload { 47 | """ 48 | The number of nodes that have been affected by the Batch operation. 49 | """ 50 | count: Long! 51 | } 52 | 53 | scalar DateTime 54 | 55 | """ 56 | The `Long` scalar type represents non-fractional signed whole numeric values. 57 | Long can represent values between -(2^63) and 2^63 - 1. 58 | """ 59 | scalar Long 60 | 61 | type Mutation { 62 | createPost(data: PostCreateInput!): Post! 63 | createUser(data: UserCreateInput!): User! 64 | updatePost(data: PostUpdateInput!, where: PostWhereUniqueInput!): Post 65 | updateUser(data: UserUpdateInput!, where: UserWhereUniqueInput!): User 66 | deletePost(where: PostWhereUniqueInput!): Post 67 | deleteUser(where: UserWhereUniqueInput!): User 68 | upsertPost( 69 | where: PostWhereUniqueInput! 70 | create: PostCreateInput! 71 | update: PostUpdateInput! 72 | ): Post! 73 | upsertUser( 74 | where: UserWhereUniqueInput! 75 | create: UserCreateInput! 76 | update: UserUpdateInput! 77 | ): User! 78 | updateManyPosts(data: PostUpdateInput!, where: PostWhereInput!): BatchPayload! 79 | updateManyUsers(data: UserUpdateInput!, where: UserWhereInput!): BatchPayload! 80 | deleteManyPosts(where: PostWhereInput!): BatchPayload! 81 | deleteManyUsers(where: UserWhereInput!): BatchPayload! 82 | } 83 | 84 | enum MutationType { 85 | CREATED 86 | UPDATED 87 | DELETED 88 | } 89 | 90 | """ 91 | An object with an ID 92 | """ 93 | interface Node { 94 | """ 95 | The id of the object. 96 | """ 97 | id: ID! 98 | } 99 | 100 | """ 101 | Information about pagination in a connection. 102 | """ 103 | type PageInfo { 104 | """ 105 | When paginating forwards, are there more items? 106 | """ 107 | hasNextPage: Boolean! 108 | """ 109 | When paginating backwards, are there more items? 110 | """ 111 | hasPreviousPage: Boolean! 112 | """ 113 | When paginating backwards, the cursor to continue. 114 | """ 115 | startCursor: String 116 | """ 117 | When paginating forwards, the cursor to continue. 118 | """ 119 | endCursor: String 120 | } 121 | 122 | """ 123 | A connection to a list of items. 124 | """ 125 | type PostConnection { 126 | """ 127 | Information to aid in pagination. 128 | """ 129 | pageInfo: PageInfo! 130 | """ 131 | A list of edges. 132 | """ 133 | edges: [PostEdge]! 134 | aggregate: AggregatePost! 135 | } 136 | 137 | input PostCreateInput { 138 | isPublished: Boolean 139 | title: String! 140 | text: String! 141 | author: UserCreateOneWithoutPostsInput! 142 | } 143 | 144 | input PostCreateManyWithoutAuthorInput { 145 | create: [PostCreateWithoutAuthorInput!] 146 | connect: [PostWhereUniqueInput!] 147 | } 148 | 149 | input PostCreateWithoutAuthorInput { 150 | isPublished: Boolean 151 | title: String! 152 | text: String! 153 | } 154 | 155 | """ 156 | An edge in a connection. 157 | """ 158 | type PostEdge { 159 | """ 160 | The item at the end of the edge. 161 | """ 162 | node: Post! 163 | """ 164 | A cursor for use in pagination. 165 | """ 166 | cursor: String! 167 | } 168 | 169 | enum PostOrderByInput { 170 | id_ASC 171 | id_DESC 172 | createdAt_ASC 173 | createdAt_DESC 174 | updatedAt_ASC 175 | updatedAt_DESC 176 | isPublished_ASC 177 | isPublished_DESC 178 | title_ASC 179 | title_DESC 180 | text_ASC 181 | text_DESC 182 | } 183 | 184 | type PostPreviousValues { 185 | id: ID! 186 | createdAt: DateTime! 187 | updatedAt: DateTime! 188 | isPublished: Boolean! 189 | title: String! 190 | text: String! 191 | } 192 | 193 | type PostSubscriptionPayload { 194 | mutation: MutationType! 195 | node: Post 196 | updatedFields: [String!] 197 | previousValues: PostPreviousValues 198 | } 199 | 200 | input PostSubscriptionWhereInput { 201 | """ 202 | Logical AND on all given filters. 203 | """ 204 | AND: [PostSubscriptionWhereInput!] 205 | """ 206 | Logical OR on all given filters. 207 | """ 208 | OR: [PostSubscriptionWhereInput!] 209 | """ 210 | The subscription event gets dispatched when it's listed in mutation_in 211 | """ 212 | mutation_in: [MutationType!] 213 | """ 214 | The subscription event gets only dispatched when one of the updated fields names is included in this list 215 | """ 216 | updatedFields_contains: String 217 | """ 218 | The subscription event gets only dispatched when all of the field names included in this list have been updated 219 | """ 220 | updatedFields_contains_every: [String!] 221 | """ 222 | The subscription event gets only dispatched when some of the field names included in this list have been updated 223 | """ 224 | updatedFields_contains_some: [String!] 225 | node: PostWhereInput 226 | } 227 | 228 | input PostUpdateInput { 229 | isPublished: Boolean 230 | title: String 231 | text: String 232 | author: UserUpdateOneWithoutPostsInput 233 | } 234 | 235 | input PostUpdateManyWithoutAuthorInput { 236 | create: [PostCreateWithoutAuthorInput!] 237 | connect: [PostWhereUniqueInput!] 238 | disconnect: [PostWhereUniqueInput!] 239 | delete: [PostWhereUniqueInput!] 240 | update: [PostUpdateWithWhereUniqueWithoutAuthorInput!] 241 | upsert: [PostUpsertWithWhereUniqueWithoutAuthorInput!] 242 | } 243 | 244 | input PostUpdateWithoutAuthorDataInput { 245 | isPublished: Boolean 246 | title: String 247 | text: String 248 | } 249 | 250 | input PostUpdateWithWhereUniqueWithoutAuthorInput { 251 | where: PostWhereUniqueInput! 252 | data: PostUpdateWithoutAuthorDataInput! 253 | } 254 | 255 | input PostUpsertWithWhereUniqueWithoutAuthorInput { 256 | where: PostWhereUniqueInput! 257 | update: PostUpdateWithoutAuthorDataInput! 258 | create: PostCreateWithoutAuthorInput! 259 | } 260 | 261 | input PostWhereInput { 262 | """ 263 | Logical AND on all given filters. 264 | """ 265 | AND: [PostWhereInput!] 266 | """ 267 | Logical OR on all given filters. 268 | """ 269 | OR: [PostWhereInput!] 270 | id: ID 271 | """ 272 | All values that are not equal to given value. 273 | """ 274 | id_not: ID 275 | """ 276 | All values that are contained in given list. 277 | """ 278 | id_in: [ID!] 279 | """ 280 | All values that are not contained in given list. 281 | """ 282 | id_not_in: [ID!] 283 | """ 284 | All values less than the given value. 285 | """ 286 | id_lt: ID 287 | """ 288 | All values less than or equal the given value. 289 | """ 290 | id_lte: ID 291 | """ 292 | All values greater than the given value. 293 | """ 294 | id_gt: ID 295 | """ 296 | All values greater than or equal the given value. 297 | """ 298 | id_gte: ID 299 | """ 300 | All values containing the given string. 301 | """ 302 | id_contains: ID 303 | """ 304 | All values not containing the given string. 305 | """ 306 | id_not_contains: ID 307 | """ 308 | All values starting with the given string. 309 | """ 310 | id_starts_with: ID 311 | """ 312 | All values not starting with the given string. 313 | """ 314 | id_not_starts_with: ID 315 | """ 316 | All values ending with the given string. 317 | """ 318 | id_ends_with: ID 319 | """ 320 | All values not ending with the given string. 321 | """ 322 | id_not_ends_with: ID 323 | createdAt: DateTime 324 | """ 325 | All values that are not equal to given value. 326 | """ 327 | createdAt_not: DateTime 328 | """ 329 | All values that are contained in given list. 330 | """ 331 | createdAt_in: [DateTime!] 332 | """ 333 | All values that are not contained in given list. 334 | """ 335 | createdAt_not_in: [DateTime!] 336 | """ 337 | All values less than the given value. 338 | """ 339 | createdAt_lt: DateTime 340 | """ 341 | All values less than or equal the given value. 342 | """ 343 | createdAt_lte: DateTime 344 | """ 345 | All values greater than the given value. 346 | """ 347 | createdAt_gt: DateTime 348 | """ 349 | All values greater than or equal the given value. 350 | """ 351 | createdAt_gte: DateTime 352 | updatedAt: DateTime 353 | """ 354 | All values that are not equal to given value. 355 | """ 356 | updatedAt_not: DateTime 357 | """ 358 | All values that are contained in given list. 359 | """ 360 | updatedAt_in: [DateTime!] 361 | """ 362 | All values that are not contained in given list. 363 | """ 364 | updatedAt_not_in: [DateTime!] 365 | """ 366 | All values less than the given value. 367 | """ 368 | updatedAt_lt: DateTime 369 | """ 370 | All values less than or equal the given value. 371 | """ 372 | updatedAt_lte: DateTime 373 | """ 374 | All values greater than the given value. 375 | """ 376 | updatedAt_gt: DateTime 377 | """ 378 | All values greater than or equal the given value. 379 | """ 380 | updatedAt_gte: DateTime 381 | isPublished: Boolean 382 | """ 383 | All values that are not equal to given value. 384 | """ 385 | isPublished_not: Boolean 386 | title: String 387 | """ 388 | All values that are not equal to given value. 389 | """ 390 | title_not: String 391 | """ 392 | All values that are contained in given list. 393 | """ 394 | title_in: [String!] 395 | """ 396 | All values that are not contained in given list. 397 | """ 398 | title_not_in: [String!] 399 | """ 400 | All values less than the given value. 401 | """ 402 | title_lt: String 403 | """ 404 | All values less than or equal the given value. 405 | """ 406 | title_lte: String 407 | """ 408 | All values greater than the given value. 409 | """ 410 | title_gt: String 411 | """ 412 | All values greater than or equal the given value. 413 | """ 414 | title_gte: String 415 | """ 416 | All values containing the given string. 417 | """ 418 | title_contains: String 419 | """ 420 | All values not containing the given string. 421 | """ 422 | title_not_contains: String 423 | """ 424 | All values starting with the given string. 425 | """ 426 | title_starts_with: String 427 | """ 428 | All values not starting with the given string. 429 | """ 430 | title_not_starts_with: String 431 | """ 432 | All values ending with the given string. 433 | """ 434 | title_ends_with: String 435 | """ 436 | All values not ending with the given string. 437 | """ 438 | title_not_ends_with: String 439 | text: String 440 | """ 441 | All values that are not equal to given value. 442 | """ 443 | text_not: String 444 | """ 445 | All values that are contained in given list. 446 | """ 447 | text_in: [String!] 448 | """ 449 | All values that are not contained in given list. 450 | """ 451 | text_not_in: [String!] 452 | """ 453 | All values less than the given value. 454 | """ 455 | text_lt: String 456 | """ 457 | All values less than or equal the given value. 458 | """ 459 | text_lte: String 460 | """ 461 | All values greater than the given value. 462 | """ 463 | text_gt: String 464 | """ 465 | All values greater than or equal the given value. 466 | """ 467 | text_gte: String 468 | """ 469 | All values containing the given string. 470 | """ 471 | text_contains: String 472 | """ 473 | All values not containing the given string. 474 | """ 475 | text_not_contains: String 476 | """ 477 | All values starting with the given string. 478 | """ 479 | text_starts_with: String 480 | """ 481 | All values not starting with the given string. 482 | """ 483 | text_not_starts_with: String 484 | """ 485 | All values ending with the given string. 486 | """ 487 | text_ends_with: String 488 | """ 489 | All values not ending with the given string. 490 | """ 491 | text_not_ends_with: String 492 | author: UserWhereInput 493 | } 494 | 495 | input PostWhereUniqueInput { 496 | id: ID 497 | } 498 | 499 | type Query { 500 | posts( 501 | where: PostWhereInput 502 | orderBy: PostOrderByInput 503 | skip: Int 504 | after: String 505 | before: String 506 | first: Int 507 | last: Int 508 | ): [Post]! 509 | users( 510 | where: UserWhereInput 511 | orderBy: UserOrderByInput 512 | skip: Int 513 | after: String 514 | before: String 515 | first: Int 516 | last: Int 517 | ): [User]! 518 | post(where: PostWhereUniqueInput!): Post 519 | user(where: UserWhereUniqueInput!): User 520 | postsConnection( 521 | where: PostWhereInput 522 | orderBy: PostOrderByInput 523 | skip: Int 524 | after: String 525 | before: String 526 | first: Int 527 | last: Int 528 | ): PostConnection! 529 | usersConnection( 530 | where: UserWhereInput 531 | orderBy: UserOrderByInput 532 | skip: Int 533 | after: String 534 | before: String 535 | first: Int 536 | last: Int 537 | ): UserConnection! 538 | """ 539 | Fetches an object given its ID 540 | """ 541 | node( 542 | """ 543 | The ID of an object 544 | """ 545 | id: ID! 546 | ): Node 547 | } 548 | 549 | type Subscription { 550 | post(where: PostSubscriptionWhereInput): PostSubscriptionPayload 551 | user(where: UserSubscriptionWhereInput): UserSubscriptionPayload 552 | } 553 | 554 | """ 555 | A connection to a list of items. 556 | """ 557 | type UserConnection { 558 | """ 559 | Information to aid in pagination. 560 | """ 561 | pageInfo: PageInfo! 562 | """ 563 | A list of edges. 564 | """ 565 | edges: [UserEdge]! 566 | aggregate: AggregateUser! 567 | } 568 | 569 | input UserCreateInput { 570 | email: String! 571 | password: String! 572 | name: String! 573 | posts: PostCreateManyWithoutAuthorInput 574 | } 575 | 576 | input UserCreateOneWithoutPostsInput { 577 | create: UserCreateWithoutPostsInput 578 | connect: UserWhereUniqueInput 579 | } 580 | 581 | input UserCreateWithoutPostsInput { 582 | email: String! 583 | password: String! 584 | name: String! 585 | } 586 | 587 | """ 588 | An edge in a connection. 589 | """ 590 | type UserEdge { 591 | """ 592 | The item at the end of the edge. 593 | """ 594 | node: User! 595 | """ 596 | A cursor for use in pagination. 597 | """ 598 | cursor: String! 599 | } 600 | 601 | enum UserOrderByInput { 602 | id_ASC 603 | id_DESC 604 | email_ASC 605 | email_DESC 606 | password_ASC 607 | password_DESC 608 | name_ASC 609 | name_DESC 610 | updatedAt_ASC 611 | updatedAt_DESC 612 | createdAt_ASC 613 | createdAt_DESC 614 | } 615 | 616 | type UserPreviousValues { 617 | id: ID! 618 | email: String! 619 | password: String! 620 | name: String! 621 | } 622 | 623 | type UserSubscriptionPayload { 624 | mutation: MutationType! 625 | node: User 626 | updatedFields: [String!] 627 | previousValues: UserPreviousValues 628 | } 629 | 630 | input UserSubscriptionWhereInput { 631 | """ 632 | Logical AND on all given filters. 633 | """ 634 | AND: [UserSubscriptionWhereInput!] 635 | """ 636 | Logical OR on all given filters. 637 | """ 638 | OR: [UserSubscriptionWhereInput!] 639 | """ 640 | The subscription event gets dispatched when it's listed in mutation_in 641 | """ 642 | mutation_in: [MutationType!] 643 | """ 644 | The subscription event gets only dispatched when one of the updated fields names is included in this list 645 | """ 646 | updatedFields_contains: String 647 | """ 648 | The subscription event gets only dispatched when all of the field names included in this list have been updated 649 | """ 650 | updatedFields_contains_every: [String!] 651 | """ 652 | The subscription event gets only dispatched when some of the field names included in this list have been updated 653 | """ 654 | updatedFields_contains_some: [String!] 655 | node: UserWhereInput 656 | } 657 | 658 | input UserUpdateInput { 659 | email: String 660 | password: String 661 | name: String 662 | posts: PostUpdateManyWithoutAuthorInput 663 | } 664 | 665 | input UserUpdateOneWithoutPostsInput { 666 | create: UserCreateWithoutPostsInput 667 | connect: UserWhereUniqueInput 668 | delete: Boolean 669 | update: UserUpdateWithoutPostsDataInput 670 | upsert: UserUpsertWithoutPostsInput 671 | } 672 | 673 | input UserUpdateWithoutPostsDataInput { 674 | email: String 675 | password: String 676 | name: String 677 | } 678 | 679 | input UserUpsertWithoutPostsInput { 680 | update: UserUpdateWithoutPostsDataInput! 681 | create: UserCreateWithoutPostsInput! 682 | } 683 | 684 | input UserWhereInput { 685 | """ 686 | Logical AND on all given filters. 687 | """ 688 | AND: [UserWhereInput!] 689 | """ 690 | Logical OR on all given filters. 691 | """ 692 | OR: [UserWhereInput!] 693 | id: ID 694 | """ 695 | All values that are not equal to given value. 696 | """ 697 | id_not: ID 698 | """ 699 | All values that are contained in given list. 700 | """ 701 | id_in: [ID!] 702 | """ 703 | All values that are not contained in given list. 704 | """ 705 | id_not_in: [ID!] 706 | """ 707 | All values less than the given value. 708 | """ 709 | id_lt: ID 710 | """ 711 | All values less than or equal the given value. 712 | """ 713 | id_lte: ID 714 | """ 715 | All values greater than the given value. 716 | """ 717 | id_gt: ID 718 | """ 719 | All values greater than or equal the given value. 720 | """ 721 | id_gte: ID 722 | """ 723 | All values containing the given string. 724 | """ 725 | id_contains: ID 726 | """ 727 | All values not containing the given string. 728 | """ 729 | id_not_contains: ID 730 | """ 731 | All values starting with the given string. 732 | """ 733 | id_starts_with: ID 734 | """ 735 | All values not starting with the given string. 736 | """ 737 | id_not_starts_with: ID 738 | """ 739 | All values ending with the given string. 740 | """ 741 | id_ends_with: ID 742 | """ 743 | All values not ending with the given string. 744 | """ 745 | id_not_ends_with: ID 746 | email: String 747 | """ 748 | All values that are not equal to given value. 749 | """ 750 | email_not: String 751 | """ 752 | All values that are contained in given list. 753 | """ 754 | email_in: [String!] 755 | """ 756 | All values that are not contained in given list. 757 | """ 758 | email_not_in: [String!] 759 | """ 760 | All values less than the given value. 761 | """ 762 | email_lt: String 763 | """ 764 | All values less than or equal the given value. 765 | """ 766 | email_lte: String 767 | """ 768 | All values greater than the given value. 769 | """ 770 | email_gt: String 771 | """ 772 | All values greater than or equal the given value. 773 | """ 774 | email_gte: String 775 | """ 776 | All values containing the given string. 777 | """ 778 | email_contains: String 779 | """ 780 | All values not containing the given string. 781 | """ 782 | email_not_contains: String 783 | """ 784 | All values starting with the given string. 785 | """ 786 | email_starts_with: String 787 | """ 788 | All values not starting with the given string. 789 | """ 790 | email_not_starts_with: String 791 | """ 792 | All values ending with the given string. 793 | """ 794 | email_ends_with: String 795 | """ 796 | All values not ending with the given string. 797 | """ 798 | email_not_ends_with: String 799 | password: String 800 | """ 801 | All values that are not equal to given value. 802 | """ 803 | password_not: String 804 | """ 805 | All values that are contained in given list. 806 | """ 807 | password_in: [String!] 808 | """ 809 | All values that are not contained in given list. 810 | """ 811 | password_not_in: [String!] 812 | """ 813 | All values less than the given value. 814 | """ 815 | password_lt: String 816 | """ 817 | All values less than or equal the given value. 818 | """ 819 | password_lte: String 820 | """ 821 | All values greater than the given value. 822 | """ 823 | password_gt: String 824 | """ 825 | All values greater than or equal the given value. 826 | """ 827 | password_gte: String 828 | """ 829 | All values containing the given string. 830 | """ 831 | password_contains: String 832 | """ 833 | All values not containing the given string. 834 | """ 835 | password_not_contains: String 836 | """ 837 | All values starting with the given string. 838 | """ 839 | password_starts_with: String 840 | """ 841 | All values not starting with the given string. 842 | """ 843 | password_not_starts_with: String 844 | """ 845 | All values ending with the given string. 846 | """ 847 | password_ends_with: String 848 | """ 849 | All values not ending with the given string. 850 | """ 851 | password_not_ends_with: String 852 | name: String 853 | """ 854 | All values that are not equal to given value. 855 | """ 856 | name_not: String 857 | """ 858 | All values that are contained in given list. 859 | """ 860 | name_in: [String!] 861 | """ 862 | All values that are not contained in given list. 863 | """ 864 | name_not_in: [String!] 865 | """ 866 | All values less than the given value. 867 | """ 868 | name_lt: String 869 | """ 870 | All values less than or equal the given value. 871 | """ 872 | name_lte: String 873 | """ 874 | All values greater than the given value. 875 | """ 876 | name_gt: String 877 | """ 878 | All values greater than or equal the given value. 879 | """ 880 | name_gte: String 881 | """ 882 | All values containing the given string. 883 | """ 884 | name_contains: String 885 | """ 886 | All values not containing the given string. 887 | """ 888 | name_not_contains: String 889 | """ 890 | All values starting with the given string. 891 | """ 892 | name_starts_with: String 893 | """ 894 | All values not starting with the given string. 895 | """ 896 | name_not_starts_with: String 897 | """ 898 | All values ending with the given string. 899 | """ 900 | name_ends_with: String 901 | """ 902 | All values not ending with the given string. 903 | """ 904 | name_not_ends_with: String 905 | posts_every: PostWhereInput 906 | posts_some: PostWhereInput 907 | posts_none: PostWhereInput 908 | } 909 | 910 | input UserWhereUniqueInput { 911 | id: ID 912 | email: String 913 | } 914 | -------------------------------------------------------------------------------- /src/codegen/types.ts: -------------------------------------------------------------------------------- 1 | export type GeneratorType = 'typescript' | 'javascript' 2 | 3 | export type Interpolation

= 4 | | FlattenInterpolation

5 | | ReadonlyArray< 6 | FlattenInterpolation

| ReadonlyArray> 7 | > 8 | export type FlattenInterpolation

= 9 | | InterpolationValue 10 | | InterpolationFunction

11 | export type InterpolationValue = string | number | boolean 12 | export type SimpleInterpolation = 13 | | InterpolationValue 14 | | ReadonlyArray> 15 | export interface InterpolationFunction

{ 16 | (props: P): Interpolation

17 | } 18 | 19 | export type Maybe = null | undefined | T 20 | -------------------------------------------------------------------------------- /src/codegen/utils/flatten.ts: -------------------------------------------------------------------------------- 1 | import { Interpolation } from '../types' 2 | 3 | const flatten = ( 4 | chunks: Interpolation[], 5 | executionContext: T, 6 | ): Interpolation[] => 7 | chunks.reduce((ruleSet: Interpolation[], chunk?: Interpolation) => { 8 | /* Remove falsey values */ 9 | if ( 10 | chunk === undefined || 11 | chunk === null || 12 | chunk === false || 13 | chunk === '' 14 | ) { 15 | return ruleSet 16 | } 17 | /* Flatten ruleSet */ 18 | if (Array.isArray(chunk)) { 19 | return [...ruleSet, ...flatten(chunk, executionContext)] 20 | } 21 | 22 | /* Either execute or defer the function */ 23 | if (typeof chunk === 'function') { 24 | return executionContext 25 | ? ruleSet.concat( 26 | ...flatten([chunk(executionContext)], executionContext), 27 | ) 28 | : ruleSet.concat(chunk) 29 | } 30 | 31 | return ruleSet.concat(chunk.toString()) 32 | }, []) 33 | 34 | export default flatten 35 | -------------------------------------------------------------------------------- /src/codegen/utils/interleave.ts: -------------------------------------------------------------------------------- 1 | import { Interpolation } from '../types' 2 | 3 | export const interleave = ( 4 | strings: TemplateStringsArray, 5 | interpolations: Interpolation[], 6 | ): Interpolation[] => 7 | interpolations.reduce( 8 | (array: Interpolation[], interp: Interpolation, i: number) => 9 | array.concat(interp, strings[i + 1]), 10 | [strings[0]], 11 | ) 12 | -------------------------------------------------------------------------------- /src/fragmentReplacements.ts: -------------------------------------------------------------------------------- 1 | import { FragmentReplacement } from './types' 2 | import { IResolvers } from 'graphql-tools-fork' 3 | 4 | export function extractFragmentReplacements( 5 | resolvers: IResolvers, 6 | ): FragmentReplacement[] { 7 | const fragmentReplacements: FragmentReplacement[] = [] 8 | 9 | for (const typeName in resolvers) { 10 | const fieldResolvers: any = resolvers[typeName] 11 | for (const fieldName in fieldResolvers) { 12 | const fieldResolver = fieldResolvers[fieldName] 13 | if (typeof fieldResolver === 'object' && fieldResolver.fragment) { 14 | fragmentReplacements.push({ 15 | field: fieldName, 16 | fragment: fieldResolver.fragment, 17 | }) 18 | } 19 | } 20 | } 21 | 22 | return fragmentReplacements 23 | } 24 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export { addFragmentToInfo } from './utils/addFragmentToInfo' 2 | 3 | export { extractFragmentReplacements } from './fragmentReplacements' 4 | export { buildInfo } from './info' 5 | export { Binding } from './Binding' 6 | export { Delegate } from './Delegate' 7 | export { BindingOptions, FragmentReplacement, Options } from './types' 8 | export { forwardTo } from './utils' 9 | export { makeBindingClass } from './makeBindingClass' 10 | export { Generator } from './codegen/Generator' 11 | export { TypescriptGenerator } from './codegen/TypescriptGenerator' 12 | export { FlowGenerator } from './codegen/FlowGenerator' 13 | -------------------------------------------------------------------------------- /src/info.test.ts: -------------------------------------------------------------------------------- 1 | import test, { ExecutionContext } from 'ava' 2 | import { 3 | buildSchema, 4 | SelectionNode, 5 | FieldNode, 6 | GraphQLResolveInfo, 7 | } from 'graphql' 8 | import { 9 | buildInfoForAllScalars, 10 | buildInfoFromFragment, 11 | makeSubInfo, 12 | } from './info' 13 | import { omitDeep } from './utils/removeKey' 14 | import { printDocumentFromInfo } from './utils' 15 | 16 | test('buildInfoForAllScalars: 1 field', t => { 17 | const schema = buildSchema(` 18 | type Query { 19 | book: Book 20 | } 21 | 22 | type Book { 23 | title: String 24 | } 25 | `) 26 | const info = buildInfoForAllScalars('book', schema, 'query') 27 | const selections = info.fieldNodes[0].selectionSet!.selections 28 | 29 | assertFields(t, selections, ['title']) 30 | }) 31 | 32 | test('buildInfoForAllScalars: 2 fields', t => { 33 | const schema = buildSchema(` 34 | type Query { 35 | book: Book 36 | } 37 | 38 | type Book { 39 | title: String 40 | number: Float 41 | } 42 | `) 43 | const info = buildInfoForAllScalars('book', schema, 'query') 44 | const selections = info.fieldNodes[0].selectionSet!.selections 45 | 46 | assertFields(t, selections, ['title', 'number']) 47 | t.is(info.fieldName, 'book') 48 | }) 49 | 50 | test('buildInfoForAllScalars: excludes object type fields', t => { 51 | const schema = buildSchema(` 52 | type Query { 53 | book: Book 54 | } 55 | 56 | type Book { 57 | title: String 58 | number: Float 59 | otherBook: Book 60 | } 61 | `) 62 | const info = buildInfoForAllScalars('book', schema, 'query') 63 | const selections = info.fieldNodes[0].selectionSet!.selections 64 | 65 | assertFields(t, selections, ['title', 'number']) 66 | t.is(info.fieldName, 'book') 67 | }) 68 | 69 | test('buildInfoForAllScalars: support interfaces', t => { 70 | const schema = buildSchema(` 71 | type Query { 72 | book: IBook 73 | } 74 | 75 | type Book implements IBook { 76 | title: String 77 | number: Float 78 | otherBook: IBook 79 | } 80 | 81 | interface IBook { 82 | title: String 83 | number: Float 84 | otherBook: Book 85 | } 86 | `) 87 | const info = buildInfoForAllScalars('book', schema, 'query') 88 | const selections = info.fieldNodes[0].selectionSet!.selections 89 | 90 | assertFields(t, selections, ['title', 'number']) 91 | t.is(info.fieldName, 'book') 92 | }) 93 | 94 | test('buildInfoForAllScalars: enums', t => { 95 | const schema = buildSchema(` 96 | type Query { 97 | book: Book 98 | } 99 | 100 | type Book { 101 | color: Color 102 | } 103 | 104 | enum Color { Red, Blue } 105 | `) 106 | const info = buildInfoForAllScalars('book', schema, 'query') 107 | const selections = info.fieldNodes[0].selectionSet!.selections 108 | 109 | assertFields(t, selections, ['color']) 110 | }) 111 | 112 | test('buildInfoForAllScalars: minimal static root field', t => { 113 | const schema = buildSchema(` 114 | type Query { 115 | count: Int 116 | } 117 | `) 118 | const info = buildInfoForAllScalars('count', schema, 'query') 119 | t.is(info.fieldNodes.length, 1) 120 | }) 121 | 122 | test('buildInfoForAllScalars: mutation', t => { 123 | const schema = buildSchema(` 124 | type Query { 125 | book: Int # use name root field name but different type 126 | } 127 | 128 | type Mutation { 129 | book: Book 130 | } 131 | 132 | type Book { 133 | title: String 134 | } 135 | `) 136 | const info = buildInfoForAllScalars('book', schema, 'mutation') 137 | const selections = info.fieldNodes[0].selectionSet!.selections 138 | 139 | assertFields(t, selections, ['title']) 140 | }) 141 | 142 | test('buildInfoForAllScalars: throws error when field not found', t => { 143 | const schema = buildSchema(` 144 | type Query { 145 | count: Int 146 | } 147 | `) 148 | t.throws(() => buildInfoForAllScalars('other', schema, 'query')) 149 | }) 150 | 151 | test('buildInfoFromFragment: 1 field', t => { 152 | const schema = buildSchema(` 153 | type Query { 154 | book: Book 155 | } 156 | 157 | type Book { 158 | title: String 159 | } 160 | `) 161 | const info = buildInfoFromFragment('book', schema, 'query', `{ title }`) 162 | const selections = info.fieldNodes[0].selectionSet!.selections 163 | 164 | assertFields(t, selections, ['title']) 165 | }) 166 | 167 | test('buildInfoFromFragment: nested', t => { 168 | const schema = buildSchema(` 169 | type Query { 170 | book: Book 171 | } 172 | 173 | type Book { 174 | title: String 175 | otherBook: Book 176 | } 177 | `) 178 | const fragment = `{ title otherBook { otherBook { title } } }` 179 | const info = buildInfoFromFragment('book', schema, 'query', fragment) 180 | const selections = info.fieldNodes[0].selectionSet!.selections as any 181 | 182 | t.is(selections[0].name.value, 'title') 183 | t.is(selections[1].name.value, 'otherBook') 184 | t.is(selections[1].selectionSet.selections[0].name.value, 'otherBook') 185 | t.is( 186 | selections[1].selectionSet.selections[0].selectionSet.selections[0].name 187 | .value, 188 | 'title', 189 | ) 190 | }) 191 | 192 | test('buildInfoFromFragment: invalid selection', t => { 193 | const schema = buildSchema(` 194 | type Query { 195 | book: Book 196 | } 197 | 198 | type Book { 199 | title: String 200 | } 201 | `) 202 | try { 203 | buildInfoFromFragment('book', schema, 'query', `{ xxx }`) 204 | t.fail() 205 | } catch (err) { 206 | t.pass() 207 | } 208 | }) 209 | 210 | test('makeSubInfo: works when path has been selected', t => { 211 | const schema = buildSchema(` 212 | type Query { 213 | book: Book 214 | } 215 | 216 | type Book { 217 | title: String 218 | extraField: String 219 | page: Page 220 | } 221 | 222 | type Page { 223 | content: String 224 | wordCount: Int 225 | } 226 | `) 227 | const info = buildInfoFromFragment( 228 | 'book', 229 | schema, 230 | 'query', 231 | `{ title page { content wordCount } }`, 232 | ) 233 | 234 | const subInfo = makeSubInfo(info, 'page')! 235 | 236 | t.snapshot(printDocumentFromInfo(subInfo)) 237 | t.snapshot(getRelevantPartsFromInfo(subInfo)) 238 | }) 239 | 240 | test('makeSubInfo: works when path has been selected and adds fragment', t => { 241 | const schema = buildSchema(` 242 | type Query { 243 | book: Book 244 | } 245 | 246 | type Book { 247 | title: String 248 | extraField: String 249 | page: Page 250 | } 251 | 252 | type Page { 253 | content: String 254 | wordCount: Int 255 | } 256 | `) 257 | const info = buildInfoFromFragment( 258 | 'book', 259 | schema, 260 | 'query', 261 | `{ title page { content } }`, 262 | ) 263 | 264 | const subInfo = makeSubInfo( 265 | info, 266 | 'page', 267 | 'fragment Frag on Page { wordCount }', 268 | )! 269 | 270 | t.snapshot(printDocumentFromInfo(subInfo)) 271 | }) 272 | 273 | test('makeSubInfo: works with inline fragment', t => { 274 | const schema = buildSchema(` 275 | type Query { 276 | book: Book 277 | } 278 | 279 | type Book { 280 | title: String 281 | extraField: String 282 | page: Page 283 | } 284 | 285 | type Page { 286 | content: String 287 | wordCount: Int 288 | } 289 | `) 290 | const info = buildInfoFromFragment( 291 | 'book', 292 | schema, 293 | 'query', 294 | `{ title ... on Book { page { content } } }`, 295 | ) 296 | 297 | const subInfo = makeSubInfo(info, 'page')! 298 | 299 | t.snapshot(printDocumentFromInfo(subInfo)) 300 | t.snapshot(getRelevantPartsFromInfo(subInfo)) 301 | }) 302 | 303 | function getRelevantPartsFromInfo(info: GraphQLResolveInfo) { 304 | const { 305 | fragments, 306 | fieldName, 307 | returnType, 308 | parentType, 309 | path, 310 | rootValue, 311 | operation, 312 | variableValues, 313 | fieldNodes, 314 | } = info 315 | 316 | return { 317 | fragments, 318 | fieldName, 319 | returnType: returnType.toString(), 320 | parentType: parentType.toString(), 321 | path, 322 | rootValue, 323 | operation, 324 | variableValues, 325 | selectionSet: omitDeep(fieldNodes[0].selectionSet, 'loc'), 326 | } 327 | } 328 | 329 | test('makeSubInfo: returns null when path has not been selected', t => { 330 | const schema = buildSchema(` 331 | type Query { 332 | book: Book 333 | } 334 | 335 | type Book { 336 | title: String 337 | extraField: String 338 | page: Page 339 | } 340 | 341 | type Page { 342 | content: String 343 | wordCount: Int 344 | } 345 | `) 346 | const info = buildInfoFromFragment('book', schema, 'query', `{ title }`) 347 | 348 | const subInfo = makeSubInfo(info, 'page')! 349 | 350 | t.is(subInfo, null) 351 | }) 352 | 353 | export function assertFields( 354 | t: ExecutionContext, 355 | selections: ReadonlyArray, 356 | names: string[], 357 | ) { 358 | const fields = names.map(value => ({ 359 | kind: 'Field', 360 | name: { kind: 'Name', value }, 361 | })) 362 | 363 | for (const field of fields) { 364 | t.true( 365 | selections.some( 366 | s => s.kind === 'Field' && s.name.value === field.name.value, 367 | ), 368 | ) 369 | } 370 | 371 | t.is(selections.length, names.length) 372 | } 373 | -------------------------------------------------------------------------------- /src/info.test.ts.md: -------------------------------------------------------------------------------- 1 | # Snapshot report for `src/info.test.ts` 2 | 3 | The actual snapshot is saved in `info.test.ts.snap`. 4 | 5 | Generated by [AVA](https://ava.li). 6 | 7 | ## makeSubInfo: works when path has been selected 8 | 9 | > Snapshot 1 10 | 11 | `{␊ 12 | content␊ 13 | wordCount␊ 14 | }␊ 15 | ` 16 | 17 | > Snapshot 2 18 | 19 | { 20 | fieldName: 'page', 21 | fragments: {}, 22 | operation: { 23 | kind: 'OperationDefinition', 24 | operation: 'page', 25 | selectionSet: { 26 | kind: 'SelectionSet', 27 | selections: [], 28 | }, 29 | variableDefinitions: [], 30 | }, 31 | parentType: 'Book', 32 | path: { 33 | key: 'page', 34 | prev: { 35 | key: 'book', 36 | prev: undefined, 37 | }, 38 | }, 39 | returnType: 'Page', 40 | rootValue: undefined, 41 | selectionSet: { 42 | kind: 'SelectionSet', 43 | selections: { 44 | 0: { 45 | alias: undefined, 46 | arguments: {}, 47 | directives: {}, 48 | kind: 'Field', 49 | name: { 50 | kind: 'Name', 51 | value: 'content', 52 | }, 53 | selectionSet: undefined, 54 | }, 55 | 1: { 56 | alias: undefined, 57 | arguments: {}, 58 | directives: {}, 59 | kind: 'Field', 60 | name: { 61 | kind: 'Name', 62 | value: 'wordCount', 63 | }, 64 | selectionSet: undefined, 65 | }, 66 | }, 67 | }, 68 | variableValues: {}, 69 | } 70 | 71 | ## makeSubInfo: works when path has been selected and adds fragment 72 | 73 | > Snapshot 1 74 | 75 | `{␊ 76 | content␊ 77 | wordCount␊ 78 | }␊ 79 | ` 80 | 81 | ## makeSubInfo: works with inline fragment 82 | 83 | > Snapshot 1 84 | 85 | `{␊ 86 | page {␊ 87 | content␊ 88 | }␊ 89 | }␊ 90 | ` 91 | 92 | > Snapshot 2 93 | 94 | { 95 | fieldName: 'page', 96 | fragments: {}, 97 | operation: { 98 | kind: 'OperationDefinition', 99 | operation: 'page', 100 | selectionSet: { 101 | kind: 'SelectionSet', 102 | selections: [], 103 | }, 104 | variableDefinitions: [], 105 | }, 106 | parentType: 'Book', 107 | path: { 108 | key: 'page', 109 | prev: { 110 | key: 'book', 111 | prev: undefined, 112 | }, 113 | }, 114 | returnType: 'Page', 115 | rootValue: undefined, 116 | selectionSet: { 117 | kind: 'SelectionSet', 118 | selections: { 119 | 0: { 120 | alias: undefined, 121 | arguments: {}, 122 | directives: {}, 123 | kind: 'Field', 124 | name: { 125 | kind: 'Name', 126 | value: 'page', 127 | }, 128 | selectionSet: { 129 | kind: 'SelectionSet', 130 | selections: { 131 | 0: { 132 | alias: undefined, 133 | arguments: {}, 134 | directives: {}, 135 | kind: 'Field', 136 | name: { 137 | kind: 'Name', 138 | value: 'content', 139 | }, 140 | selectionSet: undefined, 141 | }, 142 | }, 143 | }, 144 | }, 145 | }, 146 | }, 147 | variableValues: {}, 148 | } 149 | -------------------------------------------------------------------------------- /src/info.test.ts.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dotansimha/graphql-binding/60fce38680a7b0f6e50722e5fe8c95cc39e2ea9d/src/info.test.ts.snap -------------------------------------------------------------------------------- /src/info.ts: -------------------------------------------------------------------------------- 1 | import { 2 | GraphQLSchema, 3 | GraphQLResolveInfo, 4 | FieldNode, 5 | SelectionSetNode, 6 | GraphQLOutputType, 7 | GraphQLObjectType, 8 | GraphQLScalarType, 9 | Kind, 10 | getNamedType, 11 | DocumentNode, 12 | print, 13 | parse, 14 | validate, 15 | isObjectType, 16 | isInterfaceType, 17 | isScalarType, 18 | } from 'graphql' 19 | 20 | import { Operation } from './types' 21 | import { isScalar, getTypeForRootFieldName } from './utils' 22 | import { addFragmentToInfo } from './utils/addFragmentToInfo' 23 | 24 | export function buildInfo( 25 | rootFieldName: string, 26 | operation: Operation, 27 | schema: GraphQLSchema, 28 | info?: GraphQLResolveInfo | string | DocumentNode, 29 | ): GraphQLResolveInfo { 30 | if (!info) { 31 | info = buildInfoForAllScalars(rootFieldName, schema, operation) 32 | } else if ((info as any).kind && (info as any).kind === 'Document') { 33 | info = print(info as DocumentNode) 34 | } 35 | if (typeof info === 'string') { 36 | info = buildInfoFromFragment(rootFieldName, schema, operation, info) 37 | } 38 | return info as any 39 | } 40 | 41 | export function buildInfoForAllScalars( 42 | rootFieldName: string, 43 | schema: GraphQLSchema, 44 | operation: Operation, 45 | ): GraphQLResolveInfo { 46 | const fieldNodes: FieldNode[] = [] 47 | const type = getTypeForRootFieldName(rootFieldName, operation, schema) 48 | const namedType = getNamedType(type) 49 | 50 | let selections: FieldNode[] | undefined 51 | if (isInterfaceType(namedType) || isObjectType(namedType)) { 52 | const fields = (namedType as any).getFields() 53 | selections = Object.keys(fields) 54 | .filter(f => isScalar(fields[f].type)) 55 | .map(fieldName => { 56 | const field = fields[fieldName] 57 | return { 58 | kind: 'Field', 59 | name: { kind: 'Name', value: field.name }, 60 | } 61 | }) 62 | } 63 | 64 | const fieldNode: FieldNode = { 65 | kind: 'Field', 66 | name: { kind: 'Name', value: rootFieldName }, 67 | selectionSet: selections ? { kind: 'SelectionSet', selections } : undefined, 68 | } 69 | 70 | fieldNodes.push(fieldNode) 71 | 72 | const parentType = 73 | { 74 | query: () => schema.getQueryType(), 75 | mutation: () => schema.getMutationType()!, 76 | subscription: () => schema.getSubscriptionType()!, 77 | }[operation]() || undefined 78 | 79 | return { 80 | fieldNodes, 81 | fragments: {}, 82 | schema, 83 | fieldName: rootFieldName, 84 | returnType: type, 85 | parentType: parentType!, 86 | path: undefined!, 87 | rootValue: undefined, 88 | operation: { 89 | kind: 'OperationDefinition', 90 | operation, 91 | selectionSet: { kind: 'SelectionSet', selections: [] }, 92 | variableDefinitions: [], 93 | }, 94 | variableValues: {}, 95 | } 96 | } 97 | 98 | export function buildInfoFromFragment( 99 | rootFieldName: string, 100 | schema: GraphQLSchema, 101 | operation: Operation, 102 | query: string, 103 | ): GraphQLResolveInfo { 104 | const type = getTypeForRootFieldName(rootFieldName, operation, schema) 105 | const namedType = getNamedType(type)! 106 | const fieldNode: FieldNode = { 107 | kind: 'Field', 108 | name: { kind: 'Name', value: rootFieldName }, 109 | selectionSet: extractQuerySelectionSet(query, namedType.name!, schema), 110 | } 111 | 112 | return { 113 | fieldNodes: [fieldNode], 114 | fragments: {}, 115 | schema, 116 | fieldName: rootFieldName, 117 | returnType: type, 118 | parentType: schema.getQueryType() || undefined!, 119 | path: undefined!, 120 | rootValue: undefined, 121 | operation: { 122 | kind: 'OperationDefinition', 123 | operation, 124 | selectionSet: { kind: 'SelectionSet', selections: [] }, 125 | variableDefinitions: [], 126 | }, 127 | variableValues: {}, 128 | } 129 | } 130 | 131 | function extractQuerySelectionSet( 132 | query: string, 133 | typeName: string, 134 | schema: GraphQLSchema, 135 | ): SelectionSetNode { 136 | if (!query.startsWith('fragment')) { 137 | query = `fragment tmp on ${typeName} ${query}` 138 | } 139 | const document = parse(query) 140 | const errors = validate(schema, document).filter( 141 | e => e.message.match(/Fragment ".*" is never used./) === null, 142 | ) 143 | if (errors.length > 0) { 144 | throw errors 145 | } 146 | 147 | const queryNode = document.definitions[0] 148 | if (!queryNode || queryNode.kind !== 'FragmentDefinition') { 149 | throw new Error(`Invalid query: ${query}`) 150 | } 151 | 152 | return queryNode.selectionSet 153 | } 154 | 155 | /** 156 | * Generates a sub info based on the provided path. If provided path is not included in the selection set, the function returns null. 157 | * @param info GraphQLResolveInfo 158 | * @param path string 159 | * @param fragment string | undefined 160 | */ 161 | export function makeSubInfo( 162 | info: GraphQLResolveInfo, 163 | path: string, 164 | fragment?: string, 165 | ): GraphQLResolveInfo | null { 166 | const returnType = getDeepType(info.returnType) 167 | if (isScalarType(returnType)) { 168 | throw new Error(`Can't make subInfo for type ${info.returnType.toString()}`) 169 | } 170 | 171 | const splittedPath = path.split('.') 172 | const fieldsToTraverse = splittedPath.slice() 173 | let currentType = info.returnType 174 | let currentSelectionSet = info.fieldNodes[0].selectionSet! 175 | let currentFieldName 176 | let parentType 177 | let currentPath = info.path || { 178 | prev: undefined, 179 | key: info.fieldNodes[0].name.value, 180 | } 181 | 182 | while (fieldsToTraverse.length > 0) { 183 | currentFieldName = fieldsToTraverse.shift()! 184 | if (!isObjectType(currentType)) { 185 | throw new Error( 186 | `Can't get subInfo for type ${currentType.toString()} as needs to be a GraphQLObjectType`, 187 | ) 188 | } 189 | 190 | const fields = (currentType as any).getFields() 191 | if (!fields[currentFieldName]) { 192 | throw new Error( 193 | `Type ${currentType.toString()} has no field called ${currentFieldName}`, 194 | ) 195 | } 196 | 197 | const currentFieldType = fields[currentFieldName].type 198 | if (!isObjectType(currentFieldType)) { 199 | throw new Error( 200 | `Can't get subInfo for type ${currentFieldType} of field ${currentFieldName} on type ${currentType.toString()}`, 201 | ) 202 | } 203 | parentType = currentType 204 | currentType = currentFieldType 205 | let suitableSelection = currentSelectionSet.selections!.find( 206 | selection => 207 | selection.kind === 'Field' && selection.name.value === currentFieldName, 208 | ) 209 | 210 | if (!suitableSelection) { 211 | // if there is no field selection, there still could be fragments 212 | currentSelectionSet = currentSelectionSet.selections.reduce( 213 | (acc: any, curr) => { 214 | if (acc) { 215 | return acc 216 | } 217 | if (curr.kind === 'InlineFragment') { 218 | return curr.selectionSet 219 | } 220 | }, 221 | null, 222 | )! 223 | } else if (suitableSelection.kind === 'Field') { 224 | currentSelectionSet = suitableSelection.selectionSet! 225 | } 226 | 227 | if (!currentSelectionSet) { 228 | return null 229 | } 230 | 231 | currentPath = addPath(currentPath, currentFieldName) 232 | } 233 | 234 | const fieldNode: FieldNode = { 235 | kind: 'Field', 236 | name: { kind: 'Name', value: currentFieldName }, 237 | selectionSet: currentSelectionSet, 238 | } 239 | 240 | const newInfo = { 241 | fieldNodes: [fieldNode], 242 | fragments: {}, 243 | schema: info.schema, 244 | fieldName: currentFieldName, 245 | returnType: currentType, 246 | parentType, 247 | path: currentPath, 248 | rootValue: undefined, 249 | operation: { 250 | kind: Kind.OPERATION_DEFINITION, 251 | operation: currentFieldName, 252 | selectionSet: { kind: Kind.SELECTION_SET, selections: [] }, 253 | variableDefinitions: [], 254 | }, 255 | variableValues: {}, 256 | } 257 | 258 | if (fragment) { 259 | return addFragmentToInfo(newInfo, fragment) 260 | } 261 | 262 | return newInfo 263 | } 264 | 265 | export function getDeepType( 266 | type: GraphQLOutputType, 267 | ): GraphQLObjectType | GraphQLScalarType { 268 | if ((type as any).ofType) { 269 | return getDeepType((type as any).ofType) 270 | } 271 | 272 | return type as any 273 | } 274 | 275 | export function addPath(prev, key) { 276 | return { prev, key } 277 | } 278 | -------------------------------------------------------------------------------- /src/makeBindingClass.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLSchema } from 'graphql' 2 | import { Binding as BaseBinding } from './Binding' 3 | import { BindingWithoutSchemaOptions } from './types' 4 | 5 | export function makeBindingClass({ schema }: { schema: GraphQLSchema }): T { 6 | return class Binding extends BaseBinding { 7 | constructor(options?: BindingWithoutSchemaOptions) { 8 | super({ schema, ...options }) 9 | } 10 | } as any 11 | } 12 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLResolveInfo, GraphQLSchema } from 'graphql' 2 | import { Transform } from 'graphql-tools-fork' 3 | 4 | export type Operation = 'query' | 'mutation' | 'subscription' 5 | // needed to exclude 'subscription' in delegate api 6 | export type QueryOrMutation = 'query' | 'mutation' 7 | 8 | export interface FragmentReplacement { 9 | field: string 10 | fragment: string 11 | } 12 | 13 | export interface QueryMap { 14 | [rootField: string]: ( 15 | args?: { [key: string]: any }, 16 | info?: GraphQLResolveInfo | string, 17 | context?: { [key: string]: any }, 18 | ) => Promise 19 | } 20 | 21 | export type MutationMap = QueryMap 22 | 23 | export interface SubscriptionMap { 24 | [rootField: string]: ( 25 | args?: any, 26 | info?: GraphQLResolveInfo | string, 27 | context?: { [key: string]: any }, 28 | ) => AsyncIterator | Promise> 29 | } 30 | 31 | export interface BindingOptions { 32 | fragmentReplacements?: FragmentReplacement[] 33 | schema: GraphQLSchema 34 | before?: () => void 35 | disableCache?: boolean 36 | } 37 | 38 | export interface BindingWithoutSchemaOptions { 39 | fragmentReplacements?: FragmentReplacement[] 40 | before?: () => void 41 | } 42 | 43 | export interface Args { 44 | [key: string]: any 45 | } 46 | 47 | export interface Context { 48 | [key: string]: any 49 | } 50 | 51 | export interface Options { 52 | transforms?: Transform[] 53 | context?: Context 54 | } 55 | -------------------------------------------------------------------------------- /src/utils/addFragmentToInfo.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | import { buildSchema, GraphQLResolveInfo } from 'graphql' 3 | import { buildInfoFromFragment } from '../info' 4 | import { addFragmentToInfo } from './addFragmentToInfo' 5 | import { assertFields } from '../info.test' 6 | import { omitDeep } from './removeKey' 7 | import { printDocumentFromInfo } from '.' 8 | 9 | test('addFragmentToInfo: add field by simple query', t => { 10 | const schema = buildSchema(` 11 | type Query { 12 | book: Book 13 | } 14 | 15 | type Book { 16 | title: String 17 | extraField: String 18 | } 19 | `) 20 | const info = buildInfoFromFragment('book', schema, 'query', `{ title }`) 21 | const patchedInfo = addFragmentToInfo(info, '{extraField}') 22 | const selections = patchedInfo.fieldNodes[0].selectionSet!.selections 23 | 24 | assertFields(t, selections, ['title', 'extraField']) 25 | }) 26 | 27 | test('addFragmentToInfo: add field to array payload', t => { 28 | const schema = buildSchema(` 29 | type Query { 30 | books: [Book!]! 31 | } 32 | 33 | type Book { 34 | title: String 35 | extraField: String 36 | } 37 | `) 38 | const info = buildInfoFromFragment('books', schema, 'query', `{ title }`) 39 | const patchedInfo = addFragmentToInfo(info, '{ extraField }') 40 | 41 | t.snapshot(printDocumentFromInfo(patchedInfo)) 42 | t.snapshot(getRelevantPartsFromInfo(info)) 43 | t.snapshot(getRelevantPartsFromInfo(patchedInfo)) 44 | }) 45 | 46 | test('addFragmentToInfo: add field by fragment', t => { 47 | const schema = buildSchema(` 48 | type Query { 49 | book: Book 50 | } 51 | 52 | type Book { 53 | title: String 54 | extraField: String 55 | } 56 | `) 57 | const info = buildInfoFromFragment('book', schema, 'query', `{ title }`) 58 | const patchedInfo = addFragmentToInfo( 59 | info, 60 | 'fragment F on Book { extraField }', 61 | ) 62 | const selections = patchedInfo.fieldNodes[0].selectionSet!.selections 63 | 64 | assertFields(t, selections, ['title', 'extraField']) 65 | }) 66 | 67 | test("addFragmentToInfo: dont add field by fragment when type doesn't match", t => { 68 | const schema = buildSchema(` 69 | type Query { 70 | book: Book 71 | } 72 | 73 | type Book { 74 | title: String 75 | extraField: String 76 | } 77 | `) 78 | const info = buildInfoFromFragment('book', schema, 'query', `{ title }`) 79 | t.throws(() => 80 | addFragmentToInfo(info, 'fragment F on UnknownType { extraField }'), 81 | ) 82 | }) 83 | 84 | function getRelevantPartsFromInfo(info: GraphQLResolveInfo) { 85 | const { 86 | fragments, 87 | fieldName, 88 | returnType, 89 | parentType, 90 | path, 91 | rootValue, 92 | operation, 93 | variableValues, 94 | fieldNodes, 95 | } = info 96 | 97 | return { 98 | fragments, 99 | fieldName, 100 | returnType: returnType.toString(), 101 | parentType: parentType.toString(), 102 | path, 103 | rootValue, 104 | operation, 105 | variableValues, 106 | selectionSet: omitDeep(fieldNodes[0].selectionSet, 'loc'), 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/utils/addFragmentToInfo.test.ts.md: -------------------------------------------------------------------------------- 1 | # Snapshot report for `src/utils/addFragmentToInfo.test.ts` 2 | 3 | The actual snapshot is saved in `addFragmentToInfo.test.ts.snap`. 4 | 5 | Generated by [AVA](https://ava.li). 6 | 7 | ## addFragmentToInfo: add field to array payload 8 | 9 | > Snapshot 1 10 | 11 | `{␊ 12 | title␊ 13 | extraField␊ 14 | }␊ 15 | ` 16 | 17 | > Snapshot 2 18 | 19 | { 20 | fieldName: 'books', 21 | fragments: {}, 22 | operation: { 23 | kind: 'OperationDefinition', 24 | operation: 'query', 25 | selectionSet: { 26 | kind: 'SelectionSet', 27 | selections: [], 28 | }, 29 | variableDefinitions: [], 30 | }, 31 | parentType: 'Query', 32 | path: undefined, 33 | returnType: '[Book!]!', 34 | rootValue: undefined, 35 | selectionSet: { 36 | kind: 'SelectionSet', 37 | selections: { 38 | 0: { 39 | alias: undefined, 40 | arguments: {}, 41 | directives: {}, 42 | kind: 'Field', 43 | name: { 44 | kind: 'Name', 45 | value: 'title', 46 | }, 47 | selectionSet: undefined, 48 | }, 49 | }, 50 | }, 51 | variableValues: {}, 52 | } 53 | 54 | > Snapshot 3 55 | 56 | { 57 | fieldName: 'books', 58 | fragments: {}, 59 | operation: { 60 | kind: 'OperationDefinition', 61 | operation: 'query', 62 | selectionSet: { 63 | kind: 'SelectionSet', 64 | selections: [], 65 | }, 66 | variableDefinitions: [], 67 | }, 68 | parentType: 'Query', 69 | path: undefined, 70 | returnType: '[Book!]!', 71 | rootValue: undefined, 72 | selectionSet: { 73 | kind: 'SelectionSet', 74 | selections: { 75 | 0: { 76 | alias: undefined, 77 | arguments: {}, 78 | directives: {}, 79 | kind: 'Field', 80 | name: { 81 | kind: 'Name', 82 | value: 'title', 83 | }, 84 | selectionSet: undefined, 85 | }, 86 | 1: { 87 | alias: undefined, 88 | arguments: {}, 89 | directives: {}, 90 | kind: 'Field', 91 | name: { 92 | kind: 'Name', 93 | value: 'extraField', 94 | }, 95 | selectionSet: undefined, 96 | }, 97 | }, 98 | }, 99 | variableValues: {}, 100 | } 101 | 102 | ## makeSubInfo: works when path has been selected 103 | 104 | > Snapshot 1 105 | 106 | `{␊ 107 | content␊ 108 | wordCount␊ 109 | }␊ 110 | ` 111 | 112 | > Snapshot 2 113 | 114 | { 115 | fieldName: 'page', 116 | fragments: {}, 117 | operation: { 118 | kind: 'OperationDefinition', 119 | operation: 'page', 120 | selectionSet: { 121 | kind: 'SelectionSet', 122 | selections: [], 123 | }, 124 | variableDefinitions: [], 125 | }, 126 | parentType: 'Book', 127 | path: { 128 | key: 'page', 129 | prev: { 130 | key: 'book', 131 | prev: undefined, 132 | }, 133 | }, 134 | returnType: 'Page', 135 | rootValue: undefined, 136 | selectionSet: { 137 | kind: 'SelectionSet', 138 | selections: { 139 | 0: { 140 | alias: undefined, 141 | arguments: {}, 142 | directives: {}, 143 | kind: 'Field', 144 | name: { 145 | kind: 'Name', 146 | value: 'content', 147 | }, 148 | selectionSet: undefined, 149 | }, 150 | 1: { 151 | alias: undefined, 152 | arguments: {}, 153 | directives: {}, 154 | kind: 'Field', 155 | name: { 156 | kind: 'Name', 157 | value: 'wordCount', 158 | }, 159 | selectionSet: undefined, 160 | }, 161 | }, 162 | }, 163 | variableValues: {}, 164 | } 165 | 166 | ## makeSubInfo: works when path has been selected and adds fragment 167 | 168 | > Snapshot 1 169 | 170 | `{␊ 171 | content␊ 172 | wordCount␊ 173 | }␊ 174 | ` 175 | 176 | ## makeSubInfo: works with inline fragment 177 | 178 | > Snapshot 1 179 | 180 | `{␊ 181 | page {␊ 182 | content␊ 183 | }␊ 184 | }␊ 185 | ` 186 | 187 | > Snapshot 2 188 | 189 | { 190 | fieldName: 'page', 191 | fragments: {}, 192 | operation: { 193 | kind: 'OperationDefinition', 194 | operation: 'page', 195 | selectionSet: { 196 | kind: 'SelectionSet', 197 | selections: [], 198 | }, 199 | variableDefinitions: [], 200 | }, 201 | parentType: 'Book', 202 | path: { 203 | key: 'page', 204 | prev: { 205 | key: 'book', 206 | prev: undefined, 207 | }, 208 | }, 209 | returnType: 'Page', 210 | rootValue: undefined, 211 | selectionSet: { 212 | kind: 'SelectionSet', 213 | selections: { 214 | 0: { 215 | alias: undefined, 216 | arguments: {}, 217 | directives: {}, 218 | kind: 'Field', 219 | name: { 220 | kind: 'Name', 221 | value: 'page', 222 | }, 223 | selectionSet: { 224 | kind: 'SelectionSet', 225 | selections: { 226 | 0: { 227 | alias: undefined, 228 | arguments: {}, 229 | directives: {}, 230 | kind: 'Field', 231 | name: { 232 | kind: 'Name', 233 | value: 'content', 234 | }, 235 | selectionSet: undefined, 236 | }, 237 | }, 238 | }, 239 | }, 240 | }, 241 | }, 242 | variableValues: {}, 243 | } 244 | -------------------------------------------------------------------------------- /src/utils/addFragmentToInfo.test.ts.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dotansimha/graphql-binding/60fce38680a7b0f6e50722e5fe8c95cc39e2ea9d/src/utils/addFragmentToInfo.test.ts.snap -------------------------------------------------------------------------------- /src/utils/addFragmentToInfo.ts: -------------------------------------------------------------------------------- 1 | import { 2 | GraphQLResolveInfo, 3 | GraphQLScalarType, 4 | parse, 5 | FragmentDefinitionNode, 6 | OperationDefinitionNode, 7 | } from 'graphql' 8 | import { getDeepType } from '../info' 9 | import * as immutable from 'object-path-immutable' 10 | 11 | export function addFragmentToInfo( 12 | info: GraphQLResolveInfo, 13 | fragment: string, 14 | ): GraphQLResolveInfo { 15 | const returnType = getDeepType(info.returnType) 16 | if (returnType instanceof GraphQLScalarType) { 17 | throw new Error( 18 | `Can't add fragment "${fragment}" because return type of info object is a scalar type ${info.returnType.toString()}`, 19 | ) 20 | } 21 | 22 | const ast = parse(fragment) 23 | 24 | const deepReturnType = getDeepType(returnType) 25 | 26 | if ( 27 | ast.definitions[0].kind === 'FragmentDefinition' && 28 | (ast.definitions[0] as FragmentDefinitionNode).typeCondition.name.value !== 29 | deepReturnType.toString() 30 | ) { 31 | throw new Error( 32 | `Type ${ 33 | (ast.definitions[0] as FragmentDefinitionNode).typeCondition.name.value 34 | } specified in fragment doesn't match return type ${deepReturnType.toString()}`, 35 | ) 36 | } 37 | 38 | return (immutable as any).update( 39 | info, 40 | ['fieldNodes', 0, 'selectionSet', 'selections'], 41 | selections => 42 | selections.concat( 43 | (ast.definitions[0] as OperationDefinitionNode).selectionSet.selections, 44 | ), 45 | ) 46 | } 47 | -------------------------------------------------------------------------------- /src/utils/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | GraphQLSchema, 3 | GraphQLResolveInfo, 4 | GraphQLOutputType, 5 | GraphQLObjectType, 6 | GraphQLScalarType, 7 | GraphQLInterfaceType, 8 | GraphQLUnionType, 9 | GraphQLList, 10 | GraphQLEnumType, 11 | GraphQLNonNull, 12 | print, 13 | DocumentNode, 14 | } from 'graphql' 15 | 16 | import { Operation } from '../types' 17 | 18 | export function isScalar(t: GraphQLOutputType): boolean { 19 | if (t instanceof GraphQLScalarType || t instanceof GraphQLEnumType) { 20 | return true 21 | } 22 | 23 | if ( 24 | t instanceof GraphQLObjectType || 25 | t instanceof GraphQLInterfaceType || 26 | t instanceof GraphQLUnionType || 27 | t instanceof GraphQLList 28 | ) { 29 | return false 30 | } 31 | 32 | const nnt = t as any 33 | if (nnt instanceof GraphQLNonNull) { 34 | if ( 35 | nnt.ofType instanceof GraphQLScalarType || 36 | nnt.ofType instanceof GraphQLEnumType 37 | ) { 38 | return true 39 | } 40 | } 41 | 42 | return false 43 | } 44 | 45 | export function getTypeForRootFieldName( 46 | rootFieldName: string, 47 | operation: Operation, 48 | schema: GraphQLSchema, 49 | ): GraphQLOutputType { 50 | if (operation === 'mutation' && !schema.getMutationType()) { 51 | throw new Error(`Schema doesn't have mutation type`) 52 | } 53 | 54 | if (operation === 'subscription' && !schema.getSubscriptionType()) { 55 | throw new Error(`Schema doesn't have subscription type`) 56 | } 57 | 58 | const rootType = 59 | { 60 | query: () => schema.getQueryType(), 61 | mutation: () => schema.getMutationType()!, 62 | subscription: () => schema.getSubscriptionType()!, 63 | }[operation]() || undefined! 64 | 65 | const rootField = rootType.getFields()[rootFieldName] 66 | 67 | if (!rootField) { 68 | throw new Error(`No such root field found: ${rootFieldName}`) 69 | } 70 | 71 | return rootField.type 72 | } 73 | 74 | export function forwardTo(bindingName: string) { 75 | return ( 76 | parent: PARENT, 77 | args: ARGS, 78 | context: CONTEXT, 79 | info: GraphQLResolveInfo, 80 | ) => { 81 | let message = `Forward to '${bindingName}.${info.parentType.name.toLowerCase()}.${ 82 | info.fieldName 83 | }' failed. ` 84 | if (context[bindingName]) { 85 | if ( 86 | context[bindingName][info.parentType.name.toLowerCase()][info.fieldName] 87 | ) { 88 | return context[bindingName][info.parentType.name.toLowerCase()][ 89 | info.fieldName 90 | ](args, info) 91 | } else { 92 | message += `Field '${info.parentType.name.toLowerCase()}.${ 93 | info.fieldName 94 | }' not found on binding '${bindingName}'.` 95 | } 96 | } else { 97 | message += `Binding '${bindingName}' not found.` 98 | } 99 | 100 | throw new Error(message) 101 | } 102 | } 103 | 104 | export function printDocumentFromInfo(info: GraphQLResolveInfo) { 105 | const fragments = Object.keys(info.fragments).map( 106 | fragment => info.fragments[fragment], 107 | ) 108 | const doc: DocumentNode = { 109 | kind: 'Document', 110 | definitions: [ 111 | { 112 | kind: 'OperationDefinition', 113 | operation: 'query', 114 | selectionSet: info.fieldNodes[0].selectionSet!, 115 | }, 116 | ...fragments, 117 | ], 118 | } 119 | 120 | return print(doc) 121 | } 122 | -------------------------------------------------------------------------------- /src/utils/removeKey.ts: -------------------------------------------------------------------------------- 1 | export function omitDeep(obj, key) { 2 | if (typeof obj === 'object') { 3 | return Object.keys(obj).reduce((acc, curr) => { 4 | if (curr === key) { 5 | return acc 6 | } 7 | return { ...acc, [curr]: omitDeep(obj[curr], key) } 8 | }, {}) 9 | } 10 | 11 | return obj 12 | } 13 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "moduleResolution": "node", 5 | "module": "commonjs", 6 | "sourceMap": true, 7 | "noUnusedLocals": true, 8 | "strictNullChecks": true, 9 | "noImplicitAny": false, 10 | "rootDir": "src", 11 | "outDir": "dist", 12 | "declaration": true, 13 | "strict": true, 14 | "esModuleInterop": true, 15 | "lib": ["dom", "esnext", "es2016"] 16 | }, 17 | "exclude": ["node_modules", "dist", "examples"] 18 | } 19 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["tslint-config-standard"], 3 | "rules": { 4 | "space-before-function-paren": false, 5 | "trailing-comma": [ 6 | true, 7 | { 8 | "multiline": { 9 | "typeLiterals": "never" 10 | }, 11 | "singleline": "never" 12 | } 13 | ] 14 | } 15 | } --------------------------------------------------------------------------------