├── .gitignore ├── .gitmodules ├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── __tests__ ├── __snapshots__ │ └── test-import.spec.ts.snap ├── test-import.spec.ts └── utils.spec.ts ├── babel-plugin-transform-import-duplicate.js ├── bundle.d.ts ├── examples ├── example-defer.ts ├── example-live.ts └── example.ts ├── package.json ├── resources ├── benchmark.js ├── benchmark_sw.js ├── fixModules.js ├── patchVersion.js └── updateVersion.sh ├── rollup.config.js ├── src ├── index.js └── utils.js └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !src/ 3 | !src/**/* 4 | !resources/ 5 | !resources/**/* 6 | !__tests__/ 7 | !__tests__/**/* 8 | !examples/ 9 | !examples/**/* 10 | !*.js 11 | !*.json 12 | !*.md 13 | !.gitignore 14 | !.gitmodules 15 | !.travis.yml 16 | !LICENSE 17 | !.npmignore 18 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "graphql"] 2 | path = graphql 3 | url = https://github.com/DxCx/graphql-js.git 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | * 2 | !dist/**/* 3 | !package.json 4 | !*.md 5 | !*.d.ts 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "6" 4 | - "7" 5 | 6 | cache: 7 | directories: 8 | - ${HOME}/.npm 9 | - node_modules 10 | 11 | before_install: 12 | - npm config set spin=false 13 | - npm install -g npm@4 14 | 15 | install: 16 | - npm install 17 | 18 | script: 19 | - npm run testonly 20 | 21 | # Allow Travis tests to run in containers. 22 | sudo: false 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD License 2 | 3 | For GraphQL software 4 | 5 | Copyright (c) 2015, Facebook, Inc. All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without modification, 8 | are permitted provided that the following conditions are met: 9 | 10 | * Redistributions of source code must retain the above copyright notice, this 11 | list of conditions and the following disclaimer. 12 | 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | this list of conditions and the following disclaimer in the documentation 15 | and/or other materials provided with the distribution. 16 | 17 | * Neither the name Facebook nor the names of its contributors may be used to 18 | endorse or promote products derived from this software without specific 19 | prior written permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 22 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 23 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 25 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 26 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 27 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 28 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GraphQL-RxJs 2 | 3 | fork of [graphql-js](https://github.com/graphql/graphql-js) which adds AsyncIterator & Observable support ([RxJs](http://reactivex.io/rxjs/)). 4 | 5 | [![npm version](https://badge.fury.io/js/graphql-rxjs.svg)](http://badge.fury.io/js/graphql-rxjs) 6 | [![Build Status](https://travis-ci.org/DxCx/graphql-rxjs.svg?branch=master)](https://travis-ci.org/DxCx/graphql-rxjs?branch=master) 7 | 8 | ## Intro 9 | 10 | This package adds Reactivity for GraphQLResolver, Which means you can now return: 11 | - Observables 12 | - AsyncIterator 13 | 14 | This package also adds reactive directives support: 15 | - @defer 16 | - @live 17 | 18 | The package is pretty small because it is importing the original graphql-js package, 19 | and then patches it to provide a reactive execution engine over it. 20 | 21 | ## Examples 22 | - [Simple Websocket Server with GraphiQL](https://github.com/DxCx/graphql-rxjs-websocket-example) 23 | 24 | ## Documentation 25 | 26 | There isn't much to document, all of GraphQL documentation is relevant. 27 | See the complete documentation at http://graphql.org/ and 28 | http://graphql.org/graphql-js/. 29 | 30 | ## Versioning 31 | 32 | I'll try to follow along with `graphql-js` versions, 33 | so basiclly, each graphql-js version should have a working graphql-rxjs package with it. 34 | 35 | ## API 36 | 37 | The library exports the following functions: 38 | 39 | #### AsyncIterator support 40 | 41 | ```typescript 42 | export function graphqlReactive( 43 | schema: GraphQLSchema, 44 | requestString: string, 45 | rootValue?: any, 46 | contextValue?: any, 47 | variableValues?: {[key: string]: any}, 48 | operationName?: string 49 | fieldResolver?: GraphQLFieldResolver, 50 | ): AsyncIterator; 51 | 52 | export function executeReactive( 53 | schema: GraphQLSchema, 54 | document: DocumentNode, 55 | rootValue?: any, 56 | contextValue?: any, 57 | variableValues?: {[key: string]: any}, 58 | operationName?: string 59 | fieldResolver?: GraphQLFieldResolver, 60 | ): AsyncIterator; 61 | ``` 62 | 63 | The signature is equal to GraphQL original implementation (graphql + execute), 64 | except it returns an asyncIterator instead of a promise. 65 | The asyncIterator will stream immutable results. 66 | 67 | #### Observable support 68 | 69 | ```typescript 70 | export function graphqlRx( 71 | schema: GraphQLSchema, 72 | requestString: string, 73 | rootValue?: any, 74 | contextValue?: any, 75 | variableValues?: {[key: string]: any}, 76 | operationName?: string 77 | fieldResolver?: GraphQLFieldResolver, 78 | ): Observable; 79 | 80 | export function executeRx( 81 | schema: GraphQLSchema, 82 | document: DocumentNode, 83 | rootValue?: any, 84 | contextValue?: any, 85 | variableValues?: {[key: string]: any}, 86 | operationName?: string 87 | fieldResolver?: GraphQLFieldResolver, 88 | ): Observable; 89 | 90 | export function subscribeRx( 91 | schema: GraphQLSchema, 92 | document: DocumentNode, 93 | rootValue?: any, 94 | contextValue?: any, 95 | variableValues?: {[key: string]: any}, 96 | operationName?: string, 97 | fieldResolver?: GraphQLFieldResolver, 98 | subscribeFieldResolver?: GraphQLFieldResolver 99 | ): Observable; 100 | ``` 101 | 102 | The signature is equal to GraphQL original implementation (graphql + execute + subscribe), 103 | except it returns an observable instead of a promise. 104 | The observable will stream immutable results. 105 | 106 | #### Preparing schema for GraphQL-RxJs 107 | ```typescript 108 | export function prepareSchema( 109 | schema: GraphQLSchema, 110 | ): prepareSchema; 111 | ``` 112 | 113 | This function is used to prepare schema for graphql-rxjs. 114 | wrapping resolvers, adding reactive directives support, etc.. 115 | At the moment, it will be automatically invoked when running 116 | GraphQL-RxJS. 117 | 118 | NOTE: if you are using original graphql's validate, you will have to trigger 119 | prepareSchema manually and not count on auto-trigger. 120 | 121 | if you don't want to call it directly, you can use the inner 122 | APIs seperately: 123 | 124 | ##### Reactive Directives 125 | ```typescript 126 | export function addReactiveDirectivesToSchema( 127 | schema: GraphQLSchema, 128 | ): void; 129 | ``` 130 | 131 | Calling this function on your existing `GraphQLSchema` object will 132 | enable reactive directives suppot for the schema. 133 | More information about reactive directives can be found below. 134 | 135 | ##### Observable support in resolvers 136 | ```typescript 137 | export function wrapResolvers( 138 | schema: GraphQLSchema, 139 | ): void; 140 | ``` 141 | 142 | Calling this function on your existing `GraphQLSchema` object will 143 | enable reactive directives suppot for the schema. 144 | More information about reactive directives can be found below. 145 | 146 | 147 | ## Getting Started: 148 | 149 | 1. The Data source 150 | 151 | Let's start off by explaining our Observable, or Data-source that we are going to stream. 152 | ```typescript 153 | const clockSource = Observable.interval(1000).map(() => new Date()).publishReplay(1).refCount(); 154 | ``` 155 | 156 | We can see that it is an Observable that emits `i+1` every second: 157 | ```typescript 158 | let source = Observable.interval(1000); 159 | ``` 160 | 161 | Next we are going to map the value to a the current timestep: 162 | ```typescript 163 | source = source.map(() => new Date()); 164 | ``` 165 | 166 | Finally, We are going to convert the observable into an hot observable, or multicast. 167 | 168 | This means that there is a ref count on the observable, 169 | only once the first subscriber subscribe, the provider function will be triggered, 170 | and from then on, each subscriber will get the last value (without triggering the provider function) 171 | and then, both subscribers will get `next` values. 172 | 173 | Once a subscriber unsubscribe, the refCount will decrease, 174 | until the counter reachs zero, and only then the unsubscribe function will be triggered. 175 | ```typescript 176 | const clockSource = source.publishReplay(1).refCount(); 177 | ``` 178 | 179 | This approach will let us manage resources much more efficiently. 180 | 181 | 2. The Scheme 182 | 183 | Next, let's look at our scheme 184 | 185 | ```graphql 186 | # Root Query 187 | type Query { 188 | someInt: Int 189 | } 190 | 191 | # Root Subscription 192 | type Subscription { 193 | clock: String 194 | } 195 | ``` 196 | 197 | `Query` type exposes `someInt`, just because it cannot be empty, let's ignore that. 198 | 199 | `Subscription` type expoes `clock` which is basiclly periodic timestamp strings. 200 | 201 | 3. Wrapping them togather 202 | 203 | So, here is a basic code to demonstrate the concept: 204 | ```typescript 205 | import { Observable } from 'rxjs'; 206 | import { makeExecutableSchema } from 'graphql-tools'; 207 | import { prepareSchema, graphqlRx } from 'graphql-rxjs'; 208 | 209 | const clockSource = Observable.interval(1000).map(() => new Date()).publishReplay(1).refCount(); 210 | 211 | const typeDefs = ` 212 | # Root Query 213 | type Query { 214 | someInt: Int 215 | } 216 | 217 | # Root Subscription 218 | type Subscription { 219 | clock: String 220 | } 221 | `; 222 | 223 | const resolvers = { 224 | Subscription: { 225 | clock(root, args, ctx) { 226 | return ctx.clockSource; 227 | }, 228 | }, 229 | }; 230 | 231 | // Compose togather resolver and typeDefs. 232 | const scheme = makeExecutableSchema({typeDefs: typeDefs, resolvers: resolvers}); 233 | prepareSchema(schema); 234 | 235 | // subscribe the clock 236 | const query = ` 237 | subscription { 238 | clock 239 | } 240 | ` 241 | 242 | // Calling the reactive version of graphql 243 | graphqlRx(scheme, query, null, { clockSource }) 244 | .subscribe(console.log.bind(console), console.error.bind(console)); 245 | ``` 246 | 247 | The following response will emit in console: 248 | ```json 249 | {"data":{"clock":"Fri Feb 02 2017 20:28:01 GMT+0200 (IST)"}} 250 | {"data":{"clock":"Fri Feb 02 2017 20:28:02 GMT+0200 (IST)"}} 251 | {"data":{"clock":"Fri Feb 02 2017 20:28:03 GMT+0200 (IST)"}} 252 | ... 253 | ``` 254 | 255 | ## Reactive Directives 256 | This library also implements reactive directives, those are supported at the moment: 257 | 258 | 1. `GraphQLDeferDirective` (`@defer`) 259 | - This directive does not require any arguments. 260 | - This directive instructs the executor to not resolve this field immedaitly, 261 | but instead returning the response without the deferred field, 262 | once the field is deferred, it will emit a corrected result. 263 | - executor will ignore this directive if resolver for this value is not async. 264 | - can be applied on: 265 | - specific field 266 | - spread fragment 267 | - named fragment 268 | - Example: 269 | ```typescript 270 | import { Observable } from 'rxjs'; 271 | import { makeExecutableSchema } from 'graphql-tools'; 272 | import { prepareSchema, graphqlReactive } from 'graphql-rxjs'; 273 | 274 | const remoteString = new Promise((resolve, reject) => { 275 | setTimeout(() => resolve('Hello World!'), 5000); 276 | }); 277 | 278 | const typeDefs = ` 279 | # Root Query 280 | type Query { 281 | remoteString: String 282 | } 283 | `; 284 | 285 | const resolvers = { 286 | Query: { 287 | remoteString: (root, args, ctx) => ctx.remoteString, 288 | }, 289 | }; 290 | 291 | const scheme = makeExecutableSchema({typeDefs, resolvers}); 292 | prepareSchema(scheme); 293 | 294 | const query = ` 295 | query { 296 | remoteString @defer 297 | } 298 | `; 299 | 300 | const log = (result) => console.log("[" + (new Date).toLocaleTimeString() + "] " + JSON.stringify(result)); 301 | 302 | graphqlReactive(scheme, query, null, { remoteString }) 303 | .subscribe(log, console.error.bind(console)); 304 | ``` 305 | 306 | The following response will emit in console: 307 | ``` 308 | [8:58:05 PM] {"data":{}} 309 | [8:58:10 PM] {"data":{"remoteString":"Hello World!"}} 310 | ``` 311 | 2. `GraphQLLiveDirective` (`@live`) 312 | - This directive does not require any arguments. 313 | - This directive instructs the executor that the value should be monitored live 314 | which means that once updated, it will emit the updated respose. 315 | - executor will ignore this directive if field is not resolved 316 | with an observable (or at least have a parent observable). 317 | - can be applied on: 318 | - specific field 319 | - spread fragment 320 | - named fragment 321 | - fragment definition - (Live Fragment) 322 | - Example: 323 | ```typescript 324 | import { Observable } from 'rxjs'; 325 | import { makeExecutableSchema } from 'graphql-tools'; 326 | import { prepareSchema, graphqlReactive } from '..'; 327 | 328 | const clockSource = Observable.interval(1000).map(() => new Date()).publishReplay(1).refCount(); 329 | 330 | const typeDefs = ` 331 | # Root Query 332 | type Query { 333 | clock: String 334 | } 335 | `; 336 | 337 | const resolvers = { 338 | Query: { 339 | clock: (root, args, ctx) => ctx.clockSource, 340 | }, 341 | }; 342 | 343 | const scheme = makeExecutableSchema({typeDefs, resolvers}); 344 | prepareSchema(scheme); 345 | 346 | const query = ` 347 | query { 348 | clock 349 | } 350 | `; 351 | 352 | const liveQuery = ` 353 | query { 354 | clock @live 355 | } 356 | `; 357 | 358 | graphqlReactive(scheme, query, null, { clockSource }) 359 | .subscribe(console.log.bind(console, "standard: "), console.error.bind(console)); 360 | 361 | graphqlReactive(scheme, liveQuery, null, { clockSource }) 362 | .subscribe(console.log.bind(console, "live: "), console.error.bind(console)); 363 | ``` 364 | 365 | The following response will emit in console: 366 | ``` 367 | standard: { data: { clock: 'Sun Apr 16 2017 21:04:57 GMT+0300 (EEST)' } } 368 | live: { data: { clock: 'Sun Apr 16 2017 21:04:57 GMT+0300 (EEST)' } } 369 | live: { data: { clock: 'Sun Apr 16 2017 21:04:58 GMT+0300 (EEST)' } } 370 | live: { data: { clock: 'Sun Apr 16 2017 21:04:59 GMT+0300 (EEST)' } } 371 | live: { data: { clock: 'Sun Apr 16 2017 21:05:00 GMT+0300 (EEST)' } } 372 | ... 373 | ``` 374 | 375 | ## Typescript support 376 | 377 | Just install `@types/graphql` for initial GraphQL support, 378 | then the package will automatically add typings for the new functions it provides. 379 | 380 | ## Benchmarks 381 | ``` 382 | * graphqlOri x 7,496 ops/sec ±9.20% (78 runs sampled) 383 | * graphqlRx x 6,790 ops/sec ±3.48% (75 runs sampled) 384 | => Fastest is graphqlOri 385 | ✓ compare performance for simple query (11088ms) 386 | 387 | * graphqlOri x 3,040 ops/sec ±9.81% (70 runs sampled) 388 | * graphqlRx x 1,303 ops/sec ±4.19% (74 runs sampled) 389 | => Fastest is graphqlOri 390 | ✓ compare performance for deep query (17515ms) 391 | 392 | * graphqlOri x 5,360 ops/sec ±20.55% (12 runs sampled) 393 | * graphqlRx x 3,067 ops/sec ±32.41% (10 runs sampled) 394 | => Fastest is graphqlOri 395 | ✓ compare performance for serial mutation (27004ms) 396 | ``` 397 | 398 | as results shows above, the reactive engine is abit slower then the original one, 399 | however that was expected, i have yet to put the effort on optimizations, 400 | and i am planing to optimize on the future. 401 | 402 | ## Issues 403 | 404 | If you found an issue or have an idea, you are welcome to open a new ticket in [Issues Page](https://github.com/DxCx/graphql-rxjs/issues) 405 | 406 | ## Support 407 | 408 | Using this approach, feels much more intuative then the other approaches to stream results so far. 409 | Because of that I tried to push it into upstream `graphql-js` but got rejected, 410 | if you want to support the project you can follow/thumbs up the following: 411 | 412 | 1. [Issue on graphql-js](https://github.com/graphql/graphql-js/issues/501) 413 | 2. [PR on graphql-js](https://github.com/graphql/graphql-js/pull/502) 414 | 3. [Draft design for apollo subscriptions](https://github.com/apollographql/graphql-subscriptions/pull/30) 415 | 416 | ### Contributing 417 | 418 | All pull requests are welcome, 419 | if you think something needs to be done, just open an issue or a PR :) 420 | -------------------------------------------------------------------------------- /__tests__/__snapshots__/test-import.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`graphql-rxjs import tests able to add reactive directives to schema 1`] = ` 4 | Array [ 5 | Object { 6 | "data": Object { 7 | "counter": 0, 8 | }, 9 | }, 10 | Object { 11 | "data": Object { 12 | "counter": 1, 13 | }, 14 | }, 15 | Object { 16 | "data": Object { 17 | "counter": 2, 18 | }, 19 | }, 20 | Object { 21 | "data": Object { 22 | "counter": 3, 23 | }, 24 | }, 25 | Object { 26 | "data": Object { 27 | "counter": 4, 28 | }, 29 | }, 30 | Object { 31 | "data": Object { 32 | "counter": 5, 33 | }, 34 | }, 35 | Object { 36 | "data": Object { 37 | "counter": 6, 38 | }, 39 | }, 40 | Object { 41 | "data": Object { 42 | "counter": 7, 43 | }, 44 | }, 45 | Object { 46 | "data": Object { 47 | "counter": 8, 48 | }, 49 | }, 50 | Object { 51 | "data": Object { 52 | "counter": 9, 53 | }, 54 | }, 55 | ] 56 | `; 57 | 58 | exports[`graphql-rxjs import tests also works for subscriptions 1`] = ` 59 | Array [ 60 | Object { 61 | "data": Object { 62 | "counter": 0, 63 | }, 64 | }, 65 | Object { 66 | "data": Object { 67 | "counter": 1, 68 | }, 69 | }, 70 | Object { 71 | "data": Object { 72 | "counter": 2, 73 | }, 74 | }, 75 | ] 76 | `; 77 | 78 | exports[`graphql-rxjs import tests also works with vanilla graphql objects 1`] = ` 79 | Array [ 80 | Object { 81 | "data": Object { 82 | "someInt": 123, 83 | }, 84 | }, 85 | ] 86 | `; 87 | 88 | exports[`graphql-rxjs import tests can also query static values 1`] = ` 89 | Array [ 90 | Object { 91 | "data": Object { 92 | "someInt": 123321, 93 | }, 94 | }, 95 | ] 96 | `; 97 | 98 | exports[`graphql-rxjs import tests can work with graphql-tools 1`] = ` 99 | Object { 100 | "data": Object { 101 | "someInt": 1, 102 | }, 103 | } 104 | `; 105 | 106 | exports[`graphql-rxjs import tests introspectionQuery works as expected 1`] = ` 107 | Object { 108 | "__schema": Object { 109 | "directives": Array [ 110 | Object { 111 | "args": Array [ 112 | Object { 113 | "defaultValue": null, 114 | "description": "Skipped when true.", 115 | "name": "if", 116 | "type": Object { 117 | "kind": "NON_NULL", 118 | "name": null, 119 | "ofType": Object { 120 | "kind": "SCALAR", 121 | "name": "Boolean", 122 | "ofType": null, 123 | }, 124 | }, 125 | }, 126 | ], 127 | "description": "Directs the executor to skip this field or fragment when the \`if\` argument is true.", 128 | "locations": Array [ 129 | "FIELD", 130 | "FRAGMENT_SPREAD", 131 | "INLINE_FRAGMENT", 132 | ], 133 | "name": "skip", 134 | }, 135 | Object { 136 | "args": Array [ 137 | Object { 138 | "defaultValue": null, 139 | "description": "Included when true.", 140 | "name": "if", 141 | "type": Object { 142 | "kind": "NON_NULL", 143 | "name": null, 144 | "ofType": Object { 145 | "kind": "SCALAR", 146 | "name": "Boolean", 147 | "ofType": null, 148 | }, 149 | }, 150 | }, 151 | ], 152 | "description": "Directs the executor to include this field or fragment only when the \`if\` argument is true.", 153 | "locations": Array [ 154 | "FIELD", 155 | "FRAGMENT_SPREAD", 156 | "INLINE_FRAGMENT", 157 | ], 158 | "name": "include", 159 | }, 160 | Object { 161 | "args": Array [ 162 | Object { 163 | "defaultValue": "\\"No longer supported\\"", 164 | "description": "Explains why this element was deprecated, usually also including a suggestion for how to access supported similar data. Formatted in [Markdown](https://daringfireball.net/projects/markdown/).", 165 | "name": "reason", 166 | "type": Object { 167 | "kind": "SCALAR", 168 | "name": "String", 169 | "ofType": null, 170 | }, 171 | }, 172 | ], 173 | "description": "Marks an element of a GraphQL schema as no longer supported.", 174 | "locations": Array [ 175 | "FIELD_DEFINITION", 176 | "ENUM_VALUE", 177 | ], 178 | "name": "deprecated", 179 | }, 180 | Object { 181 | "args": Array [], 182 | "description": "Returns initial value as null and update value when available", 183 | "locations": Array [ 184 | "FIELD", 185 | "FRAGMENT_SPREAD", 186 | "INLINE_FRAGMENT", 187 | ], 188 | "name": "defer", 189 | }, 190 | Object { 191 | "args": Array [], 192 | "description": "Directs the executor to keep field or fragment as live observable", 193 | "locations": Array [ 194 | "FIELD", 195 | "FRAGMENT_SPREAD", 196 | "INLINE_FRAGMENT", 197 | "FRAGMENT_DEFINITION", 198 | ], 199 | "name": "live", 200 | }, 201 | ], 202 | "mutationType": null, 203 | "queryType": Object { 204 | "name": "Query", 205 | }, 206 | "subscriptionType": null, 207 | "types": Array [ 208 | Object { 209 | "description": "Root Subscription", 210 | "enumValues": null, 211 | "fields": Array [ 212 | Object { 213 | "args": Array [], 214 | "deprecationReason": null, 215 | "description": "", 216 | "isDeprecated": false, 217 | "name": "someInt", 218 | "type": Object { 219 | "kind": "SCALAR", 220 | "name": "Int", 221 | "ofType": null, 222 | }, 223 | }, 224 | ], 225 | "inputFields": null, 226 | "interfaces": Array [], 227 | "kind": "OBJECT", 228 | "name": "Query", 229 | "possibleTypes": null, 230 | }, 231 | Object { 232 | "description": "The \`Int\` scalar type represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1. ", 233 | "enumValues": null, 234 | "fields": null, 235 | "inputFields": null, 236 | "interfaces": null, 237 | "kind": "SCALAR", 238 | "name": "Int", 239 | "possibleTypes": null, 240 | }, 241 | Object { 242 | "description": "A GraphQL Schema defines the capabilities of a GraphQL server. It exposes all available types and directives on the server, as well as the entry points for query, mutation, and subscription operations.", 243 | "enumValues": null, 244 | "fields": Array [ 245 | Object { 246 | "args": Array [], 247 | "deprecationReason": null, 248 | "description": "A list of all types supported by this server.", 249 | "isDeprecated": false, 250 | "name": "types", 251 | "type": Object { 252 | "kind": "NON_NULL", 253 | "name": null, 254 | "ofType": Object { 255 | "kind": "LIST", 256 | "name": null, 257 | "ofType": Object { 258 | "kind": "NON_NULL", 259 | "name": null, 260 | "ofType": Object { 261 | "kind": "OBJECT", 262 | "name": "__Type", 263 | "ofType": null, 264 | }, 265 | }, 266 | }, 267 | }, 268 | }, 269 | Object { 270 | "args": Array [], 271 | "deprecationReason": null, 272 | "description": "The type that query operations will be rooted at.", 273 | "isDeprecated": false, 274 | "name": "queryType", 275 | "type": Object { 276 | "kind": "NON_NULL", 277 | "name": null, 278 | "ofType": Object { 279 | "kind": "OBJECT", 280 | "name": "__Type", 281 | "ofType": null, 282 | }, 283 | }, 284 | }, 285 | Object { 286 | "args": Array [], 287 | "deprecationReason": null, 288 | "description": "If this server supports mutation, the type that mutation operations will be rooted at.", 289 | "isDeprecated": false, 290 | "name": "mutationType", 291 | "type": Object { 292 | "kind": "OBJECT", 293 | "name": "__Type", 294 | "ofType": null, 295 | }, 296 | }, 297 | Object { 298 | "args": Array [], 299 | "deprecationReason": null, 300 | "description": "If this server support subscription, the type that subscription operations will be rooted at.", 301 | "isDeprecated": false, 302 | "name": "subscriptionType", 303 | "type": Object { 304 | "kind": "OBJECT", 305 | "name": "__Type", 306 | "ofType": null, 307 | }, 308 | }, 309 | Object { 310 | "args": Array [], 311 | "deprecationReason": null, 312 | "description": "A list of all directives supported by this server.", 313 | "isDeprecated": false, 314 | "name": "directives", 315 | "type": Object { 316 | "kind": "NON_NULL", 317 | "name": null, 318 | "ofType": Object { 319 | "kind": "LIST", 320 | "name": null, 321 | "ofType": Object { 322 | "kind": "NON_NULL", 323 | "name": null, 324 | "ofType": Object { 325 | "kind": "OBJECT", 326 | "name": "__Directive", 327 | "ofType": null, 328 | }, 329 | }, 330 | }, 331 | }, 332 | }, 333 | ], 334 | "inputFields": null, 335 | "interfaces": Array [], 336 | "kind": "OBJECT", 337 | "name": "__Schema", 338 | "possibleTypes": null, 339 | }, 340 | Object { 341 | "description": "The fundamental unit of any GraphQL Schema is the type. There are many kinds of types in GraphQL as represented by the \`__TypeKind\` enum. 342 | 343 | Depending on the kind of a type, certain fields describe information about that type. Scalar types provide no information beyond a name and description, while Enum types provide their values. Object and Interface types provide the fields they describe. Abstract types, Union and Interface, provide the Object types possible at runtime. List and NonNull types compose other types.", 344 | "enumValues": null, 345 | "fields": Array [ 346 | Object { 347 | "args": Array [], 348 | "deprecationReason": null, 349 | "description": null, 350 | "isDeprecated": false, 351 | "name": "kind", 352 | "type": Object { 353 | "kind": "NON_NULL", 354 | "name": null, 355 | "ofType": Object { 356 | "kind": "ENUM", 357 | "name": "__TypeKind", 358 | "ofType": null, 359 | }, 360 | }, 361 | }, 362 | Object { 363 | "args": Array [], 364 | "deprecationReason": null, 365 | "description": null, 366 | "isDeprecated": false, 367 | "name": "name", 368 | "type": Object { 369 | "kind": "SCALAR", 370 | "name": "String", 371 | "ofType": null, 372 | }, 373 | }, 374 | Object { 375 | "args": Array [], 376 | "deprecationReason": null, 377 | "description": null, 378 | "isDeprecated": false, 379 | "name": "description", 380 | "type": Object { 381 | "kind": "SCALAR", 382 | "name": "String", 383 | "ofType": null, 384 | }, 385 | }, 386 | Object { 387 | "args": Array [ 388 | Object { 389 | "defaultValue": "false", 390 | "description": null, 391 | "name": "includeDeprecated", 392 | "type": Object { 393 | "kind": "SCALAR", 394 | "name": "Boolean", 395 | "ofType": null, 396 | }, 397 | }, 398 | ], 399 | "deprecationReason": null, 400 | "description": null, 401 | "isDeprecated": false, 402 | "name": "fields", 403 | "type": Object { 404 | "kind": "LIST", 405 | "name": null, 406 | "ofType": Object { 407 | "kind": "NON_NULL", 408 | "name": null, 409 | "ofType": Object { 410 | "kind": "OBJECT", 411 | "name": "__Field", 412 | "ofType": null, 413 | }, 414 | }, 415 | }, 416 | }, 417 | Object { 418 | "args": Array [], 419 | "deprecationReason": null, 420 | "description": null, 421 | "isDeprecated": false, 422 | "name": "interfaces", 423 | "type": Object { 424 | "kind": "LIST", 425 | "name": null, 426 | "ofType": Object { 427 | "kind": "NON_NULL", 428 | "name": null, 429 | "ofType": Object { 430 | "kind": "OBJECT", 431 | "name": "__Type", 432 | "ofType": null, 433 | }, 434 | }, 435 | }, 436 | }, 437 | Object { 438 | "args": Array [], 439 | "deprecationReason": null, 440 | "description": null, 441 | "isDeprecated": false, 442 | "name": "possibleTypes", 443 | "type": Object { 444 | "kind": "LIST", 445 | "name": null, 446 | "ofType": Object { 447 | "kind": "NON_NULL", 448 | "name": null, 449 | "ofType": Object { 450 | "kind": "OBJECT", 451 | "name": "__Type", 452 | "ofType": null, 453 | }, 454 | }, 455 | }, 456 | }, 457 | Object { 458 | "args": Array [ 459 | Object { 460 | "defaultValue": "false", 461 | "description": null, 462 | "name": "includeDeprecated", 463 | "type": Object { 464 | "kind": "SCALAR", 465 | "name": "Boolean", 466 | "ofType": null, 467 | }, 468 | }, 469 | ], 470 | "deprecationReason": null, 471 | "description": null, 472 | "isDeprecated": false, 473 | "name": "enumValues", 474 | "type": Object { 475 | "kind": "LIST", 476 | "name": null, 477 | "ofType": Object { 478 | "kind": "NON_NULL", 479 | "name": null, 480 | "ofType": Object { 481 | "kind": "OBJECT", 482 | "name": "__EnumValue", 483 | "ofType": null, 484 | }, 485 | }, 486 | }, 487 | }, 488 | Object { 489 | "args": Array [], 490 | "deprecationReason": null, 491 | "description": null, 492 | "isDeprecated": false, 493 | "name": "inputFields", 494 | "type": Object { 495 | "kind": "LIST", 496 | "name": null, 497 | "ofType": Object { 498 | "kind": "NON_NULL", 499 | "name": null, 500 | "ofType": Object { 501 | "kind": "OBJECT", 502 | "name": "__InputValue", 503 | "ofType": null, 504 | }, 505 | }, 506 | }, 507 | }, 508 | Object { 509 | "args": Array [], 510 | "deprecationReason": null, 511 | "description": null, 512 | "isDeprecated": false, 513 | "name": "ofType", 514 | "type": Object { 515 | "kind": "OBJECT", 516 | "name": "__Type", 517 | "ofType": null, 518 | }, 519 | }, 520 | ], 521 | "inputFields": null, 522 | "interfaces": Array [], 523 | "kind": "OBJECT", 524 | "name": "__Type", 525 | "possibleTypes": null, 526 | }, 527 | Object { 528 | "description": "An enum describing what kind of type a given \`__Type\` is.", 529 | "enumValues": Array [ 530 | Object { 531 | "deprecationReason": null, 532 | "description": "Indicates this type is a scalar.", 533 | "isDeprecated": false, 534 | "name": "SCALAR", 535 | }, 536 | Object { 537 | "deprecationReason": null, 538 | "description": "Indicates this type is an object. \`fields\` and \`interfaces\` are valid fields.", 539 | "isDeprecated": false, 540 | "name": "OBJECT", 541 | }, 542 | Object { 543 | "deprecationReason": null, 544 | "description": "Indicates this type is an interface. \`fields\` and \`possibleTypes\` are valid fields.", 545 | "isDeprecated": false, 546 | "name": "INTERFACE", 547 | }, 548 | Object { 549 | "deprecationReason": null, 550 | "description": "Indicates this type is a union. \`possibleTypes\` is a valid field.", 551 | "isDeprecated": false, 552 | "name": "UNION", 553 | }, 554 | Object { 555 | "deprecationReason": null, 556 | "description": "Indicates this type is an enum. \`enumValues\` is a valid field.", 557 | "isDeprecated": false, 558 | "name": "ENUM", 559 | }, 560 | Object { 561 | "deprecationReason": null, 562 | "description": "Indicates this type is an input object. \`inputFields\` is a valid field.", 563 | "isDeprecated": false, 564 | "name": "INPUT_OBJECT", 565 | }, 566 | Object { 567 | "deprecationReason": null, 568 | "description": "Indicates this type is a list. \`ofType\` is a valid field.", 569 | "isDeprecated": false, 570 | "name": "LIST", 571 | }, 572 | Object { 573 | "deprecationReason": null, 574 | "description": "Indicates this type is a non-null. \`ofType\` is a valid field.", 575 | "isDeprecated": false, 576 | "name": "NON_NULL", 577 | }, 578 | ], 579 | "fields": null, 580 | "inputFields": null, 581 | "interfaces": null, 582 | "kind": "ENUM", 583 | "name": "__TypeKind", 584 | "possibleTypes": null, 585 | }, 586 | Object { 587 | "description": "The \`String\` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text.", 588 | "enumValues": null, 589 | "fields": null, 590 | "inputFields": null, 591 | "interfaces": null, 592 | "kind": "SCALAR", 593 | "name": "String", 594 | "possibleTypes": null, 595 | }, 596 | Object { 597 | "description": "The \`Boolean\` scalar type represents \`true\` or \`false\`.", 598 | "enumValues": null, 599 | "fields": null, 600 | "inputFields": null, 601 | "interfaces": null, 602 | "kind": "SCALAR", 603 | "name": "Boolean", 604 | "possibleTypes": null, 605 | }, 606 | Object { 607 | "description": "Object and Interface types are described by a list of Fields, each of which has a name, potentially a list of arguments, and a return type.", 608 | "enumValues": null, 609 | "fields": Array [ 610 | Object { 611 | "args": Array [], 612 | "deprecationReason": null, 613 | "description": null, 614 | "isDeprecated": false, 615 | "name": "name", 616 | "type": Object { 617 | "kind": "NON_NULL", 618 | "name": null, 619 | "ofType": Object { 620 | "kind": "SCALAR", 621 | "name": "String", 622 | "ofType": null, 623 | }, 624 | }, 625 | }, 626 | Object { 627 | "args": Array [], 628 | "deprecationReason": null, 629 | "description": null, 630 | "isDeprecated": false, 631 | "name": "description", 632 | "type": Object { 633 | "kind": "SCALAR", 634 | "name": "String", 635 | "ofType": null, 636 | }, 637 | }, 638 | Object { 639 | "args": Array [], 640 | "deprecationReason": null, 641 | "description": null, 642 | "isDeprecated": false, 643 | "name": "args", 644 | "type": Object { 645 | "kind": "NON_NULL", 646 | "name": null, 647 | "ofType": Object { 648 | "kind": "LIST", 649 | "name": null, 650 | "ofType": Object { 651 | "kind": "NON_NULL", 652 | "name": null, 653 | "ofType": Object { 654 | "kind": "OBJECT", 655 | "name": "__InputValue", 656 | "ofType": null, 657 | }, 658 | }, 659 | }, 660 | }, 661 | }, 662 | Object { 663 | "args": Array [], 664 | "deprecationReason": null, 665 | "description": null, 666 | "isDeprecated": false, 667 | "name": "type", 668 | "type": Object { 669 | "kind": "NON_NULL", 670 | "name": null, 671 | "ofType": Object { 672 | "kind": "OBJECT", 673 | "name": "__Type", 674 | "ofType": null, 675 | }, 676 | }, 677 | }, 678 | Object { 679 | "args": Array [], 680 | "deprecationReason": null, 681 | "description": null, 682 | "isDeprecated": false, 683 | "name": "isDeprecated", 684 | "type": Object { 685 | "kind": "NON_NULL", 686 | "name": null, 687 | "ofType": Object { 688 | "kind": "SCALAR", 689 | "name": "Boolean", 690 | "ofType": null, 691 | }, 692 | }, 693 | }, 694 | Object { 695 | "args": Array [], 696 | "deprecationReason": null, 697 | "description": null, 698 | "isDeprecated": false, 699 | "name": "deprecationReason", 700 | "type": Object { 701 | "kind": "SCALAR", 702 | "name": "String", 703 | "ofType": null, 704 | }, 705 | }, 706 | ], 707 | "inputFields": null, 708 | "interfaces": Array [], 709 | "kind": "OBJECT", 710 | "name": "__Field", 711 | "possibleTypes": null, 712 | }, 713 | Object { 714 | "description": "Arguments provided to Fields or Directives and the input fields of an InputObject are represented as Input Values which describe their type and optionally a default value.", 715 | "enumValues": null, 716 | "fields": Array [ 717 | Object { 718 | "args": Array [], 719 | "deprecationReason": null, 720 | "description": null, 721 | "isDeprecated": false, 722 | "name": "name", 723 | "type": Object { 724 | "kind": "NON_NULL", 725 | "name": null, 726 | "ofType": Object { 727 | "kind": "SCALAR", 728 | "name": "String", 729 | "ofType": null, 730 | }, 731 | }, 732 | }, 733 | Object { 734 | "args": Array [], 735 | "deprecationReason": null, 736 | "description": null, 737 | "isDeprecated": false, 738 | "name": "description", 739 | "type": Object { 740 | "kind": "SCALAR", 741 | "name": "String", 742 | "ofType": null, 743 | }, 744 | }, 745 | Object { 746 | "args": Array [], 747 | "deprecationReason": null, 748 | "description": null, 749 | "isDeprecated": false, 750 | "name": "type", 751 | "type": Object { 752 | "kind": "NON_NULL", 753 | "name": null, 754 | "ofType": Object { 755 | "kind": "OBJECT", 756 | "name": "__Type", 757 | "ofType": null, 758 | }, 759 | }, 760 | }, 761 | Object { 762 | "args": Array [], 763 | "deprecationReason": null, 764 | "description": "A GraphQL-formatted string representing the default value for this input value.", 765 | "isDeprecated": false, 766 | "name": "defaultValue", 767 | "type": Object { 768 | "kind": "SCALAR", 769 | "name": "String", 770 | "ofType": null, 771 | }, 772 | }, 773 | ], 774 | "inputFields": null, 775 | "interfaces": Array [], 776 | "kind": "OBJECT", 777 | "name": "__InputValue", 778 | "possibleTypes": null, 779 | }, 780 | Object { 781 | "description": "One possible value for a given Enum. Enum values are unique values, not a placeholder for a string or numeric value. However an Enum value is returned in a JSON response as a string.", 782 | "enumValues": null, 783 | "fields": Array [ 784 | Object { 785 | "args": Array [], 786 | "deprecationReason": null, 787 | "description": null, 788 | "isDeprecated": false, 789 | "name": "name", 790 | "type": Object { 791 | "kind": "NON_NULL", 792 | "name": null, 793 | "ofType": Object { 794 | "kind": "SCALAR", 795 | "name": "String", 796 | "ofType": null, 797 | }, 798 | }, 799 | }, 800 | Object { 801 | "args": Array [], 802 | "deprecationReason": null, 803 | "description": null, 804 | "isDeprecated": false, 805 | "name": "description", 806 | "type": Object { 807 | "kind": "SCALAR", 808 | "name": "String", 809 | "ofType": null, 810 | }, 811 | }, 812 | Object { 813 | "args": Array [], 814 | "deprecationReason": null, 815 | "description": null, 816 | "isDeprecated": false, 817 | "name": "isDeprecated", 818 | "type": Object { 819 | "kind": "NON_NULL", 820 | "name": null, 821 | "ofType": Object { 822 | "kind": "SCALAR", 823 | "name": "Boolean", 824 | "ofType": null, 825 | }, 826 | }, 827 | }, 828 | Object { 829 | "args": Array [], 830 | "deprecationReason": null, 831 | "description": null, 832 | "isDeprecated": false, 833 | "name": "deprecationReason", 834 | "type": Object { 835 | "kind": "SCALAR", 836 | "name": "String", 837 | "ofType": null, 838 | }, 839 | }, 840 | ], 841 | "inputFields": null, 842 | "interfaces": Array [], 843 | "kind": "OBJECT", 844 | "name": "__EnumValue", 845 | "possibleTypes": null, 846 | }, 847 | Object { 848 | "description": "A Directive provides a way to describe alternate runtime execution and type validation behavior in a GraphQL document. 849 | 850 | In some cases, you need to provide options to alter GraphQL's execution behavior in ways field arguments will not suffice, such as conditionally including or skipping a field. Directives provide this by describing additional information to the executor.", 851 | "enumValues": null, 852 | "fields": Array [ 853 | Object { 854 | "args": Array [], 855 | "deprecationReason": null, 856 | "description": null, 857 | "isDeprecated": false, 858 | "name": "name", 859 | "type": Object { 860 | "kind": "NON_NULL", 861 | "name": null, 862 | "ofType": Object { 863 | "kind": "SCALAR", 864 | "name": "String", 865 | "ofType": null, 866 | }, 867 | }, 868 | }, 869 | Object { 870 | "args": Array [], 871 | "deprecationReason": null, 872 | "description": null, 873 | "isDeprecated": false, 874 | "name": "description", 875 | "type": Object { 876 | "kind": "SCALAR", 877 | "name": "String", 878 | "ofType": null, 879 | }, 880 | }, 881 | Object { 882 | "args": Array [], 883 | "deprecationReason": null, 884 | "description": null, 885 | "isDeprecated": false, 886 | "name": "locations", 887 | "type": Object { 888 | "kind": "NON_NULL", 889 | "name": null, 890 | "ofType": Object { 891 | "kind": "LIST", 892 | "name": null, 893 | "ofType": Object { 894 | "kind": "NON_NULL", 895 | "name": null, 896 | "ofType": Object { 897 | "kind": "ENUM", 898 | "name": "__DirectiveLocation", 899 | "ofType": null, 900 | }, 901 | }, 902 | }, 903 | }, 904 | }, 905 | Object { 906 | "args": Array [], 907 | "deprecationReason": null, 908 | "description": null, 909 | "isDeprecated": false, 910 | "name": "args", 911 | "type": Object { 912 | "kind": "NON_NULL", 913 | "name": null, 914 | "ofType": Object { 915 | "kind": "LIST", 916 | "name": null, 917 | "ofType": Object { 918 | "kind": "NON_NULL", 919 | "name": null, 920 | "ofType": Object { 921 | "kind": "OBJECT", 922 | "name": "__InputValue", 923 | "ofType": null, 924 | }, 925 | }, 926 | }, 927 | }, 928 | }, 929 | Object { 930 | "args": Array [], 931 | "deprecationReason": "Use \`locations\`.", 932 | "description": null, 933 | "isDeprecated": true, 934 | "name": "onOperation", 935 | "type": Object { 936 | "kind": "NON_NULL", 937 | "name": null, 938 | "ofType": Object { 939 | "kind": "SCALAR", 940 | "name": "Boolean", 941 | "ofType": null, 942 | }, 943 | }, 944 | }, 945 | Object { 946 | "args": Array [], 947 | "deprecationReason": "Use \`locations\`.", 948 | "description": null, 949 | "isDeprecated": true, 950 | "name": "onFragment", 951 | "type": Object { 952 | "kind": "NON_NULL", 953 | "name": null, 954 | "ofType": Object { 955 | "kind": "SCALAR", 956 | "name": "Boolean", 957 | "ofType": null, 958 | }, 959 | }, 960 | }, 961 | Object { 962 | "args": Array [], 963 | "deprecationReason": "Use \`locations\`.", 964 | "description": null, 965 | "isDeprecated": true, 966 | "name": "onField", 967 | "type": Object { 968 | "kind": "NON_NULL", 969 | "name": null, 970 | "ofType": Object { 971 | "kind": "SCALAR", 972 | "name": "Boolean", 973 | "ofType": null, 974 | }, 975 | }, 976 | }, 977 | ], 978 | "inputFields": null, 979 | "interfaces": Array [], 980 | "kind": "OBJECT", 981 | "name": "__Directive", 982 | "possibleTypes": null, 983 | }, 984 | Object { 985 | "description": "A Directive can be adjacent to many parts of the GraphQL language, a __DirectiveLocation describes one such possible adjacencies.", 986 | "enumValues": Array [ 987 | Object { 988 | "deprecationReason": null, 989 | "description": "Location adjacent to a query operation.", 990 | "isDeprecated": false, 991 | "name": "QUERY", 992 | }, 993 | Object { 994 | "deprecationReason": null, 995 | "description": "Location adjacent to a mutation operation.", 996 | "isDeprecated": false, 997 | "name": "MUTATION", 998 | }, 999 | Object { 1000 | "deprecationReason": null, 1001 | "description": "Location adjacent to a subscription operation.", 1002 | "isDeprecated": false, 1003 | "name": "SUBSCRIPTION", 1004 | }, 1005 | Object { 1006 | "deprecationReason": null, 1007 | "description": "Location adjacent to a field.", 1008 | "isDeprecated": false, 1009 | "name": "FIELD", 1010 | }, 1011 | Object { 1012 | "deprecationReason": null, 1013 | "description": "Location adjacent to a fragment definition.", 1014 | "isDeprecated": false, 1015 | "name": "FRAGMENT_DEFINITION", 1016 | }, 1017 | Object { 1018 | "deprecationReason": null, 1019 | "description": "Location adjacent to a fragment spread.", 1020 | "isDeprecated": false, 1021 | "name": "FRAGMENT_SPREAD", 1022 | }, 1023 | Object { 1024 | "deprecationReason": null, 1025 | "description": "Location adjacent to an inline fragment.", 1026 | "isDeprecated": false, 1027 | "name": "INLINE_FRAGMENT", 1028 | }, 1029 | Object { 1030 | "deprecationReason": null, 1031 | "description": "Location adjacent to a schema definition.", 1032 | "isDeprecated": false, 1033 | "name": "SCHEMA", 1034 | }, 1035 | Object { 1036 | "deprecationReason": null, 1037 | "description": "Location adjacent to a scalar definition.", 1038 | "isDeprecated": false, 1039 | "name": "SCALAR", 1040 | }, 1041 | Object { 1042 | "deprecationReason": null, 1043 | "description": "Location adjacent to an object type definition.", 1044 | "isDeprecated": false, 1045 | "name": "OBJECT", 1046 | }, 1047 | Object { 1048 | "deprecationReason": null, 1049 | "description": "Location adjacent to a field definition.", 1050 | "isDeprecated": false, 1051 | "name": "FIELD_DEFINITION", 1052 | }, 1053 | Object { 1054 | "deprecationReason": null, 1055 | "description": "Location adjacent to an argument definition.", 1056 | "isDeprecated": false, 1057 | "name": "ARGUMENT_DEFINITION", 1058 | }, 1059 | Object { 1060 | "deprecationReason": null, 1061 | "description": "Location adjacent to an interface definition.", 1062 | "isDeprecated": false, 1063 | "name": "INTERFACE", 1064 | }, 1065 | Object { 1066 | "deprecationReason": null, 1067 | "description": "Location adjacent to a union definition.", 1068 | "isDeprecated": false, 1069 | "name": "UNION", 1070 | }, 1071 | Object { 1072 | "deprecationReason": null, 1073 | "description": "Location adjacent to an enum definition.", 1074 | "isDeprecated": false, 1075 | "name": "ENUM", 1076 | }, 1077 | Object { 1078 | "deprecationReason": null, 1079 | "description": "Location adjacent to an enum value definition.", 1080 | "isDeprecated": false, 1081 | "name": "ENUM_VALUE", 1082 | }, 1083 | Object { 1084 | "deprecationReason": null, 1085 | "description": "Location adjacent to an input object type definition.", 1086 | "isDeprecated": false, 1087 | "name": "INPUT_OBJECT", 1088 | }, 1089 | Object { 1090 | "deprecationReason": null, 1091 | "description": "Location adjacent to an input object field definition.", 1092 | "isDeprecated": false, 1093 | "name": "INPUT_FIELD_DEFINITION", 1094 | }, 1095 | ], 1096 | "fields": null, 1097 | "inputFields": null, 1098 | "interfaces": null, 1099 | "kind": "ENUM", 1100 | "name": "__DirectiveLocation", 1101 | "possibleTypes": null, 1102 | }, 1103 | ], 1104 | }, 1105 | } 1106 | `; 1107 | 1108 | exports[`graphql-rxjs import tests reactive directives works without prepareSchema 1`] = ` 1109 | Array [ 1110 | Object { 1111 | "data": Object { 1112 | "counter": 0, 1113 | }, 1114 | }, 1115 | Object { 1116 | "data": Object { 1117 | "counter": 1, 1118 | }, 1119 | }, 1120 | Object { 1121 | "data": Object { 1122 | "counter": 2, 1123 | }, 1124 | }, 1125 | Object { 1126 | "data": Object { 1127 | "counter": 3, 1128 | }, 1129 | }, 1130 | Object { 1131 | "data": Object { 1132 | "counter": 4, 1133 | }, 1134 | }, 1135 | Object { 1136 | "data": Object { 1137 | "counter": 5, 1138 | }, 1139 | }, 1140 | Object { 1141 | "data": Object { 1142 | "counter": 6, 1143 | }, 1144 | }, 1145 | Object { 1146 | "data": Object { 1147 | "counter": 7, 1148 | }, 1149 | }, 1150 | Object { 1151 | "data": Object { 1152 | "counter": 8, 1153 | }, 1154 | }, 1155 | Object { 1156 | "data": Object { 1157 | "counter": 9, 1158 | }, 1159 | }, 1160 | ] 1161 | `; 1162 | 1163 | exports[`graphql-rxjs import tests unsubscribe from observable as expected 1`] = ` 1164 | Array [ 1165 | Object { 1166 | "data": Object { 1167 | "counter": 0, 1168 | }, 1169 | }, 1170 | Object { 1171 | "data": Object { 1172 | "counter": 1, 1173 | }, 1174 | }, 1175 | Object { 1176 | "data": Object { 1177 | "counter": 2, 1178 | }, 1179 | }, 1180 | Object { 1181 | "data": Object { 1182 | "counter": 3, 1183 | }, 1184 | }, 1185 | Object { 1186 | "data": Object { 1187 | "counter": 4, 1188 | }, 1189 | }, 1190 | Object { 1191 | "data": Object { 1192 | "counter": 5, 1193 | }, 1194 | }, 1195 | Object { 1196 | "data": Object { 1197 | "counter": 6, 1198 | }, 1199 | }, 1200 | Object { 1201 | "data": Object { 1202 | "counter": 7, 1203 | }, 1204 | }, 1205 | Object { 1206 | "data": Object { 1207 | "counter": 8, 1208 | }, 1209 | }, 1210 | Object { 1211 | "data": Object { 1212 | "counter": 9, 1213 | }, 1214 | }, 1215 | ] 1216 | `; 1217 | 1218 | exports[`import-tests Rx Engines @live with infinate + static result sibling 1`] = ` 1219 | Array [ 1220 | Array [ 1221 | Object { 1222 | "inf": Object { 1223 | "value": "2", 1224 | }, 1225 | "liveInt": Object { 1226 | "root": 2, 1227 | "value": 0, 1228 | }, 1229 | "root": 2, 1230 | }, 1231 | ], 1232 | Array [ 1233 | Object { 1234 | "inf": Object { 1235 | "value": "2", 1236 | }, 1237 | "liveInt": Object { 1238 | "root": 2, 1239 | "value": 2, 1240 | }, 1241 | "root": 2, 1242 | }, 1243 | ], 1244 | Array [ 1245 | Object { 1246 | "inf": Object { 1247 | "value": "2", 1248 | }, 1249 | "liveInt": Object { 1250 | "root": 2, 1251 | "value": 4, 1252 | }, 1253 | "root": 2, 1254 | }, 1255 | ], 1256 | Array [ 1257 | Object { 1258 | "inf": Object { 1259 | "value": "2", 1260 | }, 1261 | "liveInt": Object { 1262 | "root": 2, 1263 | "value": 6, 1264 | }, 1265 | "root": 2, 1266 | }, 1267 | ], 1268 | Array [ 1269 | Object { 1270 | "inf": Object { 1271 | "value": "2", 1272 | }, 1273 | "liveInt": Object { 1274 | "root": 2, 1275 | "value": 8, 1276 | }, 1277 | "root": 2, 1278 | }, 1279 | ], 1280 | Array [ 1281 | Object { 1282 | "inf": Object { 1283 | "value": "2", 1284 | }, 1285 | "liveInt": Object { 1286 | "root": 2, 1287 | "value": 10, 1288 | }, 1289 | "root": 2, 1290 | }, 1291 | ], 1292 | Array [ 1293 | Object { 1294 | "inf": Object { 1295 | "value": "2", 1296 | }, 1297 | "liveInt": Object { 1298 | "root": 2, 1299 | "value": 12, 1300 | }, 1301 | "root": 2, 1302 | }, 1303 | ], 1304 | Array [ 1305 | Object { 1306 | "inf": Object { 1307 | "value": "2", 1308 | }, 1309 | "liveInt": Object { 1310 | "root": 2, 1311 | "value": 14, 1312 | }, 1313 | "root": 2, 1314 | }, 1315 | ], 1316 | Array [ 1317 | Object { 1318 | "inf": Object { 1319 | "value": "2", 1320 | }, 1321 | "liveInt": Object { 1322 | "root": 2, 1323 | "value": 16, 1324 | }, 1325 | "root": 2, 1326 | }, 1327 | ], 1328 | Array [ 1329 | Object { 1330 | "inf": Object { 1331 | "value": "2", 1332 | }, 1333 | "liveInt": Object { 1334 | "root": 2, 1335 | "value": 18, 1336 | }, 1337 | "root": 2, 1338 | }, 1339 | ], 1340 | ] 1341 | `; 1342 | 1343 | exports[`import-tests Rx Engines @live with infinate result sibling 1`] = ` 1344 | Array [ 1345 | Array [ 1346 | Object { 1347 | "inf": Object { 1348 | "value": "2", 1349 | }, 1350 | "liveInt": Object { 1351 | "root": 2, 1352 | "value": 0, 1353 | }, 1354 | }, 1355 | ], 1356 | Array [ 1357 | Object { 1358 | "inf": Object { 1359 | "value": "2", 1360 | }, 1361 | "liveInt": Object { 1362 | "root": 2, 1363 | "value": 2, 1364 | }, 1365 | }, 1366 | ], 1367 | Array [ 1368 | Object { 1369 | "inf": Object { 1370 | "value": "2", 1371 | }, 1372 | "liveInt": Object { 1373 | "root": 2, 1374 | "value": 4, 1375 | }, 1376 | }, 1377 | ], 1378 | Array [ 1379 | Object { 1380 | "inf": Object { 1381 | "value": "2", 1382 | }, 1383 | "liveInt": Object { 1384 | "root": 2, 1385 | "value": 6, 1386 | }, 1387 | }, 1388 | ], 1389 | Array [ 1390 | Object { 1391 | "inf": Object { 1392 | "value": "2", 1393 | }, 1394 | "liveInt": Object { 1395 | "root": 2, 1396 | "value": 8, 1397 | }, 1398 | }, 1399 | ], 1400 | Array [ 1401 | Object { 1402 | "inf": Object { 1403 | "value": "2", 1404 | }, 1405 | "liveInt": Object { 1406 | "root": 2, 1407 | "value": 10, 1408 | }, 1409 | }, 1410 | ], 1411 | Array [ 1412 | Object { 1413 | "inf": Object { 1414 | "value": "2", 1415 | }, 1416 | "liveInt": Object { 1417 | "root": 2, 1418 | "value": 12, 1419 | }, 1420 | }, 1421 | ], 1422 | Array [ 1423 | Object { 1424 | "inf": Object { 1425 | "value": "2", 1426 | }, 1427 | "liveInt": Object { 1428 | "root": 2, 1429 | "value": 14, 1430 | }, 1431 | }, 1432 | ], 1433 | Array [ 1434 | Object { 1435 | "inf": Object { 1436 | "value": "2", 1437 | }, 1438 | "liveInt": Object { 1439 | "root": 2, 1440 | "value": 16, 1441 | }, 1442 | }, 1443 | ], 1444 | Array [ 1445 | Object { 1446 | "inf": Object { 1447 | "value": "2", 1448 | }, 1449 | "liveInt": Object { 1450 | "root": 2, 1451 | "value": 18, 1452 | }, 1453 | }, 1454 | ], 1455 | ] 1456 | `; 1457 | 1458 | exports[`import-tests Rx Engines executeRx works as expected 1`] = ` 1459 | Object { 1460 | "data": Object { 1461 | "someInt": 1, 1462 | }, 1463 | } 1464 | `; 1465 | 1466 | exports[`import-tests Rx Engines mutation is not allowed to use reactive directives 1`] = ` 1467 | Array [ 1468 | Object { 1469 | "errors": Array [ 1470 | [GraphQLError: Anonymous Mutation is executed serially and cannot use @defer/@live], 1471 | ], 1472 | }, 1473 | ] 1474 | `; 1475 | 1476 | exports[`import-tests Rx Engines nested @live as expected 1`] = ` 1477 | Array [ 1478 | Array [ 1479 | Object { 1480 | "liveInt": Object { 1481 | "root": 2, 1482 | "value": 0, 1483 | }, 1484 | "root": 2, 1485 | }, 1486 | ], 1487 | Array [ 1488 | Object { 1489 | "liveInt": Object { 1490 | "root": 2, 1491 | "value": 2, 1492 | }, 1493 | "root": 2, 1494 | }, 1495 | ], 1496 | Array [ 1497 | Object { 1498 | "liveInt": Object { 1499 | "root": 2, 1500 | "value": 4, 1501 | }, 1502 | "root": 2, 1503 | }, 1504 | ], 1505 | Array [ 1506 | Object { 1507 | "liveInt": Object { 1508 | "root": 2, 1509 | "value": 6, 1510 | }, 1511 | "root": 2, 1512 | }, 1513 | ], 1514 | Array [ 1515 | Object { 1516 | "liveInt": Object { 1517 | "root": 2, 1518 | "value": 8, 1519 | }, 1520 | "root": 2, 1521 | }, 1522 | ], 1523 | Array [ 1524 | Object { 1525 | "liveInt": Object { 1526 | "root": 2, 1527 | "value": 10, 1528 | }, 1529 | "root": 2, 1530 | }, 1531 | ], 1532 | Array [ 1533 | Object { 1534 | "liveInt": Object { 1535 | "root": 2, 1536 | "value": 12, 1537 | }, 1538 | "root": 2, 1539 | }, 1540 | ], 1541 | Array [ 1542 | Object { 1543 | "liveInt": Object { 1544 | "root": 2, 1545 | "value": 14, 1546 | }, 1547 | "root": 2, 1548 | }, 1549 | ], 1550 | Array [ 1551 | Object { 1552 | "liveInt": Object { 1553 | "root": 2, 1554 | "value": 16, 1555 | }, 1556 | "root": 2, 1557 | }, 1558 | ], 1559 | Array [ 1560 | Object { 1561 | "liveInt": Object { 1562 | "root": 2, 1563 | "value": 18, 1564 | }, 1565 | "root": 2, 1566 | }, 1567 | ], 1568 | ] 1569 | `; 1570 | 1571 | exports[`import-tests Rx Engines nested @live unsubscribes as expected 1`] = ` 1572 | Array [ 1573 | Object { 1574 | "liveInt": 0, 1575 | "root": 1, 1576 | }, 1577 | Object { 1578 | "liveInt": 1, 1579 | "root": 1, 1580 | }, 1581 | Object { 1582 | "liveInt": 2, 1583 | "root": 1, 1584 | }, 1585 | Object { 1586 | "liveInt": 3, 1587 | "root": 1, 1588 | }, 1589 | Object { 1590 | "liveInt": 0, 1591 | "root": 2, 1592 | }, 1593 | Object { 1594 | "liveInt": 2, 1595 | "root": 2, 1596 | }, 1597 | Object { 1598 | "liveInt": 4, 1599 | "root": 2, 1600 | }, 1601 | Object { 1602 | "liveInt": 6, 1603 | "root": 2, 1604 | }, 1605 | Object { 1606 | "liveInt": 0, 1607 | "root": 3, 1608 | }, 1609 | Object { 1610 | "liveInt": 3, 1611 | "root": 3, 1612 | }, 1613 | ] 1614 | `; 1615 | 1616 | exports[`import-tests Rx Engines nested @live with lists unsubscribes as expected 1`] = ` 1617 | Array [ 1618 | Array [ 1619 | Object { 1620 | "liveInt": 0, 1621 | "root": 1, 1622 | }, 1623 | ], 1624 | Array [ 1625 | Object { 1626 | "liveInt": 1, 1627 | "root": 1, 1628 | }, 1629 | ], 1630 | Array [ 1631 | Object { 1632 | "liveInt": 2, 1633 | "root": 1, 1634 | }, 1635 | ], 1636 | Array [ 1637 | Object { 1638 | "liveInt": 3, 1639 | "root": 1, 1640 | }, 1641 | ], 1642 | Array [ 1643 | Object { 1644 | "liveInt": 0, 1645 | "root": 1, 1646 | }, 1647 | Object { 1648 | "liveInt": 0, 1649 | "root": 2, 1650 | }, 1651 | ], 1652 | Array [ 1653 | Object { 1654 | "liveInt": 1, 1655 | "root": 1, 1656 | }, 1657 | Object { 1658 | "liveInt": 0, 1659 | "root": 2, 1660 | }, 1661 | ], 1662 | Array [ 1663 | Object { 1664 | "liveInt": 1, 1665 | "root": 1, 1666 | }, 1667 | Object { 1668 | "liveInt": 2, 1669 | "root": 2, 1670 | }, 1671 | ], 1672 | Array [ 1673 | Object { 1674 | "liveInt": 2, 1675 | "root": 1, 1676 | }, 1677 | Object { 1678 | "liveInt": 2, 1679 | "root": 2, 1680 | }, 1681 | ], 1682 | Array [ 1683 | Object { 1684 | "liveInt": 2, 1685 | "root": 1, 1686 | }, 1687 | Object { 1688 | "liveInt": 4, 1689 | "root": 2, 1690 | }, 1691 | ], 1692 | Array [ 1693 | Object { 1694 | "liveInt": 3, 1695 | "root": 1, 1696 | }, 1697 | Object { 1698 | "liveInt": 4, 1699 | "root": 2, 1700 | }, 1701 | ], 1702 | ] 1703 | `; 1704 | 1705 | exports[`import-tests Rx Engines subscribeRx works as expected 1`] = ` 1706 | Array [ 1707 | Object { 1708 | "data": Object { 1709 | "counter": 0, 1710 | }, 1711 | }, 1712 | Object { 1713 | "data": Object { 1714 | "counter": 1, 1715 | }, 1716 | }, 1717 | Object { 1718 | "data": Object { 1719 | "counter": 2, 1720 | }, 1721 | }, 1722 | ] 1723 | `; 1724 | 1725 | exports[`import-tests Rx Engines validation works as expected 1`] = ` 1726 | Array [ 1727 | Object { 1728 | "data": Object { 1729 | "someInt": undefined, 1730 | }, 1731 | }, 1732 | Object { 1733 | "data": Object { 1734 | "someInt": 1, 1735 | }, 1736 | }, 1737 | ] 1738 | `; 1739 | -------------------------------------------------------------------------------- /__tests__/test-import.spec.ts: -------------------------------------------------------------------------------- 1 | import { Observable } from 'rxjs'; 2 | import { makeExecutableSchema } from 'graphql-tools'; 3 | import { 4 | executeRx, 5 | subscribeRx, 6 | graphqlRx, 7 | prepareSchema, 8 | validate, 9 | } from '..'; 10 | import { 11 | parse, 12 | introspectionQuery, 13 | GraphQLInt, 14 | GraphQLSchema, 15 | GraphQLObjectType, 16 | } from 'graphql'; 17 | import 'jest'; 18 | 19 | const counterSource = Observable.interval(10); 20 | 21 | describe('graphql-rxjs import tests', () => { 22 | it("can work with graphql-tools", () => { 23 | const typeDefs = ` 24 | # Root Subscription 25 | type Query { 26 | someInt: Int 27 | } 28 | `; 29 | 30 | const resolvers = { 31 | Query: { 32 | someInt(root, args, ctx) { 33 | return Observable.of(1); 34 | }, 35 | }, 36 | }; 37 | 38 | const scheme = makeExecutableSchema({typeDefs, resolvers}); 39 | prepareSchema(scheme); 40 | const query = ` 41 | query { 42 | someInt 43 | } 44 | `; 45 | 46 | return graphqlRx(scheme, query) 47 | .take(1) 48 | .toPromise().then((values) => { 49 | expect(values).toMatchSnapshot(); 50 | }); 51 | }); 52 | 53 | it("introspectionQuery works as expected", () => { 54 | const typeDefs = ` 55 | # Root Subscription 56 | type Query { 57 | someInt: Int 58 | } 59 | `; 60 | 61 | const resolvers = { 62 | Query: { 63 | someInt(root, args, ctx) { 64 | return Observable.of(1); 65 | }, 66 | }, 67 | }; 68 | 69 | const scheme = makeExecutableSchema({typeDefs, resolvers}); 70 | prepareSchema(scheme); 71 | 72 | return graphqlRx(scheme, introspectionQuery) 73 | .take(1) 74 | .toPromise().then((values) => { 75 | expect(values.errors).toBeUndefined(); 76 | expect(values.data).toMatchSnapshot(); 77 | }); 78 | }); 79 | 80 | it("also works with vanilla graphql objects", () => { 81 | const QueryType = new GraphQLObjectType({ 82 | name: 'Query', 83 | fields: { 84 | someInt: { 85 | type: GraphQLInt, 86 | resolve: (root, args, ctx) => { 87 | return Observable.of(123); 88 | }, 89 | } 90 | } 91 | }); 92 | 93 | const scheme = new GraphQLSchema({ 94 | query: QueryType, 95 | }); 96 | prepareSchema(scheme); 97 | 98 | const query = ` 99 | query { 100 | someInt 101 | } 102 | `; 103 | 104 | return graphqlRx(scheme, query, null) 105 | .bufferCount(3) 106 | .take(1) 107 | .toPromise().then((values) => { 108 | expect(values).toMatchSnapshot(); 109 | }); 110 | }); 111 | 112 | it("can also query static values", () => { 113 | const QueryType = new GraphQLObjectType({ 114 | name: 'Query', 115 | fields: { 116 | someInt: { 117 | type: GraphQLInt, 118 | resolve: (root, args, ctx) => { 119 | return 123321; 120 | }, 121 | } 122 | } 123 | }); 124 | 125 | const scheme = new GraphQLSchema({ 126 | query: QueryType, 127 | }); 128 | prepareSchema(scheme); 129 | 130 | const query = ` 131 | query { 132 | someInt 133 | } 134 | `; 135 | 136 | return graphqlRx(scheme, query) 137 | .bufferCount(3) 138 | .take(1) 139 | .toPromise().then((values) => { 140 | expect(values).toMatchSnapshot(); 141 | }); 142 | }); 143 | 144 | it("unsubscribe from observable as expected", () => { 145 | let unsubscribe = false; 146 | 147 | const QueryType = new GraphQLObjectType({ 148 | name: 'Query', 149 | fields: { 150 | counter: { 151 | type: GraphQLInt, 152 | resolve: (root, args, ctx) => { 153 | return new Observable((observer) => { 154 | let i = 0; 155 | const to = setInterval(() => { 156 | try { 157 | expect(unsubscribe).toBe(false); 158 | 159 | if ( i >= 15 ) { 160 | return; 161 | } 162 | 163 | observer.next(i); 164 | i ++; 165 | } catch (e) { 166 | observer.error(e); 167 | } 168 | }, 10); 169 | 170 | return () => { 171 | unsubscribe = true; 172 | 173 | clearInterval(to); 174 | }; 175 | }); 176 | }, 177 | } 178 | } 179 | }); 180 | 181 | const scheme = new GraphQLSchema({ 182 | query: QueryType, 183 | }); 184 | prepareSchema(scheme); 185 | 186 | const query = ` 187 | query { 188 | counter @live 189 | } 190 | `; 191 | 192 | return graphqlRx(scheme, query, null) 193 | .bufferCount(10) 194 | .take(1) 195 | .toPromise().then((values) => { 196 | expect(values).toMatchSnapshot(); 197 | expect(unsubscribe).toBe(true); 198 | }); 199 | }); 200 | 201 | it("able to add reactive directives to schema", () => { 202 | const QueryType = new GraphQLObjectType({ 203 | name: 'Query', 204 | fields: { 205 | counter: { 206 | type: GraphQLInt, 207 | resolve: (root, args, ctx) => { 208 | return Observable.interval(10); 209 | }, 210 | } 211 | } 212 | }); 213 | 214 | const scheme = new GraphQLSchema({ 215 | query: QueryType, 216 | }); 217 | prepareSchema(scheme); 218 | 219 | const query = ` 220 | query { 221 | counter @live 222 | } 223 | `; 224 | 225 | return graphqlRx(scheme, query, null) 226 | .bufferCount(10) 227 | .take(1) 228 | .toPromise().then((values) => { 229 | expect(values).toMatchSnapshot(); 230 | }); 231 | }); 232 | 233 | it("reactive directives works without prepareSchema", () => { 234 | const QueryType = new GraphQLObjectType({ 235 | name: 'Query', 236 | fields: { 237 | counter: { 238 | type: GraphQLInt, 239 | resolve: (root, args, ctx) => { 240 | return Observable.interval(10); 241 | }, 242 | } 243 | } 244 | }); 245 | 246 | const scheme = new GraphQLSchema({ 247 | query: QueryType, 248 | }); 249 | 250 | const query = ` 251 | query { 252 | counter @live 253 | } 254 | `; 255 | 256 | return graphqlRx(scheme, query, null) 257 | .bufferCount(10) 258 | .take(1) 259 | .toPromise().then((values) => { 260 | expect(values).toMatchSnapshot(); 261 | }); 262 | }); 263 | 264 | it("also works for subscriptions", () => { 265 | const SubscriptionType = new GraphQLObjectType({ 266 | name: 'Subscription', 267 | fields: { 268 | counter: { 269 | type: GraphQLInt, 270 | resolve: (root) => root, 271 | subscribe: (root, args, ctx) => { 272 | return ctx.counterSource; 273 | }, 274 | } 275 | } 276 | }); 277 | 278 | const QueryType = new GraphQLObjectType({ 279 | name: 'Query', 280 | fields: { 281 | someInt: { 282 | type: GraphQLInt, 283 | resolve: (root, args, ctx) => { 284 | return 123321; 285 | }, 286 | } 287 | } 288 | }); 289 | 290 | const scheme = new GraphQLSchema({ 291 | query: QueryType, 292 | subscription: SubscriptionType, 293 | }); 294 | prepareSchema(scheme); 295 | 296 | const query = ` 297 | subscription { 298 | counter 299 | } 300 | `; 301 | 302 | return graphqlRx(scheme, query, null, { counterSource }) 303 | .bufferCount(3) 304 | .take(1) 305 | .toPromise().then((values) => { 306 | expect(values).toMatchSnapshot(); 307 | }); 308 | }); 309 | }); 310 | 311 | describe('import-tests Rx Engines', () => { 312 | it('subscribeRx works as expected', () => { 313 | const SubscriptionType = new GraphQLObjectType({ 314 | name: 'Subscription', 315 | fields: { 316 | counter: { 317 | type: GraphQLInt, 318 | resolve: (root) => root, 319 | subscribe: (root, args, ctx) => { 320 | return ctx.counterSource; 321 | }, 322 | } 323 | } 324 | }); 325 | 326 | const QueryType = new GraphQLObjectType({ 327 | name: 'Query', 328 | fields: { 329 | someInt: { 330 | type: GraphQLInt, 331 | resolve: (root, args, ctx) => { 332 | return 123321; 333 | }, 334 | } 335 | } 336 | }); 337 | 338 | const scheme = new GraphQLSchema({ 339 | query: QueryType, 340 | subscription: SubscriptionType, 341 | }); 342 | prepareSchema(scheme); 343 | 344 | const query = ` 345 | subscription { 346 | counter 347 | } 348 | `; 349 | 350 | return subscribeRx(scheme, parse(query), null, { counterSource }) 351 | .bufferCount(3) 352 | .take(1) 353 | .toPromise().then((values) => { 354 | expect(values).toMatchSnapshot(); 355 | }); 356 | }); 357 | 358 | it('executeRx works as expected', () => { 359 | const typeDefs = ` 360 | # Root Subscription 361 | type Query { 362 | someInt: Int 363 | } 364 | `; 365 | 366 | const resolvers = { 367 | Query: { 368 | someInt(root, args, ctx) { 369 | return Observable.of(1); 370 | }, 371 | }, 372 | }; 373 | 374 | const scheme = makeExecutableSchema({typeDefs, resolvers}); 375 | prepareSchema(scheme); 376 | const query = ` 377 | query { 378 | someInt 379 | } 380 | `; 381 | 382 | return executeRx(scheme, parse(query)) 383 | .take(1) 384 | .toPromise().then((values) => { 385 | expect(values).toMatchSnapshot(); 386 | }); 387 | }); 388 | 389 | 390 | it("validation works as expected", () => { 391 | const typeDefs = ` 392 | type Query { 393 | someInt: Int 394 | } 395 | `; 396 | 397 | const resolvers = { 398 | Query: { 399 | someInt(root, args, ctx) { 400 | return Observable.of(1); 401 | }, 402 | }, 403 | }; 404 | 405 | const scheme = makeExecutableSchema({typeDefs, resolvers}); 406 | const query = parse(` 407 | query { 408 | someInt @defer 409 | } 410 | `); 411 | 412 | const validationErrors = validate(scheme, query); 413 | expect(validationErrors).toHaveLength(0); 414 | 415 | return executeRx(scheme, query) 416 | .toArray().toPromise().then((values) => { 417 | expect(values).toMatchSnapshot(); 418 | }); 419 | }); 420 | 421 | it("mutation is not allowed to use reactive directives", () => { 422 | const typeDefs = ` 423 | type Query { 424 | someInt: Int 425 | } 426 | 427 | type Mutation { 428 | shouldntDefer: Int 429 | } 430 | `; 431 | 432 | const resolvers = { 433 | Query: { 434 | someInt(root, args, ctx) { 435 | return Observable.of(1); 436 | }, 437 | }, 438 | Mutation: { 439 | shouldntDefer(root, args, ctx) { 440 | return Promise.resolve(1); 441 | } 442 | } 443 | }; 444 | 445 | const scheme = makeExecutableSchema({typeDefs, resolvers}); 446 | prepareSchema(scheme); 447 | const query = ` 448 | mutation { 449 | shouldntDefer @defer 450 | } 451 | `; 452 | 453 | return graphqlRx(scheme, query) 454 | .toArray().toPromise().then((values) => { 455 | expect(values).toMatchSnapshot(); 456 | }); 457 | }); 458 | 459 | it("nested @live unsubscribes as expected", () => { 460 | const obs = []; 461 | 462 | const typeDefs = ` 463 | type Nested { 464 | root: Int 465 | liveInt: Int 466 | } 467 | 468 | type Query { 469 | nestedType: Nested 470 | } 471 | `; 472 | const trackObservable = (observable) => new Observable((observer) => { 473 | const sub = observable.subscribe(observer); 474 | const id = obs.length; 475 | obs.push(true); 476 | 477 | return () => { 478 | obs[id] = false; 479 | sub.unsubscribe(); 480 | }; 481 | }); 482 | 483 | 484 | const resolvers = { 485 | Nested: { 486 | root(root) { 487 | return root; 488 | }, 489 | liveInt(root, args, ctx) { 490 | return trackObservable(Observable.interval(20).map((i) => i * root)); 491 | } 492 | }, 493 | Query: { 494 | nestedType(root, args, ctx) { 495 | return trackObservable(Observable.interval(100).skip(1)); 496 | }, 497 | }, 498 | }; 499 | 500 | const scheme = makeExecutableSchema({typeDefs, resolvers}); 501 | prepareSchema(scheme); 502 | const query = ` 503 | query { 504 | nestedType @live { 505 | root 506 | liveInt @live 507 | } 508 | } 509 | `; 510 | 511 | return graphqlRx(scheme, query) 512 | .map((v) => v.data.nestedType) 513 | .take(10) 514 | .toArray().toPromise().then((values) => { 515 | 516 | // If something left it means unsubscribe was not called for it. 517 | expect(obs 518 | .map((v, i) => ({ v, i })) 519 | .filter((m) => m.v) 520 | .map((m) => m.i) 521 | ).toHaveLength(0); 522 | 523 | expect(values).toMatchSnapshot(); 524 | }); 525 | }); 526 | 527 | it("nested @live with lists unsubscribes as expected", () => { 528 | const obs = []; 529 | 530 | const typeDefs = ` 531 | type Nested { 532 | root: Int 533 | liveInt: Int 534 | } 535 | 536 | type Query { 537 | nestedType: [Nested] 538 | } 539 | `; 540 | const trackObservable = (observable) => new Observable((observer) => { 541 | const sub = observable.subscribe(observer); 542 | const id = obs.length; 543 | obs.push(true); 544 | 545 | return () => { 546 | obs[id] = false; 547 | sub.unsubscribe(); 548 | }; 549 | }); 550 | 551 | 552 | const resolvers = { 553 | Nested: { 554 | root(root) { 555 | return root; 556 | }, 557 | liveInt(root, args, ctx) { 558 | return trackObservable(Observable.interval(20).map((i) => i * root)); 559 | } 560 | }, 561 | Query: { 562 | nestedType(root, args, ctx) { 563 | return trackObservable(Observable.interval(100).skip(1).scan((o, v) => [...o, v], [])); 564 | }, 565 | }, 566 | }; 567 | 568 | const scheme = makeExecutableSchema({typeDefs, resolvers}); 569 | prepareSchema(scheme); 570 | const query = ` 571 | query { 572 | nestedType @live { 573 | root 574 | liveInt @live 575 | } 576 | } 577 | `; 578 | 579 | return graphqlRx(scheme, query) 580 | .map((v) => v.data.nestedType) 581 | .take(10) 582 | .toArray().toPromise().then((values) => { 583 | // If something left it means unsubscribe was not called for it. 584 | expect(obs 585 | .map((v, i) => ({ v, i })) 586 | .filter((m) => m.v) 587 | .map((m) => m.i) 588 | ).toHaveLength(0); 589 | expect(values).toMatchSnapshot(); 590 | }); 591 | }); 592 | 593 | it("nested @live as expected", () => { 594 | const obs = []; 595 | 596 | const typeDefs = ` 597 | type FinalObject { 598 | root: Int 599 | value: Int 600 | } 601 | 602 | type Nested { 603 | root: Int 604 | liveInt: FinalObject 605 | } 606 | 607 | type Query { 608 | nestedType: [Nested] 609 | } 610 | `; 611 | 612 | const trackObservable = (observable) => new Observable((observer) => { 613 | const sub = observable.subscribe(observer); 614 | const id = obs.length; 615 | obs.push(true); 616 | 617 | return () => { 618 | obs[id] = false; 619 | sub.unsubscribe(); 620 | }; 621 | }); 622 | 623 | const resolvers = { 624 | FinalObject: { 625 | value: (root, args, ctx) => Observable.of(root.value), 626 | root: (root, args, ctx) => Observable.of(root.root), 627 | }, 628 | Nested: { 629 | root(root) { 630 | return root; 631 | }, 632 | liveInt(root, args, ctx) { 633 | return trackObservable(Observable.interval(20).map((i) => ({ root: root, value: i * root }))); 634 | } 635 | }, 636 | Query: { 637 | nestedType(root, args, ctx) { 638 | return trackObservable(new Observable((observer) => { 639 | observer.next([2]); 640 | })); 641 | }, 642 | }, 643 | }; 644 | 645 | const scheme = makeExecutableSchema({typeDefs, resolvers}); 646 | prepareSchema(scheme); 647 | const query = `query { 648 | nestedType @live { 649 | root 650 | liveInt @live { 651 | root 652 | value 653 | } 654 | } 655 | }`; 656 | 657 | return graphqlRx(scheme, query) 658 | .map((v) => v.data.nestedType) 659 | .take(10) 660 | .toArray().toPromise().then((values) => { 661 | // If something left it means unsubscribe was not called for it. 662 | expect(obs 663 | .map((v, i) => ({ v, i })) 664 | .filter((m) => m.v) 665 | .map((m) => m.i) 666 | ).toHaveLength(0); 667 | expect(values).toMatchSnapshot(); 668 | }); 669 | }); 670 | 671 | it("@live with infinate result sibling", () => { 672 | const obs = []; 673 | 674 | const typeDefs = ` 675 | type OtherType { 676 | value: String 677 | } 678 | 679 | type FinalObject { 680 | root: Int 681 | value: Int 682 | } 683 | 684 | type Nested { 685 | inf: OtherType 686 | liveInt: FinalObject 687 | } 688 | 689 | type Query { 690 | nestedType: [Nested] 691 | } 692 | `; 693 | 694 | const trackObservable = (observable) => new Observable((observer) => { 695 | const sub = observable.subscribe(observer); 696 | const id = obs.length; 697 | obs.push(true); 698 | 699 | return () => { 700 | obs[id] = false; 701 | sub.unsubscribe(); 702 | }; 703 | }); 704 | 705 | const resolvers = { 706 | OtherType: { 707 | value: (root) => Promise.resolve(root.value), 708 | }, 709 | Nested: { 710 | inf(root) { 711 | return trackObservable(new Observable((observer) => { 712 | observer.next({ value: root }); 713 | // No Complete. 714 | })); 715 | }, 716 | liveInt(root, args, ctx) { 717 | return trackObservable(Observable.interval(20).map((i) => ({ root: root, value: i * root }))); 718 | } 719 | }, 720 | Query: { 721 | nestedType(root, args, ctx) { 722 | return trackObservable(new Observable((observer) => { 723 | observer.next([2]); 724 | })); 725 | }, 726 | }, 727 | }; 728 | 729 | const scheme = makeExecutableSchema({typeDefs, resolvers}); 730 | prepareSchema(scheme); 731 | const query = `query mixedLive { 732 | nestedType @live { 733 | inf { 734 | value 735 | } 736 | liveInt { 737 | root 738 | value 739 | } 740 | } 741 | }`; 742 | 743 | return graphqlRx(scheme, query) 744 | .map((v) => v.data.nestedType) 745 | .take(10) 746 | .toArray().toPromise().then((values) => { 747 | // If something left it means unsubscribe was not called for it. 748 | expect(obs 749 | .map((v, i) => ({ v, i })) 750 | .filter((m) => m.v) 751 | .map((m) => m.i) 752 | ).toHaveLength(0); 753 | expect(values).toMatchSnapshot(); 754 | }); 755 | }); 756 | 757 | it("@live with infinate + static result sibling", () => { 758 | const obs = []; 759 | 760 | const typeDefs = ` 761 | type OtherType { 762 | value: String 763 | } 764 | 765 | type FinalObject { 766 | root: Int 767 | value: Int 768 | } 769 | 770 | type Nested { 771 | root: Int 772 | inf: OtherType 773 | liveInt: FinalObject 774 | } 775 | 776 | type Query { 777 | nestedType: [Nested] 778 | } 779 | `; 780 | 781 | const trackObservable = (observable) => new Observable((observer) => { 782 | const sub = observable.subscribe(observer); 783 | const id = obs.length; 784 | obs.push(true); 785 | 786 | return () => { 787 | obs[id] = false; 788 | sub.unsubscribe(); 789 | }; 790 | }); 791 | 792 | const resolvers = { 793 | OtherType: { 794 | value: (root) => Promise.resolve(root.value), 795 | }, 796 | Nested: { 797 | root(root) { 798 | return root; 799 | }, 800 | inf(root) { 801 | return trackObservable(new Observable((observer) => { 802 | observer.next({ value: root }); 803 | // No Complete. 804 | })); 805 | }, 806 | liveInt(root, args, ctx) { 807 | return trackObservable(Observable.interval(20).map((i) => ({ root: root, value: i * root }))); 808 | } 809 | }, 810 | Query: { 811 | nestedType(root, args, ctx) { 812 | return trackObservable(new Observable((observer) => { 813 | observer.next([2]); 814 | })); 815 | }, 816 | }, 817 | }; 818 | 819 | const scheme = makeExecutableSchema({typeDefs, resolvers}); 820 | prepareSchema(scheme); 821 | const query = `query mixedLive { 822 | nestedType @live { 823 | root 824 | inf { 825 | value 826 | } 827 | liveInt { 828 | root 829 | value 830 | } 831 | } 832 | }`; 833 | 834 | return graphqlRx(scheme, query) 835 | .map((v) => v.data.nestedType) 836 | .take(10) 837 | .toArray().toPromise().then((values) => { 838 | // If something left it means unsubscribe was not called for it. 839 | expect(obs 840 | .map((v, i) => ({ v, i })) 841 | .filter((m) => m.v) 842 | .map((m) => m.i) 843 | ).toHaveLength(0); 844 | expect(values).toMatchSnapshot(); 845 | }); 846 | }); 847 | }); 848 | -------------------------------------------------------------------------------- /__tests__/utils.spec.ts: -------------------------------------------------------------------------------- 1 | import * as root from '..'; 2 | import { Observable } from 'rxjs'; 3 | import { getAsyncIterator } from 'iterall'; 4 | 5 | const toAsyncIterable = (root).toAsyncIterable; 6 | 7 | async function asyncToArray(iterable, limit?) { 8 | const responses = []; 9 | const infiateLoop = true; 10 | const iter = getAsyncIterator(iterable); 11 | 12 | while (infiateLoop) { 13 | const result = await iter.next(); // eslint-disable-line no-await-in-loop 14 | 15 | if ( result.done ) { 16 | break; 17 | } 18 | 19 | responses.push(result.value); 20 | if ( (limit !== undefined) && (responses.length >= limit) ) { 21 | iter.return(); 22 | break; 23 | } 24 | } 25 | 26 | return responses; 27 | } 28 | 29 | describe('toAsyncIterable', () => { 30 | it('passes sanity', async () => { 31 | const iterable = toAsyncIterable(Observable.of(1)); 32 | const result = await asyncToArray(iterable); 33 | 34 | expect(result).toEqual([1]); 35 | }); 36 | 37 | it('works for more then one value', async () => { 38 | const iterable = toAsyncIterable(Observable.interval(10)); 39 | const result = await asyncToArray(iterable, 3); 40 | 41 | expect(result).toEqual([0, 1, 2]); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /babel-plugin-transform-import-duplicate.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const pathModule = require('path') 4 | const fs = require('fs') 5 | const nodeResolveSync = require('resolve').sync; 6 | const wrapListener = require('babel-plugin-detective/wrap-listener'); 7 | const miniMatch = require('minimatch'); 8 | 9 | const FILES_EQUAL_CACHE = {}; 10 | function filesEqual(source, target) { 11 | if ( false === FILES_EQUAL_CACHE.hasOwnProperty(source) ) { 12 | FILES_EQUAL_CACHE[source] = {}; 13 | } 14 | 15 | if ( false === FILES_EQUAL_CACHE[source].hasOwnProperty(target) ) { 16 | const equal = fs.readFileSync(source, "utf-8") === fs.readFileSync(target, "utf-8"); 17 | FILES_EQUAL_CACHE[source][target] = equal; 18 | } 19 | 20 | return FILES_EQUAL_CACHE[source][target]; 21 | } 22 | 23 | function resolveTarget(inPath) { 24 | let nodeModule = inPath.match(/node_modules\/(.+?)\..+?$/); 25 | if ( (null === nodeModule) || (nodeModule.length === 0) ) { 26 | throw new Error(`couldn't resolve ${inPath} back to node_module`); 27 | } 28 | nodeModule = nodeModule[1]; 29 | 30 | if ( nodeModule.endsWith('index') ) { 31 | nodeModule = pathModule.dirname(nodeModule); 32 | } 33 | 34 | return nodeModule; 35 | } 36 | 37 | function handleMapping(path, file, externals, originalPath, target) { 38 | if ( false === filesEqual(originalPath, target) ) { 39 | // Files are not equal, cannot replace. 40 | return; 41 | } 42 | 43 | // Files are equal, resolve dependancy and replace import 44 | const resolvedTarget = resolveTarget(target); 45 | if ( externals.indexOf(resolvedTarget) <= -1 ) { 46 | // Sanity to make sure it is known export. 47 | throw new Error(`${resolvedTarget} is not an external, please add it to both plugin and rollup externals`); 48 | } 49 | 50 | path.node.value = resolvedTarget; 51 | } 52 | 53 | function handleImport(path, file, opts) { 54 | const dirName = pathModule.dirname(file.parserOpts.sourceFileName); 55 | const importPath = nodeResolveSync(pathModule.join(dirName, path.node.value)); 56 | 57 | if ( opts.newFiles.some((x) => x === importPath) ) { 58 | // this is declared as new file, import always. 59 | return; 60 | } 61 | 62 | // Search for the mapping; 63 | const matching = Object.keys(opts.mapping).map((key) => { 64 | const regexKey = new RegExp(key); 65 | const match = importPath.match(regexKey); 66 | if ( !match || match.length === 0 ) { 67 | return undefined; 68 | } 69 | return importPath.replace(regexKey, opts.mapping[key]); 70 | }) 71 | .filter((v) => v) 72 | .map((v) => pathModule.resolve(v)); 73 | 74 | if ( matching.length === 0 ) { 75 | // Mapping does not exists 76 | return; 77 | } else if ( matching.length !== 1 ) { 78 | // More then one mapping. 79 | throw new Error('more then one matching! please check your config'); 80 | } 81 | 82 | // Mapping found :) 83 | return handleMapping(path, file, opts.externals, importPath, matching[0]); 84 | } 85 | 86 | function onImportListener(path, file, opts) { 87 | const mapping = (opts && opts.mapping) || undefined; 88 | const exclude = opts.exclude || []; 89 | const externals = opts.external || []; 90 | const newFiles = opts.newFiles || []; 91 | 92 | // Make sure mapping exists 93 | if ( undefined === mapping ) { 94 | throw new Error('mapping option is missing'); 95 | } 96 | 97 | opts.exclude = exclude; 98 | opts.externals = externals; 99 | opts.newFiles = newFiles; 100 | 101 | // filter literal values 102 | if (false === path.isLiteral()) { 103 | return; 104 | } 105 | const relPath = pathModule.relative(__dirname, file.parserOpts.sourceFileName); 106 | 107 | // Filter excluded & non-relative. 108 | let excluded = exclude.reduce((lastResult, globPath) => { 109 | if ( lastResult === true ) { 110 | return lastResult; 111 | } 112 | 113 | return miniMatch(relPath, globPath); 114 | }, false); 115 | 116 | // filter non-relative 117 | excluded = excluded || (!path.node.value) || (false === path.node.value.startsWith('.')); 118 | if (excluded) { 119 | return; 120 | } 121 | 122 | return handleImport(path, file, opts); 123 | } 124 | 125 | module.exports = wrapListener(onImportListener, 'transform-import-duplicate'); 126 | -------------------------------------------------------------------------------- /bundle.d.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DocumentNode, 3 | ExecutionResult, 4 | GraphQLSchema, 5 | GraphQLDirective, 6 | GraphQLFieldResolver, 7 | } from 'graphql'; 8 | export * from 'graphql'; 9 | import { Observable } from 'rxjs/Observable'; 10 | import { Subscriber } from 'rxjs/Subscriber'; 11 | import { TeardownLogic } from 'rxjs/Subscription'; 12 | 13 | export function graphqlReactive( 14 | schema: GraphQLSchema, 15 | requestString: string, 16 | rootValue?: any, 17 | contextValue?: any, 18 | variableValues?: {[key: string]: any}, 19 | operationName?: string 20 | ): Promise>; 21 | 22 | export function executeReactive( 23 | schema: GraphQLSchema, 24 | document: DocumentNode, 25 | rootValue?: any, 26 | contextValue?: any, 27 | variableValues?: {[key: string]: any}, 28 | operationName?: string, 29 | fieldResolver?: GraphQLFieldResolver, 30 | ): AsyncIterator; 31 | 32 | export function graphqlRx( 33 | schema: GraphQLSchema, 34 | requestString: string, 35 | rootValue?: any, 36 | contextValue?: any, 37 | variableValues?: {[key: string]: any}, 38 | operationName?: string, 39 | fieldResolver?: GraphQLFieldResolver, 40 | ): Observable; 41 | 42 | export function executeRx( 43 | schema: GraphQLSchema, 44 | document: DocumentNode, 45 | rootValue?: any, 46 | contextValue?: any, 47 | variableValues?: {[key: string]: any}, 48 | operationName?: string, 49 | fieldResolver?: GraphQLFieldResolver, 50 | ): Observable; 51 | 52 | export function subscribeRx( 53 | schema: GraphQLSchema, 54 | document: DocumentNode, 55 | rootValue?: any, 56 | contextValue?: any, 57 | variableValues?: {[key: string]: any}, 58 | operationName?: string, 59 | fieldResolver?: GraphQLFieldResolver, 60 | subscribeFieldResolver?: GraphQLFieldResolver 61 | ): Observable; 62 | 63 | export function prepareSchema( 64 | schema: GraphQLSchema, 65 | ): GraphQLSchema; 66 | 67 | export function addReactiveDirectivesToSchema( 68 | schema: GraphQLSchema, 69 | ): void; 70 | 71 | export function wrapResolvers( 72 | schema: GraphQLSchema, 73 | ): void; 74 | 75 | export function AsyncGeneratorFromObserver( 76 | subscribe?: (subscriber: Subscriber) => TeardownLogic, 77 | ): AsyncIterator; 78 | 79 | export const GraphQLLiveDirective: GraphQLDirective; 80 | export const GraphQLDeferDirective: GraphQLDirective; 81 | -------------------------------------------------------------------------------- /examples/example-defer.ts: -------------------------------------------------------------------------------- 1 | import { Observable } from 'rxjs'; 2 | import { makeExecutableSchema } from 'graphql-tools'; 3 | import { addReactiveDirectivesToSchema, graphqlReactive } from '..'; 4 | 5 | const remoteString = new Promise((resolve, reject) => { 6 | setTimeout(() => resolve('Hello World!'), 5000); 7 | }); 8 | 9 | const typeDefs = ` 10 | # Root Query 11 | type Query { 12 | remoteString: String 13 | } 14 | `; 15 | 16 | const resolvers = { 17 | Query: { 18 | remoteString: (root, args, ctx) => ctx.remoteString, 19 | }, 20 | }; 21 | 22 | const scheme = makeExecutableSchema({typeDefs, resolvers}); 23 | addReactiveDirectivesToSchema(scheme); 24 | 25 | const query = ` 26 | query { 27 | remoteString @defer 28 | } 29 | ` 30 | 31 | const log = (result) => console.log("[" + (new Date).toLocaleTimeString() + "] " + JSON.stringify(result)); 32 | 33 | graphqlReactive(scheme, query, null, { remoteString }) 34 | .subscribe(log, console.error.bind(console)); 35 | -------------------------------------------------------------------------------- /examples/example-live.ts: -------------------------------------------------------------------------------- 1 | import { Observable } from 'rxjs'; 2 | import { makeExecutableSchema } from 'graphql-tools'; 3 | import { addReactiveDirectivesToSchema, graphqlReactive } from '..'; 4 | 5 | const clockSource = Observable.interval(1000).map(() => new Date()).publishReplay(1).refCount(); 6 | 7 | const typeDefs = ` 8 | # Root Query 9 | type Query { 10 | clock: String 11 | } 12 | `; 13 | 14 | const resolvers = { 15 | Query: { 16 | clock: (root, args, ctx) => ctx.clockSource, 17 | }, 18 | }; 19 | 20 | const scheme = makeExecutableSchema({typeDefs, resolvers}); 21 | addReactiveDirectivesToSchema(scheme); 22 | 23 | const query = ` 24 | query { 25 | clock 26 | } 27 | ` 28 | 29 | const liveQuery = ` 30 | query { 31 | clock @live 32 | } 33 | ` 34 | 35 | graphqlReactive(scheme, query, null, { clockSource }) 36 | .subscribe(console.log.bind(console, "standard: "), console.error.bind(console)); 37 | 38 | graphqlReactive(scheme, liveQuery, null, { clockSource }) 39 | .subscribe(console.log.bind(console, "live: "), console.error.bind(console)); 40 | -------------------------------------------------------------------------------- /examples/example.ts: -------------------------------------------------------------------------------- 1 | import { Observable } from 'rxjs'; 2 | import { makeExecutableSchema } from 'graphql-tools'; 3 | import { graphqlReactive } from '..'; 4 | 5 | const clockSource = Observable.interval(1000).map(() => new Date()).publishReplay(1).refCount(); 6 | 7 | const typeDefs = ` 8 | # Root Query 9 | type Query { 10 | someInt: Int 11 | } 12 | 13 | # Root Subscription 14 | type Subscription { 15 | clock(throttle: Int): String 16 | } 17 | `; 18 | 19 | const resolvers = { 20 | Subscription: { 21 | clock(root, args, ctx) { 22 | if ( undefined === args.throttle ) { 23 | return ctx.clockSource; 24 | } else { 25 | return ctx.clockSource.throttleTime(args.throttle); 26 | } 27 | }, 28 | }, 29 | }; 30 | 31 | const scheme = makeExecutableSchema({typeDefs, resolvers}); 32 | 33 | const query = ` 34 | subscription { 35 | clock 36 | } 37 | ` 38 | 39 | graphqlReactive(scheme, query, null, { clockSource }) 40 | .subscribe(console.log.bind(console), console.error.bind(console)); 41 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "graphql-rxjs", 3 | "version": "0.11.7-0", 4 | "description": "Addon package for GraphQL which adds support for Observables", 5 | "contributors": [ 6 | "Hagai Cohen ", 7 | "Lee Byron (http://leebyron.com/)", 8 | "Nicholas Schrock ", 9 | "Daniel Schafer " 10 | ], 11 | "license": "BSD-3-Clause", 12 | "main": "dist/bundle.js", 13 | "typings": "bundle.d.ts", 14 | "homepage": "https://github.com/DxCx/graphql-rxjs", 15 | "bugs": { 16 | "url": "https://github.com/DxCx/graphql-rxjs/issues" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "http://github.com/DxCx/graphql-rxjs.git" 21 | }, 22 | "scripts": { 23 | "prebuild": "rimraf dist && node resources/fixModules.js", 24 | "build": "rollup -c", 25 | "pretest": "npm run build", 26 | "benchmark": "babel-node --max-old-space-size=4096 ./graphql/node_modules/.bin/_mocha --require ./graphql/resources/mocha-bootload ./resources/benchmark.js", 27 | "test": "npm run testonly", 28 | "testonly": "jest", 29 | "prepublish": "npm run test" 30 | }, 31 | "peerDependencies": { 32 | "graphql": "0.11.7", 33 | "rxjs": "^5.4.3" 34 | }, 35 | "optionalDependencies": { 36 | "@types/graphql": "^0.11.4" 37 | }, 38 | "devDependencies": { 39 | "@types/graphql": "^0.11.4", 40 | "@types/jest": "^21.1.1", 41 | "babel-cli": "^6.26.0", 42 | "babel-plugin-detective": "^2.0.0", 43 | "babel-plugin-external-helpers": "^6.22.0", 44 | "babel-preset-es2015-node": "^6.1.1", 45 | "benchmark": "^2.1.4", 46 | "fs-extra": "^4.0.2", 47 | "graphql": "0.11.7", 48 | "graphql-tools": "^1.2.3", 49 | "jest": "^21.2.1", 50 | "jest-cli": "^21.2.1", 51 | "minimatch": "^3.0.4", 52 | "rimraf": "^2.6.2", 53 | "rollup": "^0.50.0", 54 | "rollup-plugin-babel": "^3.0.2", 55 | "rollup-plugin-cleanup": "^1.0.1", 56 | "rollup-plugin-commonjs": "^8.2.1", 57 | "rollup-plugin-filesize": "^1.4.2", 58 | "rollup-plugin-flow": "^1.1.1", 59 | "rollup-plugin-node-resolve": "^3.0.0", 60 | "rollup-plugin-progress": "^0.4.0", 61 | "rollup-plugin-strip": "^1.1.1", 62 | "rollup-plugin-uglify": "^2.0.1", 63 | "rxjs": "^5.4.3", 64 | "ts-jest": "^21.0.1", 65 | "typescript": "^2.5.3", 66 | "yarn": "^1.1.0" 67 | }, 68 | "jest": { 69 | "transform": { 70 | ".(ts|tsx)": "/node_modules/ts-jest/preprocessor.js" 71 | }, 72 | "testEnvironment": "node", 73 | "testRegex": ".*\\.spec\\.ts$", 74 | "moduleFileExtensions": [ 75 | "ts", 76 | "js", 77 | "json" 78 | ] 79 | } 80 | } -------------------------------------------------------------------------------- /resources/benchmark.js: -------------------------------------------------------------------------------- 1 | import Benchmark from 'benchmark'; 2 | import { describe, it } from 'mocha'; 3 | 4 | import { 5 | GraphQLSchema, 6 | GraphQLObjectType, 7 | GraphQLInt, 8 | parse, 9 | graphql as graphqlOri, 10 | buildSchema, 11 | } from 'graphql'; 12 | 13 | import { Observable } from 'rxjs/Rx'; 14 | import { graphql as graphqlRx } from '..'; 15 | import { StarWarsSchema } from './benchmark_sw'; 16 | 17 | function runBenchmark(cases = {}, callback = () => {}) { 18 | const suite = new Benchmark.Suite(); 19 | const benchmarkCases = Object.keys(cases); 20 | for (const caseName of benchmarkCases) { 21 | suite.add(caseName, cases[caseName]); 22 | } 23 | 24 | console.log(''); 25 | suite.on('cycle', function (event) { 26 | console.log(` * ${String(event.target)}`); 27 | }) 28 | .on('complete', function () { 29 | console.log(` => Fastest is ${this.filter('fastest').map('name')}`); 30 | callback(this); 31 | }) 32 | .run(); 33 | } 34 | 35 | function verifyAndRunBenchmark(cases = {}) { 36 | // Ensure the results of tasks are the same before running benchmark. 37 | const verifyTaskResultsAreTheSame = Observable.pairs(cases) 38 | .map(([ caseName, task ]) => { 39 | return Observable.create(async subscriber => { 40 | const result = await task(); 41 | if (result) { 42 | subscriber.next([ caseName, result ]); 43 | subscriber.complete(); 44 | } else { 45 | subscriber.error(new Error(`Got no result on the benchmark task ${ 46 | caseName}`)); 47 | } 48 | }); 49 | }) 50 | .combineAll((...totalResults) => { 51 | const notMatchedResults = []; 52 | const compareResult = totalResults.reduce((a, b) => { 53 | const resultA = a[1]; 54 | const resultB = b[1]; 55 | if (JSON.stringify(resultA) !== JSON.stringify(resultB)) { 56 | notMatchedResults.push(b); 57 | } 58 | // Continue using the first result (a) to compare to other results. 59 | return a; 60 | }); 61 | 62 | if (notMatchedResults.length > 0) { 63 | const standardCaseName = compareResult[0]; 64 | const standerdResult = JSON.stringify(compareResult[1], null, 2); 65 | const notMatchedInfo = notMatchedResults.reduce((acc, curr) => { 66 | return acc + `${curr[0]}: ${JSON.stringify(curr[1], null, 2)}`; 67 | }, ''); 68 | const error = new Error('Results are not matched to each others.\n' + 69 | `Standard result (${standardCaseName}): ${standerdResult}\n` + 70 | 'Not matched results:\n' + notMatchedInfo 71 | ); 72 | return Observable.throw(error); 73 | } 74 | 75 | return Observable.empty(); 76 | }) 77 | .mergeAll(); 78 | 79 | return verifyTaskResultsAreTheSame.toPromise() 80 | .then(() => { 81 | // Only passing the verification will run the benchmark. 82 | runBenchmark(cases); 83 | }); 84 | } 85 | 86 | // Copy the serial test from mutations-test.js 87 | class NumberHolder { 88 | constructor(originalNumber) { 89 | this.theNumber = originalNumber; 90 | } 91 | } 92 | 93 | class Root { 94 | constructor(originalNumber) { 95 | this.numberHolder = new NumberHolder(originalNumber); 96 | } 97 | 98 | immediatelyChangeTheNumber(newNumber) { 99 | this.numberHolder.theNumber = newNumber; 100 | return this.numberHolder; 101 | } 102 | 103 | promiseToChangeTheNumber(newNumber) { 104 | return new Promise(resolve => { 105 | process.nextTick(() => { 106 | resolve(this.immediatelyChangeTheNumber(newNumber)); 107 | }); 108 | }); 109 | } 110 | 111 | failToChangeTheNumber() { 112 | throw new Error('Cannot change the number'); 113 | } 114 | 115 | promiseAndFailToChangeTheNumber() { 116 | return new Promise((resolve, reject) => { 117 | process.nextTick(() => { 118 | reject(new Error('Cannot change the number')); 119 | }); 120 | }); 121 | } 122 | } 123 | 124 | const numberHolderType = new GraphQLObjectType({ 125 | fields: { 126 | theNumber: { type: GraphQLInt }, 127 | }, 128 | name: 'NumberHolder', 129 | }); 130 | const schema = new GraphQLSchema({ 131 | query: new GraphQLObjectType({ 132 | fields: { 133 | numberHolder: { type: numberHolderType }, 134 | }, 135 | name: 'Query', 136 | }), 137 | mutation: new GraphQLObjectType({ 138 | fields: { 139 | immediatelyChangeTheNumber: { 140 | type: numberHolderType, 141 | args: { newNumber: { type: GraphQLInt } }, 142 | resolve(obj, { newNumber }) { 143 | return obj.immediatelyChangeTheNumber(newNumber); 144 | } 145 | }, 146 | promiseToChangeTheNumber: { 147 | type: numberHolderType, 148 | args: { newNumber: { type: GraphQLInt } }, 149 | resolve(obj, { newNumber }) { 150 | return obj.promiseToChangeTheNumber(newNumber); 151 | } 152 | }, 153 | failToChangeTheNumber: { 154 | type: numberHolderType, 155 | args: { newNumber: { type: GraphQLInt } }, 156 | resolve(obj, { newNumber }) { 157 | return obj.failToChangeTheNumber(newNumber); 158 | } 159 | }, 160 | promiseAndFailToChangeTheNumber: { 161 | type: numberHolderType, 162 | args: { newNumber: { type: GraphQLInt } }, 163 | resolve(obj, { newNumber }) { 164 | return obj.promiseAndFailToChangeTheNumber(newNumber); 165 | } 166 | } 167 | }, 168 | name: 'Mutation', 169 | }) 170 | }); 171 | 172 | 173 | describe('Run Benchmark', function () { 174 | 175 | this.timeout(60000); 176 | 177 | it('compare performance for simple query', function (done) { 178 | const query = ` 179 | query HeroNameQuery { 180 | hero { 181 | name 182 | } 183 | } 184 | `; 185 | 186 | verifyAndRunBenchmark({ 187 | graphqlOri: () => { 188 | return graphqlOri(StarWarsSchema, query); 189 | }, 190 | graphqlRx: () => { 191 | return graphqlRx(StarWarsSchema, query); 192 | } 193 | }) 194 | .then(done, done); 195 | }); 196 | 197 | it('compare performance for deep query', function (done) { 198 | const query = ` 199 | query NestedQuery { 200 | hero { 201 | name 202 | friends { 203 | name 204 | appearsIn 205 | friends { 206 | name 207 | } 208 | } 209 | } 210 | } 211 | `; 212 | 213 | verifyAndRunBenchmark({ 214 | graphqlOri: () => { 215 | return graphqlOri(StarWarsSchema, query); 216 | }, 217 | graphqlRx: () => { 218 | return graphqlRx(StarWarsSchema, query); 219 | }, 220 | }) 221 | .then(done, done); 222 | }); 223 | 224 | it('compare performance for serial mutation', function (done) { 225 | const doc = `mutation M { 226 | first: immediatelyChangeTheNumber(newNumber: 1) { 227 | theNumber 228 | }, 229 | second: promiseToChangeTheNumber(newNumber: 2) { 230 | theNumber 231 | }, 232 | third: failToChangeTheNumber(newNumber: 3) { 233 | theNumber 234 | } 235 | fourth: promiseToChangeTheNumber(newNumber: 4) { 236 | theNumber 237 | }, 238 | fifth: immediatelyChangeTheNumber(newNumber: 5) { 239 | theNumber 240 | } 241 | sixth: promiseAndFailToChangeTheNumber(newNumber: 6) { 242 | theNumber 243 | } 244 | }`; 245 | const documentAST = parse(doc); 246 | verifyAndRunBenchmark({ 247 | graphqlOri: () => { 248 | return graphqlOri(schema, documentAST, new Root(6)); 249 | }, 250 | graphqlRx: () => { 251 | return graphqlRx(schema, documentAST, new Root(6)); 252 | } 253 | }) 254 | .then(done, done); 255 | }); 256 | 257 | }); 258 | -------------------------------------------------------------------------------- /resources/benchmark_sw.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | /** 3 | * Copyright (c) 2015, Facebook, Inc. 4 | * All rights reserved. 5 | * 6 | * This source code is licensed under the BSD-style license found in the 7 | * LICENSE file in the root directory of this source tree. An additional grant 8 | * of patent rights can be found in the PATENTS file in the same directory. 9 | */ 10 | 11 | import { 12 | GraphQLEnumType, 13 | GraphQLInterfaceType, 14 | GraphQLObjectType, 15 | GraphQLList, 16 | GraphQLNonNull, 17 | GraphQLSchema, 18 | GraphQLString, 19 | } from 'graphql'; 20 | 21 | import { getFriends, getHero, getHuman, getDroid } from '../graphql/src/__tests__/starWarsData.js'; 22 | 23 | /** 24 | * This is designed to be an end-to-end test, demonstrating 25 | * the full GraphQL stack. 26 | * 27 | * We will create a GraphQL schema that describes the major 28 | * characters in the original Star Wars trilogy. 29 | * 30 | * NOTE: This may contain spoilers for the original Star 31 | * Wars trilogy. 32 | */ 33 | 34 | /** 35 | * Using our shorthand to describe type systems, the type system for our 36 | * Star Wars example is: 37 | * 38 | * enum Episode { NEWHOPE, EMPIRE, JEDI } 39 | * 40 | * interface Character { 41 | * id: String! 42 | * name: String 43 | * friends: [Character] 44 | * appearsIn: [Episode] 45 | * } 46 | * 47 | * type Human implements Character { 48 | * id: String! 49 | * name: String 50 | * friends: [Character] 51 | * appearsIn: [Episode] 52 | * homePlanet: String 53 | * } 54 | * 55 | * type Droid implements Character { 56 | * id: String! 57 | * name: String 58 | * friends: [Character] 59 | * appearsIn: [Episode] 60 | * primaryFunction: String 61 | * } 62 | * 63 | * type Query { 64 | * hero(episode: Episode): Character 65 | * human(id: String!): Human 66 | * droid(id: String!): Droid 67 | * } 68 | * 69 | * We begin by setting up our schema. 70 | */ 71 | 72 | /** 73 | * The original trilogy consists of three movies. 74 | * 75 | * This implements the following type system shorthand: 76 | * enum Episode { NEWHOPE, EMPIRE, JEDI } 77 | */ 78 | const episodeEnum = new GraphQLEnumType({ 79 | name: 'Episode', 80 | description: 'One of the films in the Star Wars Trilogy', 81 | values: { 82 | NEWHOPE: { 83 | value: 4, 84 | description: 'Released in 1977.', 85 | }, 86 | EMPIRE: { 87 | value: 5, 88 | description: 'Released in 1980.', 89 | }, 90 | JEDI: { 91 | value: 6, 92 | description: 'Released in 1983.', 93 | }, 94 | } 95 | }); 96 | 97 | /** 98 | * Characters in the Star Wars trilogy are either humans or droids. 99 | * 100 | * This implements the following type system shorthand: 101 | * interface Character { 102 | * id: String! 103 | * name: String 104 | * friends: [Character] 105 | * appearsIn: [Episode] 106 | * secretBackstory: String 107 | * } 108 | */ 109 | const characterInterface = new GraphQLInterfaceType({ 110 | name: 'Character', 111 | description: 'A character in the Star Wars Trilogy', 112 | fields: () => ({ 113 | id: { 114 | type: new GraphQLNonNull(GraphQLString), 115 | description: 'The id of the character.', 116 | }, 117 | name: { 118 | type: GraphQLString, 119 | description: 'The name of the character.', 120 | }, 121 | friends: { 122 | type: new GraphQLList(characterInterface), 123 | description: 'The friends of the character, or an empty list if they ' + 124 | 'have none.', 125 | }, 126 | appearsIn: { 127 | type: new GraphQLList(episodeEnum), 128 | description: 'Which movies they appear in.', 129 | }, 130 | secretBackstory: { 131 | type: GraphQLString, 132 | description: 'All secrets about their past.', 133 | }, 134 | }), 135 | resolveType(character) { 136 | if (character.type === 'Human') { 137 | return humanType; 138 | } 139 | if (character.type === 'Droid') { 140 | return droidType; 141 | } 142 | } 143 | }); 144 | 145 | /** 146 | * We define our human type, which implements the character interface. 147 | * 148 | * This implements the following type system shorthand: 149 | * type Human : Character { 150 | * id: String! 151 | * name: String 152 | * friends: [Character] 153 | * appearsIn: [Episode] 154 | * secretBackstory: String 155 | * } 156 | */ 157 | const humanType = new GraphQLObjectType({ 158 | name: 'Human', 159 | description: 'A humanoid creature in the Star Wars universe.', 160 | fields: () => ({ 161 | id: { 162 | type: new GraphQLNonNull(GraphQLString), 163 | description: 'The id of the human.', 164 | }, 165 | name: { 166 | type: GraphQLString, 167 | description: 'The name of the human.', 168 | }, 169 | friends: { 170 | type: new GraphQLList(characterInterface), 171 | description: 172 | 'The friends of the human, or an empty list if they have none.', 173 | resolve: human => getFriends(human), 174 | }, 175 | appearsIn: { 176 | type: new GraphQLList(episodeEnum), 177 | description: 'Which movies they appear in.', 178 | }, 179 | homePlanet: { 180 | type: GraphQLString, 181 | description: 'The home planet of the human, or null if unknown.', 182 | }, 183 | secretBackstory: { 184 | type: GraphQLString, 185 | description: 'Where are they from and how they came to be who they are.', 186 | resolve() { 187 | throw new Error('secretBackstory is secret.'); 188 | }, 189 | }, 190 | }), 191 | interfaces: [ characterInterface ] 192 | }); 193 | 194 | /** 195 | * The other type of character in Star Wars is a droid. 196 | * 197 | * This implements the following type system shorthand: 198 | * type Droid : Character { 199 | * id: String! 200 | * name: String 201 | * friends: [Character] 202 | * appearsIn: [Episode] 203 | * secretBackstory: String 204 | * primaryFunction: String 205 | * } 206 | */ 207 | const droidType = new GraphQLObjectType({ 208 | name: 'Droid', 209 | description: 'A mechanical creature in the Star Wars universe.', 210 | fields: () => ({ 211 | id: { 212 | type: new GraphQLNonNull(GraphQLString), 213 | description: 'The id of the droid.', 214 | }, 215 | name: { 216 | type: GraphQLString, 217 | description: 'The name of the droid.', 218 | }, 219 | friends: { 220 | type: new GraphQLList(characterInterface), 221 | description: 222 | 'The friends of the droid, or an empty list if they have none.', 223 | resolve: droid => getFriends(droid), 224 | }, 225 | appearsIn: { 226 | type: new GraphQLList(episodeEnum), 227 | description: 'Which movies they appear in.', 228 | }, 229 | secretBackstory: { 230 | type: GraphQLString, 231 | description: 'Construction date and the name of the designer.', 232 | resolve() { 233 | throw new Error('secretBackstory is secret.'); 234 | }, 235 | }, 236 | primaryFunction: { 237 | type: GraphQLString, 238 | description: 'The primary function of the droid.', 239 | }, 240 | }), 241 | interfaces: [ characterInterface ] 242 | }); 243 | 244 | /** 245 | * This is the type that will be the root of our query, and the 246 | * entry point into our schema. It gives us the ability to fetch 247 | * objects by their IDs, as well as to fetch the undisputed hero 248 | * of the Star Wars trilogy, R2-D2, directly. 249 | * 250 | * This implements the following type system shorthand: 251 | * type Query { 252 | * hero(episode: Episode): Character 253 | * human(id: String!): Human 254 | * droid(id: String!): Droid 255 | * } 256 | * 257 | */ 258 | const queryType = new GraphQLObjectType({ 259 | name: 'Query', 260 | fields: () => ({ 261 | hero: { 262 | type: characterInterface, 263 | args: { 264 | episode: { 265 | description: 'If omitted, returns the hero of the whole saga. If ' + 266 | 'provided, returns the hero of that particular episode.', 267 | type: episodeEnum 268 | } 269 | }, 270 | resolve: (root, { episode }) => getHero(episode), 271 | }, 272 | human: { 273 | type: humanType, 274 | args: { 275 | id: { 276 | description: 'id of the human', 277 | type: new GraphQLNonNull(GraphQLString) 278 | } 279 | }, 280 | resolve: (root, { id }) => getHuman(id), 281 | }, 282 | droid: { 283 | type: droidType, 284 | args: { 285 | id: { 286 | description: 'id of the droid', 287 | type: new GraphQLNonNull(GraphQLString) 288 | } 289 | }, 290 | resolve: (root, { id }) => getDroid(id), 291 | }, 292 | }) 293 | }); 294 | 295 | /** 296 | * Finally, we construct our schema (whose starting query type is the query 297 | * type we defined above) and export it. 298 | */ 299 | export const StarWarsSchema = new GraphQLSchema({ 300 | query: queryType, 301 | types: [ humanType, droidType ] 302 | }); 303 | 304 | -------------------------------------------------------------------------------- /resources/fixModules.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const moveSync = require('fs-extra').moveSync; 3 | const fs = require('fs'); 4 | const execFileSync = require('child_process').execFileSync; 5 | 6 | console.log('Executing npm install of graphql'); 7 | console.log(execFileSync('yarn', [ 8 | 'install', 9 | ], { encoding: 'utf-8', cwd: path.join(__dirname, '..', 'graphql') })); 10 | 11 | const thisModulesPath = path.join(__dirname, '..', 'node_modules'); 12 | const graphqlModulesPath = path.join(__dirname, '..', 'graphql', 'node_modules'); 13 | const thisDeps = fs.readdirSync(thisModulesPath); 14 | const graphqlDeps = fs.readdirSync(graphqlModulesPath); 15 | const missingDeps = graphqlDeps 16 | .filter((i) => false === i.startsWith('.')) 17 | .filter((i) => thisDeps.indexOf(i) === -1); 18 | 19 | missingDeps.forEach((depName) => { 20 | const thisPath = path.join(thisModulesPath, depName); 21 | const graphqlPath = path.join(graphqlModulesPath, depName); 22 | fs.symlinkSync(graphqlPath, thisPath, 'dir'); 23 | }); 24 | -------------------------------------------------------------------------------- /resources/patchVersion.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | 4 | const packageJsonFile = path.resolve('.', 'package.json'); 5 | const packageJson = JSON.parse(fs.readFileSync(packageJsonFile, 'utf-8')); 6 | const targetVersion = process.argv.length > 1 && process.argv[2]; 7 | if ( !targetVersion ) { 8 | throw new Error('VERSION argument is not provided, please provide it with M.m.p format'); 9 | } 10 | 11 | // Patch fields 12 | packageJson.version = `${targetVersion}-0`; 13 | packageJson.peerDependencies.graphql = targetVersion; 14 | packageJson.devDependencies.graphql = targetVersion; 15 | 16 | fs.writeFileSync(packageJsonFile, JSON.stringify(packageJson, null, 2)); 17 | -------------------------------------------------------------------------------- /resources/updateVersion.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | if [ -z ${1+x} ]; then echo "version argument not given, please state M.m.p"; exit 1; fi 4 | VERSION=v${1}-0 5 | echo "Updating package to ${VERSION}" 6 | 7 | git remote update origin 8 | git pull origin master 9 | 10 | # Update package.json 11 | node ./resources/patchVersion.js ${1} 12 | 13 | # Update Submodule repo 14 | cd graphql 15 | git remote update origin 16 | TARGET_BRANCH=async-v${1} 17 | BRANCH_NAME=`git symbolic-ref HEAD 2>/dev/null` || BRANCH_NAME='refs/head/' 18 | BRANCH_NAME=${BRANCH_NAME##refs/heads/} 19 | if [ "${BRANCH_NAME}" != "${TARGET_BRANCH}" ]; then 20 | git branch -D ${TARGET_BRANCH} || true; 21 | git checkout -t origin/${TARGET_BRANCH}; 22 | fi 23 | cd ../ 24 | 25 | # Test the output 26 | rm -Rf node_modules/ 27 | yarn install 28 | # npm install runs also npm test 29 | 30 | # Generate version branch 31 | git checkout -b ${VERSION} 32 | git add graphql 33 | git add package.json 34 | git commit -m "chore(package): ${VERSION}" 35 | git tag ${VERSION} 36 | git push -u origin HEAD:${VERSION} --tags 37 | 38 | echo "RUN npm publish To finish the process after CI approves" 39 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import nodeResolve from 'rollup-plugin-node-resolve'; 4 | import commonjs from 'rollup-plugin-commonjs'; 5 | import babel from 'rollup-plugin-babel'; 6 | import cleanup from 'rollup-plugin-cleanup'; 7 | import strip from 'rollup-plugin-strip'; 8 | import filesize from 'rollup-plugin-filesize'; 9 | import progress from 'rollup-plugin-progress'; 10 | import * as fs from 'fs'; 11 | import * as path from 'path'; 12 | import uglify from 'rollup-plugin-uglify'; 13 | 14 | const BABEL_PLUGIN_REPLACE = { 15 | './resources/inline-invariant': require('./graphql/resources/inline-invariant'), 16 | 'transform-import-duplicate': require('./babel-plugin-transform-import-duplicate'), 17 | }; 18 | 19 | const DEBUG = false; 20 | 21 | // Load Original babel config 22 | const babelConfig = JSON.parse(fs.readFileSync(path.join( 23 | __dirname, 24 | 'graphql', 25 | '.babelrc' 26 | ), 'utf-8')); 27 | 28 | const pkg = JSON.parse(fs.readFileSync(path.join( 29 | __dirname, 30 | 'graphql', 31 | 'package.json' 32 | ))); 33 | 34 | const rxPkg = JSON.parse(fs.readFileSync(path.join( 35 | __dirname, 36 | 'package.json' 37 | ))); 38 | 39 | const external = Object.keys(pkg.dependencies || {}) 40 | .concat(Object.keys(rxPkg.dependencies || {})) 41 | .concat([ 42 | 'rxjs/Observable', 43 | 'rxjs/add/observable/fromPromise', 44 | 45 | // jsUtils 46 | 'graphql/jsutils/ObjMap', 47 | 48 | 'graphql/type', 49 | 'graphql/language', 50 | 'graphql/language/source', 51 | 'graphql/language/kinds', 52 | 'graphql/language/parser', 53 | 'graphql/language/ast', 54 | 'graphql/error', 55 | 'graphql/error/GraphQLError', 56 | 'graphql/error/locatedError', 57 | 'graphql/utilities', 58 | 'graphql/type/schema', 59 | 'graphql/jsutils/find', 60 | 'graphql/jsutils/invariant', 61 | 'graphql/jsutils/isNullish', 62 | 'graphql/utilities/typeFromAST', 63 | 'graphql/utilities/getOperationAST', 64 | 'graphql/execution/values', 65 | 'graphql/type/definition', 66 | 'graphql/type/schema', 67 | 'graphql/type/introspection', 68 | 'graphql/type/directives', 69 | 'graphql/type/scalars', 70 | 'graphql/subscription', 71 | 72 | // Validation rules 73 | 'graphql/validation/validate', 74 | 'graphql/validation/rules/ArgumentsOfCorrectType', 75 | 'graphql/validation/rules/DefaultValuesOfCorrectType', 76 | 'graphql/validation/rules/FieldsOnCorrectType', 77 | 'graphql/validation/rules/FragmentsOnCompositeTypes', 78 | 'graphql/validation/rules/KnownArgumentNames', 79 | 'graphql/validation/rules/KnownDirectives', 80 | 'graphql/validation/rules/KnownFragmentNames', 81 | 'graphql/validation/rules/KnownTypeNames', 82 | 'graphql/validation/rules/LoneAnonymousOperation', 83 | 'graphql/validation/rules/NoFragmentCycles', 84 | 'graphql/validation/rules/NoUndefinedVariables', 85 | 'graphql/validation/rules/NoUnusedFragments', 86 | 'graphql/validation/rules/NoUnusedVariables', 87 | 'graphql/validation/rules/OverlappingFieldsCanBeMerged', 88 | 'graphql/validation/rules/PossibleFragmentSpreads', 89 | 'graphql/validation/rules/ProvidedNonNullArguments', 90 | 'graphql/validation/rules/ScalarLeafs', 91 | 'graphql/validation/rules/SingleFieldSubscriptions', 92 | 'graphql/validation/rules/UniqueArgumentNames', 93 | 'graphql/validation/rules/UniqueDirectivesPerLocation', 94 | 'graphql/validation/rules/UniqueFragmentNames', 95 | 'graphql/validation/rules/UniqueInputFieldNames', 96 | 'graphql/validation/rules/UniqueOperationNames', 97 | 'graphql/validation/rules/UniqueVariableNames', 98 | 'graphql/validation/rules/VariablesAreInputTypes', 99 | 'graphql/validation/rules/VariablesInAllowedPosition', 100 | ]); 101 | 102 | // Modify babel config. 103 | babelConfig.plugins.unshift( 104 | ['transform-import-duplicate', { 105 | exclude: ['node_modules/**'], 106 | external, 107 | newFiles: [ 108 | path.join(__dirname, "graphql", "src", "type", "reactiveDirectives.js"), 109 | path.join(__dirname, "graphql", "src", "utilities", "asyncIterator.js"), 110 | path.join(__dirname, "graphql", "src", "validation", "rules", "NoReactiveMutations.js"), 111 | ], 112 | mapping: { 113 | [path.join(__dirname, "graphql", "src", "(.+?)\.js$")]: 114 | path.join(__dirname, "node_modules", "graphql", "$1.js.flow"), 115 | } 116 | }] 117 | ); 118 | babelConfig.plugins.push('external-helpers'); 119 | babelConfig.babelrc = false; 120 | babelConfig.runtimeHelpers = true; 121 | babelConfig.presets[0][1].modules = false; 122 | 123 | // Replace local plugins with required version. 124 | babelConfig.plugins = babelConfig.plugins.map((plugin) => { 125 | let name = plugin; 126 | let args = []; 127 | if ( Array.isArray(plugin) ) { 128 | name = plugin.shift(); 129 | args = plugin; 130 | } 131 | if ( typeof name === 'string' ) { 132 | const replaceIndex = Object.keys(BABEL_PLUGIN_REPLACE).indexOf(name); 133 | if (replaceIndex !== -1) { 134 | name = BABEL_PLUGIN_REPLACE[name]; 135 | } 136 | } 137 | 138 | return args.length > 0 ? [name, ...args] : name; 139 | }); 140 | 141 | const productionPlugins = [ 142 | strip(), 143 | cleanup({ 144 | maxEmptyLines: 1, 145 | comments: "none", 146 | }), 147 | uglify(), 148 | ]; 149 | if ( DEBUG ) { 150 | productionPlugins.length = 0; 151 | } 152 | 153 | export default { 154 | input: 'src/index.js', 155 | plugins: [ 156 | filesize(), 157 | progress(), 158 | nodeResolve({ 159 | jsnext: true, 160 | module: true, 161 | }), 162 | commonjs({ 163 | include: [ 164 | 'node_modules/graphql/**', 165 | 'node_modules/iterall/**', 166 | 'node_modules/rxjs/**', 167 | ], 168 | }), 169 | babel(babelConfig), 170 | ...productionPlugins, 171 | ], 172 | output: { 173 | file: 'dist/bundle.js', 174 | format: 'cjs', 175 | }, 176 | external, 177 | }; 178 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import { 2 | addReactiveDirectivesToSchema, 3 | graphqlReactive as graphqlAsync, 4 | executeReactive as executeAsync, 5 | subscribe as subscribeAsync, 6 | validate as originalValidate, 7 | defaultFieldResolver, 8 | } from '../graphql/src/index'; 9 | import { asyncToObservable, toAsyncIterable } from './utils'; 10 | import { Observable } from 'rxjs/Observable'; 11 | import 'rxjs/add/observable/fromPromise'; 12 | 13 | export * from '../graphql/src/index'; 14 | export { AsyncGeneratorFromObserver } from '../graphql/src/utilities/asyncIterator'; 15 | 16 | export function graphqlReactive(...args) { 17 | return graphqlAsync(...hookGraphqlArguments(args, false)); 18 | } 19 | 20 | export function executeReactive(...args) { 21 | return executeAsync(...hookGraphqlArguments(args, false)); 22 | } 23 | 24 | export function subscribe(...args) { 25 | return subscribeAsync(...hookGraphqlArguments(args, true)); 26 | } 27 | 28 | export function graphqlRx(...args) { 29 | return Observable.fromPromise(graphqlReactive(...args)) 30 | .flatMap((asyncIterator) => asyncToObservable(asyncIterator)); 31 | } 32 | 33 | export function executeRx(...args) { 34 | return asyncToObservable(executeReactive(...args)); 35 | } 36 | 37 | export function subscribeRx(...args) { 38 | return Observable.fromPromise(subscribe(...args)) 39 | .flatMap((asyncIterator) => asyncToObservable(asyncIterator)); 40 | } 41 | 42 | export function validate(...args) { 43 | if ( args.length > 0 ) { 44 | args[0] = prepareSchema(args[0]); 45 | } 46 | 47 | return originalValidate(...args); 48 | } 49 | 50 | export function prepareSchema(schema) { 51 | // XXX: Do we want to duplicate object and modify? 52 | // or modify original as we are doing now? 53 | if ( ! schema._reactiveReady ) { 54 | addReactiveDirectivesToSchema(schema); 55 | wrapResolvers(schema); 56 | Object.assign(schema, { 57 | _reactiveReady: true, 58 | }); 59 | } 60 | 61 | return schema; 62 | } 63 | 64 | function hookGraphqlArguments(args, isSubscription) { 65 | if (args.length === 1) { 66 | return hookGraphqlArguments([ 67 | args[0].schema, 68 | args[0].source || args.document, 69 | args[0].rootValue, 70 | args[0].contextValue, 71 | args[0].variableValues, 72 | args[0].operationName, 73 | args[0].fieldResolver, 74 | args[0].subscribeFieldResolver, 75 | ], isSubscription); 76 | } 77 | 78 | const newArgs = Array( 79 | isSubscription ? 8 : 7 80 | ).fill(undefined); 81 | args.forEach((value, i) => { newArgs[i] = value; }); 82 | 83 | // Makes sure schema is prepared. 84 | newArgs[0] = prepareSchema(newArgs[0]); 85 | 86 | // Fixes default resolver for subscription 87 | let subscribeFieldResolver; 88 | if ( isSubscription ) { 89 | subscribeFieldResolver = newArgs.pop(); 90 | 91 | if ( !subscribeFieldResolver ) { 92 | subscribeFieldResolver = defaultFieldResolver; 93 | } 94 | 95 | subscribeFieldResolver = wrapAsyncIterable(subscribeFieldResolver); 96 | } 97 | 98 | // Fixes default resolver 99 | let defaultResolver = newArgs.pop(); 100 | if ( !defaultResolver ) { 101 | defaultResolver = defaultFieldResolver; 102 | } 103 | newArgs.push(wrapAsyncIterable(defaultResolver)); 104 | if ( subscribeFieldResolver ) { 105 | newArgs.push(subscribeFieldResolver); 106 | } 107 | 108 | return newArgs; 109 | } 110 | 111 | function wrapAsyncIterable(fn) { 112 | return (...args) => toAsyncIterable(fn(...args)); 113 | } 114 | 115 | export function wrapResolvers(schema) { 116 | const typeMap = schema.getTypeMap(); 117 | const defaultResolver = wrapAsyncIterable(defaultFieldResolver); 118 | 119 | Object.keys(typeMap).forEach((typeName) => { 120 | const type = typeMap[typeName]; 121 | 122 | if ( typeof type.getFields === 'function' ) { 123 | const fields = type.getFields(); 124 | 125 | Object.keys(fields).forEach((fieldName) => { 126 | if ( typeof fields[fieldName].resolve === 'function' ) { 127 | fields[fieldName].resolve = wrapAsyncIterable(fields[fieldName].resolve); 128 | } 129 | 130 | if ( typeof fields[fieldName].subscribe === 'function' ) { 131 | fields[fieldName].subscribe = wrapAsyncIterable(fields[fieldName].subscribe); 132 | } 133 | }); 134 | } 135 | }); 136 | } 137 | 138 | // XXX: Should be removed, used for testing. 139 | export { toAsyncIterable } from './utils'; 140 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | import { Observable } from 'rxjs/Observable'; 2 | import { AsyncGeneratorFromObserver } from '../graphql/src/utilities/asyncIterator'; 3 | import { $$asyncIterator, getAsyncIterator } from 'iterall'; 4 | 5 | export function asyncToObservable(asyncIterable) { 6 | return Observable.create((observer) => { 7 | const iterator = getAsyncIterator(asyncIterable); 8 | 9 | const errorHandler = (error) => observer.error(error); 10 | const nextHandler = (result) => { 11 | if ( result.done ) { 12 | observer.complete(); 13 | return; 14 | } 15 | 16 | observer.next(result.value); 17 | return iterator.next().then(nextHandler, errorHandler);; 18 | }; 19 | 20 | iterator.next().then(nextHandler, errorHandler); 21 | return () => { 22 | if ( typeof iterator.return === 'function' ) { 23 | iterator.return.call(iterator); 24 | } 25 | }; 26 | }); 27 | } 28 | 29 | export function toAsyncIterable(result) { 30 | if ( !result ) { 31 | return result; 32 | } 33 | 34 | if ( typeof result.subscribe !== 'function' ) { 35 | return result; 36 | } 37 | 38 | return AsyncGeneratorFromObserver((observer) => { 39 | let subscription = result.subscribe({ 40 | next(x) { observer.next(x) }, 41 | error(e) { observer.error(e) }, 42 | complete() { observer.complete() }, 43 | }); 44 | 45 | return () => { 46 | if ( subscription ) { 47 | subscription.unsubscribe(); 48 | subscription = undefined; 49 | } 50 | }; 51 | }); 52 | } 53 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["esnext", "es6", "dom"], 5 | "module": "commonjs", 6 | "moduleResolution": "node", 7 | "sourceMap": true, 8 | "experimentalDecorators": true, 9 | "emitDecoratorMetadata": true, 10 | "declaration": false, 11 | "outDir": "dist", 12 | "typeRoots": [ 13 | "node_modules/@types" 14 | ] 15 | }, 16 | "files": [ 17 | "src/main.ts" 18 | ], 19 | "exclude": [ 20 | "node_modules" 21 | ] 22 | } 23 | --------------------------------------------------------------------------------